# Copyright (c) 2009 Tim Freeman # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # (This is the standard MIT License, copied from # http://www.opensource.org/licenses/mit-license.php on 24 Apr 2007.) #desc Infer an agent's voluntary actions and perceptions. The inputs are #desc training data to extrapolate from and observations of the world #desc (including the agent). Uses physics.py. from bits import ensure import bits import physics from physics import Speed_prior_explanation, Generic_physics_problem class State(object): __slots__ = ["nonmind_state", "mind_states", "behaviors", "perceptions", "timestep"] def __init__(self, timestep): self.timestep = timestep # nonmind_state is a tape describing the world external to the # minds of all of the agents. It's mostly a consequence of # the nonmind_state from previous timesteps. # mind_state is a dict mapping agent id's (other than the AI) # to tapes describing what the AI thinks that agent is thinking. # It's mostly a consequence of the mind_states from previous timesteps. # Agent id's are tapes. self.mind_states = {} # behaviors maps agent id's (including the AI's) to tapes # describing what they did. The AI's behaviors are known, and # the behaviors of others are a consequence of their mind state in # this timestep. self.behaviors = {} self.nonmind_state = None self.perceptions = {} def __repr__(self): return "State(timestep=%r, nonmind_state=%r, "\ "mind_states=%r, behaviors=%r, perceptions=%r)" % ( self.timestep, self.nonmind_state, self.mind_states, self.behaviors, self.perceptions) # A Constraint says something about a perception or behavior in the # past. Specifically, it gives a channel number and a signal strength # on that channel. Think of a channel number as a neural signal or # biochemical signal. class Constraint(object): __slots__ = ["agent_id", "channel", "strength"] def __init__(self, agent_id, channel, strength): self.agent_id = agent_id self.channel = channel assert type(channel) == int self.strength = strength def check(self, signalvec, program): ensure(program.nth(signalvec, self.channel) == self.strength) class Mind_body_explanation(Speed_prior_explanation): def __init__(self, compute_behavior = None, nonmind_physics = None, compute_perceptions = None, mind_physics = None, explanations = None, name="Unnamed"): if explanations is None: explanations = [compute_behavior, nonmind_physics, compute_perceptions, mind_physics] Speed_prior_explanation.__init__(self, explanations=explanations, name=name) def compute_behavior_program(self): return self.explanations[0] def nonmind_physics_program(self): return self.explanations[1] def compute_perceptions_program(self): return self.explanations[2] def mind_physics_program(self): return self.explanations[3] # Return a dict of perceptions. A perception is a tape. It maps # agent id's to what we think that agent will perceive from the # given nonmind_state. def compute_perceptions(self, nonmind_state, all_ids): p = self.compute_perceptions_program() perceptions = p.run(nonmind_state) result = {} for i in range(len(all_ids)): result[all_ids[i]] = p.nth(perceptions, i) return result def compute_behavior(self, agent_id, mind_state): # Only makes sense if the agent_id isn't the id for the AI. p = self.compute_behavior_program() return p.run(p.build_tuple(agent_id, mind_state)) def initial_nonmind_physics(self): p = self.nonmind_physics_program() return p.run(p.null_value()) def nonmind_physics(self, nonmind_state, behaviors, all_ids): # Behaviors is a dict mapping agent id's to behaviors. # all_ids is an ordered list of all agent id's, including the # AI, in the standard order. # This returns the new nonmind_state. p = self.nonmind_physics_program() return p.run(p.build_tuple(*([nonmind_state] + [behaviors[id] for id in all_ids]))) def initial_mind_physics(self, agent_id): p = self.mind_physics_program() return p.run(p.build_tuple(agent_id)) def mind_physics(self, agent_id, perception, mind_state): # Returns the new mind_state. p = self.mind_physics_program() return p.run(p.build_tuple(agent_id, perception, mind_state)) class Mind_body_problem(Generic_physics_problem): def __init__(self, ai_perception=None, ai_behavior=None, other_perception=None, other_behavior=None, other_agent_ids=None, ai_agent_id=None, explanation_class = Mind_body_explanation): Generic_physics_problem.__init__(self, explanation_class) # ai_perception is a vector of tapes. The index is a timestep # and the value is a tape describing what the AI perceived at # that time. self.ai_perception = ai_perception assert self.ai_perception is not None # ai_behavior is a vector of tapes. The index is a timestep # and the value is a tape describing what the AI remembers doing at # that time. self.ai_behavior = ai_behavior assert self.ai_behavior is not None # The question we're trying to answer is "how do we react to # the most recent perception?", so we have one more perception than # behavior. assert len(ai_perception) == len(ai_behavior) + 1 # other_perception is what the AI is given about the perceptions # of other entities in the world. It's a dict; the key is # a timestep and the value is a vector of Constraint objects # saying what we know about the perceptions at that time. self.other_perception = other_perception assert self.other_perception is not None # other_behavior is what the AI is given about the behavior # of other entities in the world. It's a dict; the index is # a timestep and the value is a vector of Constraint objects # saying what we know about the behaviors at that time. self.other_behavior = other_behavior assert self.other_behavior is not None # other_agent_ids is a set of agent id's. # The AI isn't one of the other agents. assert ai_agent_id not in other_agent_ids self.other_agent_ids = other_agent_ids assert self.other_agent_ids is not None self.ai_agent_id = ai_agent_id assert self.ai_agent_id is not None self.all_agent_ids = other_agent_ids + [ai_agent_id] # Check that the given state is consistent with what we know. # If it isn't, raise Doesnt_match. def check_perception(self, state, explanation): #print "In check_perception, expecting %r == %r" % ( # self.ai_perception[state.timestep], # state.perceptions[self.ai_agent_id]) ensure(self.ai_perception[state.timestep] == state.perceptions[self.ai_agent_id]) for constraint in self.other_perception.get(state.timestep, []): constraint.check(state.perceptions[constraint.agent_id], explanation.compute_perceptions_program()) def check_state(self, state, explanation): self.check_perception(state, explanation) ensure(self.ai_behavior[state.timestep] == state.behaviors[self.ai_agent_id]) for constraint in self.other_behavior.get(state.timestep, []): constraint.check(state.behaviors[constraint.agent_id], explanation.compute_behavior_program()) # Assume that the parts of the given state that are computed from # the previous timestep are already filled in. Fill in the parts # that are computed from the current timestep. def fill_in_perception(self, explanation, state): state.perceptions = explanation.compute_perceptions( state.nonmind_state, self.all_agent_ids) def fill_in_behaviors(self, explanation, state, this_ai_behavior): b = {} for id in self.other_agent_ids: b[id] = explanation.compute_behavior(id, state.mind_states[id]) b[self.ai_agent_id] = this_ai_behavior # print "In fill_in_behaviors, behaviors are %r" % (b,) state.behaviors = b def fill_in_details(self, explanation, state, this_ai_behavior): self.fill_in_perception(explanation, state) self.fill_in_behaviors(explanation, state, this_ai_behavior) def initial_state(self, explanation): # This doesn't happen to depend on self, but it's good to be # analogous with next_state, which does depend on self, so put # it here instead of a method on explanation. Putting it here # instead of there also avoids a cyclic dependency between classes. e = explanation result = State(timestep=0) result.nonmind_state = e.initial_nonmind_physics() for id in self.other_agent_ids: result.mind_states[id] = e.initial_mind_physics(id) return result def next_state(self, explanation, previous_state): if previous_state is None: return self.initial_state(explanation) else: e = explanation p = previous_state result = State(timestep = p.timestep+1) result.nonmind_state = e.nonmind_physics( p.nonmind_state, p.behaviors, self.all_agent_ids) for id in self.other_agent_ids: result.mind_states[id] = e.mind_physics( id, p.perceptions[id], p.mind_states[id]) return result # Say whether the explanation explains the observations. def matches(self, explanation): s = None # The AI is trying to figure out what to do next, so it has # one less behavior than perception. We must have at least # one perception. assert len(self.ai_perception) > 0 for i in range(0, len(self.ai_perception) - 1): #print "Top of loop in matches, i is %r" % (i,) # s is the state at the time i-1, or None if i=0. s = self.next_state(explanation, s) # Now s is the state at time i, except the perceptions and # behaviors are from time i-1. self.fill_in_details(explanation, s, self.ai_behavior[s.timestep]) # Now the perceptions and behaviors in s are at time i. #print "In matches, i is %r and we're checking state %r" % (i, s) self.check_state(s, explanation) #print "Check passed in matches." #print "Done with loop in matches." # Now s is at time len(self.ai_perception) - 2, or None if # len(self.ai_perception) == 1. s = self.next_state(explanation, s) #print "Got next_state %r" % (s,) # Now s (except for perceptions and behaviors) is at time # len(self.ai_perception) - 1. self.fill_in_perception(explanation, s) #print "Filled in perception, now s is %r" % (s,) # Now s (including perceptions but not behaviors) is at time # len(self.ai_perception) - 1. We don't have a behavior for # time len(self.ai_perception) - 1 because computing that is # our goal, so that's all we can check. self.check_perception(s, explanation) #print "Passed check_perception."