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   
 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       
 32       
 46           
 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   
 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           
 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           
 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               
100   
101              debug.message(7,'Model change: '+best['model']) 
102              return best 
 103   
104       
105   
107          """Applies the named mental model to this entity""" 
108   
109   
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   
121   
122   
123   
124   
125   
126   
127   
128   
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           
136          """Lock the model of this agent and all mental models it may have""" 
137          self.modelChange = None 
138   
139   
140          for entity in self.getEntityBeliefs(): 
141              try: 
142                  entity.freezeModels() 
143              except AttributeError: 
144                  pass 
 145   
155   
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               
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               
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       
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           
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           
316           
317          ruleSets = [] 
318          neighbors = {} 
319          if not self.reachable(weightList,granularity,neighbors): 
320              raise UserWarning 
321   
322          for index in range(len(weightList)): 
323              weighting = weightList[index] 
324              debug((index,'Generating')) 
325   
326   
327   
328   
329   
330   
331   
332   
333   
334   
335   
336   
337   
338   
339   
340   
341   
342   
343   
344   
345   
346   
347   
348   
349   
350               
351              self.setGoals(weighting) 
352              self.policy.reset() 
353              rules = self.policy.compileRules(horizon=1,interrupt=interrupt) 
354   
355   
356   
357   
358   
359   
360   
361   
362   
363              if rules: 
364                  debug((index,'%d Rules' % (len(rules[0])))) 
365                  ruleSets.append(rules) 
366              if interrupt and interrupt.isSet(): 
367                  return 
368   
369   
370   
371   
372   
373   
374           
375          distinct = {} 
376          equivalence = {} 
377          for myIndex in range(len(weightList)): 
378              if interrupt and interrupt.isSet(): 
379                  return 
380               
381              distinct[myIndex] = {} 
382               
383              myNeighbors = filter(lambda i: i>myIndex,neighbors[myIndex]) 
384               
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                   
395                  while yrIndex > myIndex and equivalence.has_key(yrIndex): 
396                      yrIndex = equivalence[yrIndex] 
397                  if distinct[myIndex].has_key(yrIndex): 
398                       
399                      continue 
400                  elif yrIndex <= myIndex: 
401                       
402                      continue 
403                  yrRules,yrAttrs,yrVals = ruleSets[yrIndex] 
404                  if rulesEqual(myRules,yrRules): 
405                       
406                      equivalence[yrIndex] = myIndex 
407                  else: 
408                       
409                      distinct[myIndex][yrIndex] = True 
410   
411           
412           
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   
437      for myRule in myRules: 
438           
439          for yrRule in yrRules: 
440              if len(yrRule) != len(myRule): 
441                   
442                  continue 
443              for attr,val in myRule.items(): 
444                  if not yrRule.has_key(attr): 
445                       
446                      break 
447                  elif val != yrRule[attr]: 
448                       
449                      break 
450              else: 
451                   
452                  break 
453          else: 
454               
455              return False 
456      return True 
 457