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

Source Code for Module teamwork.agent.stereotypes

  1  """Mental models for stereotyping agents 
  2  @author: David V. Pynadath <pynadath@isi.edu> 
  3  """ 
  4  import string 
  5  import copy 
  6   
  7  from GoalBased import GoalBasedAgent 
  8  from teamwork.utils.Debugger import Debugger 
  9  from teamwork.policy.policyTable import PolicyTable 
 10  from teamwork.math.Keys import ModelKey 
 11   
12 -class Stereotyper(GoalBasedAgent):
13 """Mix-in class that supports stereotypical mental models 14 @cvar defaultModelChange: Class-wide default for L{modelChange} attribute 15 @type defaultModelChange: boolean 16 @ivar modelChange: Flag that specifies whether this agent will change its mental models or not 17 @type modelChange: boolean 18 @ivar models: the space of possible stereotypes that can be applied to this agent 19 @type models: dictionary of C{name: model} pairs 20 @ivar model: the current stereotype that this agent is following 21 - I{name}: the name of the current stereotype 22 - I{fixed}: a flag indicating whether this model is subject to change 23 @type model: dictionary 24 """ 25 defaultModelChange = None 26
27 - def __init__(self,name):
28 GoalBasedAgent.__init__(self,name) 29 self.modelChange = self.defaultModelChange 30 self.model = None 31 self.models = {}
32
33 - def hypotheticalPreCom(self,beliefs,actions,epoch=-1,debug=Debugger()):
34 """Returns the hypothetical model changes 35 36 Returns the mental model changes that would result from the 37 specified observed actions.""" 38 if beliefs.modelChange: 39 if len(beliefs.getEntities()) == 0: 40 return {} 41 debug.message(7,'%s reconsidering mental models' \ 42 % (beliefs.ancestry())) 43 return beliefs.updateModels(obs,debug) 44 else: 45 return {}
46
47 - def updateModels(self,actions,debug):
48 """Updates the mental models in response to the given dictionary of actions""" 49 delta = {} 50 for act in actions.values(): 51 newModel = self.updateModel(act,debug) 52 if newModel: 53 actor = self.getEntity(act['actor']) 54 if delta.has_key(actor.name): 55 delta[actor.name]['model'] = newModel 56 else: 57 delta[actor.name] = {'model':newModel} 58 return delta
59
60 - def updateModel(self,actual,debug=Debugger()):
61 """Updates the mental model in response to the single action""" 62 try: 63 actor = self.getEntity(actual[0]['actor']) 64 except KeyError: 65 return None 66 if (not actor.model) or actor.model['fixed']: 67 return None 68 # Check for expectation violation 69 predicted,explanation = actor.applyPolicy(debug=debug-1) 70 if `actual` == `predicted`: 71 return None 72 filter = lambda e,a=actor:e.name==a.name 73 debug.message(6,self.ancestry()+' observed "'+`actual`+\ 74 '" instead of "'+`predicted`) 75 # Expectation violation, so re-evaluate model choice 76 start = time.time() 77 projection = copy.deepcopy(self) 78 projection.freezeModels() 79 value,explanation = projection.acceptability(actor,0.0,filter,debug) 80 best = {'model':actor.model['name'],'value':value, 81 'explanation':explanation} 82 best['previous model'] = best['model'] 83 best['previous value'] = best['value'] 84 debug.message(5,'Current model: '+actor.model['name']+' = '+`value`) 85 for model in actor.models: 86 if model != best['model']: 87 projection.getEntity(actor).setModel(model,1) 88 value,explanation = projection.acceptability(actor,0.0, 89 filter,debug) 90 debug.message(5,'Alternate model: '+model+' = '+`value`) 91 if value > best['value']: 92 best['model'] = model 93 best['value'] = value 94 best['explanation'] = explanation 95 if best['model'] == actor.model['name']: 96 debug.message(5,'No model change') 97 return None 98 else: 99 # Model change! 100 ## actor.setModel(best['model']) 101 debug.message(7,'Model change: '+best['model']) 102 return best
103 104 # Belief model methods 105
106 - def setModel(self,model,fixed=False):
107 """Applies the named mental model to this entity""" 108 ## if self.model: 109 ## self.invalidateCache() 110 if model: 111 try: 112 myModel = self.models[model] 113 except KeyError: 114 raise KeyError,'%s has no model "%s"' % (self.ancestry(),model) 115 self.model = {'name':model,'fixed':fixed} 116 else: 117 myModel = None 118 keyList = self.models.keys() 119 keyList.sort() 120 ## try: 121 ## delta = 1./float(len(keyList)) 122 ## value = (float(keyList.index(model))+0.5)*delta 123 ## except ZeroDivisionError: 124 ## value = 0.5 125 ## frozen = self.state.unfreeze() 126 ## self.state.join(ModelKey({'entity':self.name}),value) 127 ## if frozen: 128 ## self.state.freeze() 129 if myModel: 130 self.setGoals(myModel.getGoals()) 131 self.policy = copy.copy(myModel.policy) 132 if isinstance(self.policy,PolicyTable): 133 self.policy.entity = self
134
135 - def freezeModels(self):
136 """Lock the model of this agent and all mental models it may have""" 137 self.modelChange = None 138 ## if self.model: 139 ## self.model['fixed'] = True 140 for entity in self.getEntityBeliefs(): 141 try: 142 entity.freezeModels() 143 except AttributeError: 144 pass
145
146 - def __copy__(self,new=None):
147 if not new: 148 new = GoalBasedAgent.__copy__(self) 149 new.models = self.models 150 new.model = None 151 new.modelChange = self.modelChange 152 if self.model: 153 new.setModel(self.model['name'],self.model['fixed']) 154 return new
155
156 - def __str__(self):
157 rep = GoalBasedAgent.__str__(self) 158 if self.model and self.model['name']: 159 rep = rep + '\tModel: '+self.model['name']+'\n' 160 return rep
161
162 - def __xml__(self):
163 doc = GoalBasedAgent.__xml__(self) 164 root = doc.createElement('models') 165 doc.documentElement.appendChild(root) 166 if self.modelChange: 167 doc.documentElement.setAttribute('modelChange','true') 168 else: 169 doc.documentElement.setAttribute('modelChange','false') 170 if self.model: 171 if isinstance(self.model,dict): 172 if self.model['name']: 173 doc.documentElement.setAttribute('modelName', 174 self.model['name']) 175 if self.model['fixed']: 176 doc.documentElement.setAttribute('modelFixed','True') 177 else: 178 doc.documentElement.setAttribute('modelFixed','False') 179 else: 180 doc.documentElement.setAttribute('modelName',self.model) 181 for name,model in self.models.items(): 182 node = doc.createElement('model') 183 node.setAttribute('name',name) 184 node.appendChild(model.__xml__().documentElement) 185 root.appendChild(node) 186 return doc
187
188 - def parse(self,element):
189 GoalBasedAgent.parse(self,element) 190 child = element.firstChild 191 while child: 192 if child.nodeType == child.ELEMENT_NODE \ 193 and child.tagName == 'models': 194 node = child.firstChild 195 while node: 196 if node.nodeType == node.ELEMENT_NODE: 197 assert(node.tagName == 'model') 198 name = str(node.getAttribute('name')) 199 model = self.__class__(name) 200 subNode = node.firstChild 201 while subNode and \ 202 subNode.nodeType != subNode.ELEMENT_NODE: 203 subNode = subNode.nextSibling 204 if subNode: 205 model.parse(subNode) 206 self.models[name] = model 207 node = node.nextSibling 208 child = child.nextSibling 209 if string.lower(str(element.getAttribute('modelChange'))) == 'true': 210 self.modelChange = True 211 else: 212 self.modelChange = False 213 name = str(element.getAttribute('modelName')) 214 if len(name) > 0: 215 if string.lower(str(element.getAttribute('modelFixed'))) == 'true': 216 self.setModel(name,True) 217 else: 218 self.setModel(name,False)
219
220 - def generateSpace(self,granularity=10,goals=None,weightList=None,availableWeight=1.):
221 """Generates a space of possible goal weights for this agent 222 @param granularity: the number of positive goal weights to consider for each goal (i.e., a granularity of I{n} will generate values of [0,1/I{n},2/I{n}, ..., 1]. This is a dictionary of numbers, indexed by individual goals. 223 @type granularity: dict 224 @param goals: the list of goals left to consider (typically omitted, the default is the current set of goals for this agent) 225 @type goals: L{teamwork.reward.MinMaxGoal.MinMaxGoal}[] 226 @param weightList: the initial list of possible goal weightings, to be extended in combination with the newly generated goal weightings (typically omitted) 227 @type weightList: dict 228 @param availableWeight: the weight to be distributed across the possible goals (typically omitted, the default is 1) 229 @type availableWeight: float 230 @return: all possible combinations of goal weights that sum to the C{availableWeight} 231 @rtype: (dict:L{teamwork.reward.MinMaxGoal.MinMaxGoal}S{->}float)[] 232 """ 233 if goals is None: 234 goals = self.getGoals() 235 if len(goals) == 0: 236 return [{}] 237 goals.sort() 238 if weightList is None: 239 weightList = [{}] 240 if len(goals) == 1: 241 # Only one goal left, so it must have whatever leftover weight is available 242 for weighting in weightList: 243 weighting[goals[0]] = availableWeight 244 return weightList 245 result = [] 246 weight = 0. 247 while weight < availableWeight+.0001: 248 newList = [] 249 for weighting in weightList: 250 weighting = copy.copy(weighting) 251 weighting[goals[0]] = weight 252 newList.append(weighting) 253 result += self.generateSpace(granularity,goals[1:],newList, 254 availableWeight-weight) 255 weight += 1./float(granularity-1) 256 return result
257
258 - def reachable(self,weightList,granularity,neighbors=None):
259 if neighbors is None: 260 neighbors = {} 261 reachable = {0:True} 262 toExplore = [0] 263 while len(toExplore) > 0: 264 index1 = toExplore.pop() 265 try: 266 myNeighbors = neighbors[index1] 267 except KeyError: 268 neighbors[index1] = [] 269 weight1 = weightList[index1] 270 weight2 = copy.copy(weight1) 271 for goal1 in self.getGoals(): 272 delta = 1./float(granularity-1) 273 if weight1[goal1] > delta/2.: 274 weight2[goal1] -= delta 275 found = -1 276 for goal2 in self.getGoals(): 277 if goal1 != goal2 and weight2[goal2] < 1.-delta/2.: 278 weight2[goal2] += delta 279 for index2 in range(len(weightList)): 280 if self.weightEqual(weight2, 281 weightList[index2]): 282 neighbors[index1].append(index2) 283 break 284 weight2[goal2] -= delta 285 weight2[goal1] += delta 286 neighbors[index1].sort() 287 myNeighbors = neighbors[index1] 288 for index2 in neighbors[index1]: 289 if not reachable.has_key(index2): 290 reachable[index2] = True 291 toExplore.append(index2) 292 return len(reachable) == len(weightList)
293
294 - def weightEqual(self,weight1,weight2):
295 for goal in self.getGoals(): 296 if abs(weight1[goal]-weight2[goal]) > 0.0001: 297 return False 298 return True
299
300 - def clusterSpace(self,granularity,weightList=None,debug=None, 301 finish=None,interrupt=None):
302 if debug is None: 303 debug = lambda msg: None 304 # Generate all candidate points in goal space 305 if weightList is None: 306 weightList = self.generateSpace(granularity) 307 goals = self.getGoals() 308 goals.sort() 309 weightDict = {} 310 for index in range(len(weightList)): 311 weighting = weightList[index] 312 key = string.join(map(lambda k:'%6.4f' % \ 313 (abs(weighting[k])),goals)) 314 weightDict[key] = index 315 # Generate the policies for all candidate points, as well as fill in 316 # adjacency matrix 317 ruleSets = [] 318 neighbors = {} 319 if not self.reachable(weightList,granularity,neighbors): 320 raise UserWarning 321 ## delta = 1./float(granularity-1) 322 for index in range(len(weightList)): 323 weighting = weightList[index] 324 debug((index,'Generating')) 325 ## # Find neighboring points in goal space 326 ## for goal,value in weighting.items(): 327 ## if value > delta/2.: 328 ## for other in weighting.keys(): 329 ## if other != goal: 330 ## newWeighting = copy.copy(weighting) 331 ## newWeighting[goal] -= delta 332 ## newWeighting[other] += delta 333 ## key = string.join(map(lambda k:'%6.4f' % \ 334 ## (abs(newWeighting[k])), 335 ## goals)) 336 ## try: 337 ## newIndex = weightDict[key] 338 ## except KeyError: 339 ## print weightDict.keys() 340 ## raise UserWarning,key 341 ## if newIndex >= 0: 342 ## try: 343 ## neighbors[index][newIndex] = True 344 ## except KeyError: 345 ## neighbors[index] = {newIndex: True} 346 ## try: 347 ## neighbors[newIndex][index] = True 348 ## except KeyError: 349 ## neighbors[newIndex] = {index: True} 350 # Compute the policy at this point 351 self.setGoals(weighting) 352 self.policy.reset() 353 rules = self.policy.compileRules(horizon=1,interrupt=interrupt) 354 ## print rules[1].values() 355 ## attr1,attr2 = filter(lambda v:not isinstance(v,bool), 356 ## rules[1].values()) 357 ## print attr1.weights.getArray() 358 ## print attr2.weights.getArray() 359 ## print sum(abs(attr1.weights.getArray()-attr2.weights.getArray())) 360 ## print attr1.compare(attr2) 361 ## if index >= 0: 362 ## raise UserWarning 363 if rules: 364 debug((index,'%d Rules' % (len(rules[0])))) 365 ruleSets.append(rules) 366 if interrupt and interrupt.isSet(): 367 return 368 ## for index in range(len(weightList)): 369 ## try: 370 ## neighbors[index] = neighbors[index].keys() 371 ## except KeyError: 372 ## neighbors[index] = [] 373 ## neighbors[index].sort() 374 # Cluster points goal space that have equivalent policies 375 distinct = {} 376 equivalence = {} 377 for myIndex in range(len(weightList)): 378 if interrupt and interrupt.isSet(): 379 return 380 # Keep track of comparisons already made 381 distinct[myIndex] = {} 382 # Consider only those neighbors we haven't already covered 383 myNeighbors = filter(lambda i: i>myIndex,neighbors[myIndex]) 384 # Find the base equivalent for this rule set 385 original = myIndex 386 while equivalence.has_key(myIndex): 387 myIndex = equivalence[myIndex] 388 if original != myIndex: 389 debug((original,'= %d' % (myIndex))) 390 else: 391 debug((original,'Unique')) 392 myRules,myAttrs,myVals = ruleSets[myIndex] 393 for yrIndex in myNeighbors: 394 # Find the base equivalent for the rule set to compare against 395 while yrIndex > myIndex and equivalence.has_key(yrIndex): 396 yrIndex = equivalence[yrIndex] 397 if distinct[myIndex].has_key(yrIndex): 398 # Already shown that they are not equal 399 continue 400 elif yrIndex <= myIndex: 401 # We've already done this comparison 402 continue 403 yrRules,yrAttrs,yrVals = ruleSets[yrIndex] 404 if rulesEqual(myRules,yrRules): 405 # All the rules have equivalents 406 equivalence[yrIndex] = myIndex 407 else: 408 # The rule sets differed in at least one way 409 distinct[myIndex][yrIndex] = True 410 411 # Extract partition on goal space, as well as abstract adjacency 412 # matrix 413 adjacency = {} 414 for myIndex in range(len(weightList)): 415 myNeighbors = neighbors[myIndex] 416 while equivalence.has_key(myIndex): 417 myIndex = equivalence[myIndex] 418 if not adjacency.has_key(myIndex): 419 adjacency[myIndex] = {} 420 for yrIndex in myNeighbors: 421 while equivalence.has_key(yrIndex): 422 yrIndex = equivalence[yrIndex] 423 if myIndex != yrIndex: 424 adjacency[myIndex][yrIndex] = True 425 unique = adjacency.keys() 426 unique.sort() 427 for myIndex in unique: 428 myNeighbors = adjacency[myIndex].keys() 429 myNeighbors.sort() 430 print myIndex,myNeighbors 431 adjacency[myIndex]['_policy'] = ruleSets[myIndex] 432 if finish: 433 finish(adjacency) 434 debug((-1,None))
435
436 -def rulesEqual(myRules,yrRules):
437 for myRule in myRules: 438 # Look for a matching rule in yrRules 439 for yrRule in yrRules: 440 if len(yrRule) != len(myRule): 441 # Different # of rules, these two rules do not match 442 continue 443 for attr,val in myRule.items(): 444 if not yrRule.has_key(attr): 445 # Missing key, these two rules do not match 446 break 447 elif val != yrRule[attr]: 448 # Different attribute value, these two rules do not match 449 break 450 else: 451 # Rules match on every attribute 452 break 453 else: 454 # No equivalent rule 455 return False 456 return True
457