Package teamwork :: Package widgets :: Package PsychGUI :: Module TreeAAR
[hide private]
[frames] | no frames]

Source Code for Module teamwork.widgets.PsychGUI.TreeAAR

  1  import string 
  2  from Tkinter import * 
  3  import tkMessageBox 
  4  import Pmw 
  5  import tkFileDialog 
  6  from xml.dom.minidom import * 
  7   
  8  from teamwork.messages.PsychMessage import Message as PsychMessage 
  9  from teamwork.widgets.MultiWin import InnerWindow 
 10  from teamwork.widgets.TreeWidget import * 
 11  from teamwork.agent.Agent import Agent 
 12  from teamwork.action.PsychActions import Action 
 13  from teamwork.math.probability import Distribution 
 14  from teamwork.math.matrices import epsilon 
 15  from teamwork.math.Keys import Key 
 16  from teamwork.math.KeyedVector import KeyedVector,UnchangedRow 
 17   
 18  try: 
 19      from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer 
 20      from reportlab.rl_config import defaultPageSize 
 21      from reportlab.lib.styles import getSampleStyleSheet 
 22      from reportlab.lib.units import inch 
 23      pdfCapability = True 
 24  except ImportError: 
 25      pdfCapability = False 
 26   
 27   
28 -class JiveTalkingAAR(InnerWindow):
29 """ 30 @ivar history: step history (allows collapsing of redundant nodes) 31 @type history: dict 32 @ivar doc: cumulative XML history (includes all steps, even those that are not displayed due to redundancy) 33 @type doc: Element 34 """ 35
36 - def __init__(self,frame,**kw):
37 optiondefs = ( 38 ('title', 'AAR', self.setTitle), 39 ('font', ("Helvetica", 10, "normal"), Pmw.INITOPT), 40 ('fitCommand', None, Pmw.INITOPT), 41 ('msgCommand', None, Pmw.INITOPT), 42 ('reportTitle', "Scenario History Report", None), 43 ('pageinfo', "history report", None), 44 ('expert', False, None), 45 ('entities',{},None), 46 ) 47 self.defineoptions(kw,optiondefs) 48 InnerWindow.__init__(self,frame) 49 Button(self.component('frame'), text="Generate report", 50 command=self.generateReport).pack(side='top') 51 widget = self.createcomponent('Tree',(),None,EasyTree, 52 (self.component('frame'),), 53 root_label="Simulation Trace", 54 font=self['font'], 55 ) 56 57 sb=Scrollbar(widget.master) 58 sb.pack(side=RIGHT, fill=Y) 59 widget.configure(yscrollcommand=sb.set) 60 sb.configure(command=widget.yview) 61 62 widget.pack(side='top',fill='both',expand='yes') 63 self.initialiseoptions() 64 self.history = {} 65 self.clear()
66
67 - def generateReport(self):
68 if not self['entities']: 69 tkMessageBox.showerror('Report Error','There are no agents on which to report.') 70 return 71 widget = self.component('Tree') 72 paragraphs = [] 73 self.getNodeText(widget.easyRoot, 0, paragraphs) 74 ftypes = [('XML file', '.xml')] 75 if pdfCapability: 76 ftypes.append(('PDF file', '.pdf')) 77 filename = tkFileDialog.asksaveasfilename(filetypes=ftypes, 78 defaultextension='.xml') 79 if filename: 80 if filename[-4:] == '.pdf': 81 # PDF format 82 doc = SimpleDocTemplate(filename) 83 Story = [Spacer(1, 2*inch)] 84 style = getSampleStyleSheet()["Normal"] 85 for (para,level) in paragraphs: 86 indent = level*8 87 para = "<para firstLineIndent=\"0\" leftIndent=\"" + str(indent) + "\">" + para + "</para>" 88 p = Paragraph(para, style) 89 Story.append(p) 90 Story.append(Spacer(1, 0.2*inch)) 91 doc.build(Story, onFirstPage=self.myFirstPage, onLaterPages=self.myLaterPages) 92 elif filename[-4:] == '.xml': 93 # XML format 94 f = open(filename,'w') 95 f.write(self.doc.toxml()) 96 f.close() 97 else: 98 raise NotImplementedError,'Unable to output history in %s format' % (filename[-3:].upper())
99
100 - def myFirstPage(self, canvas, doc):
101 PAGE_WIDTH = defaultPageSize[0] 102 PAGE_HEIGHT = defaultPageSize[1] 103 canvas.saveState() 104 canvas.setFont('Times-Bold', 16) 105 canvas.drawCentredString(PAGE_WIDTH/2.0, PAGE_HEIGHT-108, 106 self['reportTitle']) 107 canvas.setFont('Times-Roman', 9) 108 canvas.drawString(inch, 0.75 * inch, 109 "First Page / %s" % self['pageinfo']) 110 canvas.restoreState()
111
112 - def myLaterPages(canvas, doc):
113 canvas.saveState() 114 canvas.setFont('Times-Roman', 9) 115 canvas.drawString(inch, 0.75 * inch, 116 "Page %d %s" % (doc.page, self['pageinfo'])) 117 canvas.restoreState()
118
119 - def getNodeText(self, node, level, paragraphs):
120 paragraphs.append((node['label'], level)) 121 for child in node['children']: 122 self.getNodeText(child, level+1, paragraphs)
123
124 - def clear(self):
125 """Removes any existing explanations from the display 126 """ 127 widget = self.component('Tree') 128 self.nodes = [] 129 rootNode = widget.easyRoot 130 rootNode.deleteChildren() 131 132 # Initialize XML content 133 self.doc = Document() 134 root = self.doc.createElement('history') 135 self.doc.appendChild(root) 136 # Initialize step history (allows collapsing of redundant nodes) 137 self.history.clear()
138
139 - def displayAAR(self,elements,parent=None):
140 widget = self.component('Tree') 141 widget.inhibitDraw = True 142 step = elements.firstChild 143 while step: 144 self.doc.documentElement.appendChild(step) 145 if step.tagName == 'step': 146 parent = self.addStep(step) 147 #we might as well expand the root node 148 widget.root.expand() 149 else: 150 raise UserWarning,step.tagName 151 step = step.nextSibling 152 widget.inhibitDraw = False
153
154 - def addStep(self,step):
155 widget = self.component('Tree') 156 index = int(step.getAttribute('time')) 157 prefix = 'Step %d' % (index) 158 #add the overarching step node to the root of the simulation history tree 159 parentStepNode = self.findNode(prefix,widget.easyRoot) 160 if str(step.getAttribute('hypothetical')) == str(True): 161 prefix += ' (hypothetical)' 162 parentStepNode['label'] = prefix 163 # Generate a string for the overall decisions 164 field = step.firstChild 165 decisionStr = [] 166 actors = {} 167 while field: 168 if field.tagName == 'turn': 169 # The decision of a single agent 170 child = field.firstChild 171 while child: 172 if child.tagName == 'decision': 173 # Set branch for this agent's turn 174 name = str(field.getAttribute('agent')) 175 actors[name] = True 176 turnStr = '%s ' % (name) 177 label = '%s\'s decision' % (name) 178 forced = str(field.getAttribute('forced')) == str(True) 179 node = self.findNode(label,parentStepNode) 180 option = extractAction(child) 181 actionStr = actionString(option) 182 decisionStr.append(turnStr+actionStr) 183 if forced: 184 node['label'] = label + ' (forced)' 185 node['isLeaf'] = True 186 else: 187 node['action'] = lambda n,e,s=self,a=name,o=option,\ 188 t=index:s.confirmAction(n,e,a,o,t) 189 elif child.tagName == 'explanation': 190 self.addExplanation(name,child,node,index) 191 elif child.tagName == 'suggestions': 192 self.addSuggestions(name,child,node) 193 child = child.nextSibling 194 elif field.tagName == 'effect': 195 self.addEffect(field,parentStepNode) 196 else: 197 print 'Unhandled:',field.tagName 198 field = field.nextSibling 199 # Check whether we want to collapse a previous node 200 names = actors.keys() 201 names.sort() 202 key = '%d %s' % (index,' '.join(names)) 203 try: 204 index = widget.easyRoot.childIndex(self.history[key]) 205 except KeyError: 206 index = -1 207 if index == -1: 208 self.history[key] = parentStepNode 209 # if index >= 0: 210 # del widget.child[0].child[-1] 211 # widget.child[0].child[index] = parent 212 parentStepNode['label'] = prefix + ': ' + '; '.join(decisionStr) 213 parentStepNode.refresh(widget) 214 return parentStepNode
215
216 - def fitAction(self,agent,element,step):
217 """ 218 Activates the fitting function for the given agent and alternative 219 @param agent: the agent whose behavior is being fit 220 @type agent: str 221 @param element: the explanation structure for this alternative action 222 @type element: Element 223 @param step: the time that this action should occur 224 @type step: int 225 """ 226 option = [] 227 child = element.firstChild 228 while child: 229 if child.tagName == 'action': 230 action = Action() 231 action.parse(child) 232 option.append(action) 233 child = child.nextSibling 234 self['fitCommand'](agent,option,step-1)
235
236 - def actionMenu(self,event,args):
237 menu = Menu(self.component('frame'),tearoff=0) 238 label = string.join(map(str,args['decision']),', ') 239 menu.add_command(label='%s is correct' % (label), 240 command=lambda s=self,a=args['decision'],i=args:\ 241 s['fitCommand'](a,i)) 242 if len(args['agent'].actions.getOptions()) > 1: 243 menu.add_separator() 244 for action in args['agent'].actions.getOptions(): 245 if action != args['decision']: 246 menu.add_command(label=string.join(map(str,action),', '), 247 command=lambda s=self,a=action,i=args:\ 248 s['fitCommand'](a,i)) 249 menu.bind("<Leave>",self.unpost) 250 menu.post(event.x_root, event.y_root)
251
252 - def sendMenu(self,event,args):
253 menu = Menu(self.component('frame'),tearoff=0) 254 agent = self['entities'][args['violator']] 255 entities = filter(lambda e: e.name != args['violator'] and \ 256 len(e.entities) > 0,agent.getEntityBeliefs()) 257 for entity in entities: 258 menu.add_command(label='Send from %s' % \ 259 (entity.name), 260 command=lambda o=args,e=entity.name,s=self: \ 261 s['msgCommand'](e,o)) 262 menu.bind("<Leave>",self.unpost) 263 menu.post(event.x_root, event.y_root)
264
265 - def pop(self):
266 """Removes the bottom-most bullet from the tree""" 267 widget = self.component('Tree') 268 del widget.child[0].child[-1]
269
270 - def unpost(self,event):
271 event.widget.unpost()
272
273 - def findNode(self,label,parent):
274 """ 275 @param label: the label prefix for the child node 276 @type label: str 277 @param parent: the parent node 278 @return: the child node for the given parent with the given label. If no such child already exists, then returns a newly created child node with the given label 279 """ 280 widget = self.component('Tree') 281 n = None 282 for node in parent['children']: 283 if node['label'][:len(label)] == label: 284 n = node 285 break 286 287 if not n: 288 n = parent.addChild(widget,label=label) 289 n['label'] = label 290 291 return n
292
293 - def addExplanation(self,agent,element,parent,step):
294 """Extracts an agent's decision from an XML element and adds the corresponding subtree to the given parent tree 295 @param agent: the agent whose decision is being explained 296 @type agent: str 297 @param element: the XML decision description 298 @type element: Element 299 @param parent: the node to add the decision explanation below 300 @type parent: L{Node} 301 @param step: the time that this decision occurred at 302 @type step: int 303 """ 304 widget = self.component('Tree') 305 child = element.firstChild 306 while child: 307 if child.tagName == 'alternatives': 308 subNode = self.findNode('Alternatives:',parent) 309 subNode.deleteChildren() 310 alternatives = [] 311 subChild = child.firstChild 312 while subChild: 313 if subChild.tagName == 'goals': 314 goals = KeyedVector() 315 goals.parse(subChild.firstChild,True) 316 else: 317 alternatives.append(subChild) 318 subChild = subChild.nextSibling 319 for subChild in alternatives: 320 # Add the action 321 option = extractAction(subChild) 322 label = actionString(option) 323 try: 324 rank = int(subChild.getAttribute('rank')) 325 label += ' (rank #%d)' % (rank+1) 326 except ValueError: 327 pass 328 actNode = subNode.addChild(widget,label=label) 329 actNode['action'] = lambda l,e,s=self,a=agent,o=subChild,\ 330 t=step:s.fitAction(a,o,t) 331 field = subChild.firstChild 332 while field: 333 if field.tagName == 'vector': 334 # Find out which goals this action helps/hurts 335 good = [] 336 bad = [] 337 delta = KeyedVector() 338 delta.parse(field,True) 339 for key in goals.keys(): 340 value = goals[key]*delta[key] 341 if value > epsilon: 342 good.append(key) 343 elif value < -epsilon: 344 bad.append(key) 345 if len(good) > 0: 346 self.addGoals('which would help:',actNode, 347 good,goals) 348 if len(bad) > 0: 349 self.addGoals('but which would not:', 350 actNode,bad,goals) 351 if len(good)+len(bad) == 0: 352 expNode = actNode.addChild(widget,label='which was just as good',isLeaf=True) 353 expNode['label']='which was just as good' 354 field = field.nextSibling 355 elif child.tagName == 'expectations': 356 # Display expected responses from other agents 357 subNode = self.findNode('Expected countermoves:',parent) 358 subNode.deleteChildren() 359 subChild = child.firstChild 360 while subChild: 361 if not subChild is child.firstChild: 362 # Everything but the first step is a response 363 assert subChild.tagName == 'turn' 364 name = str(subChild.getAttribute('agent')) 365 option = extractAction(subChild) 366 label = '%s %s' % (name,actionString(option)) 367 subNode.addChild(widget,label=label,isLeaf=True) 368 subChild = subChild.nextSibling 369 elif child.tagName == 'decision': 370 pass 371 else: 372 print 'Unknown explanation field:',child.tagName 373 child = child.nextSibling
374
375 - def addGoals(self,label,node,keys,goals):
376 widget = self.component('Tree') 377 expNode = node.addChild(widget,label=label) 378 expNode['expanded'] = True 379 for key in keys: 380 if goals[key] > epsilon: 381 goalStr = 'maximize ' 382 else: 383 goalStr = 'minimize ' 384 goalStr += str(key) 385 expNode.addChild(widget,label=goalStr,isLeaf=True)
386
387 - def addEffect(self,element,parent):
388 """Extracts an effect from an XML element and adds the corresponding subtree to the given parent tree 389 @param element: the XML effect description 390 @type element: Element 391 @param parent: the node to add the effect below 392 @type parent: L{Node} 393 """ 394 widget = self.component('Tree') 395 node = self.findNode('Effect',parent) 396 node.deleteChildren() 397 child = element.firstChild 398 while child: 399 if child.tagName == 'state': 400 # Describe the effect on the state 401 elements = child.getElementsByTagName('distribution') 402 assert len(elements) == 1 403 distribution = Distribution() 404 distribution.parse(elements[0],KeyedVector) 405 if len(distribution) > 1: 406 for row,prob in distribution.items(): 407 subNode = node.addChild(widget,label='with probability %5.3f' % (prob)) 408 for key,value in row.items(): 409 if value > 0.0: 410 label = '%s increases by %5.3f' % (str(key),value) 411 subNode.addChild(widget,label=label,isLeaf=True) 412 elif value < 0.0: 413 subNode.addChild(widget,label='%s decreases by %5.3f' % (str(key),-value),isLeaf=True) 414 if len(subNode.child) == 0: 415 subNode.addChild(widget,label='no change',isLeaf=True) 416 else: 417 row = distribution.expectation() 418 for key,value in row.items(): 419 if value > 0.0: 420 node.addChild(widget,label='%s increases by %5.3f' % (str(key),value),isLeaf=True) 421 elif value < 0.0: 422 node.addChild(widget,label='%s decreases by %5.3f' % (str(key),-value),isLeaf=True) 423 if len(node['children']) == 0: 424 node.addChild(widget,label='no change',isLeaf=True) 425 elif child.tagName == 'hearer': 426 name = str(child.getAttribute('agent')) 427 isLeaf=False 428 if str(child.getAttribute('decision')) == str(True): 429 label = '%s accept(s) message' % (name) 430 else: 431 label = '%s reject(s) message' % (name) 432 if str(child.getAttribute('forced')) == str(True): 433 isLeaf=True 434 label += ' (forced)' 435 subNode = node.addChild(widget,label=label,isLeaf=isLeaf) 436 subChild = child.firstChild 437 while subChild: 438 assert subChild.tagName == 'factor' 439 if str(subChild.getAttribute('positive')) == str(True): 440 label = 'sufficient ' 441 else: 442 label = 'insufficient ' 443 label += str(subChild.getAttribute('type')) 444 subNode.addChild(widget,label=label,isLeaf=True) 445 subChild = subChild.nextSibling 446 child = child.nextSibling
447
448 - def confirmAction(self,name,event,agent,option,step):
449 msg = 'Would you like to confirm this decision as the desired one? '\ 450 'If you would like to specify another option as desired, '\ 451 'you can now do so by clicking on the alternative '\ 452 'action below.' 453 if tkMessageBox.askyesno('Fitting',msg): 454 self['fitCommand'](agent,option,step-1)
455
456 - def addSuggestions(self,agent,element,parent):
457 widget = self.component('Tree') 458 # Display the overall stats regarding objective satisfaction 459 total = int(element.getAttribute('objectives')) 460 if total == 0: 461 # Don't write anything if no objectives 462 return 463 count = total - int(element.getAttribute('violations')) 464 label = 'Satisfied %d / %d objectives' % (count,total) 465 root = self.findNode('Satisfied',parent) 466 root.deleteChildren() 467 root['label'] = label 468 child = element.firstChild 469 # Extract violations, suggestions, and belief state 470 violations = [] 471 while child: 472 if child.tagName == 'objective': 473 objective = {'who':str(child.getAttribute('who')), 474 'what':str(child.getAttribute('what')), 475 'how':str(child.getAttribute('how'))} 476 suggestions = [] 477 suggestion = child.firstChild 478 while suggestion: 479 assert suggestion.tagName == 'suggestion' 480 values = {} 481 belief = suggestion.firstChild 482 while belief: 483 key = Key() 484 key = key.parse(belief) 485 values[key] = {'min':float(belief.getAttribute('min')), 486 'max':float(belief.getAttribute('max')), 487 'key':key, 488 'violator':agent} 489 belief = belief.nextSibling 490 suggestions.append(values) 491 suggestion = suggestion.nextSibling 492 violations.append((objective,suggestions)) 493 elif child.tagName == 'distribution': 494 state = Distribution() 495 state.parse(child,KeyedVector) 496 state = state.expectation() 497 else: 498 print 'Unknown tag in suggestions:',child.tagName 499 child = child.nextSibling 500 # Display the violated objectives 501 for objective,suggestions in violations: 502 node = root.addChild(widget,label='To satisfy %s %s:' % (objective['how'],objective['what'])) 503 first = True 504 messages = {} 505 for suggestion in suggestions: 506 # Each suggestion is a conjunction of beliefs 507 beliefs = [] 508 for key,threshold in suggestion.items(): 509 if state[key] < threshold['min']: 510 # Generate a message about a belief that is too low 511 if self['expert']: 512 sugg = '%s is greater than %5.3f' % \ 513 (str(key),threshold['min']) 514 elif abs(threshold['min']) < epsilon: 515 sugg = '%s: positive %s' % \ 516 (key['entity'],key['feature']) 517 else: 518 sugg = '%s: increased %s' % \ 519 (key['entity'],key['feature']) 520 suggestion[key]['label'] = sugg 521 beliefs.append(sugg) 522 elif state[key] > threshold['max']: 523 # Generate a message about a belief that is too high 524 if self['expert']: 525 sugg = '%s is less than %5.3f' % \ 526 (str(key),threshold['max']) 527 elif abs(threshold['max']) < epsilon: 528 sugg = '%s: no %s' % \ 529 (key['entity'],key['feature']) 530 else: 531 sugg = '%s: decreased %s' % \ 532 (key['entity'],key['feature']) 533 suggestion[key]['label'] = sugg 534 beliefs.append(sugg) 535 if len(beliefs) > 0: 536 if first: 537 first = False 538 else: 539 # Add a separator (not a very attractive one) 540 node.addChild(widget,label=' ',isLeaf=True) 541 for key,args in suggestion.items(): 542 if args.has_key('label'): 543 messages[args['label']] = args 544 button = node.addChild(widget,label=args['label'], 545 isLeaf=True) 546 if self['msgCommand']: 547 button['action'] = lambda l,e,s=self,o=args:\ 548 s.sendMenu(e,o) 549 # Add launcher for campaign analysis 550 args = {'violator':agent, 551 'messages':messages.values()} 552 node['action'] = lambda l,e,s=self,o=args:s.sendMenu(e,o)
553
554 -def extractAction(parent):
555 """ 556 @type parent: Element 557 @return: the actions in this XML Element 558 @rtype: L{Action}[] 559 """ 560 children = parent.getElementsByTagName('action') 561 option = [] 562 for element in children: 563 # The label for an individual action 564 action = Action() 565 action = action.parse(element) 566 option.append(action) 567 return option
568
569 -def actionString(option):
570 """ 571 @type option: L{Action}[] 572 @return: a string representation of these actions 573 @rtype: str 574 """ 575 actions = [] 576 for action in option: 577 if isinstance(action,PsychMessage): 578 content = [] 579 for factor in action['factors']: 580 if factor.has_key('matrix'): 581 matrix = factor['matrix'].domain()[0] 582 for key,row in matrix.items(): 583 if not isinstance(row,UnchangedRow): 584 content.append('%s: %s' % \ 585 (str(key),row.simpleText())) 586 actions.append('sends "%s"' % (', '.join(content))) 587 elif action['object']: 588 actions.append('%s %s' % (action['type'], 589 action['object'])) 590 else: 591 actions.append('%s' % (action['type'])) 592 593 if len(actions) > 0: 594 content = ', '.join(actions) 595 else: 596 content = 'do nothing' 597 return content
598