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

Source Code for Module teamwork.widgets.PsychGUI.Gui

   1  # standard Python packages 
   2  import os 
   3  from Queue import Queue,Empty 
   4  import re 
   5  import threading 
   6  import time 
   7   
   8  # Standard widget packages 
   9  import Tkinter 
  10  # Hack to fix Tkinter bug  
  11  Tkinter.wantobjects = 0 
  12  import tkMessageBox 
  13  import tkFileDialog 
  14  import Pmw 
  15  # Homegrown widget packages 
  16  from teamwork.widgets.images import getImage 
  17  from teamwork.widgets.balloon import EntityBalloon 
  18  from teamwork.widgets.MultiWin import * 
  19  from teamwork.widgets.player import PlayerControl 
  20  from teamwork.widgets.htmlViewer.tkhtml import tkHTMLViewer 
  21  import teamwork.widgets.Grapher as Grapher 
  22  #from Graph import * 
  23  from ScenarioWizard import ScenarioWizard 
  24  from objectives import ObjectivesDialog 
  25  from TurnDialog import TurnDialog 
  26  from MAID import MAIDFrame 
  27  from CampaignAnalysis import AnalysisWizard 
  28  from TreeAAR import JiveTalkingAAR 
  29  from RadioBox import Rbox 
  30  from AgentWindow.AgentWindow import AgentWin 
  31  from NetworkView import PsymWindow 
  32  from ThemeSelector import ThemeSel 
  33  # PsychSim stuff 
  34  from teamwork.math.fitting import findAllConstraints 
  35  from teamwork.math.Interval import Interval 
  36  from teamwork.utils.PsychUtils import dict2str 
  37  from teamwork.shell.PsychShell import * 
  38  from teamwork.multiagent.pwlSimulation import PWLSimulation 
  39  from teamwork.multiagent.GenericSociety import GenericSociety 
  40  from teamwork.multiagent.Historical import HistoricalAgents 
  41  # Authentication 
  42  from getpass import getuser 
  43  ##def getuser(): return 'marsella' 
  44   
45 -class GuiShell(PsychShell):
46 """Subclass for Tk interface to PsychSim 47 48 Key components for manipulating L{Generic Societies<teamwork.multiagent.GenericSociety>}: 49 - L{Class hierarchy viewer<PsymWindow>} 50 - L{Windows for individual agents<AgentWin>} 51 Key components for manipulating L{Instantiated Scenarios<teamwork.multiagent.Simulation.MultiagentSimulation>}: 52 - L{Scenario creation wizard<ScenarioWizard>} 53 - L{Social network viewer<PsymWindow>} 54 - L{Windows for individual agents<AgentWin>} 55 - L{Simulation history and explanation<JiveTalkingAAR>} 56 - L{Dialog for entering objectives<ObjectivesDialog>} 57 - L{Wizard for analyzing and comparing sets of messages<AnalysisWizard>} 58 @ivar dev: flag, iff C{True}, then show development features 59 @type dev: bool 60 @ivar menus: dictionary of all of the individual pulldown menus (indexed by label of menu 61 @type menus: strS{->}Menu 62 """ 63 titleLabel = "PsychSim" 64 # Menu titles 65 simulationLabel = 'Simulation' 66 explanationLabel = 'expcascade' 67
68 - def __init__(self, 69 scenario=None, 70 classes=None, 71 agentClass=None, 72 multiagentClass=None, 73 expert=False, 74 dev=False, 75 debug=0):
76 """Constructor 77 @param scenario: initial scenario 78 @param classes: initial generic society 79 @param agentClass: object class used to create new agents 80 @param multiagentClass: object class used to create new scenarios 81 @param debug: level of detail in debugging messages 82 """ 83 self.queue = Queue() 84 self.themewins = [] 85 self.entitywins = {} 86 self.psymwindow = None 87 self.aarWin = None 88 self.helpWin = None 89 self.otherHist = {} 90 self.resetFlag = 1 91 self.supportgraph = None 92 self.graph = None 93 self.fontSize = 14 94 # Start up Tk 95 self.root = Tk() 96 ## self.root.withdraw() 97 self.root.title(self.titleLabel) 98 # Set some default options 99 self.root.option_add('*font', 'Helvetica -%d bold' % (self.fontSize)) 100 101 # Bind the close-window button to stopping PsychSim 102 self.root.protocol('WM_DELETE_WINDOW',self.stop) 103 104 # Set icon for minimization (doesn't work under some WMs) 105 self.root.iconbitmap('@%s' % (getImage('icon.xbm'))) 106 107 # Start up PMW 108 Pmw.initialise(self.root) 109 version = self.__VERSION__ 110 try: 111 f = open('.svn/dir-wcprops') 112 except IOError: 113 try: 114 f = open('.svn/all-wcprops') 115 except IOError: 116 f = None 117 if f: 118 # Try to parse source code SVN version info 119 data = f.read() 120 f.close() 121 exp = re.search('(?<=/ver/)\d+(?=/trunk/teamwork)',data,re.DOTALL) 122 if exp: 123 version += '.%s' % (exp.group(0)) 124 else: 125 print 'Unable to parse SVN info' 126 Pmw.aboutversion(version) 127 Pmw.aboutcontact('USC Information Sciences Institute\n'+\ 128 'Stacy Marsella and David V. Pynadath') 129 # Set up expert mode flag 130 self.expert = IntVar() 131 if expert: 132 self.expert.set(1) 133 else: 134 self.expert.set(0) 135 self.dev = dev 136 # Set up balloons 137 self.balloon = EntityBalloon(self.root,state='balloon') 138 self.toggleBalloonVar = IntVar() 139 self.toggleBalloonVar.set(1) 140 141 self.drawMenubar() 142 self.windows = {'Agent':[], 143 'Analysis':[], 144 'Debug':[], 145 'Log':[] 146 } 147 self.toolbar = Frame(self.root,relief='raised',borderwidth=3) 148 self.modes = {} 149 modeList = [] 150 key = 'None' 151 modeList.append(key) 152 self.modes[key] = {'windows':[], 153 'help':'Hide all detail windows'} 154 key = 'Agents' 155 modeList.append(key) 156 self.modes[key] = {'windows':['Agent','Debug'], 157 'help':'View all agent windows'} 158 key = 'Run' 159 modeList.append(key) 160 self.modes[key] = {'windows':['Analysis'], 161 'help':'Run simulation and review agent behavior'} 162 key = 'All' 163 modeList.append(key) 164 self.modes[key] = {'windows':self.windows.keys(), 165 'help':'View all scenario windows'} 166 itemList = self.modes.keys() 167 width = max(map(lambda n:len(n),itemList)) 168 self.modeBox = Pmw.OptionMenu(self.toolbar, 169 menubutton_width=width+5, 170 items=itemList, 171 labelpos=W, 172 label_text='Mode:', 173 command=self.modeSelect) 174 self.modeBox.pack(side=LEFT) 175 # Simulation control buttons 176 self.control = PlayerControl(self.toolbar,orient='horizontal') 177 self.control.add('prev',state='disabled') 178 self.control.add('play',command=self.realStep) 179 self.control.addImage('play','stop') 180 self.control.add('next',state='disabled') 181 self.control.pack(side=LEFT) 182 183 ## self.slider = Scale(self.toolbar,orient=HORIZONTAL) 184 ## self.slider.pack() 185 186 self.timeDisplay = Label(self.toolbar, 187 font=('Courier',self.fontSize,'bold'), 188 background='#000000',foreground='#00bb11', 189 relief='sunken',justify='right', 190 anchor='e',width=3) 191 self.timeDisplay.pack(side='left',expand=0) 192 self.toolbar.pack(side='top',fill='x',expand=0) 193 # Main workspace 194 self.main = MultipleWin(parent = self.root, relief = 'ridge', 195 borderwidth = 4, 196 height = 500, 197 width = 1000, 198 readyText='Ready', 199 ) 200 self.main.pack(side ="top", fill = 'both', expand = 1) 201 PsychShell.__init__(self,scenario=scenario, 202 classes=classes, 203 agentClass=agentClass, 204 multiagentClass=multiagentClass, 205 debug=debug) 206 # Set view to generic/specific as appropriate 207 self.view = None 208 if self.scenario: 209 self.viewSelect('scenario',alreadyLoaded=True) 210 else: 211 self.entities = None 212 self.viewSelect('generic',alreadyLoaded=False) 213 self.setDebugLevel(debug) 214 # Dialog for asking for name of new agent 215 self.nameDialog = Pmw.PromptDialog(self.root,title='New Agent', 216 entryfield_labelpos='w', 217 label_text='Name:', 218 defaultbutton=0, 219 buttons=('OK','Cancel'), 220 entryfield_validate=self.validateName, 221 entryfield_modifiedcommand=self.checkNameValidity, 222 command=self._addAgent) 223 self.nameDialog.withdraw() 224 self.nameDialog.component('buttonbox').component('OK').configure(state='disabled') 225 self.newParent = None 226 # Dialog for viewing/editing the turn order of the agents 227 self.turnDialog = TurnDialog(self.root, 228 title='Turn Sequence', 229 defaultbutton=0, 230 buttons=('OK','Cancel'), 231 command=self._editOrder) 232 self.turnDialog.withdraw() 233 self.clipboard = {} 234 self.poll()
235
236 - def drawTime(self):
237 """Updates the simulation time in the toolbar display""" 238 if self.entities: 239 step = "%03d" % (self.entities.time) 240 else: 241 step = ' ' 242 self.timeDisplay.configure(text=step)
243
244 - def updateEditMenu(self,selection,window):
245 if selection is None: 246 self.menus['Edit'].entryconfig('Cut',state='disabled') 247 self.menus['Edit'].entryconfig('Copy',state='disabled') 248 if self.clipboard.has_key('selected'): 249 del self.clipboard['selected'] 250 else: 251 self.menus['Edit'].entryconfig('Cut',state='normal') 252 self.menus['Edit'].entryconfig('Copy',state='normal') 253 self.clipboard['selected'] = {'window':window,'value':selection}
254
255 - def copy(self,event=None,cmd='copy'):
256 try: 257 item = self.clipboard['selected'] 258 except KeyError: 259 return 260 self.clipboard['copied'] = item 261 if cmd == 'cut': 262 result = item['window'].cut(item['value']) 263 else: 264 result = item['window'].copy(item['value']) 265 if result: 266 item['value'] = result 267 self.menus['Edit'].entryconfig('Paste',state='normal')
268
269 - def cut(self,event=None):
270 self.copy(cmd='cut')
271
272 - def paste(self,event=None):
273 try: 274 item = self.clipboard['copied'] 275 except KeyError: 276 return 277 if item['window'].paste(item['value']): 278 self.menus['Edit'].entryconfig('Paste',state='disabled')
279
280 - def setupScenario( self, agents, progress=None ):
281 """initialize L{PsychShell} with given L{scenario<teamwork.multiagent.PsychAgents.PsychAgents>} 282 @param agents: the scenario to interact with 283 @type agents: L{MultiagentSimulation<teamwork.multiagent.Simulation.MultiagentSimulation>} 284 @param progress: optional progress display command 285 @type progress: C{lambda} 286 """ 287 PsychShell.setupScenario(self,agents,progress) 288 self.aarWin.clear() 289 self.viewSelect('scenario',alreadyLoaded=True)
290
291 - def showWindow(self,name):
292 """Pops up an agent window for the named entity (creates the window if it has not already been drawn) 293 """ 294 if not self.entitywins.has_key(name): 295 win = self.createAgentWin(self.entities[name]) 296 win.win.iconify_window() 297 self.entitywins[name].win.show_window() 298 if self.psymwindow.rel != '_parent': 299 self.psymwindow.setview(name=name)
300
301 - def initEntities(self,progress=None):
302 PsychShell.initEntities(self,progress) 303 if isinstance(self.entities,GenericSociety): 304 view = 'generic' 305 else: 306 view = 'scenario' 307 if view != self.view: 308 self.clear() 309 if view == 'generic': 310 filename = self.societyFile 311 else: 312 filename = self.scenarioFile 313 if filename: 314 name = os.path.split(filename)[1] 315 name = os.path.splitext(name)[0] 316 else: 317 name = "Untitled" 318 self.root.title('%s - %s - %s' % (self.titleLabel,name,view)) 319 # Network view 320 if self.psymwindow: 321 self.psymwindow.configure(entities=self.entities) 322 else: 323 self.psymwindow = PsymWindow(self.main,entities=self.entities, 324 balloon=self.balloon, 325 delete=self.removeAgent, 326 add=self.addAgent, 327 expert=self.expert.get(), 328 clipboard=self.updateEditMenu, 329 rename=self.renameAgent, 330 showWindow=self.showWindow, 331 windows=self.entitywins) 332 self.psymwindow.place(x = 1, y = 1) 333 self.psymwindow.rel = None 334 self.psymwindow.setview() 335 for aw in self.entitywins.values(): 336 aw.configure(Relationships_network=self.psymwindow) 337 # AAR 338 if self.aarWin is None: 339 self.aarWin = JiveTalkingAAR(self.main, title='History', 340 width=750,height=500, 341 font = ("Helvetica", self.fontSize, 342 "normal"), 343 fitCommand=self.doFitting, 344 msgCommand=self.seedMessage, 345 ) 346 self.windows['Analysis'].append(self.aarWin) 347 self.aarWin.configure(entities=self.scenario) 348 # Help 349 if self.helpWin is None: 350 self.helpWin = InnerWindow(parent=self.main,title='Help', 351 height=500,width=750) 352 self.helpWin.place(x=1,y=1) 353 widget = tkHTMLViewer(self.helpWin.component('frame')) 354 widget.display('%s/doc/index.html' % (self.directory)) 355 self.windows['Debug'].append(self.helpWin) 356 # Draw Graph window only if matplotlib is installed 357 if Grapher.graphAvailable and self.graph is None and \ 358 view != 'generic': 359 self.entities.__class__ = HistoricalAgents 360 gw = InnerWindow(parent = self.main,title='Graph',height=600,width=600,x=401,y=1) 361 gw.place(x=401,y=1) 362 self.graph = Grapher.TkGraphWindow(gw,self.entities) 363 self.windows['Analysis'].append(self.graph.gw) 364 # Add entries to Windows menu 365 menu = self.menus['Window'] 366 index = 0 367 try: 368 menu.index('Graph') 369 except: 370 menu.add_command(label='Graph') 371 if self.graph: 372 menu.entryconfig(index,state='normal', 373 command=self.graph.gw.show_window) 374 else: 375 menu.entryconfig(index,state='disabled') 376 index += 1 377 try: 378 menu.index('Help') 379 found = True 380 except: 381 # Haven't created that menu yet 382 menu.add_command(label='Help',command=self.helpWin.show_window) 383 index += 1 384 try: 385 menu.index('History') 386 except: 387 menu.add_command(label='History',command=self.aarWin.show_window) 388 index += 1 389 try: 390 menu.index('Network') 391 except: 392 menu.add_command(label='Network', 393 command=self.psymwindow.show_window) 394 index += 1 395 if not menu.type(index) == 'separator': 396 # Don't want to draw two 397 menu.add_separator() 398 index += 1 399 self.menuBar.entryconfig('View',state='normal') 400 self.menus['File'].entryconfigure('Properties...',state='normal') 401 # Update agent windows 402 for index in range(len(self.entities)): 403 entity = self.entities.members()[index] 404 if self.entitywins.has_key(entity.name): 405 self.entitywins[entity.name].update() 406 if isinstance(self.entities,GenericSociety): 407 for name in self.entities.root: 408 self.addToMenu(name,self.menus['Window']) 409 else: 410 names = self.entities.keys() 411 names.sort() 412 for name in names: 413 self.addToMenu(name,self.menus['Window']) 414 self.drawTime() 415 self.modeSelect('None')
416
417 - def chooseObjectives(self):
418 """Dialog box for fitting scenario to observed behavior""" 419 dialog = Pmw.Dialog(self.root,buttons=('OK','Cancel'), 420 title='Objectives') 421 obj = dialog.createcomponent('Objectives', (),None, 422 ObjectivesDialog, (dialog.interior(),), 423 psyop = 0, 424 horizflex='elastic',vertflex='elastic', 425 objectives=self.entities.objectives[:], 426 entities=self.entities.members()) 427 obj.pack(side=TOP,fill=X,expand=YES) 428 if dialog.activate() == 'OK': 429 self.entities.objectives = obj['objectives']
430
431 - def viewMAID(self):
432 dialog = Pmw.Dialog(self.root,buttons=('OK',), 433 title='MAID') 434 widget = dialog.createcomponent('MAID',(),None,MAIDFrame, 435 (dialog.interior(),), 436 entities=self.entities, 437 ) 438 widget.pack(side='top',fill='both',expand='yes') 439 dialog.activate()
440
441 - def removeAgent(self,entity):
442 """Removes the given entity from the simulation""" 443 entityList = self.entities.descendents(entity.name) 444 if len(entityList) == len(self.entities): 445 tkMessageBox.showerror('No Agents Left!', 446 'At least one agent must remain') 447 else: 448 # Delete root from Window menu 449 parents = entity.getParents() 450 if len(parents) == 0: 451 self.menus['Window'].delete(entity.name) 452 else: 453 for name in parents: 454 self.menus[name].delete(entity.name) 455 for name in entityList: 456 # Delete from society/scenario 457 if self.entities[name].attributes.has_key('window'): 458 del self.entities[name].attributes['window'] 459 del self.entities[name] 460 if self.entitywins.has_key(name): 461 # Delete window 462 self.entitywins[name].win.destroy() 463 # Check whether any parents are now leaf nodes 464 for name in parents: 465 if len(self.entities.network[name]) == 0: 466 # Deleted only child 467 parent = self.entities[name] 468 if parent.getParents(): 469 for grand in parent.getParents(): 470 index = self.menus[grand].index(name) 471 self.menus[grand].delete(name) 472 self.addToMenu(name,self.menus[grand],index) 473 else: 474 index = self.menus['Window'].index(name) 475 self.menus['Window'].delete(name) 476 self.addToMenu(name,self.menus['Window'],index) 477 for window in self.entitywins.values(): 478 for name in entityList: 479 # Remove agent as relationship filler 480 window.component('Relationships').removeFiller(name) 481 # Remove agent as object of actions 482 entity = window['entity'] 483 for option in entity.actions.getOptions(): 484 for action in option: 485 if action['object'] == name: 486 entity.actions.extras.remove(option) 487 break 488 if window.component('Actions_object').get() == name: 489 window.component('Actions_object').selectitem(0) 490 # Remove agent as object of goals 491 goals = entity.getGoals() 492 for goal in goals[:]: 493 if name in goal.entity: 494 goals.remove(goal) 495 break 496 entity.setGoals(goals) 497 window.component('Actions').drawActions() 498 window.component('Goals').recreate_goals()
499
500 - def destroyAgentWin(self,window):
501 """Cleans up after removing the window for a given entity""" 502 self.windows['Agent'].remove(window) 503 for name in self.entitywins.keys(): 504 if self.entitywins[name].win is window: 505 del self.entitywins[name] 506 break 507 else: 508 raise UserWarning,'Unable to delete agent window'
509
510 - def renameAgent(self,old,new):
511 """Changes the name and references to a given agent 512 @type old,new: str 513 """ 514 # Update name in generic society 515 self.classes.renameEntity(old,new) 516 if self.entitywins.has_key(old): 517 # Update agent window table 518 self.entitywins[new] = self.entitywins[old] 519 del self.entitywins[old] 520 for window in self.entitywins.values(): 521 window.renameEntity(old,new) 522 # Update Windows menu entry 523 if self.menus.has_key(old): 524 newMenu = self.menus[old] 525 del self.menus[old] 526 self.menus[new] = newMenu 527 parents = self.classes[new].getParents() 528 if parents: 529 # Find all of the parent menus to update 530 positions = map(lambda p: self.classes.network[p].index(new), 531 parents) 532 menus = map(lambda p: self.menus[p],parents) 533 else: 534 # Update the root Window menu 535 positions = [self.classes.root.index(new)] 536 menus = [self.menus['Window']] 537 for index in range(len(menus)): 538 # Figure out where the separator is 539 base = 1 540 while menus[index].type(base-1) != 'separator': 541 base += 1 542 menus[index].delete(old) 543 self.addToMenu(new,menus[index],base+positions[index])
544
545 - def addAgent(self,entity,agent=None):
546 """Adds an agent as a child of the given entity""" 547 self.newParent = entity 548 if agent: 549 self._addAgent('OK',agent) 550 else: 551 self.nameDialog.component('entry').focus_set() 552 self.nameDialog.activate(geometry = 'centerscreenalways', 553 globalMode=0)
554
555 - def _addAgent(self,button,agent=None):
556 """Callback from name dialog that actually adds the agent""" 557 if button == 'OK': 558 if agent is None: 559 agent = GenericModel(self.nameDialog.get()) 560 todo = True 561 else: 562 todo = False 563 if not self.newParent.name in agent.parentModels: 564 agent.parentModels.append(self.newParent.name) 565 self.entities.addMember(agent) 566 if todo: 567 window = self.createAgentWin(agent) 568 window.win.iconify_window() 569 # New relationship filler 570 for window in self.entitywins.values(): 571 window.component('Relationships').addStaticFiller(agent.name) 572 # New window menu item 573 if len(self.classes.network[self.newParent.name]) == 1: 574 # This is the first child for this parent 575 menus = map(lambda p: self.menus[p], 576 self.newParent.getParents()) 577 if not menus: 578 menus = [self.menus['Window']] 579 for menu in menus: 580 index = menu.index(self.newParent.name) 581 menu.delete(index) 582 new = Menu(menu,tearoff=0) 583 cmd = lambda s=self,n=self.newParent.name: s.showWindow(n) 584 new.add_command(label='Show Window',command=cmd) 585 new.add_separator() 586 menu.insert_cascade(index,menu=new, 587 label=self.newParent.name) 588 self.menus[self.newParent.name] = new 589 self.addToMenu(agent.name,self.menus[self.newParent.name]) 590 else: 591 todo = True 592 if todo: 593 self.nameDialog.deactivate() 594 self.nameDialog.deleteentry(0,'end') 595 self.newParent = None
596
597 - def validateName(self,name):
598 """Checks whether the new name being entered in the agent dialog is valid or not""" 599 if len(name) == 0: 600 # Empty names not allowed 601 return Pmw.PARTIAL 602 for other in self.entities.members(): 603 if other.name == name: 604 # New agent name must be unique 605 return Pmw.PARTIAL 606 return Pmw.OK
607
608 - def checkNameValidity(self):
609 """Disables the OK button on the agent name dialog if the current text entry is invalid""" 610 button = self.nameDialog.component('buttonbox').component('OK') 611 if self.nameDialog.component('entryfield').valid(): 612 button.configure(state='normal') 613 else: 614 button.configure(state='disabled')
615
616 - def editOrder(self):
617 """Edits the turn order""" 618 if self.view == 'generic': 619 order = copy.deepcopy(self.entities._keys) 620 else: 621 order = self.entities.getSequence() 622 self.turnDialog.configure(editor_order=order, 623 editor_elements=self.entities.keys()) 624 self.turnDialog.configure(editor_editable=True) 625 self.turnDialog.activate()
626
627 - def _editOrder(self,button):
628 """Callback from name dialog that actually adds the agent""" 629 if button == 'OK': 630 editor = self.turnDialog.component('editor') 631 if self.view == 'generic': 632 self.entities._keys = editor['order'][:] 633 else: 634 order = map(lambda n:self.entities[n],editor['order'][:]) 635 self.entities.applyOrder(order) 636 self.turnDialog.deactivate()
637
638 - def setState(self,state=None):
639 if not state: 640 state = self.main['readyText'] 641 self.state = state 642 self.main.Btitle.config(text=self.state)
643 644 # Fitting methods 645
646 - def doFitting(self,actor,action,step):
647 """Fits the agents' goal weights based on behavior in fit window""" 648 self.showWindow(actor) 649 window = self.entitywins[actor] 650 window.win.show_window() 651 window.win.select_window() 652 # Select goal tab 653 notebook = window.component('notebook') 654 notebook.selectpage('R') 655 target = lambda s=self,n=actor,act=action,t=step:\ 656 s.__doFitting(n,act,t) 657 self.background(target=target,label='Fitting')
658
659 - def __doFitting(self,name,action,step):
660 """Fitting helper method (for running in background)""" 661 agent = self.entities[name] 662 goalPane = self.entitywins[name].component('Goals') 663 goals = agent.fit(action,label=step) 664 if isinstance(goals,str): 665 # Unable to fit 666 self.queue.put({'command':'error','title':'Failure', 667 'message':goals}) 668 self.queue.put(None) 669 else: 670 self.queue.put({'command':'updateGoals','widget':goalPane, 671 'agent':agent,'weights':goals}) 672 # In Python 2.5, we could simply do self.queue.join() 673 while not self.queue.empty(): 674 time.sleep(1) 675 if step == self.entities.time: 676 # Replace last hypothetical action with the desired one 677 ## self.queue.put({'command':'pop','widget':self.aarWin}) 678 self.hypothetical(entity=self.scenario[name],real=True) 679 else: 680 msg = 'Fitting was successful, and the new goal weights will affect any future actions. However, the previously selected action cannot be undone and will remain in the history.' 681 self.queue.put({'command':'info','title':'Success', 682 'message':msg}) 683 self.queue.put(None)
684 685 # Simulation methods 686
687 - def doActions(self,actions,results=None):
688 """ 689 Performs the actions, provided in dictionary form 690 @type actions: strS{->}L{Action}[] 691 """ 692 if len(actions) > 1: 693 tkMessageBox.showerror('Forced Actions','I am currently unable to force multiple agents to perform fixed actions at the same time.') 694 else: 695 actor,option = actions.items()[0] 696 self.realStep(self.entities[actor],[option])
697
698 - def realStep(self,entity=None,choices=None,iterations=1):
699 """Queries and performs the given entity for its hypothetical choice""" 700 if entity is None: 701 invalid = self.actionCheck() 702 if invalid: 703 tkMessageBox.showerror('Agent Action Error','There are agents with no allowable actions:\n%s\nPlease, either go to the Action pane and select at least one possible choices, or remove the agent from the turn order.' % 704 ', '.join(invalid)) 705 return 706 args = {'entity':entity,'real':True,'choices':choices, 707 'iterations':iterations, 708 'explain':self.explanationDetail.get() > 0, 709 'suggest':self.explanationDetail.get() > 1, 710 } 711 self.background(self.hypothetical,kwargs=args)
712
713 - def hypoStep(self,entity=None,choices=None,iterations=1):
714 """Queries and displays the given entity for its hypothetical choice""" 715 if entity is None: 716 invalid = self.actionCheck() 717 if invalid: 718 tkMessageBox.showerror('Agent Action Error','There are agents with no allowable actions:\n%s\nPlease, either go to the Action pane and select at least one possible choices, or remove the agent from the turn order.' % 719 ', '.join(invalid)) 720 return 721 args = {'entity':entity,'real':False,'choices':choices, 722 'iterations':iterations, 723 'explain':self.explanationDetail.get() > 0, 724 'suggest':self.explanationDetail.get() > 1, 725 } 726 self.background(self.hypothetical,kwargs=args)
727
728 - def hypothetical(self,entity=None,real=False,choices=None,iterations=1, 729 explain=False,suggest=False):
730 results = [] 731 for t in range(iterations): 732 results.append(self.singleHypothetical(entity,real,choices, 733 explain=explain, 734 suggest=suggest)) 735 self.queue.put(None) 736 if iterations == 1: 737 return results[0] 738 else: 739 return results
740
741 - def actionCheck(self):
742 """Verifies that all agents in the current turn sequence have at 743 least one action available to perform. 744 @return: any agents who do not have any such actions 745 @rtype: str[] 746 """ 747 invalid = [] 748 for key in self.entities.order.keys(): 749 if isinstance(key,StateKey): 750 if len(self.entities[key['entity']].actions.getOptions()) == 0: 751 invalid.append(key['entity']) 752 return invalid
753
754 - def singleHypothetical(self,entity=None,real=False,choices=None, 755 history=None,explain=False,suggest=False):
756 if entity and entity.parent: 757 # This is a hypothetical action within an agent's belief space 758 world = entity.parent.entities 759 else: 760 # This is a hypothetical action within the real world 761 world = self.entities 762 if entity: 763 if history: 764 turn = [{'name':entity.name,'history':history}] 765 else: 766 turn = [{'name':entity.name}] 767 if choices: 768 turn[0]['choices'] = choices 769 else: 770 turn = [] 771 start = time.time() 772 result = world.microstep(turn,hypothetical=not real, 773 explain=explain,suggest=suggest) 774 self.queue.put({'command':'updateNetwork'}) 775 self.queue.put({'command':'AAR','message':result['explanation']}) 776 return result
777
778 - def validate(self):
779 okLabel = 'OK' 780 names = map(lambda e:e.name,self.entities.members()) 781 dialog = Pmw.SelectionDialog(self.root, 782 buttons = [okLabel,'Cancel'], 783 defaultbutton = okLabel, 784 title = 'Scenario Validation', 785 scrolledlist_listbox_selectmode = MULTIPLE, 786 scrolledlist_labelpos='NW', 787 scrolledlist_label_text='Please select the entity models that you wish to declare as validated:', 788 scrolledlist_items=names) 789 listbox = dialog.component('scrolledlist') 790 listbox.component('listbox').selection_set(0,len(names)-1) 791 if dialog.activate() == okLabel: 792 for name in listbox.getcurselection(): 793 entity = self.entities[name] 794 entry = {'what':'validated', 795 'who':getuser(), 796 'when':time.time() 797 } 798 entity.extendHistory(entry) 799 self.balloon.update()
800
801 - def modeSelect(self,but):
802 change = False # Flag to prevent infinite recursion 803 for key in self.windows.keys(): 804 for w in self.windows[key]: 805 if key in self.modes[but]['windows']: 806 if w.iconified: 807 change = True 808 w.show_window() 809 elif not w.iconified: 810 change = True 811 w.iconify_window() 812 if change: 813 # Kind of a hacked way of updating the menu and toolbar 814 menu = self.menus['View'] 815 menu.invoke(but) 816 self.modeBox.invoke(but)
817 818
819 - def splash(self,soundFile=None):
820 if soundFile: 821 aud = moduleDir + soundFile 822 popen2.popen2('aplay ' + aud) 823 try: 824 photo = PhotoImage(file=getImage('faces.gif')) 825 except: 826 photo=None 827 palette = Pmw.Color.getdefaultpalette(self.root) 828 dialog = Pmw.AboutDialog(self.root, 829 applicationname='PsychSim', 830 # hull_bg = "black", 831 # hull_fg = "white", 832 message_fg = palette['background'], 833 message_bg = palette['foreground'], 834 ## message_font=("Helvetica", 16, "bold"), 835 icon_image=photo) 836 dialog.lift() 837 dialog.activate()
838
839 - def drawMenubar(self):
840 self.menus = {} 841 self.menuBar = Menu(self.root,tearoff=0) 842 aqua = (self.root.tk.call("tk", "windowingsystem") == 'aqua') 843 844 if aqua: 845 # Apple menu 846 menu = Menu(self.menuBar,name='apple',tearoff=0) 847 menu.add_command(command=self.splash,label = 'About PsychSim') 848 self.menuBar.add_cascade(label='PsychSim',menu=menu) 849 850 # FILE 851 menu = Menu(self.menuBar,tearoff=0) 852 menu.add_command(command = self.createScenario, 853 accelerator='CTRL+N',label = 'New Scenario...') 854 self.root.bind_class(self.root,'<Control-Key-n>',self.createScenario) 855 menu.add_command(command = self.load,label = 'Open...', 856 accelerator='CTRL+O') 857 self.root.bind_class(self.root,'<Control-Key-o>',self.load) 858 menu.add_separator() 859 menu.add_command(command = self.saveBoth,accelerator='CTRL+S', 860 label = 'Save') 861 self.root.bind_class(self.root,'<Control-Key-s>',self.saveBoth) 862 menu.add_command(command = self.saveAs,label = 'Save As...') 863 menu.add_command(command = self.export, label = 'Export...') 864 menu.add_command(command = self.distill, label = 'Distill...') 865 menu.add_separator() 866 menu.add_command(command = self.revert, label = 'Revert') 867 menu.add_command(command = self.view, label = 'Properties...', 868 state='disabled') 869 menu.add_separator() 870 menu.add_command(command = self.close,accelerator='CTRL+W', 871 label = 'Close') 872 self.root.bind_class(self.root,'<Control-Key-w>',self.close) 873 menu.add_command(command = self.stop,label = 'Quit', 874 accelerator='CTRL+Q') 875 self.root.bind_class(self.root,'<Control-Key-q>',self.stop) 876 self.menuBar.add_cascade(label='File',menu=menu) 877 self.menus['File'] = menu 878 879 # EDIT 880 menu = Menu(self.menuBar,tearoff=0) 881 menu.add_command(label='Cut',state='disabled',command=self.cut, 882 accelerator='CTRL+X') 883 self.root.bind_class(self.root,'<Control-Key-x>',self.cut) 884 menu.add_command(label='Copy',state='disabled',command=self.copy, 885 accelerator='CTRL+C') 886 self.root.bind_class(self.root,'<Control-Key-c>',self.copy) 887 menu.add_command(label='Paste',state='disabled',command=self.paste, 888 accelerator='CTRL+V') 889 self.root.bind_class(self.root,'<Control-Key-v>',self.paste) 890 self.menuBar.add_cascade(label='Edit',menu=menu) 891 self.menus['Edit'] = menu 892 893 # VIEW 894 menu = Menu(self.menuBar,tearoff=0) 895 menu.add_command(label='Scenario',command=self.viewSelect) 896 if self.dev: 897 menu.add_command(label='MAID',command=self.viewMAID) 898 menu.add_command(label='Turn Order',command=self.editOrder) 899 menu.add_separator() 900 menu.add_radiobutton(label='None', 901 command = lambda s=self:s.modeSelect('None')) 902 menu.add_radiobutton(label='Agents', 903 command = lambda s=self:s.modeSelect('Agents')) 904 menu.add_radiobutton(label='Run',command = lambda s=self:\ 905 s.modeSelect('Run')) 906 menu.add_radiobutton(label='All', 907 command = lambda s=self:s.modeSelect('All')) 908 self.menuBar.add_cascade(label='View',menu=menu) 909 self.menus['View'] = menu 910 911 # OPTIONS 912 menu = Menu(self.menuBar,tearoff=0) 913 ## menu.add_command(command = self.setupSusceptibility, 914 ## label = 'Susceptibility...') 915 ## menu.add_separator() 916 self.explanationDetail = IntVar() 917 subMenu = Menu(menu,tearoff=0) 918 subMenu.add_radiobutton(variable=self.explanationDetail, 919 value = 0,label = 'No Detail') 920 subMenu.add_radiobutton(value = 1,label = 'Low Detail', 921 variable=self.explanationDetail) 922 subMenu.add_radiobutton(value = 2,label = 'High Detail', 923 variable=self.explanationDetail) 924 self.explanationDetail.set(2) 925 menu.add_cascade(menu=subMenu,label = 'Explanation Detail') 926 menu.add_separator() 927 menu.add_radiobutton(value=0,variable=self.expert,label='Normal', 928 command=self.setExpert) 929 menu.add_radiobutton(value=1,variable=self.expert,label='Expert', 930 command=self.setExpert) 931 self.menuBar.add_cascade(label='Options',menu=menu) 932 self.menus['Options'] = menu 933 934 # SIMULATION 935 menu = Menu(self.menuBar,tearoff=0) 936 menu.add_command(command = self.hypoStep,label = 'Hypothetical Step') 937 menu.add_command(command = self.realStep, label = 'Step') 938 menu.add_command(command = self.run, label = 'Run...') 939 if self.dev: 940 menu.add_command(command=self.entities.compileDynamics, 941 label = 'Compile Dynamics') 942 menu.add_separator() 943 menu.add_command(command = self.chooseObjectives,label='Objectives...') 944 menu.add_command(state='disabled',command = self.validate, 945 label='Validate...') 946 self.menuBar.add_cascade(label=self.simulationLabel,menu=menu, 947 state='disabled') 948 self.menus[self.simulationLabel] = menu 949 950 # WINDOWS 951 menu = Menu(self.menuBar,tearoff=0) 952 self.menuBar.add_cascade(label='Window',menu=menu) 953 self.menus['Window'] = menu 954 955 # HELP 956 menu = Menu(self.menuBar,tearoff=0) 957 if not aqua: 958 menu.add_command(command=self.splash,label = 'About PsychSim') 959 menu.add_checkbutton(variable = self.toggleBalloonVar, 960 command = self.toggleBalloon, 961 label = 'Balloon help') 962 self.menuBar.add_cascade(label='Help',menu=menu) 963 self.root.config(menu=self.menuBar) 964 self.menus['Help'] = menu
965
966 - def viewSelect(self,view=None,alreadyLoaded=False):
967 """Toggles between views of generic society and specific scenario""" 968 if not alreadyLoaded: 969 self.clear() 970 if view: 971 self.view = view 972 elif self.view == 'generic': 973 self.view = 'scenario' 974 else: 975 self.view = 'generic' 976 menu = self.menus['File'] 977 if self.view == 'generic': 978 label = 'Scenario' 979 new = self.classes 980 menu.entryconfigure('Export...',state='disabled') 981 menu.entryconfigure('Distill...',state='disabled') 982 menu.entryconfigure('Revert',state='disabled') 983 self.control.pack_forget() 984 self.timeDisplay.pack_forget() 985 else: 986 label = 'Generic Models' 987 new = self.scenario 988 menu.entryconfigure('Export...',state='normal') 989 menu.entryconfigure('Distill...',state='normal') 990 menu.entryconfigure('Revert',state='normal') 991 self.control.pack(side='left') 992 self.timeDisplay.pack(side='left') 993 menu = self.menus['View'] 994 if self.view == 'generic': 995 self.menuBar.entryconfig(self.simulationLabel,state='disabled') 996 if self.dev: 997 menu.entryconfigure('MAID',state='disabled') 998 else: 999 self.menuBar.entryconfig(self.simulationLabel,state='normal') 1000 if self.dev: 1001 menu.entryconfigure('MAID',state='normal') 1002 try: 1003 menu.entryconfigure('Scenario',label=label) 1004 except: 1005 menu.entryconfigure('Generic Models',label=label) 1006 if label == 'Scenario' and not self.scenario: 1007 menu.entryconfigure(label,state='disabled') 1008 else: 1009 menu.entryconfigure(label,state='normal') 1010 if not alreadyLoaded: 1011 alreadyLoaded = (new is self.entities) 1012 self.entities = new 1013 if not alreadyLoaded: 1014 self.initEntities()
1015
1016 - def toggleBalloon(self):
1017 """Toggles visibility of balloon helpIt might not be a good idea to toggle this off right now""" 1018 if self.toggleBalloonVar.get(): 1019 self.balloon.configure(state='balloon') 1020 else: 1021 self.balloon.configure(state='none')
1022
1023 - def setExpert(self):
1024 """Sets the expert mode for the overall GUI""" 1025 for win in self.entitywins.values(): 1026 win.configure(expert=bool(self.expert.get())) 1027 if self.psymwindow: 1028 self.psymwindow.configure(expert=bool(self.expert.get())) 1029 if self.aarWin: 1030 self.aarWin.configure(expert=bool(self.expert.get()))
1031
1032 - def createAgentWin(self, entity, x=0, y=0):
1033 """Creates an agent window for the given entity at the given point""" 1034 abstract = isinstance(entity,GenericModel) 1035 if abstract: 1036 society = self.classes 1037 else: 1038 society = self.entities 1039 if self.dev: 1040 valueCmd = self.expectedValue 1041 else: 1042 valueCmd = None 1043 aw = AgentWin(self.main, 1044 entity=entity, x=x, y=y,society=society, 1045 expert=self.expert.get(),balloon=self.balloon, 1046 actCmd=self.doActions,stepCmd=self.realStep, 1047 msgCmd=self.msgSend,policyCmd=self.compilePolicy, 1048 hypoCmd=self.hypoStep,valueCmd=valueCmd, 1049 abstract=abstract, 1050 destroycommand=self.destroyAgentWin, 1051 ) 1052 aw.configure(Relationships_network=self.psymwindow) 1053 self.windows['Agent'].append(aw.win) 1054 self.entitywins[entity.name] = aw 1055 entity.attributes['window'] = aw 1056 self.menuBar.entryconfig('Window',state='normal') 1057 return aw
1058
1059 - def addToMenu(self,name,menu,index='end'):
1060 """Adds a new entry to the given menu for an entity, and any descendents 1061 """ 1062 cmd = lambda s=self,n=name: s.showWindow(n) 1063 try: 1064 children = self.classes.network[name] 1065 except KeyError: 1066 children = [] 1067 if len(children) > 0: 1068 # Cascade 1069 try: 1070 new = self.menus[name] 1071 except KeyError: 1072 new = Menu(menu,tearoff=0) 1073 new.add_command(label='Show Window',command=cmd) 1074 new.add_separator() 1075 self.menus[name] = new 1076 for child in children: 1077 self.addToMenu(child,new) 1078 menu.insert_cascade(index,label=name,menu=new) 1079 else: 1080 # Command 1081 menu.insert_command(index,label=name,command=cmd)
1082
1083 - def clear(self):
1084 # Disable menus 1085 for label in [self.simulationLabel,'View']: 1086 self.menuBar.entryconfig(label,state='disabled') 1087 self.menus['Window'].delete(0,'end') 1088 if self.entities: 1089 for name in self.entities.keys(): 1090 if self.menus.has_key(name): 1091 del self.menus[name] 1092 self.menus['File'].entryconfigure('Properties...',state='disabled') 1093 # Close windows 1094 for key in ['Agent']: 1095 while len(self.windows[key]) > 0: 1096 win = self.windows[key][0] 1097 win.destroy() 1098 while len(self.themewins) > 0: 1099 win = self.themewins.pop() 1100 win.component('frame').destroy() 1101 del win 1102 self.balloon.delete() 1103 # Reset permanent windows 1104 if self.aarWin: 1105 self.aarWin.clear() 1106 if self.psymwindow: 1107 self.psymwindow.clear() 1108 if self.graph: 1109 for window in self.windows['Analysis']: 1110 if window.cget('title') == 'Graph': 1111 self.windows['Analysis'].remove(window) 1112 window.destroy() 1113 self.graph = None 1114 break 1115 self.root.title(self.titleLabel)
1116
1117 - def close(self,event=None,load=False):
1118 if self.view == 'generic': 1119 if not load: 1120 self.resetSociety() 1121 else: 1122 self.scenario = None 1123 self.scenarioFile = None 1124 if load: 1125 self.clear() 1126 else: 1127 self.viewSelect('generic')
1128 1129 # Methods for handling interaction with susceptibility component 1130
1131 - def setupSusceptibility(self,addr=None):
1132 dialog = Pmw.Dialog(self.root, 1133 title='Susceptibility Agent', 1134 buttons=['OK','Cancel'], 1135 defaultbutton='OK') 1136 host = Pmw.EntryField(dialog.component('dialogchildsite'), 1137 command=dialog.invoke, 1138 labelpos=W, 1139 label_text='Host:', 1140 label_width=7, 1141 label_justify='left' 1142 ) 1143 if self.susceptibility: 1144 str = self.susceptibility[0] 1145 else: 1146 str = '127.0.0.1' # localhost 1147 host.setentry(str) 1148 host.pack(side='top') 1149 counter = Pmw.Counter(dialog.component('dialogchildsite'), 1150 labelpos=W, 1151 label_text='Port:', 1152 label_width=7, 1153 label_justify='left' 1154 ) 1155 port = counter.component('entryfield') 1156 if self.susceptibility: 1157 port.setentry(`self.susceptibility[1]`) 1158 counter.pack(side='top') 1159 if dialog.activate() == 'OK': 1160 addr = (host.component('entry').get(), 1161 int(port.component('entry').get())) 1162 PsychShell.setupSusceptibility(self,addr)
1163
1164 - def selectSusceptibility(self):
1165 if self.susceptibility: 1166 dialog = Pmw.SelectionDialog(self.root, 1167 title='Susceptibility', 1168 buttons=['OK','Cancel'], 1169 defaultbutton='OK', 1170 scrolledlist_labelpos=N, 1171 scrolledlist_label_text='Select PTA:' 1172 ) 1173 items = map(lambda e:e.name,self.entities.members()) 1174 dialog.component('scrolledlist').setlist(items) 1175 if dialog.activate() == 'OK': 1176 for name in dialog.component('scrolledlist').getcurselection(): 1177 if not self.querySusceptibility(name): 1178 msg = 'Unable to query susceptibility agent!' 1179 tkMessageBox.showerror('Susceptibility error',msg) 1180 else: 1181 tkMessageBox.showerror('Susceptibility error', 1182 'Please configure susceptibility agent '+\ 1183 'from Options menu first. Thank you.')
1184
1185 - def handleSusceptibility(self,entity,themes):
1186 PsychShell.handleSusceptibility(self,entity,themes) 1187 if len(themes['Accepted']) == 0: 1188 msg = 'None' 1189 else: 1190 msg = string.join(map(lambda i:i[0],themes['Accepted']),'\n') 1191 for win in self.themewins: 1192 win.change('recipient',win.boxes['recipient'].getvalue()) 1193 tkMessageBox.showinfo(entity+' Susceptibility Result', 1194 'Susceptibilities:\n'+msg)
1195
1196 - def displayActions(self,fullresult,step=-1):
1197 """Displays any explanation from the performance of actions in the appropriate AAR windows 1198 @param fullresult: an explanation structure, in dictionary form: 1199 - explanation: a list of 3-tuples of strings, suitable for passing to L{JiveTalkingAAR.displayAAR} 1200 - I{agent}: explanation substructures for each actor, in dictionary form: 1201 - decision: the actions chosen by this actor, list format 1202 @type fullresult: C{dict} 1203 @param step: the current time step (defaults to the current scenario time 1204 @type step: C{int} 1205 """ 1206 if step < 0: 1207 step = self.entities.time 1208 self.aarWin.displayAAR(fullresult['explanation']) 1209 del fullresult['explanation'] 1210 for key,observed in fullresult.items(): 1211 actor = self.entities[key] 1212 self.acted.append(actor.name) 1213 print actor.name, '-->',observed['decision'] 1214 self.incUpdate(None,observed['decision'], actor)
1215 1216 # Threading methods 1217
1218 - def background(self,target,event=None,label='Running',kwargs={}):
1219 """Runs the target command in a background thread. This handles the disabling/enabling of the relevant GUI elements. 1220 @param label: optional label that appears in the status bar 1221 @type label: str 1222 @param target: the command to run in the background. It should call the L{finishBackground} method when it is done (see L{__step} method for a sample target). 1223 @type target: lambda 1224 """ 1225 self.event = event 1226 self.setState(label) 1227 self.control.toggle('play') 1228 self.control.component('play').configure(command=self.stopBackground) 1229 # Disable agent changes 1230 for win in self.entitywins.values(): 1231 win.setState('disabled') 1232 thread = threading.Thread(target=target,kwargs=kwargs) 1233 thread.start()
1234
1235 - def stopBackground(self):
1236 if self.event: 1237 self.event.set()
1238
1239 - def finishBackground(self):
1240 """Cleans up after a background simulation thread""" 1241 # Enables all of the agent window widgets""" 1242 for win in self.entitywins.values(): 1243 win.setState('normal') 1244 self.incUpdate() 1245 if self.view == 'scenario': 1246 self.drawTime() 1247 self.control.toggle('play') 1248 self.control.component('play').configure(command=self.realStep) 1249 self.balloon.update() 1250 self.setState() 1251 if self.event: 1252 del self.event 1253 self.event = None
1254 1255 # Simulation methods 1256
1257 - def expectedValue(self,entity,world=None,description=None, 1258 initial=None,background=1,display=1):
1259 """Computes and displays the reward expected by the given entity 1260 1261 The world defaults to the current scenario. The description is 1262 prepended to the AAR. The initial reward value is subtracted from the 1263 computed EV (to provide a differential result if desired).""" 1264 if not world: 1265 world = self.entities 1266 target = lambda : self.__expectedValue(entity,world,description, 1267 initial,display) 1268 if background: 1269 self.background(target) 1270 else: 1271 return target()
1272
1273 - def __expectedValue(self,entity,world,description,initial,display=1):
1274 """Background computation of expected value""" 1275 action,explanation = entity.applyPolicy() 1276 goals = entity.getGoalVector()['state'] 1277 state = world.getState() 1278 distribution = explanation['options'][str(action)]['value'] 1279 stateValue = distribution.expectation() 1280 result = {} 1281 for row,prob in goals.items(): 1282 for key,weight in row.items(): 1283 try: 1284 subValue = stateValue[key] 1285 except KeyError: 1286 subValue = 0. 1287 item = prob*weight*subValue 1288 try: 1289 result[key] += item 1290 except KeyError: 1291 result[key] = item 1292 if initial: 1293 result -= initial 1294 if not description: 1295 description = '%s expects the current state of the world to:\n' % \ 1296 entity.name 1297 value = self.translateReward(result,entity.name) 1298 if display: 1299 if self.explanationDetail.get() > 0: 1300 self.aarWin.displayAAR([description,value,'']) 1301 self.queue.put(None) 1302 return result
1303
1304 - def step(self,length=1,results=None):
1305 """Step forward by a single round of actions by the entities""" 1306 # Extract any manual entries from campaign window 1307 for win in self.themewins: 1308 self.user_policy(win) 1309 self.background(self.__step)
1310
1311 - def __step(self):
1312 """Execution of a step intended for background running""" 1313 fullresult = self.entities.microstep(hypothetical=False, 1314 explain=True, 1315 debug=self.debug) 1316 for str1,str2,str3 in fullresult['explanation']: 1317 self.queue.put({'command':'AAR','message':[s1,s2,s3]}) 1318 self.queue.put({'command':'updateTime'}) 1319 self.queue.put(None)
1320
1321 - def compilePolicy(self,entity):
1322 """Displays the rules of entity's policy in the given widget""" 1323 win = self.entitywins[entity.name] 1324 options = win.component('Actions').getActions() 1325 if options: 1326 event = threading.Event() 1327 cmd = lambda : self.__compilePolicy(entity,options) 1328 self.background(cmd,event,'Compiling')
1329
1330 - def __compilePolicy(self,entity,options):
1331 """Displays the decision-tree of entity's policy using the given command""" 1332 cmd = lambda s=self,e=entity: s.displayPolicy(e) 1333 policies = {} 1334 for agent in self.scenario.activeMembers(): 1335 policies[agent.name] = agent.policy 1336 if entity.policy.solve(choices=options,interrupt=self.event, 1337 policies=policies,search='abstract', 1338 debug=False,progress=cmd): 1339 pass 1340 elif self.event.isSet(): 1341 tkMessageBox.showwarning('Interrupted!','The best policy found so far is displayed.') 1342 else: 1343 tkMessageBox.showerror('Overflow Error','The full specification of this agent\'s behavior is too complicated to compute and display.') 1344 self.displayPolicy(entity) 1345 self.queue.put(None)
1346
1347 - def displayPolicy(self,entity):
1348 if self.queue.empty(): 1349 self.queue.put({'command':'policy', 1350 'window':self.entitywins[entity.name]})
1351
1352 - def run(self,num=0):
1353 if not num: 1354 dialog = Pmw.CounterDialog(self.root, 1355 title = 'Number of Steps', 1356 defaultbutton = 'OK', 1357 counter_orient = 'vertical', 1358 counter_entryfield_value=1, 1359 counter_entryfield_entry_width=3, 1360 counter_entryfield_validate = {'min':1, 1361 'validator':'numeric'}, 1362 buttons = ('OK','Cancel')) 1363 counter = dialog.component('counter') 1364 if dialog.activate() == 'OK': 1365 num = int(counter.component('entryfield').component('entry').get()) 1366 else: 1367 return 1368 self.realStep(iterations=num)
1369
1370 - def msgSend(self, sender, receiver, subject, msg, force, overhear, 1371 evaluateOnly=None):
1372 if evaluateOnly == 'subjective': 1373 # Hypothetical subjective 1374 target = lambda s=self,a=sender,r=receiver,j=subject,\ 1375 m=msg,f=force,o=overhear:s.query(a,r,j,m,f,o) 1376 self.background(target) 1377 elif evaluateOnly: 1378 # Hypothetical objective 1379 target = lambda s=self,a=sender,r=receiver,j=subject,\ 1380 m=msg,f=force,o=overhear:s.evaluateMsg(a,r,j,m,f,o) 1381 self.background(target) 1382 else: 1383 world = self.entities 1384 self.__msgSend(sender,receiver,subject,msg,force,overhear,world) 1385 ## # Update audit trail 1386 ## for entity in self.entities.members(): 1387 ## entity.extendHistory({'what':'message', 1388 ## 'who':getuser(), 1389 ## 'when':time.time()}) 1390 ## self.balloon.update() 1391 self.incUpdate()
1392
1393 - def query(self,sender,receiver,subject,msg,force,overhear):
1394 world = copy.deepcopy(self.entities[sender].entities) 1395 value = self.expectedValue(world[sender],world=world,background=0, 1396 display=0) 1397 self.__msgSend(sender,receiver,subject,msg,force,overhear,world,0, 1398 hypothetical=1) 1399 delta = self.expectedValue(world[sender],world, 1400 '%s thinks the message will help:\n'\ 1401 % (sender),value,0,display=0) 1402 msgTxt=[{'text':sender,'tag':'bolden'}] 1403 if float(delta) > 0.0: 1404 msgTxt.append({'text':' chooses to send\n\n'}) 1405 else: 1406 msgTxt.append({'text':' chooses not to send\n\n'}) 1407 self.aarWin.displayTaggedText(msgTxt) 1408 self.queue.put(None)
1409
1410 - def evaluateMsg(self,sender,receiver,subject,msg,force,overhear):
1411 world = copy.deepcopy(self.entities) 1412 value = -self.expectedValue(world[sender],world=world,background=0, 1413 display=0) 1414 self.__msgSend(sender,receiver,subject,msg,force,overhear,world,0) 1415 value += self.expectedValue(world[sender],world, 1416 'Sending the message will help:',value,0, 1417 display=0) 1418 if float(value) > 0.: 1419 content = 'Sending the message will help' 1420 else: 1421 content = 'Sending the message will not help' 1422 self.queue.put({'command':'AAR','message':['',m,'\n']}) 1423 self.queue.put(None)
1424
1425 - def __msgSend(self, sender, receiver, subject, msg, force, 1426 overhear,world,background=1,hypothetical=None):
1427 if type(overhear) is ListType: 1428 pass 1429 elif overhear == None or overhear == 'N' or overhear == '': 1430 overhear = [] 1431 else: 1432 overhear = [overhear] 1433 if receiver == 'All': 1434 receiver = '*' 1435 elif type(receiver) is ListType: 1436 pass 1437 else: 1438 receiver = [receiver] 1439 if type(msg) is StringType: 1440 pmsg = Message(msg) 1441 else: 1442 pmsg = msg 1443 if force != 'none': 1444 pmsg.force(value=force) 1445 target = lambda s=self,w=world,m=pmsg,a=sender,r=receiver,\ 1446 o=overhear,j=subject,h=hypothetical:\ 1447 s.performMsg(world=w,msg=m,sender=a,receiver=r, 1448 overhear=o,subject=j,hypothetical=h) 1449 if background: 1450 self.background(target) 1451 else: 1452 target() 1453 return
1454
1455 - def performMsg(self,world,msg,sender,receiver,overhear,subject, 1456 background=1,hypothetical=None):
1457 explanation = world.performMsg(msg,sender,receiver,overhear, 1458 explain=True) 1459 self.queue.put({'command':'AAR','message':explanation}) 1460 if background: 1461 self.queue.put(None)
1462
1463 - def view(self):
1464 """Pops up the properties dialog""" 1465 if self.view == 'generic': 1466 agents = self.entities.keys() 1467 agents.sort() 1468 dialog = Pmw.Dialog(self.root,title='Scenario Properties', 1469 defaultbutton='OK') 1470 book = Pmw.NoteBook(dialog.interior(),hull_width=600, 1471 hull_height=400) 1472 # How many states, actions, & goals 1473 page = book.add('Progress') 1474 frame = Pmw.ScrolledFrame(page,horizflex='expand', 1475 vertflex='expand') 1476 columns = ['Agent','States','Actions','Goals'] 1477 for col in range(len(columns)): 1478 frame.interior().grid_columnconfigure(col,weight=1) 1479 name = columns[col] 1480 widget = Label(frame.interior(),text=name,anchor='nw', 1481 relief='raised') 1482 widget.grid(row=0,column=col,sticky='ew') 1483 for row in range(len(agents)): 1484 agent = self.entities[agents[row]] 1485 for col in range(len(columns)): 1486 if columns[col] == 'Agent': 1487 name = agent.name 1488 elif columns[col] == 'States': 1489 name = len(agent.getStateFeatures()) 1490 elif columns[col] == 'Actions': 1491 name = len(agent.actions.getOptions()) 1492 elif columns[col] == 'Goals': 1493 name = len(agent.getGoals()) 1494 widget = Label(frame.interior(),text=name, 1495 relief='sunken') 1496 if isinstance(name,int): 1497 widget.configure(anchor='e') 1498 else: 1499 widget.configure(anchor='w') 1500 widget.grid(row=row+1,column=col,sticky='ew') 1501 frame.pack(side='top',fill='x',expand='yes') 1502 # Which dynamics 1503 page = book.add('Dynamics') 1504 frame = Pmw.ScrolledFrame(page,horizflex='expand', 1505 vertflex='expand') 1506 actions = {} 1507 for agent in self.entities.members(): 1508 for option in agent.actions.getOptions(): 1509 for action in option: 1510 actions[action['type']] = True 1511 actions = actions.keys() 1512 actions.sort() 1513 widget = Label(frame.interior(),text='State',anchor='nw', 1514 relief='raised') 1515 widget.grid(row=0,column=0,sticky='ewns') 1516 for col in range(len(actions)): 1517 frame.interior().grid_columnconfigure(col+1,weight=1) 1518 name = actions[col] 1519 widget = Label(frame.interior(),text=name,anchor='nw', 1520 relief='raised',width=12,wraplength=96) 1521 widget.grid(row=0,column=col+1,sticky='ewns') 1522 row = 1 1523 for name in agents: 1524 agent = self.entities[name] 1525 keyList = agent.state.domainKeys().keys() 1526 keyList.sort() 1527 for key in keyList: 1528 widget = Label(frame.interior(),text=str(key), 1529 relief='sunken',anchor='nw') 1530 widget.grid(row=row,column=0,sticky='ewns') 1531 for col in range(len(actions)): 1532 try: 1533 dynamics = agent.dynamics[key['feature']][actions[col]] 1534 widget = Label(frame.interior(),relief='sunken', 1535 bg='#22dd22') 1536 except KeyError: 1537 # Unfinished button for editing missing dynamics 1538 widget = Button(frame.interior(),relief='sunken', 1539 command=lambda s=self,k=key,a=actions[col],d=dialog:s.popupDynamics(k,a,d)) 1540 widget.grid(row=row,column=col+1,sticky='ewns') 1541 row += 1 1542 frame.pack(fill='both',expand='yes') 1543 book.pack(fill='both',expand='yes') 1544 else: 1545 dialog = Pmw.TextDialog(self.root,title='Scenario Properties') 1546 msg = 'Unable to view scenario properties at this time.' 1547 dialog.component('scrolledtext').settext(msg) 1548 dialog.activate()
1549
1550 - def popupDynamics(self,key,action,dialog):
1551 dialog.deactivate() 1552 window = self.entitywins[key['entity']] 1553 frame = window.component('State') 1554 # if self.expert: 1555 # frame.viewDynamics(key['feature']) 1556 # else: 1557 tkMessageBox.showerror('Not quite ready','I am currently unable to pop up the dialog for editing the effect of %s on %s.' % (action,key))
1558
1559 - def export(self,filename=None,results=None):
1560 """Writes a Web page representation of the current scenario""" 1561 if not filename: 1562 ftypes = [('Web page', '.html'), 1563 ('All files', '*')] 1564 filename = tkFileDialog.asksaveasfilename(filetypes=ftypes, 1565 defaultextension='.html') 1566 if filename: 1567 # Clicked OK 1568 PsychShell.export(self,filename)
1569
1570 - def distill(self,filename=None,results=None):
1571 """Writes a lightweight representation of the current scenario""" 1572 if not filename: 1573 ftypes = [('XML file', '.xml')] 1574 filename = tkFileDialog.asksaveasfilename(filetypes=ftypes, 1575 defaultextension='.xml') 1576 if filename: 1577 # Clicked OK 1578 self.background(lambda s=self,f=filename: 1579 s._distill(filename=f),label='Distilling')
1580
1581 - def _distill(self,filename):
1582 PsychShell.distill(self,filename) 1583 self.queue.put(None)
1584
1585 - def createScenario(self,args=None):
1586 """Pops up a wizard dialog to instantiate scenario""" 1587 # This is the code using ScenarioWizard 1588 dialog = ScenarioWizard( self, self.root, 1589 image=getImage('wizard.gif'), 1590 name='Scenario Setup', 1591 society=self.classes, 1592 leavesOnly=False, 1593 balloon=self.balloon, 1594 root=self.root, 1595 expert=self.expert.get(), 1596 command=self.setupEntities, 1597 scenario=self.scenario, 1598 agentClass=self.agentClass, 1599 ) 1600 dialog.run() 1601 if dialog.scenario: 1602 if dialog.toImport: 1603 dialog.scenario.objectives = self.scenario.objectives[:] 1604 self.setupScenario(dialog.scenario)
1605
1606 - def saveBoth(self,event=None):
1607 if self.view == 'generic': 1608 self.saveSociety(self.societyFile) 1609 else: 1610 self.saveScenario(self.scenarioFile)
1611
1612 - def saveAs(self, filename=None):
1613 if self.view == 'generic': 1614 self.saveSociety() 1615 else: 1616 self.saveScenario()
1617
1618 - def saveScenario(self,filename=None):
1619 if not self.scenario: 1620 tkMessageBox.showerror('Save Scenario','No scenario present!') 1621 filename = None 1622 elif not filename: 1623 if isinstance(self.scenario,PWLSimulation): 1624 ftypes = [('Lightweight scenario file', '.xml')] 1625 else: 1626 ftypes = [('Scenario file', '.scn')] 1627 ext = ftypes[0][1] 1628 filename = tkFileDialog.asksaveasfilename(filetypes=ftypes, 1629 defaultextension=ext) 1630 if filename: 1631 # Clicked OK 1632 name = os.path.split(filename)[1] 1633 name = os.path.splitext(name)[0] 1634 self.root.title('%s - %s - %s' % (self.titleLabel,name,self.view)) 1635 self.background(lambda s=self,f=filename: 1636 s._saveScenario(filename=f),label='Saving')
1637
1638 - def _saveScenario(self,filename):
1639 PsychShell.save(self,filename) 1640 self.queue.put(None)
1641
1642 - def saveSociety(self, filename=None):
1643 if not self.classes: 1644 tkMessageBox.showerror('Save Generic Society', 1645 'No generic society present!') 1646 return 1647 if not filename: 1648 ftypes = [('Generic society files', '.soc')] 1649 filename = tkFileDialog.asksaveasfilename(filetypes=ftypes, 1650 defaultextension='.soc') 1651 if filename: 1652 name = os.path.split(filename)[1] 1653 name = os.path.splitext(name)[0] 1654 self.root.title('%s - %s - %s' % (self.titleLabel,name,self.view)) 1655 self.background(lambda s=self,f=filename: 1656 s._saveSociety(filename=f),label='Saving')
1657
1658 - def _saveSociety(self,filename):
1659 PsychShell.saveSociety(self,filename) 1660 self.queue.put(None)
1661
1662 - def load(self, event=None, filename=None,results=[]):
1663 """Loads a scenario in from a file""" 1664 if filename is None: 1665 ftypes = [('Generic society files', '.soc'), 1666 ('Scenario files', '.scn'), 1667 ('XML files', '.xml')] 1668 filename = tkFileDialog.askopenfilename(filetypes=ftypes, 1669 defaultextension='.soc') 1670 1671 if not filename: return 1672 self.close(load=True) 1673 society = (filename[-3:] == 'soc') 1674 try: 1675 warnings = PsychShell.load(self,filename) 1676 except IOError: 1677 tkMessageBox.showerror('Open','No such file or directory') 1678 return 1679 if society: 1680 if len(warnings) > 0: 1681 msg = 'The new society contained conflicting values for some '\ 1682 'model parameters. The original values have been '\ 1683 'preserved for the following:\n\n' 1684 for warning in warnings: 1685 msg += '\t%s\n' % (warning) 1686 tkMessageBox.showwarning('Import Conflict',msg) 1687 if self.view == 'generic': 1688 self.entities = self.classes 1689 self.initEntities() 1690 else: 1691 self.aarWin.clear() 1692 if self.supportgraph: 1693 self.supportgraph.loadHistory(self.entities) 1694 self.viewSelect('scenario',alreadyLoaded=True)
1695
1696 - def revert(self):
1697 """Reverts the current scenario back to the last saved version""" 1698 PsychShell.revert(self) 1699 if self.supportgraph: 1700 self.supportgraph.loadHistory(self.entities) 1701 self.incUpdate()
1702
1703 - def incUpdate(self,res=None, actType=None, actor=None):
1704 if self.psymwindow: 1705 if actType: 1706 if isinstance(actType,dict): 1707 actList = [actType] 1708 else: 1709 actList = actType 1710 for actType in actList: 1711 if actType['object']: 1712 obj = actType['object'] 1713 if type(obj) is InstanceType: 1714 obj = obj.name 1715 self.psymwindow.redrawSupportLabel(actor.name, obj, 1716 `actType`) 1717 else: 1718 self.psymwindow.redrawSupport() 1719 # Update agent windows 1720 for name,window in self.entitywins.items(): 1721 window.update() 1722 if self.graph: 1723 self.graph.updateGraphWindow() 1724 self.root.update()
1725
1726 - def seedMessage(self,name,args):
1727 # Transform arguments into concrete message content 1728 if args.has_key('key'): 1729 msgList = [args] 1730 else: 1731 msgList = args['messages'] 1732 for msg in msgList: 1733 msg['sender'] = name 1734 msg['receiver'] = msg['violator'] 1735 msg['subject'] = msg['key']['entity'] 1736 msg['type'] = msg['key']['feature'] 1737 # Find new value for this feature 1738 belief = self.entities[msg['receiver']].getBelief(msg['subject'], 1739 msg['type']) 1740 value = float(belief) 1741 if value < msg['min']: 1742 # Need to send a higher number. How much higher? 1743 if msg['min'] < 0.9: 1744 msg['value'] = msg['min']+0.1 1745 else: 1746 msg['value'] = msg['min']+0.01 1747 elif value > msg['max']: 1748 # Need to send a lower number. How much lower? 1749 if msg['max'] > -0.9: 1750 msg['value'] = msg['max']-0.1 1751 else: 1752 msg['value'] = msg['max']-0.01 1753 else: 1754 # Could happen if using out-of-date suggestions 1755 msg['value'] = value 1756 print '%s already has satisfactory belief about %s: %5.3f' % \ 1757 (msg['receiver'],msg['key'],value) 1758 if args.has_key('key'): 1759 # Configure message pane to display message 1760 self.showWindow(name) 1761 window = self.entitywins[name] 1762 window.win.show_window() 1763 window.win.select_window() 1764 notebook = window.component('notebook') 1765 notebook.selectpage('Messages') 1766 window.component('Messages').seedMessage(msgList[0]) 1767 else: 1768 # Launch campaign analysis 1769 dialog = AnalysisWizard(self.entities,name,[args['violator']], 1770 msgList,self.root) 1771 dialog.run()
1772
1773 - def translateReward(self, rew,name):
1774 entity = self.entities[name] 1775 content = '' 1776 good = [] 1777 bad = [] 1778 goals = entity.getGoalVector()['state'].expectation() 1779 for key,value in rew.items(): 1780 if value > 0.: 1781 good.append(key) 1782 elif value < 0.: 1783 bad.append(key) 1784 for key in good: 1785 if goals[key] > 0.: 1786 verb = 'maximize' 1787 else: 1788 verb = 'minimize' 1789 content += '\t%s %s\n' % (verb,key) 1790 if len(good) == 0: 1791 content += '\tnothing\n' 1792 if len(bad) > 0: 1793 content += 'but the goal(s) to\n' 1794 for key in bad: 1795 if goals[key] > 0.: 1796 verb = 'maximize' 1797 else: 1798 verb = 'minimize' 1799 content += '\t%s %s\n' % (verb,key) 1800 content += 'may suffer \n' 1801 return content
1802
1803 - def mainloop(self):
1804 self.splash() 1805 self.root.deiconify() 1806 self.done = False 1807 try: 1808 self.root.mainloop() 1809 ## while not self.done: 1810 ## self.root.mainloop(10) 1811 ## self.root.update() 1812 except KeyboardInterrupt: 1813 pass
1814
1815 - def poll(self):
1816 try: 1817 result = self.queue.get_nowait() 1818 except Empty: 1819 result = True 1820 if isinstance(result,dict): 1821 self.handleCommand(result) 1822 elif result is None: 1823 self.finishBackground() 1824 elif result == 'quit': 1825 PsychShell.stop(self) 1826 # Destroy windows separately 1827 for child in self.main.children: 1828 child.destroy() 1829 # Close overall app 1830 self.root.destroy() 1831 self.root.after(100,self.poll)
1832
1833 - def handleCommand(self,args):
1834 if args['command'] == 'error': 1835 tkMessageBox.showerror(args['title'],args['message']) 1836 elif args['command'] == 'info': 1837 tkMessageBox.showinfo(args['title'],args['message']) 1838 elif args['command'] == 'setState': 1839 args['widget'].setState(args['state']) 1840 elif args['command'] == 'pop': 1841 args['widget'].pop() 1842 elif args['command'] == 'updateGoals': 1843 if args.has_key('weights'): 1844 vector = args['weights'] 1845 for goal in args['agent'].getGoals(): 1846 key = goal.toKey() 1847 args['agent'].setGoalWeight(goal,abs(vector[key]),False) 1848 args['agent'].normalizeGoals() 1849 args['widget'].recreate_goals() 1850 elif args['command'] == 'updateNetwork': 1851 self.psymwindow.redrawSupport() 1852 elif args['command'] == 'updateTime': 1853 self.drawTime() 1854 elif args['command'] == 'AAR': 1855 widget = self.aarWin.component('Tree') 1856 if args['message'].__class__.__name__ != 'list': 1857 self.aarWin.displayAAR(args['message']) 1858 else: 1859 print 'non-doc explanation' 1860 elif args['command'] == 'policy': 1861 args['window'].component('Policy').displayPolicy() 1862 else: 1863 raise NotImplementedError,'Unable to handle queue commands of type %s' % (args['command'])
1864
1865 - def stop(self,event=None):
1866 result = tkMessageBox.askyesno('Quit?', 1867 'Are you sure you want to exit?') 1868 if result: 1869 self.queue.put('quit')
1870