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