Package teamwork :: Package agent :: Module RecursiveAgent
[hide private]
[frames] | no frames]

Source Code for Module teamwork.agent.RecursiveAgent

   1  """Defines the agent layer that handles beliefs about other agents""" 
   2  from copy import copy 
   3  from xml.dom.minidom import * 
   4   
   5  from Agent import * 
   6   
   7  from teamwork.action.PsychActions import * 
   8  from teamwork.action.DecisionSpace import parseSpace 
   9  from teamwork.math.Keys import * 
  10  from teamwork.math.probability import * 
  11  from teamwork.utils.PsychUtils import * 
  12  from teamwork.multiagent.sequential import SequentialAgents 
  13  from teamwork.policy.policyTable import PolicyTable 
  14  from teamwork.dynamics.pwlDynamics import * 
  15   
16 -class RecursiveAgent(Agent):
17 """Base class for an agent that keeps recursive models of the other agents in its world. 18 @ivar state: the probability distribution over the state of this agent's world 19 @type state: L{Distribution} 20 @ivar parent: the agent whose subjective view this agent represents (C{None}, if this is an objective view) 21 @type parent: L{RecursiveAgent} 22 @ivar dynamics: the dynamics by which this agent's state evolves 23 @type dynamics: dict 24 @ivar relationships: inter-agent relationships from this agent. In dictionary form: 25 - I{relationship name}: list of agent names 26 """ 27
28 - def __init__(self,name=''):
29 """ 30 @param name: label for this instance 31 @type name: string 32 """ 33 # Initialize entity state 34 self.state = Distribution({KeyedVector():1.}) 35 # Create agent 36 Agent.__init__(self,name) 37 # Start with an empty policy 38 self.policy = None 39 # Fixed links to other agents 40 self.relationships = {} 41 # Effects of actions on state features of this agent 42 self.dynamics = {} 43 # The state of this agent at time 0 44 self.initial = None 45 # The agent (if any) in whose subjective view this agent exists 46 self.parent = None
47
48 - def setName(self,name):
49 """Sets the name of this agent 50 @param name: the unique ID for this agent 51 @type name: C{str} 52 """ 53 Agent.setName(self,name) 54 # Update state 55 frozen = self.state.unfreeze() 56 for vector,prob in self.state.items(): 57 del self.state[vector] 58 for oldKey,value in vector.items(): 59 del vector[oldKey] 60 newKey = StateKey({'entity':self.name, 61 'feature':oldKey['feature']}) 62 vector[newKey] = value 63 vector._updateString() 64 self.state[vector] = prob 65 if frozen: 66 self.state.freeze()
67 68 # State accessor methods 69
70 - def getState(self,feature):
71 """Returns the current L{Distribution} over the specified feature 72 @type feature: string 73 @rtype: L{Distribution}""" 74 key = StateKey({'entity':self.name,'feature':feature}) 75 return self.state.getMarginal(key)
76
77 - def setState(self,feature,value):
78 """Sets this entity's state value for the specified feature 79 @type feature: string 80 @type value: either a float or a L{Distribution}. If the value is a float, it is first converted into a point distribution.""" 81 key = StateKey({'entity':self.name,'feature':feature}) 82 frozen = self.state.unfreeze() 83 self.state.join(key,value) 84 if frozen: 85 self.state.freeze()
86
87 - def getObservation(self,name):
88 """Returns the current L{Distribution} over the specified observation flag 89 @type name: string 90 @rtype: L{Distribution}""" 91 key = ObservationKey({'type':name}) 92 return self.state.getMarginal(key)
93
94 - def setObservation(self,name,value):
95 """Sets this entity's observation flag for the observation type 96 @type name: string 97 @type value: either a float or a L{Distribution}. If the value is a float, it is first converted into a point distribution.""" 98 key = ObservationKey({'type':name}) 99 self.state.unfreeze() 100 self.state.join(key,value) 101 self.state.freeze()
102
103 - def getStateFeatures(self):
104 """ 105 @return: names of the valid state features 106 @rtype: C{str[]} 107 """ 108 keyList = [] 109 for key in self.state.domainKeys(): 110 if isinstance(key,StateKey) and key['entity'] == self.name: 111 keyList.append(key['feature']) 112 return keyList
113 114 # Methods for accessing subjective beliefs about entities 115
116 - def getEntity(self,name):
117 """ 118 @return: the agent representing the beliefs about the given entity 119 @type name: string 120 @rtype: L{RecursiveAgent} 121 """ 122 if not isinstance(name,str): 123 raise DeprecationWarning,'getEntity requires string argument' 124 try: 125 return self.entities[name] 126 except KeyError: 127 raise KeyError,'Entity %s has no belief about %s' \ 128 % (self.ancestry(),name)
129
130 - def __getitem__(self,index):
131 """Allows indexing into entity beliefs (i.e., C{entity['Bill']})""" 132 return self.getEntity(index)
133
134 - def hasBelief(self,entity):
135 """ 136 @param entity: the name of the entity being queried about 137 @type entity: C{str} 138 @return: C{True} iff I have a belief about the named entity 139 @rtype: C{boolean} 140 """ 141 return self.entities.has_key(entity)
142
143 - def getEntities(self):
144 """ 145 @return: the names of entities that I have beliefs about 146 @rtype: C{str[]} 147 """ 148 return self.entities.keys()
149
150 - def getEntityBeliefs(self):
151 """ 152 @return: the subjective entity views that I have 153 @rtype: L{Agent}[] 154 """ 155 return self.entities.values()
156
157 - def getBeliefKeys(self):
158 """ 159 @return: the state features across all beliefs that this agent has 160 @rtype: L{StateKey}[] 161 """ 162 return self.entities.getStateKeys().keys()
163
164 - def getBelief(self,entity,feature):
165 """Shortcut for C{self.L{getEntity}(entity).L{getState}(feature)}""" 166 subjectiveEntity = self.getEntity(entity) 167 return subjectiveEntity.getState(feature)
168
169 - def getNestedBelief(self,keys):
170 """Accesses a nested belief by following the branches specified by keys (a list of strings). 171 @warning: may not actually work anymore! 172 """ 173 currentBelief = self 174 for key in keys: 175 if type(currentBelief) is InstanceType: 176 if key == 'state': 177 currentBelief = currentBelief.state 178 continue 179 elif key == 'entities': 180 continue 181 # Translate special key "self" into my name 182 if key == 'self': 183 key = self.name 184 try: 185 currentBelief = currentBelief[key] 186 except KeyError: 187 try: 188 currentBelief = currentBelief.getEntity(key) 189 except AttributeError,e: 190 raise AttributeError,'Illegal belief spec: %s' % (`keys`) 191 except AttributeError,e: 192 raise AttributeError,'Illegal belief spec: %s' % (`keys`) 193 return currentBelief
194
195 - def getSelfBelief(self,feature):
196 """Shortcut for C{self.L{getEntity}(self.name).L{getState}(feature)}""" 197 selfImage = self.getEntity(self.name) 198 return selfImage.getState(feature)
199 200 # Methods for manipulating subjective beliefs about entities 201
202 - def setBelief(self,name,feature,value):
203 """Shortcut for C{self.L{getEntity}(entity).{setState}(feature,value)}""" 204 subjectiveEntity = self.getEntity(name) 205 subjectiveEntity.setState(feature,value)
206
207 - def setSelfBelief(self,feature,value):
208 """Shortcut for C{self.setBelief(self,feature,value)}""" 209 self.setBelief(self.name,feature,value)
210
211 - def setEntity(self,entity):
212 """Sets my belief about the given entity to be the provided entity object. Basically equivalent to C{self.entities[entity.name] = entity}. 213 @type entity: L{RecursiveAgent} 214 """ 215 self.entities.addMember(entity) 216 entity.parent = self
217
218 - def setRecursiveBelief(self,name,feature,value):
219 """Helper method for setting the beliefs of a feature 220 @param name: the name of the other entity about whom our belief is is subject to change. 221 @type name: C{str} 222 @param feature: the state feature to change 223 @type feature: C{str} 224 @param value: the new value for the named state feature 225 226 @note: this will change all of the subjective beliefs that this entityd holds. Thus, not only will my beliefs about C{name}, but also my beliefs about I{X}'s beliefs about C{name}.""" 227 if self.hasBelief(name): 228 self.setBelief(name,feature,value) 229 for entity in self.getEntityBeliefs(): 230 entity.setRecursiveBelief(name,feature,value)
231
232 - def getAllBeliefs(self):
233 """Packages up all of this agent's beliefs into a handy dictionary 234 @return: the dictionary has the following indices: 235 - state: what this agent believes about the state of the world 236 - I{name}: what this agent think agent, I{name}, believes (i.e., a recursive call to L{getAllBeliefs}) 237 @rtype: dict 238 """ 239 result = {'state':self.entities.getState(), 240 'turn':self.entities.order, 241 'observations':self.entities.getActions() 242 } 243 for agent in self.getEntityBeliefs(): 244 result[agent.name] = agent.getAllBeliefs() 245 # Hey, what about those poor souls at the bottom of the belief 246 # hierarchy that don't have any real state? Let's be generous 247 # and loan them ours 248 if len(agent.entities.members()) == 0: 249 result[agent.name]['_cheating'] = result 250 return result
251 252 # Policy methods 253
254 - def applyPolicy(self,state=None,actions=[],history=None,debug=None, 255 explain=False):
256 """Generates a decision chosen according to the agent's current policy 257 @param state: the current state vector 258 @type state: L{Distribution}(L{KeyedVector}) 259 @param actions: the possible actions the agent can consider (defaults to all available actions) 260 @type actions: L{Action}[] 261 @param history: a dictionary of actions that have already been performed (and which should not be performed again if they are labeled as not repeatable) 262 @type history: L{Action}[]:bool 263 @param explain: flag indicating whether an explanation should be generated 264 @type explain: bool 265 @return: a list of actions and an explanation, the latter provided by L{execute<PolicyTable.execute>} 266 @rtype: C{(L{Action}[],Element)} 267 """ 268 if state is None: 269 state = self.getAllBeliefs() 270 return self.policy.execute(state=state,choices=actions, 271 history=history,debug=debug,explain=explain)
272
273 - def invalidateCache(self):
274 """Removes any cached policy value, as well as any cached policy values of my parents""" 275 if self.policy: 276 self.policy.reset() 277 if self.parent: 278 self.parent.invalidateCache()
279 280 # Update methods 281
282 - def initialStateEstimator(self):
283 """Initializes beliefs""" 284 beliefs = {} 285 beliefs['entities'] = self.entities = SequentialAgents() 286 self.resetHistory(beliefs) 287 return beliefs
288
289 - def stateEstimator(self,beliefs,actions,epoch=-1,debug=None):
290 """Trying to unify L{preComStateEstimator} and L{postComStateEstimator}""" 291 return self.preComStateEstimator(beliefs,actions,epoch,debug)
292
293 - def preComStateEstimator(self,beliefs,actions,epoch=-1,debug=None):
294 """ 295 Computes the hypothetical changes to the given beliefs in response to the given actions 296 @param beliefs: the beliefs to be updated (traditionally, the result from L{getAllBeliefs}) 297 @type beliefs: dict 298 @param actions: the actions observed by this agent 299 @type actions: C{dict:strS{->}L{Action}} 300 @param epoch: the current epoch in which these observations occurred (currently ignored, but potentially important) 301 @type epoch: C{int} 302 @type debug: L{Debugger} 303 @return: the belief changes that would result from the specified observed actions, in dictionary form: 304 - beliefs: results as returned by L{SequentialAgents.hypotheticalAct} 305 - observations: the given actions 306 @rtype: C{dict} 307 """ 308 # Compute the effect of these actions in the given belief state 309 delta = self.entities.hypotheticalAct(actions,beliefs) 310 if beliefs is not None: 311 # Update the belief state 312 self.applyChanges(beliefs,delta,descend=False) 313 return delta
314
315 - def incorporateMessage(self,msg,log=None):
316 """Update an entity's beliefs in response to set of messages 317 @param msg: the message whose dynamics we wish to compute 318 @type msg: L{Message<teamwork.messages.PsychMessage.Message>} 319 @param log: not sure what this is for, probably auditing at some point, but nothing happens to it right now, so it's not very useful 320 @return: the dynamics matrix corresponding to the desired change of beliefs implied by the given message 321 @rtype: L{KeyedMatrix} 322 """ 323 delta = None 324 explanation = [] 325 for factor in msg['factors']: 326 try: 327 result = factor['matrix'] 328 except KeyError: 329 result = self.__incorporateMessage(factor) 330 if delta is None: 331 delta = result 332 else: 333 delta *= result 334 explanation.append(factor) 335 return {'state':delta},explanation
336
337 - def __incorporateMessage(self,factor):
338 """Update an entity's beliefs in response to a received message 339 @param factor: the individual message factor 340 @type factor: dict 341 @return: the dynamics corresponding to an individual factor 342 @rtype: L{KeyedMatrix} 343 """ 344 # Start with an identity matrix 345 matrix = KeyedMatrix() 346 keyList = self.entities.state.expectation().keys() 347 matrix.fill(keyList) 348 for key in keyList: 349 matrix.set(key,key,1.) 350 delta = Distribution({matrix:1.}) 351 # Incorporate the given message factor into this distribution 352 if factor['topic'] == 'state': 353 state = None 354 currentBelief = self 355 path = [] 356 for key in factor['lhs'][:len(factor['lhs'])-1]: 357 if key == 'entities': 358 currentBelief = currentBelief.entities 359 elif type(currentBelief) is InstanceType: 360 if key == 'state': 361 state = 1 362 # currentBelief = currentBelief.state 363 continue 364 elif key == 'policy': 365 currentBelief = currentBelief.policy 366 else: 367 currentBelief = currentBelief.beliefs 368 currentBelief = currentBelief[key] 369 else: 370 try: 371 currentBelief = currentBelief[key] 372 path.append(key) 373 except KeyError,ex: 374 # Belief is too far deep for this entity 375 break 376 else: 377 if len(path) != 1: 378 raise NotImplementedError,'My apologies, I am unable to handle such rich messages right now' 379 else: 380 feature = factor['lhs'][len(factor['lhs'])-1] 381 key = StateKey({'entity':path[0], 382 'feature':feature}) 383 if factor['relation'] == '=': 384 # This message is trying to directly set a specific belief 385 if isinstance(factor['value'],float): 386 for element,prob in delta.items(): 387 del delta[element] 388 element.set(key,key,0.) 389 element.set(key,keyConstant,factor['value']) 390 delta[element] = prob 391 elif isinstance(factor['value'],Distribution): 392 if len(factor['value']) == 1: 393 for element,prob in delta.items(): 394 del delta[element] 395 element.set(key,key,0.) 396 element.set(key,keyConstant, 397 factor['value'].expectation()) 398 delta[element] = prob 399 else: 400 for matrix,oldProb in delta.items(): 401 del delta[matrix] 402 for value,newProb in factor['value'].items(): 403 newMatrix = copy.deepcopy(matrix) 404 newMatrix.set(key,key,0.) 405 newMatrix.set(key,keyConstant,value) 406 delta[newMatrix] = oldProb*newProb 407 else: 408 # This message is trying to make a comparative statement 409 raise NotImplementedError,'Unable to handle messages with relationship, "%s"' % (factor['relation']) 410 elif factor['topic'] == 'observation': 411 raise NotImplementedError,'Currently unable to handle messages about actions' 412 ## actions = {factor['actor']: factor['action']} 413 ## result = self.stateEstimator(actions=actions) 414 ## print result.keys() 415 ## delta.update(result) 416 elif factor['topic'] == 'model': 417 raise NotImplementedError,'Currently unable to handle messages about mental models' 418 ## try: 419 ## entity = self.getEntity(factor['entity']) 420 ## except KeyError: 421 ## entity = None 422 ## if entity: 423 ## diff = delta 424 ## for name in factor['entity']: 425 ## if not diff.has_key(name): 426 ## diff['beliefs'] = {name: {}} 427 ## diff = diff['beliefs'][name] 428 ## diff['model'] = {'previous model':entity.model, 429 ## 'model':factor['value']} 430 delta.freeze() 431 return delta
432 433 # Handle observation history 434
435 - def getObservations(self):
436 """ 437 @return: the sequence of events that this entity has witnessed 438 @rtype: C{list} 439 """ 440 return self.beliefs['observations']
441
442 - def saveObservations(self,obs):
443 """Adds the given observation to the history 444 """ 445 self.beliefs['observations'].insert(0,obs)
446
447 - def findObservation(self,action,depth=0):
448 """Returns true iff the agent has observed the given action 449 @param action: the observation to look for 450 @type action: L{Action} 451 @param depth: the maximum number of steps to look into the past (by default, look arbitrarily far) 452 @type depth: int 453 @rtype: a tuple (boolean,int) 454 @return: the first return value is true iff the agent has observed the given action within the past depth time steps. The second return value is the number of steps in the past that the observation occurs. This value is negative if no such observation is found within the specified depth.""" 455 obsList = self.getObservations() 456 if depth and depth < len(obsList): 457 limit = depth 458 else: 459 limit = len(obsList) 460 for index in range(limit): 461 for actor,actions in obsList[index].items(): 462 for obs in actions: 463 if obs.matchTemplate(action): 464 return obs,index 465 return None,-1
466
467 - def resetHistory(self,beliefs=None):
468 """Resets the observation history of the specified beliefs (defaults to this entity's beliefs if none provided)""" 469 if not beliefs: 470 beliefs = self.beliefs 471 beliefs['observations'] = []
472
473 - def observe(self,actionDict={}):
474 """ 475 @param actionDict: the performed actions, indexed by actor name 476 @type actionDict: C{dict:strS{->}L{Action}[]} 477 @return: observations this entity would make of the given actions, in the same format as the provided action dictionary 478 @rtype: C{dict} 479 """ 480 observations = {} 481 for actor,action in actionDict.items(): 482 if actor == self.name: 483 # Always observe our own actions (I assume this is OK) 484 observations[actor] = action 485 else: 486 observation = [] 487 # Placeholder while we modify observation model 488 for subAct in action: 489 # By default, assume observable 490 observation.append(subAct) 491 if len(observation) > 0: 492 # Only add observations if we have any (questionable?) 493 observations[actor] = observation 494 return observations
495
496 - def getActionKeys(self):
497 """ 498 @return: the minimal set of observations relevant to this agent 499 @rtype: C{L{teamwork.math.Keys.ActionKey}[]}""" 500 actionKeys = [] 501 # Find any observations that I have goals for 502 for key in self.getGoalVector()['action'].keys(): 503 actionKeys.append(key) 504 # Find any observations that are on the LHS of my policy rules 505 for key in self.policy.tree.getKeys(): 506 if key['class'] == 'observation': 507 if not key in actionKeys: 508 actionKeys.append(key) 509 return actionKeys
510 511 # Handle the effects of actions on state 512
513 - def multistep(self,horizon=1,start={},state=None,debug=None):
514 """Steps this entity the specified number of steps into the future (where a step is one entity performing its policy-specified action) 515 @param state: the world state to evaluate the actions in (defaults to current world state) 516 @type state: L{Distribution}(L{KeyedVector}) 517 @warning: This method still needs to be handed an updated turn vector 518 """ 519 if state is None: 520 state = self.getAllBeliefs() 521 sequence = [] 522 # Lookahead 523 for t in range(horizon): 524 if debug: 525 debug.message(9,'Time %d' % (t)) 526 if t == 0: 527 entityDict = start 528 else: 529 entityDict = {} 530 nextGroup = self.entities.next(state['turn']) 531 for entity in nextGroup: 532 if isinstance(entity,dict): 533 try: 534 choices = entity['choices'] 535 except KeyError: 536 choices = [] 537 entity = entity['name'] 538 else: 539 raise DeprecationWarning,'Turns should be expressed in dictionary form' 540 if len(entityDict) < len(nextGroup) and \ 541 not entityDict.has_key(entity): 542 entityDict[entity] = choices 543 # Apply these entities' actions 544 delta = self.step(entityDict,state,debug) 545 self.updateStateDict(state,delta['effect']) 546 # Accumulate results 547 sequence.append(delta) 548 return sequence
549
550 - def updateStateDict(self,original,delta):
551 for key,diff in delta.items(): 552 if key == 'turn': 553 original[key] = diff[original[key]] * original[key] 554 elif self.hasBelief(key): 555 self.updateStateDict(original[key],diff) 556 else: 557 pass
558
559 - def step(self,actDict,state=None,debug=None):
560 """Modifies the current entity in response to the 561 policy-driven actions selected by the provided dictionary of 562 entities. 563 @param state: the world state to evaluate the actions in (defaults to current world state) 564 @type state: L{Distribution}(L{KeyedVector}) 565 """ 566 explanation = {} 567 # By default, use my beliefs as the world 568 if state is None: 569 state = self.getAllBeliefs() 570 # Construct the set of actions performed by these entities 571 for name,action in actDict.items(): 572 if debug: 573 debug.message(4,'%s predicting %s...' % (self.name,name)) 574 try: 575 entity = self.getEntity(name) 576 except KeyError: 577 # Don't do anything if we have no belief about this entity 578 entity = None 579 # Extract entity's state from current beliefs 580 if entity and entity.model: 581 if debug: 582 debug.message(7,'%s is modeled as %s' % 583 (name,entity.model['name'])) 584 if isinstance(action,list) and len(action) > 0: 585 # Pre-specified action 586 if isinstance(action[0],list): 587 # Set of possible actions to choose from 588 action,exp = entity.applyPolicy(state=state[entity.name], 589 actions=action, 590 debug=debug) 591 else: 592 # A single action for you to do 593 exp = {'forced':True, 594 'decision':action} 595 elif entity: 596 # Unconstrained set of possible actions 597 action,exp = entity.applyPolicy(state=state[entity.name], 598 debug=debug) 599 else: 600 action = None 601 exp = {} 602 if action and entity: 603 if debug: 604 debug.message(9,'\t%s expects %s to %s' % 605 (self.name,entity.name,action)) 606 actDict[name] = action 607 explanation[name] = exp 608 if len(actDict) > 0: 609 result = {'effect':self.preComStateEstimator(state,actDict, 610 debug=debug), 611 'action':actDict 612 } 613 result['state'] = state['state'] 614 result['turn'] = state['turn'] 615 else: 616 result = {'effect':None, 617 'action':actDict 618 } 619 result['breakdown'] = explanation 620 return result
621
622 - def getDynamics(self,act,feature):
623 """ 624 @return: this entity's dynamics model for the given action 625 @param act: the action whose effect we are interested in 626 @type act: L{Action} 627 @param feature: if the optional feature argument is provided, then this method returns the dynamics for only the given feature; otherwise, returns the effect over all state features (but this latter capability is now deprecated) 628 @type feature: C{str} 629 @rtype: L{PWLDynamics} 630 """ 631 try: 632 # Try to find dynamics specific to this particular action 633 dynFun = self.dynamics[feature][act] 634 except KeyError: 635 # If not, find a more general dynamics and then instantiate 636 try: 637 dynFun = self.dynamics[feature][act['type']] 638 except KeyError: 639 # It's OK for an action to have no dynamics 640 # (e.g., the "wait" action) 641 try: 642 self.dynamics[feature][act] = None 643 except KeyError: 644 self.dynamics[feature] = {act: None} 645 return None 646 if isinstance(dynFun,str): 647 dynFun = self.hierarchy[dynFun].dynamics[feature][act['type']] 648 dynFun = dynFun.instantiate(self,act) 649 # Check whether dynamics is well formed 650 vector = self.state.domain()[0] 651 for leaf in dynFun.getTree().leaves(): 652 for key in leaf.rowKeys(): 653 if not vector.has_key(key): 654 print 'Dynamics of %s\'s %s in response to %s has extraneous key, %s' % (self.ancestry(),feature,str(act),str(key)) 655 for key in leaf.colKeys(): 656 if not vector.has_key(key): 657 print 'Dynamics of %s\'s %s in response to %s has extraneous key, %s' % (self.ancestry(),feature,str(act),str(key)) 658 for branch in dynFun.getTree().branches().values(): 659 if not isinstance(branch,Distribution): 660 for key in branch.weights.keys(): 661 if not vector.has_key(key): 662 print 'Dynamics of %s\'s %s in response to %s has extraneous key, %s' % (self.ancestry(),feature,str(act),str(key)) 663 self.dynamics[feature][act] = dynFun 664 return dynFun
665
666 - def applyChanges(self,beliefs,delta,descend=1,rewind=0):
667 """Takes changes and modifies the given beliefs accordingly 668 @param descend: If the descend flag is set, then the recursive changes in the delta will also be applied. 669 @type descend: C{boolean} 670 @param rewind: If the rewind flag is set, then the changes will be undone, instead of applied. 671 @type rewind: C{boolean}""" 672 for feature,diff in delta.items(): 673 if feature == 'state': 674 beliefs['state'] = diff*beliefs['state'] 675 elif feature == 'turn': 676 pass 677 elif feature == 'observations': 678 beliefs['observations'] = diff*beliefs['observations'] 679 elif feature == 'relationships': 680 pass 681 else: 682 if descend: 683 self.applyChanges(beliefs[feature],diff,descend,rewind)
684
685 - def freeze(self):
686 """Takes the current state of beliefs and marks it as the initial, beginning-of-the-world state of beliefs. 687 @note: as a side effect, it resets the observation history""" 688 self.initial = None 689 ## self.initial = copy.deepcopy(self) 690 ## for entity in self.initial.getEntities(): 691 ## self.initial.getEntity(entity).freeze() 692 ## self.getEntity(entity).freeze() 693 ## self.initial.initial = None 694 ## self.initial.resetHistory() 695 self.resetHistory()
696 697 # Utility methods 698
699 - def ancestry(self):
700 """ 701 @return: a string representation of this entity's position in the recursive belief tree. 702 - If C{self.parent == None}, then returns C{self.name} 703 - Otherwise, returns C{self.parent.ancestry()+'->'+self.name} 704 @rtype: C{str}""" 705 name = self.name 706 parent = self.parent 707 while parent: 708 name = parent.name + '->' + name 709 parent = parent.parent 710 return name
711
712 - def beliefDepth(self):
713 """ 714 @return: the recursive depth of the current entity (0 is the depth of a root entity) 715 @rtype: C{int}""" 716 if self.parent: 717 return 1 + self.parent.beliefDepth() 718 else: 719 return 0
720
721 - def __copy__(self):
722 """ 723 @warning: the dynamics are not copied, but simply linked 724 """ 725 new = Agent.__copy__(self) 726 ## new.beliefs['observations'] = self.beliefs['observations'] 727 new.parent = self.parent 728 new.dynamics = self.dynamics 729 ## for feature in self.getStateFeatures(): 730 ## value = self.getState(feature) 731 ## new.setState(feature,value) 732 return new
733
734 - def __deepcopy__(self,memo):
735 new = copy.copy(self) 736 new.dynamics = copy.deepcopy(self.dynamics,memo) 737 memo[id(self)] = new 738 new.beliefs['observations'] = self.beliefs['observations'][:] 739 new.entities = copy.deepcopy(self.entities,memo) 740 return new
741
742 - def __str__(self):
743 """Returns a string representation of this entity""" 744 content = '\n'+self.name+':' 745 content += ' ('+self.ancestry() + ')\n' 746 content += '\tState:\n' 747 ## content += `self.state` 748 for feature in self.getStateFeatures(): 749 resAct = self.getState(feature) 750 for value, prob in resAct.items(): 751 content += '\t\t'+feature+' '+str(value)+' prob '+`prob`+'\n' 752 if len(self.getEntities()) > 0: 753 content += '\tBeliefs:' 754 for otherName in self.getEntities(): 755 other = self.getEntity(otherName) 756 content += str(other).replace('\n','\n\t\t') 757 content += '\n\tPolicy:\n' 758 substr = `self.policy` 759 content += '\t\t' + substr.replace('\n','\n\t\t') 760 content += '\n\tDynamics:\n' 761 content += '\t\t'+`self.dynamics.keys()` 762 return content
763
764 - def toHTML(self):
765 content = '<TABLE RULES="rows" BORDER="3">\n' 766 substr = self.name 767 entity = self 768 while entity.parent: 769 entity = entity.parent 770 substr = entity.name + "'s beliefs about " + substr 771 content += '<FONT SIZE="+2">%s</FONT>' % (substr) 772 content += '<CAPTION>' 773 content += '</CAPTION>\n' 774 content += '<TBODY>\n' 775 # Add my state 776 content += '<TR>' 777 content += '<TH BGCOLOR="#ffff00"><FONT SIZE="+1">State</FONT></TH>' 778 ## content += '<TD>' + self.state.toHTML() + '</TD>\n' 779 content += '<TD><TABLE BORDER="3" WIDTH="100%%" CELLPADDING="0">\n' 780 content += '<TBODY>\n' 781 for feature in self.getStateFeatures(): 782 content += '<TR>' 783 content += '<TH>' + feature + '</TH>\n' 784 content += '<TD WIDTH="200" HEIGHT="50">' 785 value = float(self.getState(feature)) 786 content += float2bar(value,False) 787 content += '</TD>' 788 ## content += '<TD><OL><FONT SIZE="-1">' 789 ## history = self.getHistory(feature) 790 ## for index in range(len(history)): 791 ## content += '<LI VALUE="%d">%s</LI>\n' % (len(history)-index, 792 ## history[index]) 793 ## content += '</FONT></OL></TD>\n' 794 content += '</TR>\n' 795 content += '</TBODY>\n' 796 content += '</TABLE></TD>\n' 797 content += '</TR>\n' 798 # Add my goals 799 content += '<TR>' 800 content += '<TH BGCOLOR="#ffff00"><FONT SIZE="+1">Goals</FONT></TH>' 801 content += '<TD>' 802 content += '<TABLE BORDER="3" WIDTH="100%%">\n' 803 content += '<TBODY>\n' 804 ## content += '<THEAD><TH>Goal</TH><TH>Weight</TH><TH>History</TH></THEAD>\n' 805 for goal in self.getGoals(): 806 807 content += '<TR><TD>%s</TD><TD WIDTH="100" HEIGHT="50">' % (goal) 808 content += float2bar(self.getGoalWeight(goal)) 809 content += '</TD>' 810 ## content += '<TD><OL><FONT SIZE="-1">' 811 ## history = self.getHistory(goal) 812 ## for index in range(len(history)): 813 ## content += '<LI VALUE="%d">%s</LI>\n' % (len(history)-index, 814 ## history[index]) 815 ## content += '</FONT></OL></TD>\n' 816 content += '</TR>\n' 817 content += '</TBODY>\n' 818 content += '</TABLE>\n' 819 content += '</TD>\n' 820 content += '</TR>\n' 821 # Add my beliefs about other entities 822 content += '<TH BGCOLOR="#ffff00"><FONT SIZE="+1">Beliefs</FONT></TH>' 823 content += '<TD>' 824 content += '<TABLE BORDER="3" WIDTH="100%%">\n' 825 content += '<TBODY>\n' 826 odd = None 827 for entity in self.getEntityBeliefs(): 828 content += '<TR' 829 if odd: 830 content += ' BGCOLOR="#aaaaaa"' 831 else: 832 content += ' BGCOLOR="#ffffff"' 833 content += '>' 834 content += '<TD>' + entity.toHTML() + '</TD>' 835 content += '</TR>\n' 836 odd = not odd 837 content += '</TBODY>\n' 838 content += '</TABLE>\n' 839 # Close 840 content += '</TBODY>\n' 841 content += '</TABLE>\n' 842 return content
843
844 - def __ne__(self,entity):
845 return not (self == entity)
846
847 - def __eq__(self,entity):
848 """Equality test between agents 849 @warning: only partially implemented 850 @param entity: the entity to be compared against 851 @type entity: L{RecursiveAgent} 852 @rtype: C{boolean}""" 853 if type(entity)== StringType: 854 return self.name == entity 855 if self.name != entity.name: 856 return False 857 if self.parent != entity.parent: 858 return False 859 if self.state != entity.state: 860 return False 861 return True
862
863 - def __xml__(self):
864 doc = Agent.__xml__(self) 865 root = doc.documentElement 866 # Beliefs 867 node = doc.createElement('beliefs') 868 root.appendChild(node) 869 node.appendChild(self.entities.__xml__().documentElement) 870 # Actions 871 node = doc.createElement('actions') 872 root.appendChild(node) 873 node.appendChild(self.actions.__xml__().documentElement) 874 # Relationships 875 node = doc.createElement('relationships') 876 root.appendChild(node) 877 for label,agents in self.relationships.items(): 878 relationshipNode = doc.createElement('relationship') 879 node.appendChild(relationshipNode) 880 relationshipNode.setAttribute('label',label) 881 for name in agents: 882 subNode = doc.createElement('relatee') 883 subNode.appendChild(doc.createTextNode(name)) 884 relationshipNode.appendChild(subNode) 885 if not self.parent: 886 # Dynamics 887 node = doc.createElement('dynamics') 888 root.appendChild(node) 889 for feature,subDict in self.dynamics.items(): 890 featureNode = doc.createElement('feature') 891 node.appendChild(featureNode) 892 featureNode.setAttribute('label',feature) 893 for actType,dynamic in subDict.items(): 894 if isinstance(actType,str): 895 # This test prevents the saving of compiled dynamics 896 actNode = doc.createElement('action') 897 featureNode.appendChild(actNode) 898 actNode.setAttribute('type',actType) 899 if isinstance(dynamic,str): 900 dynamic = self.hierarchy[dynamic].dynamics[feature][actType] 901 actNode.appendChild(dynamic.__xml__().documentElement) 902 elif actType is None: 903 # Default dynamics 904 actNode = doc.createElement('action') 905 featureNode.appendChild(actNode) 906 if isinstance(dynamic,str): 907 dynamic = self.hierarchy[dynamic].dynamics[feature][actType] 908 actNode.appendChild(dynamic.__xml__().documentElement) 909 # Policy 910 if self.policy: 911 if isinstance(self.policy,list): 912 node = doc.createElement('policy') 913 root.appendChild(node) 914 node.setAttribute('type','list') 915 node.setAttribute('value',str(self.policy)) 916 else: 917 root.appendChild(self.policy.__xml__().documentElement) 918 return doc
919
920 - def parse(self,element):
921 """Extracts this agent's recursive belief structure from the given XML Element 922 @type element: Element 923 """ 924 Agent.parse(self,element) 925 child = element.firstChild 926 while child: 927 if child.nodeType == Node.ELEMENT_NODE: 928 if child.tagName == 'beliefs': 929 node = child.firstChild 930 while node: 931 if node.nodeType == node.ELEMENT_NODE: 932 self.entities.parse(node,self.__class__) 933 node = node.nextSibling 934 for entity in self.entities.members(): 935 entity.parent = self 936 elif child.tagName == 'state': 937 self.state = Distribution() 938 subNodes = child.getElementsByTagName('distribution') 939 if len(subNodes) == 1: 940 self.state.parse(subNodes[0],KeyedVector) 941 elif len(subNodes) > 1: 942 raise UserWarning,'Multiple distributions in state of %s' % (self.ancestry()) 943 elif child.tagName == 'actions': 944 subNode = child.firstChild 945 while subNode and subNode.nodeType != Node.ELEMENT_NODE: 946 subNode = subNode.nextSibling 947 if subNode: 948 self.actions = parseSpace(subNode) 949 elif child.tagName == 'relationships': 950 node = child.firstChild 951 while node: 952 if node.nodeType == node.ELEMENT_NODE: 953 label = str(node.getAttribute('label')) 954 self.relationships[label] = [] 955 subNode = node.firstChild 956 while subNode: 957 if subNode.nodeType == subNode.ELEMENT_NODE: 958 assert(subNode.tagName == 'relatee') 959 name = subNode.firstChild.data 960 name = str(name).strip() 961 self.relationships[label].append(name) 962 subNode = subNode.nextSibling 963 node = node.nextSibling 964 elif child.tagName == 'dynamics': 965 node = child.firstChild 966 while node: 967 if node.nodeType == node.ELEMENT_NODE: 968 assert(node.tagName=='feature') 969 feature = str(node.getAttribute('label')) 970 self.dynamics[feature] = {} 971 subNode = node.firstChild 972 while subNode: 973 if subNode.nodeType == subNode.ELEMENT_NODE: 974 assert(subNode.tagName=='action') 975 actionType = str(subNode.getAttribute('type')) 976 if not actionType: 977 actionType = None 978 assert(actionType not in self.dynamics[feature].keys()) 979 dyn = str(subNode.getAttribute('link')) 980 if not dyn: 981 dyn = PWLDynamics() 982 subchild = subNode.firstChild 983 while subchild and subchild.nodeType != child.ELEMENT_NODE: 984 subchild = subchild.nextSibling 985 dyn.parse(subchild) 986 self.dynamics[feature][actionType] = dyn 987 subNode = subNode.nextSibling 988 node = node.nextSibling 989 elif child.tagName == 'policy': 990 self.policy = PolicyTable(self,self.actions) 991 self.policy.parse(child) 992 child = child.nextSibling 993 # Some post-processing 994 for action in self.actions.getOptions(): 995 for subAct in action: 996 subAct['actor'] = self.name 997 if not self.policy: 998 self.policy = PolicyTable(self,self.actions,self.horizon)
999
1000 -def float2bar(value,positive=True):
1001 str = '<TABLE WIDTH="100%%" HEIGHT="100%%"><TR>' 1002 if positive: 1003 str += '<TD WIDTH="%d%%" BGCOLOR="#000000"></TD><TD></TD>' % (value*100.) 1004 else: 1005 if value > 0.: 1006 str += '<TD WIDTH="50%%"></TD>\n' 1007 str += '<TD WIDTH="%d%%" BGCOLOR="#000000"></TD><TD></TD>' % (value*50.) 1008 else: 1009 str += '<TD WIDTH="%d%%"></TD>\n' % ((value+1.)*50.) 1010 str += '<TD WIDTH="%d%%" BGCOLOR="#ff0000"></TD>' % (-value*50.) 1011 str += '<TD WIDTH="50%%"></TD>\n' 1012 str += '</TR></TABLE>' 1013 return str
1014 1015 if __name__ == '__main__': 1016 from unittest import TestResult 1017 import sys 1018 from teamwork.test.agent.testRecursiveAgent import TestRecursiveAgentIraq 1019 if len(sys.argv) > 1: 1020 method = sys.argv[1] 1021 else: 1022 method = 'testLocalState' 1023 case = TestRecursiveAgentIraq(method) 1024 result = TestResult() 1025 case(result) 1026 for failure in result.errors+result.failures: 1027 print failure[1] 1028