"""" Patrick E. Lanigan CS 1573 - Homework 02 Problems 2.7, 2.8, 2.9 Based on agents2.py, which itself was adapted from the AIMA website. Implements a performance measuring environment simulator for the 2D vacuum cleaner world pictured in figure 2.2 on page 34 of AIMA. Also implements a random agent, simple reflex agent and a model based reflex agent which tracks the state of the environment. The agents correctly perceive their location and their location's status. The performance measure awards points for each clean square at each time step, and penalizes for movements. The amount of reward and penalty can be specified in the environment constructor. The values may be set to zero (ie to remove the penalty). The compare_agents function can be used to compare the performance measures of the agents. The TableDrivenVacuumAgent was left unimplemented. Since the precept sequence in this case would be 1000 time-steps long, the table would require an unreasonable number of entries to complete. """ from utils import * import random, copy, operator loc_A, loc_B = (0, 0), (1, 0) # The two locations for the Vacuum world #______________________________________________________________________________ class Object: """This represents any physical object that can appear in an Environment. You subclass Object to get the objects you want. Each object can have a .__name__ slot (used for output only).""" def __repr__(self): return '<%s>' % getattr(self, '__name__', self.__class__.__name__) def is_alive(self): """MOD: Objects should never die""" return True class Agent(Object): """An Agent is a subclass of Object with one required slot, .program, which should hold a function that takes one argument, the percept, and returns an action. (What counts as a percept or action will depend on the specific environment in which the agent exists.) Note that 'program' is a slot, not a method. If it were a method, then the program could 'cheat' and look at aspects of the agent. It's not supposed to do that: the program can only look at the percepts. An agent program that needs a model of the world (and of the agent itself) will have to build and maintain its own model. There is an optional slot, .performance, which is a number giving the performance measure of the agent in its environment.""" def __init__(self): def program(percept): return raw_input('Percept=%s; action? ' % percept) self.program = program self.alive = True def TraceAgent(agent): """Modify the agent's program to print its input and output. This will let you see what the agent is doing in the environment.""" old_program = agent.program def new_program(percept): action = old_program(percept) print '%s perceives %s and does %s' % (agent, percept, action) return action agent.program = new_program return agent #______________________________________________________________________________ #class TableDrivenVacuumAgent(Agent): # """This agent selects an action based current precept using a table # lookup based on the percept sequence. Since the precept sequence # in this case would be 1000 time steps long, it would be unreasonable # to implement the entire table.""" #table = {((loc_A, 'Clean'),): 'Right', # ((loc_A, 'Dirty'),): 'Suck', # ((loc_B, 'Clean'),): 'Left', # ((loc_B, 'Dirty'),): 'Suck', # ((loc_A, 'Clean'), (loc_A, 'Clean')): 'Right', # ((loc_A, 'Clean'), (loc_A, 'Dirty')): 'Suck', # # ... # ((loc_A, 'Clean'), (loc_A, 'Clean'), (loc_A, 'Clean')): 'Right', # ((loc_A, 'Clean'), (loc_A, 'Clean'), (loc_A, 'Dirty')): 'Suck', # # ... # } # def __init__(self): # "Supply as table a dictionary of all {percept_sequence:action} pairs." # ## The agent program could in principle be a function, but because # ## it needs to store state, we make it a callable instance of a class. # percepts = [] # def program(percept): # percepts.append(percept) # action = self.table.get(tuple(percepts)) # return action # self.program = program class RandomVacuumAgent(Agent): "An agent that chooses an action at random, ignoring all percepts." def __init__(self, actions = ['Right', 'Left', 'Suck', 'NoOp']): self.program = lambda percept: random.choice(actions) class ReflexVacuumAgent(Agent): "A reflex agent for the two-state vacuum environment. [Fig. 2.8]" def __init__(self): def program((location, status)): if status == 'Dirty': return 'Suck' elif location == loc_A: return 'Right' elif location == loc_B: return 'Left' self.program = program class ModelBasedVacuumAgent(Agent): "An reflex agent that keeps track of what locations are clean or dirty." def __init__(self): model = {loc_A: None, loc_B: None} def program((location, status)): model[location] = status # Update the model here if model[loc_A] == model[loc_B] == 'Clean': return 'NoOp' elif status == 'Dirty': return 'Suck' elif location == loc_A: return 'Right' elif location == loc_B: return 'Left' self.program = program #______________________________________________________________________________ class Environment: """Abstract class representing an Environment. 'Real' Environment classes inherit from this. Your Environment will typically need to implement: percept: Define the percept that an agent sees. execute_action: Define the effects of executing an action. Also update the agent.performance slot. The environment keeps a list of .objects and .agents (which is a subset of .objects). Each agent has a .performance slot, initialized to 0. Each object has a .location slot, even though some environments may not need this.""" object_classes = [] ## List of classes that can go into environment def __init__(self,): self.objects = [] self.agents = [] def add_object(self, object, location = random.choice([loc_A, loc_B])): """Add an object to the environment, setting its location. Also keep track of objects that are agents. Shouldn't need to override this.""" object.location = location self.objects.append(object) if isinstance(object, Agent): object.performance = 0 self.agents.append(object) return self def execute_action(self, agent, action): "Change the world to reflect this action. Override this." abstract() def is_done(self): "By default, we're done when we can't find a live agent." return not find_if(method('is_alive'), self.agents) def percept(self, agent): "Return the percept that the agent sees at this point. Override this." abstract() def run(self, steps = 1000): """Run the Environment for given number of time steps.""" for step in range(steps): if self.is_done(): return self.step() def step(self): """Run the environment for one time step. If the actions and exogenous changes are independent, this method will do. If there are interactions between them, you'll need to override this method.""" if not self.is_done(): actions = [agent.program(self.percept(agent)) for agent in self.agents] for (agent, action) in zip(self.agents, actions): self.execute_action(agent, action) #______________________________________________________________________________ class VacuumEnvironment(Environment): """This environment has two locations, A and B. Each can be Dirty or Clean. The agent perceives its location and the location's status. This serves as an example of how to implement a simple Environment.""" def __init__(self): "Randomly initializes the clean/dirty status of the two squares" Environment.__init__(self) self.status = {loc_A:random.choice(['Clean', 'Dirty']), loc_B:random.choice(['Clean', 'Dirty'])} self.reward = 1 self.penalty = 0 def __init__(self, status_A, status_B, reward, penalty): "MOD: Constructor to specify the clean/dirty status of the two squares" Environment.__init__(self) self.status = {loc_A:status_A, loc_B:status_B } self.reward = reward self.penalty = penalty def execute_action(self, agent, action): """Change agent's location and/or location's status; track performance. Score 1 for each clean square; no movement penalty""" if action == 'Right': agent.location = loc_B agent.performance -= self.penalty elif action == 'Left': agent.location = loc_A agent.performance -= self.penalty elif action == 'Suck': self.status[agent.location] = 'Clean' # MOD: update performace measure for each clean square at each time step if self.status[loc_A] == 'Clean': agent.performance += self.reward; if self.status[loc_B] == 'Clean': agent.performance += self.reward; def percept(self, agent): "Returns the agent's location, and the location status (Dirty/Clean)." return (agent.location, self.status[agent.location]) #______________________________________________________________________________ def compare_agents(envs, AgentFactories, init_loc, steps): """See how well each of several agents do in n instances of an environment. Pass in a factory (constructor) for environments, and several for agents. Create n instances of the environment, and run each agent in copies of each one. Return a list of (agent-name, scores, avg) tuples.""" results = [] for A in AgentFactories: scores = [] total = 0 for env in copy.deepcopy(envs): agent = A() env.add_object(agent, init_loc) env.run(steps) total += agent.performance scores.append(agent.performance); # MOD: save each score for later results.append((agent, scores, total / float(len(envs)))) return results #______________________________________________________________________________ ### Output results header ### print "\n" + ("-" * 72) print """Output takes the following form: \t\t[Raw Scores*]\tAverage Score \t*Raw scores are listed by configuration... \t(Clean, Clean), (Clean, Dirty), (Dirty, Clean), (Dirty, Dirty)""" ### Experiments for 2.7 & 2.8 ### agents = [RandomVacuumAgent, ReflexVacuumAgent, ModelBasedVacuumAgent] envs = [VacuumEnvironment('Clean', 'Clean', 1, 0), VacuumEnvironment('Clean', 'Dirty', 1, 0), VacuumEnvironment('Dirty', 'Clean', 1, 0), VacuumEnvironment('Dirty', 'Dirty', 1, 0)] results_A = compare_agents(envs, agents, loc_A, 1000) results_B = compare_agents(envs, agents, loc_B, 1000) ### Output results of experiments ### print "\n:::Problems 2.7 & 2.8:::" print "\nFrom location (%s, %s)" % loc_A print "\t%-23s\t%-16s\t%s" % results_A[0] print "\t%-23s\t%-16s\t%s" % results_A[1] print "\t%-23s\t%-16s\t%s" % results_A[2] print "\nFrom location (%s, %s)" % loc_B print "\t%-23s\t%-16s\t%s" % results_B[0] print "\t%-23s\t%-16s\t%s" % results_B[1] print "\t%-23s\t%-16s\t%s" % results_B[2] ### Experiments for 2.9b ### agents = [RandomVacuumAgent, ReflexVacuumAgent, ModelBasedVacuumAgent] envs = [VacuumEnvironment('Clean', 'Clean', 1, 1), VacuumEnvironment('Clean', 'Dirty', 1, 1), VacuumEnvironment('Dirty', 'Clean', 1, 1), VacuumEnvironment('Dirty', 'Dirty', 1, 1)] results_A = compare_agents(envs, agents, loc_A, 1000) results_B = compare_agents(envs, agents, loc_B, 1000) ### Output results of experiments ### print "\n:::Problem 2.9b:::" print "\nFrom location (%s, %s)" % loc_A print "\t%-23s\t%-16s\t%s" % results_A[0] print "\t%-23s\t%-16s\t%s" % results_A[1] print "\t%-23s\t%-16s\t%s" % results_A[2] print "\nFrom location (%s, %s)" % loc_B print "\t%-23s\t%-16s\t%s" % results_B[0] print "\t%-23s\t%-16s\t%s" % results_B[1] print "\t%-23s\t%-16s\t%s" % results_B[2]