Related
I recently started learning AI and i watched this video of Code Bullet, presenting a very simple genetic algorithm of a simple game where dots need to reach to a goal. I wanted to recreate this game in python using pygame. Since it didn't work at all, I tried redesigning it a bit.
Here is the code:
import pygame as pg
from DotGame.DotGameFunctions import *
import random as r
import time
pg.init()
WIN_WIDTH = 500
WIN_HEIGHT = 500
win = pg.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
pg.display.set_caption('DotGame')
SPAWN = Vector2(WIN_WIDTH / 2, WIN_HEIGHT - 40)
GOAL = Vector2(WIN_WIDTH / 2, 10)
class Dot:
def __init__(self, pos: Vector2):
self.pos = pos
self.dead = False
self.reachedGoal = False
self.is_best = False
self.fitness = 0
self.brain = Brain(1000)
self.vel = Vector2()
def move(self):
if self.brain.step < len(self.brain.directions):
self.vel = self.brain.directions[self.brain.step]
self.brain.step += 1
else:
self.dead = True
self.pos = self.pos + self.vel
def update(self):
if not self.dead and not self.reachedGoal:
self.move()
if self.pos.x < 5 or self.pos.y < 5 or self.pos.x > WIN_WIDTH - 5 or self.pos.y > WIN_HEIGHT - 5:
self.dead = True
elif dis(self.pos.x, self.pos.y, GOAL.x, GOAL.y) < 5:
self.reachedGoal = True
if self.is_best:
color = (0, 255, 0)
width = 7
else:
color = (0, 0, 0)
width = 5
if self.fitness > .005:
color = (0, 0, 255)
width = 7
pg.draw.circle(win, color, tuple(self.pos), width)
def calculate_fitness(self):
if self.reachedGoal:
self.fitness = 1 / 16 + 10000 / self.brain.step
else:
distance_to_goal = dis(self.pos.x, self.pos.y, GOAL.x, GOAL.y)
self.fitness = 1 / distance_to_goal
def make_baby(self):
baby = Dot(SPAWN)
baby.brain = self.brain.clone()
return baby
class Goal:
def __init__(self):
self.pos = GOAL
def update(self):
pg.draw.circle(win, (255, 0, 0), tuple(self.pos), 10)
class Brain:
def __init__(self, size):
self.size = size
self.step = 0
self.directions = []
self.randomize()
self.mutationRate = 1
def randomize(self):
self.directions = []
for i in range(self.size):
self.directions.append(Vector2.from_angle(r.random() * 2 * math.pi) * 4)
def clone(self):
clone = Brain(len(self.directions))
clone.directions = self.directions
return clone
def mutate(self):
for i in range(len(self.directions) - 1):
rand = r.random()
if rand < self.mutationRate:
self.directions[i] = Vector2.from_angle(r.random() * 2 * math.pi) * 4
class Population:
def __init__(self, size):
self.size = size
self.Dots = []
for i in range(size):
self.Dots.append(Dot(SPAWN))
self.minStep = 20
self.gen = 0
self.bestDot = 0
self.fit_sum = 0
def update(self):
for d in self.Dots:
d.update()
def fitness(self):
for d in self.Dots:
d.calculate_fitness()
def end_of_generation(self):
for d in self.Dots:
if not d.dead and not d.reachedGoal:
return False
return True
def natural_selection(self):
self.fitness()
new_dots = sorted(self.Dots, key=lambda x: x.fitness)
new_dots.reverse()
new_dots = new_dots[:len(new_dots)//2] * 2
print([i.fitness for i in new_dots])
self.Dots = [d.make_baby() for d in new_dots]
self.Dots[0].is_best = True
for d in range(len(self.Dots) // 2, len(self.Dots)):
self.Dots[d].brain.mutate()
test = Population(100)
goal = Goal()
run = True
while run:
win.fill('white')
goal.update()
if test.end_of_generation():
test.natural_selection()
time.sleep(1)
else:
test.update()
# for event in pg.event.get():
# if event.type == pg.QUIT:
# run = False
pg.display.update()
time.sleep(0.005)
pg.quit()
The important part of this code is the natural_selection() function in the population class:
def natural_selection(self):
self.fitness()
new_dots = sorted(self.Dots, key=lambda x: x.fitness)
new_dots.reverse()
new_dots = new_dots[:len(new_dots)//2] * 2
print([i.fitness for i in new_dots])
self.Dots = [d.make_baby() for d in new_dots]
self.Dots[0].is_best = True
for d in range(len(self.Dots) // 2, len(self.Dots)):
self.Dots[d].brain.mutate()
What it does (or supposed to do) is to calculate the fitness of the dots, sort the list of dots by fitness from highest to lowest, cuts the list in half and duplicating it, so the first and second halves are the same, and then sets the first dot to be the best and mutates the second half.
The problem is that as the print in line 6 shows, it doesn't really mutate the dots, and the result is that in every generation it just takes the same repeating list and sorts it, and the fitnesses look like this:
[0.003441755148372998, 0.0034291486755453414, 0.003070574887525978, 0.0030339596318951327, 0.003030855079397534,...]
[0.00481387362410465, 0.00481387362410465, 0.003468488512721536, 0.003468488512721536, 0.0032419920180191478,...]
[0.004356736047656708, 0.004356736047656708, 0.004356736047656708, 0.004356736047656708, 0.003056862712974015,...]
I checked the brain.mutate function and it seems to work just fine.
I've rewritten your natural_selection method to be a little more structured (to me at least), and it seems to work for my simple test case. Can you try if this works for you? I added some comments to explain my steps.
def natural_selection(self):
# Calculate fitness
self.fitness()
# Print initial generation
print(pop.Dots)
# Sort dots by fitness
new_dots = sorted(self.Dots, key=lambda x: x.fitness)
new_dots.reverse()
# Save first half of best individuals
best_half = new_dots[:len(new_dots)//2]
# Get offspring dots from best half
offspring = [d.make_baby() for d in best_half]
# copy best half and mutate all dots
mutated_offspring = [d.make_baby() for d in best_half]
for d in mutated_offspring:
d.brain.mutate()
# Join original best half and mutated best half to get new dots
new_dots = offspring + mutated_offspring
# Set first new dot as best dot
new_dots[0].is_best = True
# Set new_dots as self.Dots
self.Dots = new_dots
# Print new generation
self.fitness()
print(pop.Dots)
Full test case:
import random
# Brains only consist of some random number.
# Mutations just add one to that number.
class Brain:
def __init__(self):
self.num = self.randomize()
def clone(self):
clone = Brain()
clone.num = self.num
return clone
def randomize(self):
return random.randint(1000, 9000)
def mutate(self):
self.num += 1
# Dots consist of a brain an a fitness.
# Fitness is simply the brain's number.
class Dot:
def __init__(self):
self.brain = Brain()
self.fitness = None
def __repr__(self):
return str(self.fitness)
def make_baby(self):
baby = Dot()
baby.brain = self.brain.clone()
return baby
class Population:
def __init__(self):
self.Dots = [Dot() for i in range(10)]
def fitness(self):
for d in self.Dots:
d.fitness = d.brain.num
def natural_selection(self):
# Calculate fitness
self.fitness()
# Print initial generation
print(pop.Dots)
# Sort dots by fitness
new_dots = sorted(self.Dots, key=lambda x: x.fitness)
new_dots.reverse()
# Save first half of best individuals
best_half = new_dots[:len(new_dots)//2]
# Get offspring dots from best half
offspring = [d.make_baby() for d in best_half]
# copy best half and mutate all dots
mutated_offspring = [d.make_baby() for d in best_half]
for d in mutated_offspring:
d.brain.mutate()
# Join original best half and mutated best half to get new dots
new_dots = offspring + mutated_offspring
# Set first new dot as best dot
new_dots[0].is_best = True
# Set new_dots as self.Dots
self.Dots = new_dots
# Print new generation
self.fitness()
print(pop.Dots)
random.seed(1)
pop = Population()
pop.natural_selection()
Returns:
[2100, 5662, 7942, 7572, 7256, 1516, 3089, 1965, 5058, 7233]
[7942, 7572, 7256, 7233, 5662, 7943, 7573, 7257, 7234, 5663]
So the original population fitness is printed on the first line. In the second line, the mutated population fitness is shown. My mutations simply add one to the dots brain value, so the second half is a copy of the first half with 1 added to each value.
Moved from an edit to the question by the OP to an answer.
There is a problem with copying the list in natural_selection:
from copy import deepcopy
...
def natural_selection(self):
# Sort dots by fitness
new_dots = sorted(self.Dots, key=lambda x: x.fitness)
new_dots.reverse()
# Save first half of best individuals
best_half = new_dots[:len(new_dots)//2]
# Get offspring dots from best half
offspring = [d.make_baby() for d in best_half]
# copy best half and mutate all dots
mutated_offspring = deepcopy(offspring)
for d in mutated_offspring:
d.brain.mutate()
# Join original best half and mutated best half to get new dots
new_dots = best_half + mutated_offspring
# Set first new dot as best dot
new_dots[0].is_best = True
# Set new_dots as self.Dots
self.Dots = new_dots
Your friend bought you a present for the New Year, it's a puzzle! The puzzle consists of a number of
wooden rectangular pieces of varying lengths and widths and a board. The goal is to position the
wooden pieces on the board in a way such that all of the pieces will fit.
I have this program and I need help fixing my breadth first search algorithm.
Right now it is very slow and using a lot of memory. I think it is because I deep copy multiple times. The solve function is the main function and will do the heavy work.
I added a text file that has the first line as the dimensions of the puzzle and the rest of the lines are pieceID, pieceWidth and pieceLength respectively.
This is the Input File. Thank you so much.
10,10
1,10,1
2,1,10
3,1,5
4,3,5
5,20,2
6,1,5
7,1,5
8,2,5
import argparse, copy
import queue
import copy
import numpy as np
class PuzzleBoard():
def __init__(self, board_length, board_width ):
self.l = board_length
self.w = board_width
self.state = [[0 for _ in range(board_width)] for _ in range(board_length)]
self.used_piece = []
# Input: point - tuple cotaining (row_index, col_index) of point in self.state
# Returns true if point is out of bounds; otherwise, returns false
def __out_of_bounds(self, point):
# TODO: Implement this function
if(point < 0 or point > (len(self.state)) or (point > (self.state[0]))):
return True
return False
# Finds the next available open space in the PuzzleBoard (looking from the top-left in row-major order)
def __next(self):
for i in range(len(self.state)) :
for j in range(len(self.state[0])):
if (self.state[i][j] == 0):
return (i, j)
return False
# Input: piece - PuzzlePiece object
# Check if piece fits in the next available space (determined by __next method above)
def fits(self, piece):
position = self.__next()
if not position:
return False
#TODO: Check if any part of the piece is out of bounds
#if piece will be out bounds when place rotate to see if that helps
if((( piece.w + position[0] ) > len( self.state )) or (( piece.l + position[1] )> len( self.state[0] ))):
piece.rotate()
if((( piece.w + position[0] ) > len( self.state )) or (( piece.l + position[1] )> len( self.state[0] ))):
return False
#TODO: Check if piece can be placed without intersecting another placed piece
return True
# Input: piece - PuzzlePiece object
# Insert piece into the next available position on the board and update state
def place(self, piece):
# TODO: Bug in this function. Pieces not being placed correctly.
position = self.__next()
if self.fits(piece):
for i in range(position[0], position[0] + piece.w ):
for j in range(position[1], position[1] + piece.l):
if((( piece.w + position[0] ) > len( self.state )) or (( piece.l + position[1] )> len( self.state[0] ))):
return
if(self.state[i][j]== 0):
#self.used_piece.append(piece)
self.state[i][j] = piece.id
else:
continue
return position
def check(self, piece):
position = self.__next()
if(position[0] + piece.w > self.w or position[1] + piece.l > self.l):
return False
return True
# Returns whether the board has been filledwith pieces
def completed(self):
return True if not self.__next() else False
def copy(self):
copied = PuzzleBoard(self.l, self.w)
copied.state = copy.deepcopy(self.state)
return copied
class PuzzlePiece():
def __init__(self, pid, length, width):
self.id = pid
self.l = length
self.w = width
itfits = False
def rotate(self):
#TODO: Bug in this function. Pieces are not rotating correctly
temp = self.l
self.l = self.w
self.w = temp
def orientation(self):
return "H" if self.w >= self.l else "V"
def __str__(self):
return f"ID: {self.id}, LENGTH: {self.l}, WIDTH: {self.w}, ROTATED: {self.rotated}"
def parse_input(filepath) :
#TODO: Bug in this function. Error raised when called
parsed = {'board' : {}, 'pieces' : {}}
with open(filepath, 'r') as f:
file_contents = f.read().strip().split("\n")
board_length, board_width = file_contents[0].strip().split(",")
parsed['board']['length'] = int(board_length)
parsed['board']['width'] = int(board_width)
for i in range(1, len(file_contents)):
#FIX: the issue was fix
pid, l, w = file_contents[i].strip().split(",")
pid, l, w = int(pid), int(l), int(w)
parsed['pieces'][pid] = {}
parsed['pieces'][pid]['length'] = l
parsed['pieces'][pid]['width'] = w
return parsed
def helper(board, piece):
unused = []
#for piece in pieces:
if board.fits(piece):
position = board.place(piece)
board.used_piece.append((piece, position))
return board
def solve(board, remaining, used_pieces=[]):
# TODO: Implement a solution for a variable amount of pieces and puzzle board size.
# HINT: Recursion might help.7
poss = queue.Queue()
poss.put(board)
currboard = PuzzleBoard(len(board.state), len(board.state[0]))
while not currboard.completed():
currboard = poss.get()
#print(currboard.state)
for piece in remaining:
fakeboard = copy.deepcopy(currboard)
if(not (piece.id in np.array(fakeboard.state))):
#if( fakeboard.check(piece)):
poss.put(helper(fakeboard, piece))
print("Suff done")
return currboard
'''if(len(remaining) != 0):
board, used_pieces, unused_pieces = helper(board, remaining, used_pieces)
if board.completed():
return board, used_pieces
for i in board.state:
print(i)
print("\n \n")
return solve(board, unused_pieces, used_pieces)
return board'''
def main():
#TODO: Bug in this function. Positions are not correct after solution is found.
parser = argparse.ArgumentParser()
parser.add_argument('input')
args = parser.parse_args()
parsed = parse_input(args.input)
board = PuzzleBoard(parsed['board']['length'], parsed['board']['width'])
pieces = []
for k, v in parsed['pieces'].items():
pieces.append(PuzzlePiece(k, v['length'], v['width']))
solved = solve(board, pieces)
if not solved:
print("No solution found for given input.")
else:
print("Solution found.")
board = solved
for u, position in solved.used_piece:
print(f"Piece ID: {u.id}, Position:{position}, Orientation: {u.orientation()}")
if __name__ == "__main__":
main()
I need to code a 2d physics engine (for the moment without rotation) with a model of a wheel (here : one non-rotating Disc with small discs attached to it with springs in a circle to simulate the tyre).
It worked quite well until now (given that I choose a short enough time step), but now I have to add friction (it can be full friction : no relative speed between the tyre and the floor).
So when I'm computing the collisions, I want to know the speed BEFORE the acceleration due to forces. So instead of (Forces)>(Collisions)>(Change speed from acceleration)>(Update position),
I used (Forces)>(Change speed from acceleration)>(Collisions)>(Update position).
But then, no matter the time step, I have strange results, especially when colliding.
I could maybe have friction with the first order of steps, but it will be more complicated I guess.
In the code here, I tried to focus on the main things (but it's not THAT minimal either), so I removed friction for example, since the problem seems to be in the order of my steps.
In the tkinter window, there are several time steps available if you want to test (for example the first one completely fails).
Thanks in advance
PS : I know the springs are very strong (k = 1e7), that sould be a wheel.
import numpy as np
import math as m
import random as rd
import tkinter as tk
import time
def CC2(coords,size=500,zoom=160,offset=[100,100]):#Change x,y coordinates into canvas coordinates
x = int(coords[0]*zoom+offset[0])
y = int((size-coords[1]*zoom)-offset[1])
return x,y
def CC4(coords):#Change from (x1,y1),(x2,y2)
return CC2(coords[0]),CC2(coords[1])
def normalize(vec):#Normalize the vector
return (1/norm(vec))*vec
def norm(vec):#Norm of the vector
return m.sqrt(sum(vec**2))
def sqnorm(vec):#Square norm
return sum(vec**2)
class Scene:
def __init__(self,objectlist,canvas):
self.can = canvas
self.objects = objectlist
self.time = 0#Scene timer
g = 9.81
self.gravity = np.array([0,-g])
def makeStep(self,dt=0.01,display = True):
#Acceleration from gravity
for obj in self.objects:
if obj.invmass != 0:
obj.accel = self.gravity.copy()
#Get accelerations from other forces (here : spring joints)
for obj in self.objects:
if obj.invmass != 0:
#From special joints i.e. spring joints
for joint in obj.joints:#Joint → Force
j = joint.objId
o1 = self.objects[j]
force = joint.getForce(o1,obj)
o1.accel += o1.invmass*force
obj.accel -= obj.invmass*force
"""
Works quite well when the following loop is AFTER the collisions
But in order to add (full) friction properly I wanted to know the speed AFTER applying the forces hence the acceleration
(I can maybe do otherwise but it's more complicated and might not work either...)
"""
#Change speeds from acceleration
for obj in self.objects:
obj.accelerate(dt)
#Apply collisions and change speeds
self.findCollisions(dt)
#Move objects
for obj in self.objects:
obj.move(dt)
if display:
self.display()
self.time += dt
def play(self,dt=0.0001,total_time=5,get_energies=False):#Play the simulation (dt is the time step)
realtime = time.time()
starting_time=realtime
last_display = realtime
while self.time-starting_time <= total_time:
#Just for display
display = False
if time.time()-last_display >= 0.1:
display = True
last_display = time.time()
#Next step
self.makeStep(dt,display)
def findCollisions(self,dt):#Find all collisions, get normal vectors from getCollision and call resolveCollision
n = len(self.objects)
for i in range(n):
o2 = self.objects[i]
joints = o2.joints
for j in range(i):# j<i
o1 = self.objects[j]#Objects 1 & 2
if o1.classCollide(o2):#Classes compatible for collision
if o1.bboxIntersect(o2):
normal = self.getCollision(o1,o2)
self.resolveCollision(o1,o2,normal)#Resolve collision
def resolveCollision(self,o1,o2,normal):#Change speed and position to resolve collision
if normal.any():#normal is not 0,0 (collision)
depth = norm(normal)
normal = 1/depth*normal
relative_speed = o2.speed - o1.speed
normal_speed = relative_speed # normal#Norm of projection of relative speed
total_invmass = o1.invmass + o2.invmass#Sum of inverse masses
if normal_speed > 0:#Real collision:
e=1
coef = (1+e)*normal_speed
o1.speed += coef*(o1.invmass/total_invmass)*normal
o2.speed += -coef*(o2.invmass/total_invmass)*normal
if 0.001<depth:#Positional correction
correction = 0.2*depth/total_invmass*normal
o1.center += o1.invmass*correction
o2.center -= o2.invmass*correction
def getCollision(self,o1,o2,display=False):#Intersection between objects with intersecting bbox: returns normal vector with norm = penetration depth (directed towards o1)
if o1.type == "box" and o2.type == "box":
delta = o2.center-o1.center
dim_sum = o1.dimensions+o2.dimensions#Sum of half-widths and heights
dsides = [delta[0]+dim_sum[0],-delta[0]+dim_sum[0],delta[1]+dim_sum[1],-delta[1]+dim_sum[1]]#Left, right, bottom, top, bottom, left, right of o1
imin = np.argmin(dsides)
if imin == 0:#Left
normal = np.array([dsides[0],0])#Orientation : right = positive
elif imin == 1:#Right
normal = np.array([-dsides[1],0])
elif imin == 2:#Bottom
normal = np.array([0,dsides[2]])
else:#Top
normal = np.array([0,-dsides[3]])
return normal
if o1.type == "disc":
return o1.getCollisionVector(o2)
if o2.type == "disc":
return -o2.getCollisionVector(o1)
def display(self):#Just display the scene
self.can.delete('all')
for obj in self.objects:
color = "yellow"
if obj.type == "box":
if obj.invmass==0:#Unmoveable
color = "black"
can.create_rectangle(CC4(obj.bbox()),fill=color)
if obj.type == "disc":
can.create_oval(CC4(obj.bbox()),fill="springgreen")
for joint in obj.joints:
can.create_line(CC2(obj.center),CC2(self.objects[joint.objId].center+joint.offset),dash=(3,2))
fen.update()
## Objects
class Object2D:#Abstract class for circles and boxes
def bboxIntersect(self,object2):#Intersection of bounding boxes
bbox1 = self.bbox()
bbox2 = object2.bbox()
if (bbox1[1][0]<bbox2[0][0] or bbox1[0][0]>bbox2[1][0]):#No intersecting on x axis
return False
if (bbox1[1][1]<bbox2[0][1] or bbox1[0][1]>bbox2[1][1]):#No intersecting on y axis
return False
return True
def move(self,dt):
if self.invmass == 0:
return None
self.center += dt*self.speed
def accelerate(self,dt):
if self.invmass == 0:
return None
self.speed += self.accel*dt
def classCollide(self,obj):
if (self.cls == "nc1" or obj.cls == "nc1"):#No collision at all
return False
if (self.cls == "nc2" and obj.cls == "nc2"):#No collision inside this class
return False
return True
class Box(Object2D):
def __init__(self,mass,center,width,height,initspeed=[0.0,0.0],joints=[],cls=""):
self.invmass = 1/mass
self.center = np.array(center,dtype=float)#x,y
self.hheight = height/2#Half height
self.hwidth = width/2
self.dimensions=np.array([self.hwidth,self.hheight])
self.speed = np.array(initspeed,dtype=float)#Initial speed (x,y)
self.accel = np.zeros(2)#x,y acceleration
self.type = "box"
self.joints = joints
self.cls=cls
def bbox(self):
return (self.center[0]-self.hwidth,self.center[1]-self.hheight),(self.center[0]+self.hwidth,self.center[1]+self.hheight)
class Disc(Object2D):
def __init__(self,mass,center,radius,initspeed=[0.0,0.0],joints = [],cls=""):
self.invmass = 1/mass
self.center = np.array(center,dtype=float)#x,y
self.radius = radius
self.speed = np.array(initspeed,dtype=float)#Initial speed (x,y)
self.accel = np.zeros(2)#x,y acceleration
self.type = "disc"
self.joints = joints
self.cls=cls
def bbox(self):
return (self.center[0]-self.radius,self.center[1]-self.radius),(self.center[0]+self.radius,self.center[1]+self.radius)
def getCollisionVector(self,obj):
if obj.type == "box":#VS BOX
box = obj
bbox = box.bbox()
delta = self.center-box.center
if (bbox[0][0] <= self.center[0] <= bbox[1][0]):#Vertical collision
return np.sign(delta[1])*np.array([0,self.radius+box.hheight-abs(delta[1])])
if (bbox[0][1] <= self.center[1] <= bbox[1][1]):#Horizontal collision
return np.sign(delta[0])*np.array([self.radius+box.hwidth-abs(delta[0]),0])
#else find closest corner
if delta[1] > 0:#Top
if delta[0] > 0:#Right
delta_corner = self.center - (box.center+box.dimensions)
else:#Left
delta_corner = self.center - (box.center+np.array([-box.hwidth,box.hheight]))
else:#Bottom
if delta[0] > 0:#Right
delta_corner = self.center - (box.center+np.array([box.hwidth,-box.hheight]))
else:#Left
delta_corner = self.center - (box.center-box.dimensions)
distance = norm(delta_corner)
if distance > self.radius:#No collision
return np.zeros(2)
return (self.radius-distance)/distance*delta_corner
elif obj.type == "disc":#VS DISC
delta = self.center - obj.center
norm_delta = norm(delta)
depth = self.radius + obj.radius - norm_delta
if depth > 0:#Collision
return depth*normalize(delta)
return np.zeros(2)
class Floor(Box):
def __init__(self,y,xmin=-500,xmax=500):
self.invmass = 0#Infinite mass
self.y = y
self.hwidth = (xmax-xmin)/2
self.hheight = 50
self.dimensions=np.array([self.hwidth,self.hheight])
self.center = np.array([(xmin+xmax)/2,y-50])
self.type = "box"
self.accel = np.zeros(2)
self.speed = np.zeros(2)
self.joints = []
self.cls=""
## Forces & joints
class SpringJoint:
def __init__(self,objId,k,l0,damper=10,offset=[0,0]):
self.objId = objId
self.l0 = l0
self.k = k
self.offset = np.array(offset)
self.damper = damper
def getForce(self,o1,o2):
delta = o2.center - (o1.center+self.offset)
normal = normalize(delta)
diff = delta - self.l0*normal
delta_speed = o2.speed - o1.speed
return self.k*diff + self.damper*delta_speed#normal*normal
## Objects definitions
#Test wheel with spring : generates a "wheel" model
def getWheel(Radius,IntRadius,IntMass,ExtMass,kr,ks,x=0,y=0.5,n=14,initspeed=[0,0]):
arc = 2*m.pi*Radius/n
r = 0.35*arc
l0s = 2*(Radius-r)*m.sin(m.pi/n)
R = IntRadius - r
l0r = Radius - r
core = Disc(IntMass,[x,y],R,initspeed=initspeed)
tyre= []
for k in range(n):
a = k/n*2*m.pi
tyre.append(Disc(ExtMass/n,[x+l0r*m.cos(a),y+l0r*m.sin(a)],r,joints=[SpringJoint(0,kr,l0r),SpringJoint(k%n,ks,l0s)],cls="nc2"))
#Discs from the outside don't interact with each other except with the spring joints
tyre[-1].joints.append(SpringJoint(1,ks,l0s))
del tyre[0].joints[1]
return [core] + tyre
#Objects in the scene
#☺Simple wheel with n=5
objects = getWheel(0.5,0.25,500,1,1e7,1e7,y=0.5,initspeed=[5,0],n=5) + [Floor(0)]
## Scene
fen = tk.Tk()
can = tk.Canvas(fen,width = 1000,height=500)
can.pack()
scene = Scene(objects,can)
scene.display()
tk.Button(fen,text="Go quick (10**-3 s)",command = lambda : scene.play(0.001,3,get_energies)).pack()
tk.Button(fen,text="Go medium (10**-4)",command = lambda : scene.play(0.0001,3,get_energies)).pack()
tk.Button(fen,text="Go slowly (3*10**-5)",command = lambda : scene.play(0.00003,1,get_energies)).pack()
tk.Button(fen,text="Go very slowly (10**-5)",command = lambda : scene.play(0.00001,1,get_energies)).pack()
tk.Button(fen,text="Do 0.01s",command = lambda : scene.play(0.0001,0.01,get_energies)).pack()
tk.Button(fen,text="Do 1 step",command = lambda : scene.play(0.01,0.01,get_energies)).pack()
fen.mainloop()
Edit: misunderstood the question.
Would it help to have the move step before the collision step? Movement should happen right after acceleration.
Try to calculate the acceleration before the collision in order to get frictional forces without ever applying it to the objects.
Eventually I kept the original order and found another way to implement friction, so now it works quite well
I'm trying to implement a genetic algorithm for solving the Travelling Salesman Problem (TSP).
I have 2 classes, which are City and Fitness.
I have done the code for initialization.
class City:
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self, city):
xDis = abs(self.x - city.x)
yDis = abs(self.y - city.y)
distance = np.sqrt((xDis ** 2) + (yDis ** 2))
return distance
def __repr__(self):
return "(" + str(self.x) + "," + str(self.y) + ")"
class Fitness:
def __init__(self, route):
self.route = route
self.distance = None
self.fitness = None
def routeDistance(self):
if self.distance == None:
pathDistance = 0.0
for i in range(0, len(self.route)):
fromCity = self.route[i]
toCity = None
if i+1 < len(self.route):
toCity = self.route[i+1]
else:
toCity = self.route[0]
pathDistance += fromCity.distance(toCity)
self.distance = pathDistance
return self.distance
def routeFitness(self):
if self.fitness == None:
self.fitness = 1 / float(self.routeDistance())
return self.fitness
def selection(population, size=None):
if size== None:
size= len(population)
matingPool = []
fitnessResults = {}
for i in range(0, size):
fitnessResults[i] = Fitness(population[i]).routeFitness()
matingPool.append(random.choice(population))
return matingPool
The code above just randomly selects a parent in the selection method.
My question is: How to code to select a parent using roulette wheels?
You could try this [1, 2]:
from numpy.random import choice
def selection(population, size=None):
if size== None:
size= len(population)
fitnessResults = []
for i in range(0, size):
fitnessResults.append(Fitness(population[i]).routeFitness())
sum_fitness = sum(fitnessResults)
probability_lst = [f/sum_fitness for f in fitnessResults]
matingPool = choice(population, size=size, p=probability_lst)
return matingPool
Read this
So basically, the higher a fitness value, the higher are its chances to be chosen. But that is when high fitness value means a high fitness. But in TSP a lower value of fitness is better so to implement this, we need to implement the concept where probability is indirectly proportional to the fitness value.
Here is something I had implemented in python with some changes
def choose_parent_using_RWS(genes, S):
P = random.uniform(0, S)
for x in genes:
P += get_fitness_value(x)
if P > S:
return x
return genes[-1]
where S is the sum of the inverse of the fitness values of the current population (i.e, 1/f1 + 1/f2 + 1/f3 + ...)
and
get_fitness_value(x) returns the inverse of the distance, just like your routeFitness() function
TeeHee
I'm trying to implementing A* from the pseudo code from wikipedia however I'm getting some weird results.
The implementation finds what at first looks like a good path, but with a further look it always produces the same path!
Can anyone spot anything wrong? The code is written in python 3.1 and uses pygame.
import pygame
import sys, traceback
import random
import math
TILE_WIDTH = 30
TILE_HEIGHT = 30
NUM_TILES_X = 30
NUM_TILES_Y = 30
NUM_TILES = NUM_TILES_X * NUM_TILES_Y
GRID_WIDTH = TILE_WIDTH * NUM_TILES_X
GRID_HEIGHT = TILE_HEIGHT * NUM_TILES_Y
# h(x,y)
def heuristic_dist(source,dest):
return int(( (source.x - dest.x)**2 + (source.y - dest.y)**2 ) **0.5)
def a_star(nodes,start,goal):
# Set up data structures
closedset = []
openset = [start]
came_from={}
g_score = {}
g_score[start.index] = 0
h_score = {}
h_score[start.index] = heuristic_dist(start,goal)
f_score = {}
f_score[start.index] = h_score[start.index]
while len(openset) > 0:
# Find node with least f_score in openset
x = min(openset,key=lambda el:f_score[el.index])
# We have reached our goal!
if x.index == goal.index:
path = reconstruct_path(came_from,goal.index)
# Mark the path with green color
for node in path:
nodes[node].color=(0,255,0)
print( "Yihaaa!" )
return True
# Filter out x from openset and add it to closedset
openset = list(filter(lambda y:y.index!=x.index,openset))
closedset.append(x)
# Go through all neighbours
for y in x.get_neighbours():
# If this neighbour has been closed, skip it
if y in closedset: continue
# Not sure that this is correct.
tentative_g_score = g_score[x.index] + heuristic_dist(x,y)
if y not in openset:
openset.append(y)
tentative_is_better = True
elif tentative_g_score < g_score[y.index]:
tentative_is_better = True
else:
tentative_is_better = False
if tentative_is_better:
if y.index in came_from:
if f_score[x.index] < f_score[came_from[y].index]:
came_from[y.index] = x
else:
came_from[y.index] = x
g_score[y.index] = tentative_g_score
h_score[y.index] = heuristic_dist(y, goal)
f_score[y.index] = g_score[y.index] + h_score[y.index]
print("Couldn't find a path!")
return False
# Traverse the path backwards
def reconstruct_path(came_from,current_node,depth=0):
if current_node in came_from:
p = reconstruct_path(came_from,came_from[current_node].index)
return p + [current_node]
else:
return [current_node]
def draw_string(surface,string,x,y):
s = font.render(string,True,(0,0,0))
surface.blit(s,(x,y))
# Tile or Node that has a cuple of attributes: color, cost and x,y
class Tile:
def __init__(self,x,y,cost,index):
self.x=x
self.y=y
self.cost=cost
self.index=index
self.color = (255,255,255)
def draw(self,surface):
surface.fill(self.color,pygame.Rect(self.x*TILE_WIDTH,self.y*TILE_HEIGHT,TILE_WIDTH,TILE_HEIGHT))
pygame.draw.rect(surface,(255, 180, 180),pygame.Rect(self.x*TILE_WIDTH,self.y*TILE_HEIGHT,TILE_WIDTH,TILE_HEIGHT),2)
draw_string(surface,str(self.cost),self.x*TILE_WIDTH+TILE_WIDTH//3,self.y*TILE_HEIGHT+TILE_HEIGHT//3)
def get_neighbours(self):
nbs = []
# Where are our neighbours?
offsets = [(0,-1),(-1,0),(1,0),(0,1)]
for offset in offsets:
x = self.x + offset[0]
y = self.y + offset[1]
try: # coord_to_tile throws exception if no such neighbour exists (out of bounds for example)
nbs.append(coord_to_tile(x,y))
except Exception as e:
pass
return nbs
def __eq__(self,other):
return self.x == other.x and self.y==other.y
# Small helper function to convert x,y coords to a tile instance
nodes_lookup={}
def coord_to_tile(x,y):
return nodes_lookup[(x,y)]
def main():
global nodes_lookup
screen = pygame.display.set_mode((GRID_WIDTH, GRID_HEIGHT))
tiles = []
for x in range(NUM_TILES_X):
for y in range(NUM_TILES_Y):
# Create a random distribution where max grows
cost = random.randint(1,min(x*y,98)+1)
# Let the bottom line cost 1 as well
if y == NUM_TILES_Y-1: cost = 1
t = Tile(x,y,cost,len(tiles))
nodes_lookup[(x,y)] = t
tiles.append(t)
# Do a*
a_star(tiles,tiles[0],tiles[len(tiles)-1])
while True:
event = pygame.event.wait()
if event.type == pygame.QUIT:
break
for tile in tiles:
tile.draw(screen)
pygame.display.flip()
pygame.init()
font = pygame.font.SysFont("Times New Roman",18)
try:
main()
except Exception as e:
tb = sys.exc_info()[2]
traceback.print_exception(e.__class__, e, tb)
pygame.quit()
I really have no clue, since I think I have pretty much implemented the pseudo code statement by statement.
Here's a screenshot as well:
http://andhen.mine.nu/uploads/astar.dib
Thanks!
You access came_from on time with y, and one time with y.index in
if tentative_is_better:
if y.index in came_from:
if f_score[x.index] < f_score[came_from[y].index]: // index by y
came_from[y.index] = x // index by y.index
else:
You probably meant
if f_score[x.index] < f_score[came_from[y.index].index]:
in the first line.
Besides that, the code looks ok.
Anyway, what do you mean by always produces the same path? The algorithm is supposed to return the optimal path which should always be the same... (or did you mean, it always produces the same path independently of start and goal?)`
EDIT:
You don't use your random cost anywhere in the algorithm. The 'costs' the algorithm is using are always the distance between two adjacent nodes: They are defined in heuristic_distance and used in the line
tentative_g_score = g_score[x.index] + heuristic_dist(x,y)
If you want to define random costs, you must first realize that this algorithm assigns costs to edges, not to vertices. You'll have to define some function real_costs(x,y) which calculates the costs for going from node x to node y and use this cost function instead of heuristic_dist in the above line.