diff --git a/Final/python files/ackland_simulation.py b/Final/python files/ackland_simulation.py new file mode 100644 index 0000000000000000000000000000000000000000..8ef9c755689f335597f6f37e45b3162922223676 --- /dev/null +++ b/Final/python files/ackland_simulation.py @@ -0,0 +1,609 @@ +from graphics import * # get here: http://mcsp.wartburg.edu/zelle/python/graphics.py +import time +import random +import math +import csv + +# imports from our files +from help_functions import * +from classes import * +from check_boundarys import * + +''' +plot is not used here, because we could not figure +out a way to close the plot and continue with the next generation +''' +# ------------------------------------------------------------------ +# global varibles + + +# window dimensions +winWidth = 700 +winHeight = 700 + +# set true to show visualisation/write data in csv +visual = True +writecsv = False + +# names for csvs +GENERATIONS = "generations.csv" +TIMESTEPS = "timesteps.csv" + +if visual: + window = GraphWin("Window", winWidth, winHeight) +else: + window = None + +# max time for one generation +maxTime = 10000 + +# number of agents +agentNum = 80 +# number of predators +predNum = agentNum//10 +# number of generations +generations = 500 + +# food cluster variables +# soll insgesamt immer 128 ergeben +foodCluster = 16 +clusterSize = 8 +clusterradius = 20 + +# isFood turns on food simulation, +# isPred turns on predator simulation +isFood = False +isPred = True +Both = False + +# timestep +t = 0.1 + +# values of Agents as given in the Paper +rr = 5 +# area an agent can see +As = 5000 * (rr**2) +Am = 5 * (rr**2) * t + +# mutation values, +# determine how fast aspects of Agents can mutate +m_zoo_r = 5/2.0 +m_zoa_r = 10/2.0 +m_speed = 0.1/2.0 +m_food = 0.1/2.0 +m_pred = 0.1/2.0 +m_noise = 0.05/2.0 + + +# ------------------------------------------------------------------ +# Ackland + +# returns three lists, one for each zone, +# contaning all other agent in the zone. +# ignores al agents in the angle behind the current agent defined by blind. +# the zones zor/zoo/zoa are defined by their radii zor_r/zoo_r/zoa_r + +# exactly the same as in couzin, but gets more arguments, +# because they are not global here + +def neigbour_in_zones(agent, agents, zor_r, zoo_r, zoa_r, blind): + zor = [] + zoo = [] + zoa = [] + + for neighbour in agents: + # find distance in x and y direction, + # and use them to calculate the angle between + # the movement-vector of agent and + # the directiction-vector between agent and neighbour + + disVecX = neighbour.point.getX() - agent.point.getX() + disVecY = neighbour.point.getY() - agent.point.getY() + alpha = calc_angle(agent.x_velocity, agent.y_velocity, disVecX, disVecY) + + if (agent == neighbour): + True + elif alpha < 180 - blind and alpha > 180 + blind: + True + + # if neighbour can be seen: + # get distance dis between agent and neighbour, + # and check if neighbour is in a zone of agent + else: + dis = absvec(neighbour.point.getX() - agent.point.getX(), neighbour.point.getY() - agent.point.getY()) + if dis <= zor_r: + zor.append(neighbour) + elif dis <= zoo_r: + zoo.append(neighbour) + elif dis <= zoa_r: + zoa.append(neighbour) + + return [zor, zoo, zoa] + + +# update Velocity a la couzin +# exactly the same as in couzin, but noise is +# not global and attributes are named differently +# for some reason... +def updateV_couzin(agent, zones): + dx = 0 + dy = 0 + + #zor not empty + if zones[0] != []: + for neighbour in zones[0]: + disX = neighbour.point.getX() - agent.point.getX() + disY = neighbour.point.getY() - agent.point.getY() + + rX = disX / absvec(disX, disY) + rY = disY / absvec(disX, disY) + + dx += rX / absvec(rX, rY) + dy += rY / absvec(rX, rY) + + dx = -dx + dy = -dy + + # zoo not empty; zoa empty + elif zones[1] != [] and zones[2] == []: + for neighbour in zones[1]: + dx += neighbour.x_velocity / absvec(neighbour.x_velocity, neighbour.y_velocity) + dy += neighbour.y_velocity / absvec(neighbour.x_velocity, neighbour.y_velocity) + + dx += agent.x_velocity / absvec(agent.x_velocity, agent.y_velocity) + dy += agent.y_velocity / absvec(agent.x_velocity, agent.y_velocity) + + + # zoo empty; zoa not empty + elif zones[1] == [] and zones[2] != []: + for neighbour in zones[2]: + disX = neighbour.point.getX() - agent.point.getX() + disY = neighbour.point.getY() - agent.point.getY() + + rX = disX / absvec(disX, disY) + rY = disY / absvec(disX, disY) + + dx += rX / absvec(rX, rY) + dy += rY / absvec(rX, rY) + + # zoo and zoa not empty + elif zones[1] != [] and zones[2] != []: + for neighbour in zones[1]: + dx += neighbour.x_velocity / absvec(neighbour.x_velocity, neighbour.y_velocity) + dy += neighbour.y_velocity / absvec(neighbour.x_velocity, neighbour.y_velocity) + + dx += agent.x_velocity / absvec(agent.x_velocity, agent.y_velocity) + dy += agent.y_velocity / absvec(agent.x_velocity, agent.y_velocity) + + for neighbour in zones[2]: + disX = neighbour.point.getX() - agent.point.getX() + disY = neighbour.point.getY() - agent.point.getY() + + rX = disX / absvec(disX, disY) + rY = disY / absvec(disX, disY) + + dx += rX / absvec(rX, rY) + dy += rY / absvec(rX, rY) + + dx = 0.5*dx + dy = 0.5*dy + + # all zones empty + else: + dx = agent.x_velocity + dy = agent.y_velocity + + # randomness factor / error + dx += random.uniform(-agent.noise/2, agent.noise/2) + dy += random.uniform(-agent.noise/2, agent.noise/2) + + return [dx, dy] + + +# checks if neares food is in zor, if yes, eat it +# else checks if neares food is in zoa, +# and returns vector pointing in it´s direction +def check_food(agent, foods): + dX = 0 + dY = 0 + + if foods == []: + return [dX,dY] + + nearestFood = nearest_neighbour(agent, foods) + dis = distance(agent.point.getX(), nearestFood.point.getX(), agent.point.getY(), nearestFood.point.getY()) + + if dis <= agent.zor_r: + if visual: + nearestFood.point.undraw() + foods.remove(nearestFood) + agent.foodLevel += 1 + + elif dis <= agent.zoa_r: + disX = nearestFood.point.getX() - agent.point.getX() + disY = nearestFood.point.getY() - agent.point.getY() + + rX = disX / absvec(disX, disY) + rY = disY / absvec(disX, disY) + + dX = rX / absvec(rX, rY) + dY = rY / absvec(rX, rY) + + return [dX, dY] + + +# else checks if neares predator is in zoa, +# and returns vector pointing in it´s direction +def check_predator(agent, predators): + dX=0 + dY=0 + + if predators == []: + return [dX,dY] + + nearestPred = nearest_neighbour(agent, predators) + dis = distance(agent.point.getX(), nearestPred.point.getX(), agent.point.getY(), nearestPred.point.getY()) + + if dis <= agent.zoa_r: + disX = nearestPred.point.getX() - agent.point.getX() + disY = nearestPred.point.getY() - agent.point.getY() + + rX = disX / absvec(disX, disY) + rY = disY / absvec(disX, disY) + + dX = rX / absvec(rX, rY) + dY = rY / absvec(rX, rY) + + return [dX, dY] + + +# checks if neares prey is in zor, if yes, eat it +# else checks if neares prey is in zoa, +# and returns vector pointing in it´s direction +def check_prey(predator, agents): + dX = predator.x_velocity + dY = predator.y_velocity + + if agents == []: + return [dX,dY] + + nearestPrey = nearest_neighbour(predator, agents) + dis = distance(predator.point.getX(), nearestPrey.point.getX(), predator.point.getY(), nearestPrey.point.getY()) + + if dis <= predator.zor_r: + if visual: + nearestPrey.point.undraw() + nearestPrey.line.undraw() + agents.remove(nearestPrey) + + predator.hasEaten = True + + elif dis <= predator.zoa_r: + disX = nearestPrey.point.getX() - predator.point.getX() + disY = nearestPrey.point.getY() - predator.point.getY() + + rX = disX / absvec(disX, disY) + rY = disY / absvec(disX, disY) + + dX = rX / absvec(rX, rY) + dY = rY / absvec(rX, rY) + + return [dX, dY] + + +# return new velocity, taking food and predators into acount +def updateV_final(agent, matrix, foods, predators): + vc = updateV_couzin(agent, matrix) + vf = check_food(agent, foods) + vp = check_predator(agent, predators) + + vvX = vc[0] + agent.food_pref * vf[0] - agent.anti_pred * vp[0] + vvY = vc[1] + agent.food_pref * vf[1] - agent.anti_pred * vp[1] + + return [vvX, vvY] + +# ------------------------------------------------------------------ +# update functions + +#update agents +def update_couzin(agents, foods, predators, winWidth, winHeight, window): + # Velocity update + for agent in agents: + neigh_matrix = neigbour_in_zones(agent, agents, agent.zor_r, agent.zoo_r, agent.zoa_r, agent.blind_angle,) + agent.tempvelocity = updateV_final(agent, neigh_matrix, foods, predators) + + # move, draw + for agent in agents: + # test if in ragne of agent.turn_angle, if not rotate angle by maxTurn in + # direction of new direction + radTurn = math.radians(agent.turn_angle) + negRadTurn = math.radians(360-agent.turn_angle) + + # check if rotation is in range of turn-angle + agent = move_agent_couzin(agent, agent.turn_angle, radTurn, negRadTurn) + + # normalise direction vector to 1, and multiply by constant speed + x = agent.x_velocity/absvec(agent.x_velocity, agent.y_velocity) * agent.speed + agent.y_velocity = agent.y_velocity/absvec(agent.x_velocity, agent.y_velocity) * agent.speed + agent.x_velocity = x + + agent = checkBoundary(agent, winWidth, winHeight) + + # draw agent + if visual: + agent.drawLine() + + return agents + + +#update predator +def update_predator(predator, agents, winWidth, winHeight, window): + predator.tempvelocity = check_prey(predator, agents) + + radTurn = math.radians(predator.turn_angle) + negRadTurn = math.radians(360-predator.turn_angle) + + # check if rotation is in range of turn-angle + predator = move_agent_couzin(predator, predator.turn_angle, radTurn, negRadTurn) + + # normalise diection vector to 1, and multiply by constant speed + x = predator.x_velocity/absvec(predator.x_velocity, predator.y_velocity) * predator.speed + predator.y_velocity = predator.y_velocity/absvec(predator.x_velocity, predator.y_velocity) * predator.speed + predator.x_velocity = x + + predator = checkBoundary(predator, winWidth, winHeight) + + # draw predator + if visual: + predator.drawLine() + + return predator + + +# ------------------------------------------------------------------ +# next gen functions + +# mutate agent in order to get a child +def mutate(parent): + + point = Point(random.uniform(0,winWidth/2), random.uniform(0,winHeight/2)) + child = AcklandAgent(point, window) + + # give them a random starting direction + child.x_velocity = random.uniform(-1, 1) + child.y_velocity = random.uniform(-1, 1) + + child.zoa_r = random.uniform(parent.zoa_r - m_zoa_r, parent.zoa_r + m_zoa_r ) + child.zoa_r = max (child.zoa_r, math.sqrt(As/(2*math.pi)) ) + + child.zoo_r = random.uniform(parent.zoo_r - m_zoo_r, parent.zoo_r + m_zoo_r) + child.zoo_r = min( child.zoa_r ,max(child.zoo_r, child.zor_r)) + + child.speed = random.uniform(parent.speed - m_speed, parent.speed + m_speed) + child.speed = min( 5 ,max(child.speed, 1)) + + child.blind_angle = (360 - math.degrees(As/(child.zoa_r**2)) ) / 2 + child.turn_angle = math.degrees(Am/(2*(child.speed**2))) + + child.food_pref = random.uniform(parent.food_pref - m_food, parent.food_pref + m_food) + child.anti_pred = random.uniform(parent.anti_pred - m_pred, parent.anti_pred + m_pred) + + child.noise = random.uniform(parent.noise - m_noise, parent.noise + m_noise) + + return child + + +# -------------------------------------------------------------- +# get next generation food simulation + +# generate a "wheel of fortune" +# depending on foodLevel of agent: +# has eaten more <-> +# higher probability to get picked as parent +def generateWheel(agents): + wheel = [] + currentsum = 0 + for agent in agents: + currentsum += agent.foodLevel + wheel.append(currentsum) + return wheel + + +# use wheel to pick an agent to mutate +# in order to get a child +def pickParent(agents, wheel): + r = random.randrange(wheel[-1]) +1 + + for i in range(len(wheel)): + if r <= wheel[i]: + return agents[i] + + +# generate next Generation of agents for the food simulation +def nextGen_food(agents): + wheel = generateWheel(agents) + tempAgents = [] + + for i in range(agentNum): + if wheel[-1] == 0: + parent = pickParent_simple_random(agents) + else: + parent = pickParent(agents, wheel) + child = mutate(parent) + tempAgents.append(child) + + return tempAgents + +# -------------------------------------------------------------- +# get next generation predator simulation + +# just pick one of those that survived randomly as parent +def pickParent_simple_random(agents): + r = random.randrange(len(agents)) + return agents[r] + +# generate next Generation of agents for the predator simulation +def nextGen_pred(agents): + tempAgents = [] + + for i in range(agentNum): + parent = pickParent_simple_random(agents) + child = mutate(parent) + tempAgents.append(child) + + return tempAgents + +# -------------------------------------------------------------- +# main + +def main(): + + agents = [] + foods = [] + predators = [] + + # open csv to collect data + if writecsv: + with open(GENERATIONS,'w') as csvfile: + filewriter = csv.writer(csvfile, delimiter=',',quotechar='|',quoting = csv.QUOTE_MINIMAL) + filewriter.writerow(["Iterations","Speed","turn","food pref","zoa","zoo","blind","noise","antipred"]) + + with open(TIMESTEPS,'w') as csvfile: + filewriter = csv.writer(csvfile, delimiter=',',quotechar='|',quoting = csv.QUOTE_MINIMAL) + filewriter.writerow(["Generations","Timestep","x","y","x_velocity","y_velocity","foodlevel"]) + + widthTemp = winWidth + heightTemp = winHeight + + maxLife = 1000 + + global window + + # first generation , random + for i in range (agentNum): + agent = AcklandAgent(Point(random.uniform(0,winWidth/2), random.uniform(0,winHeight/2)) , window) + agents.append(agent.create_random_agent(rr, As, Am)) + + for j in range(generations): + if isPred or Both: + for i in range(predNum): + predator = Predator(Point(random.uniform(0,winWidth), random.uniform(0,winHeight)) , window) + predators.append(predator.create_random_predator(rr, As)) + + # -----ausgabe------ + print ("generation " + str(j)) + + avgSpeed = 0 + avgTurn = 0 + avgFood = 0 + avgPred = 0 + avgZOA = 0 + avgZOO = 0 + avgBlind = 0 + avgNoise = 0 + + maxSpeed = 0 + maxTurnAngle = 0 + for agent in agents: + avgSpeed += agent.speed + avgPred += agent.anti_pred + avgTurn += agent.turn_angle + avgFood += agent.food_pref + avgZOA += agent.zoa_r + avgZOO += agent.zoo_r + avgBlind += agent.blind_angle + avgNoise += agent.noise + + if agent.speed > maxSpeed: + maxSpeed = agent.speed + if agent.turn_angle > maxTurnAngle: + maxTurnAngle = agent.turn_angle + + avgSpeed /= agentNum + avgTurn /= agentNum + avgFood /= agentNum + avgZOA /= agentNum + avgZOO /= agentNum + avgBlind /= agentNum + avgNoise /= agentNum + avgPred /= agentNum + + if writecsv: + for agent in agents: + with open(GENERATIONS,'a') as csvfile: + filewriter = csv.writer(csvfile, delimiter=',',quotechar='|',quoting = csv.QUOTE_MINIMAL) + filewriter.writerow([j, agent.speed, agent.turn_angle, agent.food_pref, agent.zoa_r, agent.zoo_r, agent.blind_angle, agent.noise, agent.anti_pred]) + + # -----ausgabe ende------ + + if isFood or Both: + widthTemp = winWidth/2 + heightTemp = winHeight/2 + else: + widthTemp = winWidth + heightTemp = winHeight + + # main loop + for timeStep in range(maxTime): + # every 50 timesteps collect data + if timeStep % 50 == 0 and j % 50 == 0 and writecsv: + for agent in agents: + with open(TIMESTEPS,'a') as csvfile: + filewriter = csv.writer(csvfile, delimiter=',',quotechar='|',quoting = csv.QUOTE_MINIMAL) + filewriter.writerow([j,timeStep,agent.point.getX(),agent.point.getY(),agent.x_velocity,agent.y_velocity,agent.foodLevel]) + + + # generate food and release agents at 1/4 of maxtime + if timeStep == maxTime * 0.025 and (isFood or Both): + foods = [] + for j in range (foodCluster): + x = random.uniform(widthTemp + clusterradius, winWidth-clusterradius) + y = random.uniform(heightTemp + clusterradius, winHeight- clusterradius) + for k in range (clusterSize): + xk = random.uniform(x - clusterradius, x + clusterradius) + yk = random.uniform(y - clusterradius, y + clusterradius) + f = Food(Point(xk, yk)) + if visual: + f.point.setFill("blue") + f.point.draw(window) + foods.append(f) + widthTemp = winWidth + heightTemp = winHeight + + # exit generation if enough food has been eaten + if timeStep >= maxTime * 0.025 and isFood: + if len(foods) <= 1.0/8 * clusterSize * foodCluster: + break + + # start predating / update predators + if timeStep >= maxTime * 0.025 and (isPred or Both): + if predators == []: + break + else: + # delete current predator if it has eaten + if(predators[-1].hasEaten or predators[-1].lifeTime >= maxLife): + if visual: + predators[-1].line.undraw() + predators.pop() + + else: + predators[-1] = update_predator(predators[-1], agents, winWidth, winHeight, window) + predators[-1].lifeTime += 1 + + # update agents + agents = update_couzin(agents, foods, predators, widthTemp, heightTemp, window) + + + print ("eating time: " + str(timeStep)) + + # new windos and new generation + if visual: + window.close() + window = GraphWin("Window", winWidth, winHeight) + if isFood or Both: + agents = nextGen_food(agents) + elif isPred: + agents = nextGen_pred(agents) + + +main()