Skip to content

Commit

Permalink
Cleaned up, improved comments
Browse files Browse the repository at this point in the history
  • Loading branch information
ChadFulton committed Feb 19, 2013
1 parent f71b0de commit 6b24b83
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 45 deletions.
53 changes: 32 additions & 21 deletions ant.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,44 @@ def __init__(self, world, location, direction = None):
self.direction = direction or self.FORWARDS
self.history = [] # a list of trips
self.trip = [location] # a list of nodes forming a complete cycle
self.return_trip = [location]
self.return_length = None
self.return_trip = [location] # the complete trip cycle, without loops
self.return_length = None # the final return trip length

def move(self):
"""Move the ant"""
#x = self.trip[:]
self.trip.append(self.choose())

#print map(reverse_node, x), '->', map(reverse_node, self.trip)
#print map(reverse_node, self.return_trip)
# 1. Move the ant
next = self.choose()
self.trip.append(next)

# Add the return trip information
# 2. Let the observers know there the ant moved
self.notify()

# 3. Update the ant's memory (add return trip information)
if self.direction == self.FORWARDS:
self.return_trip.append(self.trip[-1])
# check for and remove cycles
# Check for and remove cycles in the return trip
index = self.return_trip.index(self.trip[-1])
if index != len(self.return_trip)-1:
self.return_trip = self.return_trip[:index+1]

# Let the observers know there the ant moved (only on the return journey)
if self.direction == self.BACKWARDS:
self.notify()

# If we're headed for the destination (FOOD)
# 4. Check if we reached a destination and need to turn the ant around
# If we made it to the destination (FOOD)
if self.direction == self.FORWARDS and self.trip[-1] == self.world.TERMINAL_NODE:
self.direction = self.BACKWARDS
# need to remove the last node here, which is the TERMINAL_NODE by
# definition, since we're already there
# We need to remove the last node on the return trip, which is the
# TERMINAL_NODE by definition, since we're already there
self.return_trip.pop()
# We have to save the return length here because when we traverse the
# path back to the nest, we'll lose the total length of the trip when
# we pop() the list.
self.return_length = len(self.return_trip)

# If we made it back home (NEST)
if self.direction == self.BACKWARDS and self.trip[-1] == self.world.SOURCE_NODE:
self.direction = self.FORWARDS
# Save the trip just made, and reset the current trip to an empty one
self.history.append(self.trip)
self.trip = [self.trip[-1]]
self.return_trip = [self.trip[0]]
Expand All @@ -56,7 +60,9 @@ def move(self):
def choose(self):
"""Select the next node the ant travels to"""

# If we're going towards the destination, make a probabilistic choice
if self.direction == self.FORWARDS:

# Get the pheromones on the arcs to each possible destination node
pheromones = self.world.get_pheromones(self.trip[-1])

Expand All @@ -65,21 +71,26 @@ def choose(self):
if len(self.trip) > 1 and self.trip[-2] in pheromones and len(pheromones) > 1:
del pheromones[self.trip[-2]]

nodes = pheromones.keys()
# Modify the pheromone levels to reflect pheromone amplification
levels = map(lambda i: i**settings.alpha, pheromones.values())
ranges = map(lambda i: float(sum(levels[:i+1])), range(len(levels)))

# Map the pheromone levels of each node to cut points relative to the
# total pheromone placed on all nodes together.
cuts = map(lambda i: float(sum(levels[:i+1])), range(len(levels)))

# Get a random draw from a standard uniform distribution
draw = uniform(0, sum(levels))

# Get the number of the destination node
index = [i for i in range(len(ranges)) if ranges[i] > draw][0]
# Get the index of the cut point that corresponds to the random draw
index = [i for i in range(len(cuts)) if cuts[i] > draw][0]

node = nodes[index]
# Get a mapping from the index of the cut points to the node ids
nodes = pheromones.keys()

#if self.trip[-1] == NEST:
# print nodes, levels, ranges, draw, node
# Map the index of the cut point to the node it represents
node = nodes[index]

# If we're going back home, just go back the way we came
else:
node = self.return_trip.pop()

Expand Down
2 changes: 1 addition & 1 deletion settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def reverse_node(node):
deposit = 1 # amount of pheromone to deposit
autocatalysis = False # is deposit inversely related to found path length?
alpha = 1 # pheromone amplification
rho = 0.00 # evaporation
rho = 0.00 # evaporation rate

# Graphs
double_bridge = {
Expand Down
48 changes: 34 additions & 14 deletions simulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def simulate(n, T, graph, source_node, terminal_node):
return world

def main():
"""Function called by running simulate.py on the command line"""
t0 = time.time()

p = []
Expand All @@ -50,22 +51,41 @@ def main():
print ''

if __name__ == "__main__":
# Set up the command line arguments
parser = argparse.ArgumentParser(description='Run Ant Colony Optimization simulation.')
parser.add_argument('-i', help='Number of trials (default=%d)' % settings.trials, type=int, default = settings.trials)
parser.add_argument('-n', help='Number of ants (default=%d)' % settings.n, type=int, default = settings.n)
parser.add_argument('-T', help='Number of steps each ant takes (default=%d)' % settings.T, type=int, default = settings.T)
parser.add_argument('-d', '--deposit', help='Amount of pheromone deposited at each step (default=%.1f)' % settings.deposit, type=float, default = settings.deposit)
parser.add_argument('-a', '--alpha', help='pheromone amplification parameter (default=%.1f)' % settings.alpha, type=float, default = settings.alpha)
parser.add_argument('-r', '--rho', help='Rate of pheromone evaporation (default=%.1f)' % settings.rho, type=float, default = settings.rho)
parser.add_argument('-c', '--autocatalysis', help='Is deposit inversely related to found path length? (default=%s)' % str(settings.autocatalysis), action="store_true", default = settings.autocatalysis)
parser.add_argument('-i',
help='Number of trials (default=%d)' % settings.trials,
type=int, default = settings.trials
)
parser.add_argument('-n',
help='Number of ants (default=%d)' % settings.n,
type=int, default = settings.n
)
parser.add_argument('-T',
help='Number of steps each ant takes (default=%d)' % settings.T,
type=int, default = settings.T
)
parser.add_argument('-d', '--deposit',
help='Amount of pheromone deposited at each step (default=%.1f)' % settings.deposit,
type=float, default = settings.deposit
)
parser.add_argument('-a', '--alpha',
help='pheromone amplification parameter (default=%.1f)' % settings.alpha,
type=float, default = settings.alpha
)
parser.add_argument('-r', '--rho',
help='Rate of pheromone evaporation (default=%.1f)' % settings.rho,
type=float, default = settings.rho
)
parser.add_argument('-c', '--autocatalysis',
help='Is deposit inversely related to found path length? (default=%s)' % str(settings.autocatalysis),
action="store_true", default = settings.autocatalysis
)
args = parser.parse_args()

settings.trials = args.i
settings.n = args.n
settings.T = args.T
settings.deposit = args.deposit
settings.alpha = args.alpha
settings.rho = args.rho
settings.autocatalysis = args.autocatalysis
# Modify the global settings based on the given arguments
for name in ['i', 'n', 'T', 'deposit', 'alpha', 'rho', 'autocatalysis']:
setattr(settings, name, getattr(args, name))

# Initialize the simulation
main()
34 changes: 25 additions & 9 deletions world.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ def __init__(self, graph, source_node, terminal_node):
self.current = self.base.copy() # pheromone changes for the period
self.history = [] # history of pheromone settings for each period
self.memo = { arc:1 for arc in self.arcs } # running tally of pheromones on nodes
self.memo_history = [] # history of runny tally for each period
self.path_lengths = {}
self.memo_history = [] # history of running tally for each period
self.path_lengths = {} # history of path lengths found by the ants

def advance(self):
"""Advance to the next time period"""
for arc, level in self.current.iteritems():
# add the new level
self.memo[arc] += level
Expand All @@ -40,25 +41,40 @@ def advance(self):
self.current = self.base.copy()

def get_ant(self, i):
"""Get the ith ant"""
return self.ants[i]

def get_pheromones(self, node):
"""Get the pheromones on all arcs proceeding from a given node"""
pheromones = {}
for destination in self.graph[node]:
pheromones[destination] = self.memo[node | destination]
return pheromones

def populate(self, ant):
"""Add an ant to the world population"""
self.ants.append(ant)
ant.attach(self)
return len(self.ants)

def update(self, ant):
#print reverse_node(ant.trip[-1]), '|', reverse_node(ant.trip[-2])
level = settings.deposit / ant.return_length if settings.autocatalysis else settings.deposit
self.current[ant.trip[-1] | ant.trip[-2]] += level
"""Observer pattern update method
if ant.trip[-2] == self.TERMINAL_NODE:
if ant.return_length not in self.path_lengths:
self.path_lengths[ant.return_length] = 0
self.path_lengths[ant.return_length] += 1
Update pheromone levels when an ant moves across an arc, and, after the ant
completes the journey to the destination (FOOD), store the length of the
path they found.
"""

# Only want to update pheromone on the return journey
if ant.direction == ant.BACKWARDS:
# Check if we want the amount of pheromone to inversely correspond to path length
level = settings.deposit / ant.return_length if settings.autocatalysis else settings.deposit
self.current[ant.trip[-1] | ant.trip[-2]] += level

# Save the path length if the ant just left the TERMINAL_NODE
# (i.e. trip[-1] is the first node the ant went to after leaving the
# TERMINAL_NODE, so trip[-2] would be the TERMINAL_NODE)
if ant.trip[-2] == self.TERMINAL_NODE:
if ant.return_length not in self.path_lengths:
self.path_lengths[ant.return_length] = 0
self.path_lengths[ant.return_length] += 1

0 comments on commit 6b24b83

Please sign in to comment.