1 """A minimal agent implementation that uses only the PWL representations of an agent's beliefs, policy, etc.
2 """
3 from Agent import Agent
4 from teamwork.dynamics.pwlDynamics import PWLDynamics,IdentityDynamics
5 from teamwork.action.PsychActions import Action
6 from teamwork.action.DecisionSpace import parseSpace
7 from teamwork.math.Keys import StateKey,LinkKey,keyConstant
8 from teamwork.math.probability import Distribution
9 from teamwork.math.KeyedVector import KeyedVector
10 from teamwork.math.KeyedMatrix import IdentityMatrix
11 from teamwork.policy.policyTable import PolicyTable
12 from teamwork.reward.MinMaxGoal import MinMaxGoal
13 import copy
14
16 """Lightweight version of a PsychSim agent with only the barest essentials for generating behaviors
17 @ivar dynamics: a dictionary of dictionaries of L{PWLDynamics} objects. The top-level key is the state feature the dynamics affect. The keys at the level below are L{Action} types.
18 @type dynamics: strS{->}(strS{->}L{PWLDynamics})
19 @ivar parent: the parent of this agent, included for compatibility with L{RecursiveAgent<teamwork.agent.RecursiveAgent.RecursiveAgent>} instances. Always C{None}.
20 @ivar policy: this agent's policy of behavior
21 @type policy: L{PolicyTable}
22 @ivar goals: this agent's goal weights
23 @type goals: L{KeyedVector}
24 @ivar liking: the liking this agent has for others
25 @type liking: L{KeyedVector}
26 @ivar trust: the trust this agent has in others
27 @type trust: L{KeyedVector}
28 @cvar _supportFeature: the label for the liking relationship
29 @cvar _trustFeature: the label for the trust relationship
30 @type _supportFeature,_trustFeature: str
31 @ivar relationships: an empty dictionary of inter-agent relationships from this agent
32 @ivar compiled: if C{True}, the value function is compiled (default is C{False}
33 @type compiled: bool
34 """
35 _supportFeature = 'likes'
36 _trustFeature = 'trusts'
37
39 """Constructor that creates a lightweight version of a given PsychSim agent
40 @param agent: the L{RecursiveAgent<teamwork.agent.RecursiveAgent.RecursiveAgent>} to be distilled (if omitted, an empty agent is created)
41 """
42 self.dynamics = {}
43 self.parent = None
44 self.policy = None
45 self.horizon = 0
46 self.state = Distribution({KeyedVector({keyConstant:1.}):1.})
47 self.goals = Distribution({KeyedVector():1.})
48 self.relationships = {}
49 self.links = KeyedVector()
50 self.linkDynamics = {self._supportFeature:{},
51 self._trustFeature:{}}
52 self.linkTypes = [self._supportFeature,self._trustFeature]
53 self.classes = []
54 self.world = None
55 self.society = None
56 self.compiled = False
57 self.beliefs = self.state
58 self.estimators = {}
59 if agent is None:
60 name='Piecewise Linear Agent'
61 elif isinstance(agent,str):
62 name = agent
63 else:
64 name = agent.name
65 Agent.__init__(self,name)
66 if isinstance(agent,Agent):
67 self.actions = agent.actions
68 if len(agent.entities) > 0:
69 self.beliefs = copy.deepcopy(agent.entities.getState())
70
71 self.horizon = agent.horizon
72 self.policy = PolicyTable(self,self.actions,agent.policy.horizon)
73 self.policy.attributes = agent.policy.attributes[:]
74 self.policy.rules = copy.deepcopy(agent.policy.rules)
75
76 for feature,dynTable in agent.dynamics.items():
77 self.dynamics[feature] = {}
78 for action,dynamics in dynTable.items():
79 if isinstance(action,str):
80 self.dynamics[feature][action] = dynamics
81
82 self.goals = agent.getGoalVector()['state']
83
84 self.linkTypes = agent.getLinkTypes()[:]
85 for linkType in agent.getLinkTypes():
86 for name in agent.getLinkees(linkType):
87 key = agent.getLinkKey(linkType,name)
88 self.links[key] = agent.getLink(linkType,name)
89
90 for feature,dynTable in agent.linkDynamics.items():
91 self.linkDynamics[feature] = {}
92 for action,dynamics in dynTable.items():
93 if isinstance(action,str):
94 self.linkDynamics[feature][action] = dynamics
95
96 self.classes = agent.classes[:]
97
98 for relation,relatees in agent.relationships.items():
99 self.relationships[relation] = relatees[:]
100
102 """We don't need no stinking defaults"""
103 pass
104
106 """
107 @return: a string representation of this entity's position in the recursive belief tree.
108 - If C{self.parent == None}, then returns C{self.name}
109 - Otherwise, returns C{self.parent.ancestry()+'->'+self.name}
110 @rtype: C{str}
111 """
112 name = self.name
113 parent = self.parent
114 while parent:
115 name = parent.name + '->' + name
116 parent = parent.parent
117 return name
118
120 """
121 @return: True iff this entity is a member of the specified class
122 @param className: name of class to test against
123 @type className: C{str}
124 @rtype: C{boolean}
125 """
126 return className in self.classes
127
129 """
130 @return: names of the valid state features
131 @rtype: C{str[]}
132 """
133 keyList = []
134 for key in self.state.domainKeys():
135 if isinstance(key,StateKey) and key['entity'] == self.name:
136 keyList.append(key['feature'])
137 return keyList
138
140 """Returns the current L{Distribution} over the specified feature
141 @type feature: string
142 @rtype: L{Distribution}"""
143 key = StateKey({'entity':self.name,'feature':feature})
144 return self.state.getMarginal(key)
145
147 """Sets this entity's state value for the specified feature
148 @type feature: string
149 @type value: either a float or a L{Distribution}. If the value is a float, it is first converted into a point distribution."""
150 key = StateKey({'entity':self.name,'feature':feature})
151 frozen = self.state.unfreeze()
152 self.state.join(key,value)
153 if frozen:
154 self.state.freeze()
155
157 entities = {}
158 for key in self.beliefs.domain()[0].keys():
159 if isinstance(key,StateKey):
160 entities[key['entity']] = True
161 return entities.keys()
162
164 return self.world[name]
165
168
170 """
171 @return: the state features across all beliefs that this agent has
172 @rtype: L{StateKey}[]
173 """
174 return self.beliefs.expectation().keys()
175
177 """
178 @return: all of the available dynamics relationship types that
179 his entity has
180 @rtype: str[]
181 """
182 return self.linkTypes
183
185 """
186 @param relationship: the dynamic relationship (e.g., 'likes',
187 'trusts') to evaluate
188 @type relationship: str
189 @return: the others to which this entity has explicit
190 relationships of the specified type
191 @rtype: str[]
192 """
193 result = []
194 for key in self.links.keys():
195 if key['subject'] == self.name and key['verb'] == relationship:
196 result.append(key['object'])
197 return result
198
200 """
201 @return: the vector index for this entity's relation to the given
202 entity
203 @rtype: L{LinkKey}
204 """
205 return LinkKey({'subject':self.name,
206 'verb':relation,
207 'object':entity})
208
209 - def getLink(self,relationship,entity):
210 """
211 @param relationship: the dynamic relationship (e.g., 'likes',
212 'trusts') to evaluate
213 @param entity: the entity who is the object of the
214 relationship (e.g., the entity being liked or trusted)
215 @type relationship,entity: str
216 @return: the current value of the link
217 @rtype: float
218 """
219 key = self.getLinkKey(relationship,entity)
220 try:
221 return self.links[key]
222 except KeyError:
223 return 0.
224
225 - def setLink(self,relationship,entity,value):
226 """
227 @param relationship: the dynamic relationship (e.g., 'likes',
228 'trusts') to evaluate
229 @param entity: the entity who is the object of the
230 relationship (e.g., the entity being liked or trusted)
231 @type relationship,entity: str
232 @param value: the new value for my trust level
233 @type value: float
234 """
235 key = self.getLinkKey(relationship,entity)
236 self.links[key] = value
237
238
242
244 """
245 @param entity: the name of the relevant entity
246 @type entity: str
247 @param feature: the state feature of interest
248 @type feature: str
249 @return: the agent's current belief about the given entity's value for the given state feature
250 @rtype: L{Distribution}
251 """
252 key = StateKey({'entity':entity,'feature':feature})
253 return self.beliefs.getMarginal(key)
254
256 """Sets this entity's belief value for the specified entity's state feature value
257 @param entity: the name of the relevant entity
258 @type entity: str
259 @param feature: the state feature of interest
260 @type feature: string
261 @type value: either a float or a L{Distribution}. If the value is a float, it is first converted into a point distribution."""
262 key = StateKey({'entity':entity,'feature':feature})
263 self.beliefs.unfreeze()
264 self.beliefs.join(key,value)
265 self.beliefs.freeze()
266
269
271 return {'state': self.goals}
272
273 - def applyPolicy(self,state=None,actions=[],history=None,debug=None,
274 explain=False,entities={},cache={}):
275 """Generates a decision chosen according to the agent's current policy
276 @param state: the current state vector
277 @type state: L{Distribution}(L{KeyedVector})
278 @param actions: the possible actions the agent can consider (defaults to all available actions)
279 @type actions: L{Action}[]
280 @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)
281 @type history: L{Action}[]:bool
282 @param explain: flag indicating whether an explanation should be generated
283 @type explain: bool
284 @param entities: a dictionary of entities to be used as a value tree cache
285 @param cache: values computed so far
286 @return: a list of actions and an explanation, the latter provided by L{execute<PolicyTable.execute>}
287 @rtype: C{(L{Action}[],Element)}
288 """
289 if state is None:
290 state = {None:self.beliefs}
291 return self.policy.execute(state=state,choices=actions,history=history,
292 debug=debug,explain=explain,
293 entities=entities,cache=cache)
294
295 - def actionValue(self,actions,horizon=1,state=None,debug=False):
296 """Compute the expected value of performing the given action
297 @param actions: the actions whose effect we want to evaluate
298 @type actions: L{Action}[]
299 @param horizon: the length of the forward projection
300 @type horizon: int
301 @param state: the world state to evaluate the actions in (defaults to current world state)
302 @type state: L{Distribution}
303 @warning: Computation assumes that dynamics for I{instantiated} actions are already stored. Missing dynamics are taken to imply no effect.
304 """
305 if state is None:
306 state = {None:self.beliefs}
307 eState = state[None].expectation()
308 goals = self.goals.expectation()
309 projection = {}
310 total = 0.
311 for key in filter(lambda k: goals[k] > Distribution.epsilon,
312 goals.keys()):
313 if key['entity'] == self.name:
314 entity = self
315 elif self.world:
316 entity = self.world[key['entity']]
317 else:
318 raise UserWarning,'Entity %s has no world' % (self.name)
319 dynamics,delta = self.world.getEffect(actions[0],key,eState,
320 not self.compiled)
321 if dynamics:
322 if delta is None:
323 tree = dynamics.getTree()
324 delta = dynamics.apply(state[None]).expectation()
325 total += goals[key]*(delta[key]*eState)
326 for subKey in delta[key].keys():
327 try:
328 projection[subKey] += goals[key]*delta[key][subKey]
329 except KeyError:
330 projection[subKey] = goals[key]*delta[key][subKey]
331 return total,{'value':total,'projection':projection}
332
334 """Updates the agent's beliefs in response to the given agents
335 @param beliefs: the agent's current beliefs (typically C{self.beliefs})
336 @type beliefs: L{Distribution}(L{KeyedVector})
337 @param actions: the observed actions
338 @type actions: L{Action}[]
339 @param obersvation: if in a partially observable domain, this is the observation received (default is C{None})
340 @type observation: str
341 @return: the new beliefs (updates the agent's beliefs as a side effect)
342 @rtype: L{Distribution}(L{KeyedVector})
343 """
344 if beliefs is None:
345 doForReal = True
346 beliefs = self.beliefs
347 else:
348 doForReal = False
349 if observation:
350 if isinstance(beliefs,list):
351
352 beliefs = beliefs + [observation]
353 else:
354
355 SE = self.estimators[observation]
356 rule = SE.index(beliefs)
357 numerator = SE.values[rule][str(actions)]
358 beliefs = numerator*beliefs
359 beliefs *= 1./sum(beliefs.getArray())
360 else:
361 dynamics = self.dynamics[str(actions)]
362 beliefs = dynamics[beliefs]*beliefs
363 if doForReal:
364 self.beliefs = beliefs
365 return beliefs
366
367
369 """Compute state estimator
370 """
371 self.estimators.clear()
372 others = []
373 for agent in self.world.members():
374 if agent.name != self.name and len(agent.actions.getOptions()) > 0:
375 others.append(agent)
376 if len(others) > 1:
377 raise NotImplementedError,'Unable to handle more than 2 agents'
378 n = len(self.policy.tables) - 1
379 if n <= 0:
380
381 policy = others[0].policy.getTable(0)
382 else:
383
384 policy = others[0].policy.getTable(n-1)
385 for omega in obs.values()[0].keys():
386
387 T = policy.getTable()
388 for rule in range(len(T)):
389 yrAction = T.rules[rule]
390 T.values[rule].clear()
391 for myAction in self.actions.getOptions():
392 actions = {self.name:myAction,others[0].name:yrAction}
393 actionKey = ' '.join(map(str,actions.values()))
394 value = copy.copy(trans[actionKey])
395 T.values[rule][str(myAction)] = value
396
397 O = policy.getTable()
398 for rule in range(len(O)):
399 yrAction = O.rules[rule]
400 O.values[rule].clear()
401 for myAction in self.actions.getOptions():
402 actions = {self.name:myAction,others[0].name:yrAction}
403 actionKey = ' '.join(map(str,actions.values()))
404 value = copy.copy(obs[actionKey][omega])
405 O.values[rule][str(myAction)] = value
406
407 def merge(o,t):
408 result = copy.copy(t)
409 for key in result.rowKeys():
410 result[key] = result[key]*o[key]
411 return result
412 self.estimators[omega] = O.__mul__(T,merge)
413 return self.estimators
414
415
416 - def getDynamics(self,act,feature,cache=False,debug=False):
417 """
418 @return: this entity's dynamics model for the given action
419 @param act: the action whose effect we are interested in
420 @type act: L{Action}
421 @param feature: the feature whose effect we want to get the dynamics for
422 @type feature: C{str}
423 @param cache: if C{True}, then save the instantiated dynamics for future recall (default is C{False})
424 @type cache: bool
425 @rtype: L{PWLDynamics}
426 @param debug: if C{True}, prints out some debugging statements (default is C{False}
427 @type debug: bool
428 """
429 if debug:
430 print 'Computing effect of %s on %s\'s %s' % \
431 (str(act),self.name,feature)
432 try:
433 dynFun = self.dynamics[feature][act]
434 if debug:
435 print '\tAlready computed'
436 except KeyError:
437 try:
438 dynFun = self.dynamics[feature][act['type']]
439 if debug:
440 print '\tFound generic dynamics'
441 except KeyError:
442
443
444 if debug:
445 print '\tNo generic dynamics'
446 return None
447 if isinstance(dynFun,str):
448
449 if debug:
450 print '\tLink to generic dynamics'
451 dynFun = self.society[dynFun].dynamics[feature][act['type']]
452 if dynFun:
453 if debug:
454 print '\tInstantiating...'
455 dynFun = dynFun.instantiate(self,act)
456 tree = dynFun.getTree()
457 if tree.isLeaf():
458 matrix = tree.getValue()
459 if isinstance(matrix,IdentityMatrix):
460
461 dynFun = None
462 else:
463
464 key = StateKey({'entity':self.name,'feature':feature})
465 vector = matrix[key]
466 if abs(sum(vector.getArray())-1.) < Distribution.epsilon and \
467 abs(vector[key]-1.) < Distribution.epsilon:
468 dynFun = None
469 if dynFun is None:
470 if debug:
471 print '\tNull effect'
472 return None
473 if cache:
474 if dynFun and self.world:
475
476 for other in self.world.members():
477 for key,table in other.dynamics.items():
478 for action,dynamics in table.items():
479 if isinstance(action,Action) and \
480 isinstance(dynamics,PWLDynamics) and \
481 dynamics.getTree() == dynFun.getTree():
482 if debug:
483 print '\tLink to effect of %s on %s\'s %s' % (str(action),other.name,key)
484 dynFun = {'entity':other.name,
485 'feature':key,
486 'action':action}
487 break
488 if isinstance(dynFun,dict):
489 break
490 if isinstance(dynFun,dict):
491 break
492 try:
493 self.dynamics[feature][act] = dynFun
494 except KeyError:
495 self.dynamics[feature] = {act: dynFun}
496 if debug:
497 print '\tCached'
498 assert isinstance(act,Action)
499 assert self.dynamics[feature].has_key(act)
500 if isinstance(dynFun,dict):
501
502 return self.world[dynFun['entity']].dynamics[dynFun['feature']][dynFun['action']]
503 else:
504
505 return dynFun
506
508 """
509 @param dynamcis: if C{True}, instantiated dynamics are stored as well (default is C{False}
510 @type dynamics: bool
511 """
512 doc = Agent.__xml__(self)
513
514 node = doc.createElement('dynamics')
515 doc.documentElement.appendChild(node)
516 for feature,subDict in self.dynamics.items():
517 featureNode = doc.createElement('feature')
518 node.appendChild(featureNode)
519 featureNode.setAttribute('label',feature)
520 for actType,dynamic in subDict.items():
521 if isinstance(actType,str):
522
523 actNode = doc.createElement('action')
524 actNode.setAttribute('compiled','0')
525 featureNode.appendChild(actNode)
526 actNode.setAttribute('type',actType)
527 if isinstance(dynamic,str):
528 actNode.setAttribute('link',dynamic)
529 else:
530 actNode.appendChild(dynamic.__xml__().documentElement)
531 else:
532
533 actNode = actType.__xml__().documentElement
534 actNode.setAttribute('compiled','1')
535 featureNode.appendChild(actNode)
536 if dynamic is None:
537 raise UserWarning
538 elif isinstance(dynamic,dict):
539 link = doc.createElement('link')
540 link.setAttribute('entity',dynamic['entity'])
541 link.setAttribute('feature',dynamic['feature'])
542 link.appendChild(dynamic['action'].__xml__().documentElement)
543 actNode.appendChild(link)
544 else:
545 actNode.appendChild(dynamic.__xml__().documentElement)
546
547 node = doc.createElement('relationships')
548 doc.documentElement.appendChild(node)
549 for label,agents in self.relationships.items():
550 relationshipNode = doc.createElement('relationship')
551 node.appendChild(relationshipNode)
552 relationshipNode.setAttribute('label',label)
553 for name in agents:
554 subNode = doc.createElement('relatee')
555 subNode.appendChild(doc.createTextNode(name))
556 relationshipNode.appendChild(subNode)
557
558 if not self.beliefs is self.state:
559 node = doc.createElement('beliefs')
560 doc.documentElement.appendChild(node)
561 node.appendChild(self.beliefs.__xml__().documentElement)
562
563 node = doc.createElement('policy')
564 doc.documentElement.appendChild(node)
565 node.appendChild(self.policy.__xml__().documentElement)
566
567 node = doc.createElement('actions')
568 doc.documentElement.appendChild(node)
569 node.appendChild(self.actions.__xml__().documentElement)
570
571 node = doc.createElement('goals')
572 doc.documentElement.appendChild(node)
573 node.appendChild(self.goals.__xml__().documentElement)
574
575 for cls in self.classes:
576 node = doc.createElement('class')
577 node.setAttribute('name',cls)
578 doc.documentElement.appendChild(node)
579
580 node = doc.createElement('links')
581 node.appendChild(self.links.__xml__().documentElement)
582 doc.documentElement.appendChild(node)
583
584 for linkType in self.getLinkTypes():
585 node = doc.createElement('linktype')
586 node.setAttribute('name',linkType)
587 doc.documentElement.appendChild(node)
588
589 for linkType,table in self.linkDynamics.items():
590 for action,dynamics in table.items():
591 if isinstance(action,str):
592 node = doc.createElement('linkdynamics')
593 node.setAttribute('feature',linkType)
594 node.setAttribute('action',action)
595 node.appendChild(dynamics.__xml__().documentElement)
596 doc.documentElement.appendChild(node)
597 return doc
598
599 - def parse(self,element):
600 Agent.parse(self,element)
601 child = element.firstChild
602 while child:
603 if child.nodeType == child.ELEMENT_NODE:
604 if child.tagName == 'dynamics':
605 node = child.firstChild
606 while node:
607 if node.nodeType == node.ELEMENT_NODE:
608 assert(node.tagName=='feature')
609 feature = str(node.getAttribute('label'))
610 self.dynamics[feature] = {}
611 subNode = node.firstChild
612 while subNode:
613 if subNode.nodeType == subNode.ELEMENT_NODE:
614 assert(subNode.tagName=='action')
615 flag = subNode.getAttribute('compiled')
616 if flag == '':
617 flag = False
618 else:
619 flag = int(flag)
620 if flag:
621
622 self.compiled = True
623 link = subNode.firstChild
624 while link:
625 if link.nodeType == link.ELEMENT_NODE and (link.tagName == 'link' or link.tagName == 'dynamic'):
626 break
627 link = link.nextSibling
628 else:
629 raise UserWarning,subNode.toxml()
630 if link.tagName == 'link':
631
632 dyn = {'entity':str(link.getAttribute('entity')),
633 'feature':str(link.getAttribute('feature')),
634 'action':Action()}
635 action = link.firstChild
636 while action:
637 if action.nodeType == action.ELEMENT_NODE:
638 break
639 action = action.nextSibling
640 assert action.tagName == 'action'
641 dyn['action'] = dyn['action'].parse(action)
642 else:
643
644 dyn = PWLDynamics()
645 dyn.parse(link)
646
647 action = Action()
648 action.parse(subNode)
649 self.dynamics[feature][action] = dyn
650 else:
651
652 actionType = str(subNode.getAttribute('type'))
653 assert(actionType not in self.dynamics[feature].keys())
654 dyn = str(subNode.getAttribute('link'))
655 if not dyn:
656 dyn = PWLDynamics()
657 subchild = subNode.firstChild
658 while subchild and subchild.nodeType != child.ELEMENT_NODE:
659 subchild = subchild.nextSibling
660 dyn.parse(subchild)
661 self.dynamics[feature][actionType] = dyn
662 subNode = subNode.nextSibling
663 node = node.nextSibling
664 elif child.tagName == 'actions':
665 subNode = child.firstChild
666 while subNode and subNode.nodeType != subNode.ELEMENT_NODE:
667 subNode = subNode.nextSibling
668 if subNode:
669 self.actions = parseSpace(subNode)
670 for action in self.actions.getOptions():
671 for subAct in action:
672 subAct['actor'] = self.name
673 elif child.tagName == 'beliefs':
674 subNodes = child.getElementsByTagName('distribution')
675 if len(subNodes) == 1:
676 self.beliefs.parse(subNodes[0],KeyedVector)
677 elif len(subNodes) > 1:
678 raise UserWarning,'Multiple distributions in beliefs'
679 else:
680 raise UserWarning,'Missing distribution in beliefs'
681 elif child.tagName == 'policy':
682 subNode = child.firstChild
683 while subNode and subNode.nodeType != subNode.ELEMENT_NODE:
684 subNode = subNode.nextSibling
685 if subNode:
686 self.policy = PolicyTable(self,self.actions)
687 self.policy.parse(subNode)
688 elif child.tagName == 'goals':
689 self.goals.parse(child.firstChild,KeyedVector)
690 elif child.tagName == 'class':
691 self.classes.append(str(child.getAttribute('name')))
692 elif child.tagName == 'relationships':
693 node = child.firstChild
694 while node:
695 if node.nodeType == node.ELEMENT_NODE:
696 label = str(node.getAttribute('label'))
697 self.relationships[label] = []
698 subNode = node.firstChild
699 while subNode:
700 if subNode.nodeType == subNode.ELEMENT_NODE:
701 assert(subNode.tagName == 'relatee')
702 name = subNode.firstChild.data
703 name = str(name).strip()
704 self.relationships[label].append(name)
705 subNode = subNode.nextSibling
706 node = node.nextSibling
707 elif child.tagName == 'links':
708 node = child.firstChild
709 while node:
710 if node.nodeType == node.ELEMENT_NODE:
711 self.links = self.links.parse(node)
712 break
713 node = node.nextSibling
714 elif child.tagName == 'linktype':
715 linkType = str(child.getAttribute('name'))
716 if not self.linkDynamics.has_key(linkType):
717 assert not linkType in self.linkTypes
718 self.linkTypes.append(linkType)
719 self.linkDynamics[linkType] = {}
720 elif child.tagName == 'linkdynamics':
721 linkType = str(child.getAttribute('feature'))
722 action = str(child.getAttribute('action'))
723 dyn = PWLDynamics()
724 subChild = child.firstChild
725 while subChild:
726 if subChild.nodeType == child.ELEMENT_NODE:
727 dyn.parse(subChild)
728 break
729 subChild = subChild.nextSibling
730 try:
731 self.linkDynamics[linkType][action] = dyn
732 except KeyError:
733 self.linkDynamics[linkType] = {action: dyn}
734 elif child.tagName in ['trust','liking']:
735
736 pass
737 else:
738 print 'Unhandled tag %s for lightweight agent %s' % \
739 (child.tagName,self.name)
740 child = child.nextSibling
741