3
\$\begingroup\$

I was not sure if I should post about 200 lines here. I want to make this ant simulation faster. The bottleneck is at Ant.checkdistancebetweenantsandfood().

It would be great if you have some hints for some cleaner or more efficient code. I hope this is the right place to show this code.

import random
import time
import datetime


# TODO
# food - 100 portions                   okay
# bring food to the hill                okay
# bring food to the hill - direct way
# use food over time
# die if food is out
# bear if enough food is there
# trail                                 okay
# trail disolves over time
# opponent - different properties
# new food
# inside ant hill ?
# make calculations faster

#############################
# PLOT - BEGIN
#############################

import matplotlib.pyplot as plt
import numpy as np


fig, ax = plt.subplots()

# field size
size = 30

# black ants
x1 = [0]
y1 = [0]
points, = ax.plot(x1, y1, marker='o', linestyle='None', alpha=0.3,
                  markersize=5, c='black')

# ant hill
x2 = [0]
y2 = [0]
points2, = ax.plot(x2, y2, marker='o', linestyle='None', alpha=0.6,
                   markersize=30, c='brown')


# food
foodamount = 220
foodposx = []
foodposy = []
for i in range(foodamount):
    foodposx.append(random.randint(0, 2*size)-size)
    foodposy.append(random.randint(0, 2*size)-size)
points3, = ax.plot(foodposx, foodposy, marker='o', linestyle='None',
                   alpha=0.6, markersize=5, c='green')
foundenoughfood = 20
anthillfood = 0

# movement
speed = .4

# trail
trailx = [0]
traily = [0]
trail, = ax.plot(trailx, traily, marker='o', linestyle='None', alpha=0.6,
                   markersize=5, c='orange')

#trailduration = []

bearingspeed = 100  # higher value = slower bearing

# red ants
#...

ax.set_xlim(-size, size)
ax.set_ylim(-size, size)
new_x = np.random.randint(10, size=1)
new_y = np.random.randint(10, size=1)
points.set_data(new_x, new_y)
plt.pause(.0000000000001)

#############################
# PLOT - END
#############################


class Antqueen:

    counter = 2

    def __init__(self, name):
        """Initializes the data."""
        self.name = name
        print("(Initializing {0})".format(self.name))

    def bear(self):
        # print Antqueen.counter, 'ants born'
        self.name = 'ant_' + str(Antqueen.counter)
        Antqueen.counter = Antqueen.counter + 1
        ants[self.name] = None
        globals()[self.name] = Ant(self.name)


class Ant:

    population = 0

    def __init__(self, name):
        """Initializes the data."""
        self.name = name
        self.food = 10
        self.posx = 0.0
        self.posy = 0.0
        #print("(Initializing {0})".format(self.name))

        Ant.population += 1

    def die(self):
        """I am dying."""
        print("{0} is dead!".format(self.name))

        Ant.population -= 1

        if Ant.population == 0:
            print("{0} was the last one.".format(self.name))
        else:
            print("There are still {0:d} ants working.".format(Ant.population))

    def sayHi(self):
        print("Hello, call me {0}.".format(self.name))


    # move + trail
    def move(self):
        if globals()[name].food < foundenoughfood:
            self.posx = self.posx + speed*(random.random()-random.random())
            self.posy = self.posy + speed*(random.random()-random.random())
        # found enough food
        # go back to home
        else:
            # close to the ant hill
            # lay down some food to the ant hill
            if abs(self.posx) < .5:
                if abs(self.posy) < .5:
                    global anthillfood
                    anthillfood = anthillfood + globals()[name].food - 10
                    #print "anthillfood: ", anthillfood
                    # lay down everything but 10
                    globals()[name].food = 10
            # far away from the ant hill
            # set trail
            trailx.append(self.posx)
            traily.append(self.posy)
            # draw the trail                                                    ###
            trail.set_data(trailx, traily)
            # move towards the ant hill
            vecx = -self.posx
            vecy = -self.posy
            len_vec = np.sqrt(vecx**2+vecy**2)
            self.posx = self.posx + vecx*1.0/len_vec/3 + speed*(random.random()-random.random())/1.5
            self.posy = self.posy + vecy*1.0/len_vec/3 + speed*(random.random()-random.random())/1.5


    def eat(self, name, foodnumber, foodposx, foodposy):
        if foodtable[foodnumber][1] > 0:
            # food on ground decreases
            foodtable[foodnumber][1] = foodtable[foodnumber][1] - 1
            # food in ant increases
            globals()[name].food = globals()[name].food + 1
            #print name, globals()[name].food, "food"
        # food is empty
        if foodtable[foodnumber][1] == 0:
            #print "foodposx: ", foodposx
            del foodposx[foodnumber]
            del foodposy[foodnumber]
            points3.set_data(foodposx, foodposy)


    @classmethod
    def howMany(cls):
        """Prints the current population."""
        print("We have {0:d} ants.".format(cls.population))


    def checkdistancebetweenantsandfood(self):
        for name in ants.keys():
            for i in range(len(foodposx)-1):
                # measure distance between ant and food
                if -1 < globals()[name].posx - foodposx[i] < 1:
                    if -1 < globals()[name].posy - foodposy[i] < 1:
                        globals()[name].eat(name, i, foodposx, foodposy)
                # slower
                #distance = np.sqrt((globals()[name].posx - foodposx[i])**2 + (globals()[name].posy - foodposy[i])**2)
                #if distance < 1:
                #    globals()[name].eat(name, i, foodposx, foodposy)


# generate ant queen
antqueen = Antqueen("antqueen")

# generate some ants
ants = {'ant_1': None}
for name in ants.keys():
    globals()[name] = Ant(name)

# amount of food
foodtable = []
for i in range(foodamount):
    foodtable.append([i, 10])

#############################
# start simulation
#############################


for i in range(100000):

    # move all ants
    for name in ants.keys():
        globals()[name].move()

    # generate more ants
    if (i % bearingspeed == 0):
        antqueen.bear()

    # plot ants
    allposx = []
    allposy = []
    for name in ants.keys():
        allposx.append(globals()[name].posx)
        allposy.append(globals()[name].posy)

    points.set_data(allposx, allposy)
    plt.draw()
    #plt.pause(0.000000000001)  # draw is faster

    # ants find food
    for name in ants.keys():
        globals()[name].checkdistancebetweenantsandfood()
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Have you tried the "slower" code without calling np.sqrt()? (In other words, work using the square of the distance.) \$\endgroup\$ Jan 13, 2014 at 9:43
  • \$\begingroup\$ The square of the distance? Do you mean distance**(0.5)? \$\endgroup\$
    – kame
    Jan 13, 2014 at 10:47
  • 1
    \$\begingroup\$ dist_sq = (globals()[name].posx - foodposx[i])**2 + (globals()[name].posy - foodposy[i])**2; if dist_sq < 1 * 1: eat(…) \$\endgroup\$ Jan 13, 2014 at 10:50
  • 1
    \$\begingroup\$ globals() is meant for special uses only. Why don't you simply store the Ant objects in the ants dictionary? \$\endgroup\$ Jan 13, 2014 at 12:29
  • 1
    \$\begingroup\$ This could be a fun week-end CR challenge \$\endgroup\$
    – konijn
    Jan 19, 2014 at 14:16

2 Answers 2

2
\$\begingroup\$

It looks like you're looping through the ants twice, once inside checkdistancebetweenantsandfood() and once when you call it. That seems like it's probably wrong.

Method:

    def checkdistancebetweenantsandfood(self):
    for name in ants.keys():
        for i in range(len(foodposx)-1):
            # measure distance between ant and food
            if -1 < globals()[name].posx - foodposx[i] < 1:
                if -1 < globals()[name].posy - foodposy[i] < 1:
                    globals()[name].eat(name, i, foodposx, foodposy)

Call:

    # ants find food
    for name in ants.keys():
        globals()[name].checkdistancebetweenantsandfood()

Other optimizations

Another possible optimization is to exit the loop if a food source is found. Presumably the ants can only eat from one food source at a time.

Example:

    def checkdistancebetweenantsandfood(self):
    for name in ants.keys():
        for i in range(len(foodposx)-1):
            # measure distance between ant and food
            if -1 < globals()[name].posx - foodposx[i] < 1:
                if -1 < globals()[name].posy - foodposy[i] < 1:
                    globals()[name].eat(name, i, foodposx, foodposy)
                    break

If there are many food sources, you might try putting the food sources into a grid based on their position. That way the ants only need to check the for food sources in adjacent cells instead of having to loop through all of the food sources.

\$\endgroup\$
1
\$\begingroup\$

PEP-8 suggest the following about function names:

Function names should be lowercase, with words separated by underscores as necessary to improve readability.

check_distance_between_ants_and_food would follow this guidance and would be easier to read.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.