1 """A class of unique identifiers for symbolic identification of rows and columns in the PWL representations of state, dynamics, reward, etc.
2 @var keyConstant: a L{ConstantKey} instance to be reused (i.e., so as not to have to create millions of new instances
3 @var keyDelete: a flag string used internally"""
4 from xml.dom.minidom import *
5 import copy
6 import string
7 from teamwork.action.PsychActions import Action
8
9 keyDelete = '__delete__'
10
12 """Superclass for all different keys, used for indexing the various symbolic matrices and hyperplanes"""
13
14 keyType = 'generic'
15
16 slots = {}
17
18 CLASS = 0
19 ENTITY = 1
20 STATE = 2
21 ACTION = 3
22 VALUE = 4
23 ENTITIES = 5
24 TEST = 6
25
27 try:
28 return self._string
29 except AttributeError:
30 self._string = self.simpleText()
31 return self._string
32
34 """Allows L{Key} objects to be used as dictionary keys
35 @return: A hash value for this L{Key}"""
36 try:
37 return self._hash
38 except AttributeError:
39 self._hash = hash(str(self))
40 return self._hash
41
42 - def simpleText(self):
43 return dict.__str__(self)
44
46 """Utility method that takes a mapping from entity references (e.g., 'self', 'actor') to actual entity names (e.g., 'Bill', 'agent0'). It returns a new key string with the substitution applied. Subclasses should override this method for more instantiation of more specialized fields.
47 @param table: A dictionary of label-value pairs. Any slot in the key whose filler matches a label in the given table will be replaced by the associated value.
48 @type table: C{dict}
49 @return: Any new L{Key} instances with the appropriate label substitutions
50 @rtype: L{Key}[]
51 """
52 objList = [copy.copy(self)]
53 for key,value in self.items():
54 try:
55 entity = table[value]
56 except KeyError:
57 continue
58 if isinstance(entity,list):
59 entityList = entity
60 elif isinstance(entity,str):
61 entityList = [entity]
62 else:
63
64 entityList = [entity.name]
65 for obj in objList[:]:
66 objList.remove(obj)
67 for entity in entityList:
68 newObj = copy.copy(obj)
69 newObj[key] = entity
70 objList.append(newObj)
71 return objList
72
74 return self.__class__(self)
75
77 """XML Serialization
78
79 The format looks like::
80
81 <key type=\"self.keyType\">
82 <tag1>value1</tag1>
83 <tag2>value2</tag2>
84 ...
85 <tagn>valuen</tagn>
86 </key>
87
88 @return: An XML object representing this L{Key}
89 @rtype: Element"""
90 doc = Document()
91 root = doc.createElement('key')
92 doc.appendChild(root)
93 root.setAttribute('type',self.keyType)
94 for key,value in self.items():
95 node = doc.createElement(key)
96 root.appendChild(node)
97 if isinstance(value,str):
98 node.appendChild(doc.createTextNode(value))
99 elif isinstance(value,int):
100 node.appendChild(doc.createTextNode(str(value)))
101 elif value is None:
102 pass
103 elif isinstance(value,list):
104 for action in value:
105 if isinstance(action,Action):
106 node.appendChild(action.__xml__().documentElement)
107 else:
108 raise NotImplementedError,'Unable to serialize key, "%s", with lists of %s' % (key,action.__class__.__name__)
109 else:
110 raise NotImplementedError,'Unable to serialize key, "%s", with values of type %s' % (key,value.__class__.__name__)
111 return doc
112
113 - def parse(self,element):
114 """Updates the current key with the elements stored in the given XML element
115 @type element: Element
116 @return: the L{Key} object stored in the given XML element
117 """
118 assert(element.tagName == 'key')
119 keyType = element.getAttribute('type')
120 for cls in self.__class__.__subclasses__():
121 if cls.keyType == keyType:
122 key = cls()
123 node = element.firstChild
124 while node:
125 if node.nodeType == node.ELEMENT_NODE:
126
127 label = string.strip(str(node.tagName))
128 value = None
129 if label == 'action':
130
131 value = []
132 subNode = node.firstChild
133 while subNode:
134 if subNode.nodeType == node.ELEMENT_NODE:
135 act = Action()
136 act.parse(subNode)
137 value.append(act)
138 subNode = subNode.nextSibling
139 elif label == 'depth':
140
141 value = int(node.childNodes[0].data)
142 else:
143 try:
144 value = string.strip(str(node.childNodes[0].data))
145 except IndexError:
146
147 pass
148 key[label] = value
149 node = node.nextSibling
150 return key
151 else:
152 print element.getAttribute('type')
153 print map(lambda c:c.keyType,self.__class__.__subclasses__())
154 raise NotImplementedError,'Unsupported key type: %s' % (keyType)
155
157 """A L{Key} indicating the entry corresponding to a constant factor"""
158 keyType = 'constant'
159
160 - def simpleText(self):
162
163 keyConstant = ConstantKey()
164
166 """A L{Key} indicating the entry corresponding to a state feature value"""
167 keyType = 'state'
168 slots = {'entity':Key.ENTITY,
169 'feature':Key.STATE}
170
171 - def simpleText(self):
172 if self['entity'] == 'self':
173 return 'my %s' % (self['feature'])
174 else:
175 return '%s\'s %s' % (self['entity'],self['feature'])
176
178 """A L{Key} indicating the mental model corresponding to a given entity
179 """
180 keyType = 'mental model'
181 slots = {'entity':Key.ENTITY,
182 'feature':Key.STATE,
183 }
188
189 - def simpleText(self):
190 if self['entity'] == 'self':
191 return 'mental model of me'
192 else:
193 return 'mental model of %s' % (self['entity'])
194
196 """A L{StateKey} whose values will be either 0. or 1.
197 @warning: the code does not enforce this restriction; it merely exploits it
198 """
199 keyType = 'binary'
200
202 """A L{Key} indicating the entry corresponding to an observation flag
203
204 >>> key = ObservationKey({'type':'heard left'})
205 """
206 keyType = 'observation'
207 decayRate = 0.5
208
209 - def simpleText(self):
211
213 """A L{Key} indicating the entry corresponding to an observed action flag
214
215 >>> key = ActionKey({'type':'tax','entity':'manager','object':'market'})
216
217 A minimum of one of the fields must be provided. Any omitted fields are assumed to be filled by wildcards.
218 """
219 keyType = 'action'
220 slots = {'type':Key.ACTION,
221 'entity':Key.ENTITY,
222 'object':Key.ENTITY}
223
224 - def simpleText(self):
225 content = self['type']
226 if self['object']:
227 content += ' %s' % (self['object'])
228 if self['entity']:
229 content += ' by %s' % (self['entity'])
230 return content
231
233 """A L{Key} indicating the entry corresponding to a role flag. The I{relationship} slot can take on the following values:
234 - equals: tests that an agent equals the specified I{entity} field
235 - in: tests that an agent is in the specified I{entity} list of agents
236 """
237 keyType = 'identity'
238 slots = {'entity':Key.ENTITY,
239 'relationship':Key.TEST}
240
242 Key.__init__(self,args)
243 if self.has_key('entity') and 'entity' == 'self':
244 raise UserWarning,'Hey nimrod, why are you testing whether the agent is itself?'
245
247 """Utility method that takes a mapping from entity references (e.g., 'self', 'actor') to actual entity names (e.g., 'Bill', 'agent0'). It returns a new key string with the substitution applied. It also reduces any 'identity' keys to be either constant or null depending on whether the key matches the identity of the entity represented in the dictionary provided. Subclasses should override this method for more instantiation of more specialized fields.
248 @param table: A dictionary of label-value pairs. Any slot in the key whose filler matches a label in the given table will be replaced by the associated value.
249 @type table: C{dict}
250 @return: A new L{Key} instance with the appropriate label substitutions
251 @rtype: L{Key}
252 """
253 if self['relationship'] == 'equals':
254 if table.has_key(self['entity']) and \
255 table[self['entity']] == table['self'].name:
256 return keyConstant
257 else:
258 return keyDelete
259 elif self['relationship'] == 'in':
260 assert(isinstance(self['entity'],list),
261 'Non-list entity for "in" relationship: %s' \
262 % (str(self['entity'])))
263 if table['self'].name in table[self['entity']]:
264 return keyConstant
265 else:
266 return keyDelete
267 else:
268 raise NotImplementedError,'Unknown relationship, "%s", in %s instance' \
269 % (self['relationship'],self.__class__.__name__)
270
271 - def simpleText(self):
272 return 'I am %s' % (self['entity'])
273
275 """A L{Key} indicating the entry corresponding to a class membership flag"""
276 keyType = 'class'
277 slots = {'value':Key.CLASS,
278 'entity':Key.ENTITY}
279
280 - def simpleText(self):
281 if self['entity'] == 'self':
282 return 'I am %s' % (self['value'])
283 else:
284 return '%s is %s' % (self['entity'],self['value'])
285
287 """Utility method that takes a mapping from entity references (e.g., 'self', 'actor') to actual entity names (e.g., 'Bill', 'agent0'). It returns a new key string with the substitution applied. It also reduces any 'identity' keys to be either constant or null depending on whether the key matches the identity of the entity represented in the dictionary provided. Subclasses should override this method for more instantiation of more specialized fields.
288 @param table: A dictionary of label-value pairs. Any slot in the key whose filler matches a label in the given table will be replaced by the associated value.
289 @type table: C{dict}
290 @return: A new L{Key} instance with the appropriate label substitutions
291 @rtype: L{Key}
292 """
293 if self['entity'] == 'self':
294 entity = table['self']
295 else:
296 try:
297 entity = table['self'].getEntity(table[self['entity']])
298 except KeyError:
299
300 return keyDelete
301 if entity.instanceof(self['value']):
302 return keyConstant
303 else:
304 return keyDelete
305
307 """A L{Key} indicating the entry corresponding to the slot for the corresponding inter-agent relationship
308 """
309 keyType = 'relationship'
310 slots = {'feature':Key.STATE,
311 'relatee':Key.ENTITY,
312 }
313
314 - def simpleText(self):
315 if self['relatee'] == 'self':
316 content = 'I am my own %s' % (self['feature'])
317 else:
318 content = '%s is my %s' % (self['relatee'],self['feature'])
319 return content
320
322 """Utility method that takes a mapping from entity references (e.g., 'self', 'actor') to actual entity names (e.g., 'Bill', 'agent0'). It returns a new key string with the substitution applied. It also reduces any 'identity' keys to be either constant or null depending on whether the key matches the identity of the entity represented in the dictionary provided. Subclasses should override this method for more instantiation of more specialized fields.
323 @param table: A dictionary of label-value pairs. Any slot in the key whose filler matches a label in the given table will be replaced by the associated value.
324 @type table: C{dict}
325 @return: A new L{Key} instance with the appropriate label substitutions
326 @rtype: L{Key}
327 """
328 try:
329 eligible = table['self'].relationships[self['feature']]
330 except KeyError:
331 return keyDelete
332 if self['relatee'] == 'self':
333 name = table[self['relatee']].name
334 else:
335 name = table[self['relatee']]
336 if name in eligible:
337 return keyConstant
338 else:
339 return keyDelete
340
341
343 """A L{Key} corresponding to a slot for a pairwise link between two entities
344 """
345 keyType = 'link'
346 slots = {
347 'subject':Key.ENTITY,
348 'verb':Key.STATE,
349 'object':Key.ENTITY,
350 }
351
352 - def simpleText(self):
353 if self['subject'] == 'self':
354 content = 'I %s ' % (self['verb'])
355 if self['object'] == 'self':
356 content += 'myself'
357 else:
358 content += self['object']
359 else:
360 content = '%s %s ' % (self['subject'],self['verb'])
361 if self['object'] == 'self':
362 content += 'me'
363 else:
364 content += self['object']
365 return content
366
368 """A L{Key} indexing into a sample space of possible worlds
369 """
370 keyType = 'world'
371 slots = {'world': Key.VALUE}
372
373 - def simpleText(self):
374 return 'Q=%d' % (self['world'])
375
377 """Helper function for creating StateKey objects
378 @param entity: The entity to be pointed to by this key
379 @type entity: string
380 @param feature: The state feature to be pointed to by this key
381 @type feature: string
382 @return: the corresponding StateKey object"""
383 return StateKey({'entity':entity,'feature':feature})
384
386 """Helper function for creating ActionKey objects
387 @param action: The action to be flagged by this Key
388 @type action: Action instance (though a generic dictionary will work, too)
389 @return: an ActionKey instance corresponding to the given action"""
390 return ActionKey({'type':action['type'],
391 'entity':action['actor'],
392 'object':action['object']})
393
395 """Helper funtion for creating IdentityKey objects
396 @param entity: the relationship that should be matched on
397 @type entity: string (e.g., 'actor','object')
398 @return: an IdentityInstance testing for the given relationship"""
399 return IdentityKey({'entity':entity,
400 'relationship':'equals'})
401
403 """Helper function for creating ClassKey objects
404 @param entity: the entity to be tested
405 @type entity: string (e.g., 'actor','object','self')
406 @param className: the class name to test membership for
407 @type className: string
408 @return: a ClassKey instance with the appropriate class test
409 @warning: may not actually work
410 """
411 return ClassKey({'value':className,
412 'entity':entity})
413
415 """Debugging code"""
416 used = {}
417 for root in original:
418 trees = [root]
419 while len(trees) > 0:
420 tree = trees.pop()
421 trees += tree.children()
422 assert not used.has_key(id(tree))
423 used[id(tree)] = True
424 if tree.isLeaf():
425 matrix = tree.getValue()
426 assert not used.has_key(id(matrix))
427 used[id(matrix)] = True
428 for row in matrix.values():
429 assert not used.has_key(id(row))
430 used[id(row)] = True
431 else:
432 for plane in tree.split:
433 assert not plane.weights._frozen
434 assert not used.has_key(id(plane.weights))
435 assert not used.has_key(id(plane.weights._order))
436 used[id(plane.weights)] = True
437 used[id(plane.weights._order)] = True
438 for root in original:
439 trees = [root]
440 while len(trees) > 0:
441 tree = trees.pop()
442 trees += tree.children()
443 if tree.isLeaf():
444 for row in tree.getValue().values():
445 for key in row.keys():
446 if not row._order.has_key(key):
447 print 'leaf'
448 return root,key
449 else:
450 for plane in tree.split:
451 for key in plane.weights.keys():
452 if not plane.weights._order.has_key(key):
453 print 'branch'
454 return root,key
455 else:
456 return None
457