1 """
2 The base engine with handlers for typical interactions with PsychSim agents
3 @version: 1.7
4 """
5 try:
6 import inspect
7 __DOC__ = True
8 except ImportError:
9 __DOC__ = False
10 __PROFILE__ = False
11
12 import bz2
13 import os.path
14 from Queue import Queue
15 import re
16 import string
17 import sys
18 from xml.dom.minidom import parse
19
20 from teamwork.agent.Entities import *
21 from teamwork.agent.lightweight import PWLAgent
22 from teamwork.agent.AgentClasses import *
23 from teamwork.utils.PsychUtils import *
24 from teamwork.utils.Debugger import *
25 from teamwork.messages.PsychMessage import *
26 from teamwork.multiagent.sequential import SequentialAgents
27 from teamwork.action.PsychActions import *
28 from teamwork.multiagent.GenericSociety import *
29 from teamwork.multiagent.pwlSimulation import PWLSimulation
30
31 if __PROFILE__:
32 import hotshot,hotshot.stats
33
35 """The base API for the PsychSim engine
36 @cvar __VERSION__: the version of this shell
37 @cvar __UNDO__: flag indicating whether UNDO operations are supported
38 @type __UNDO__: boolean
39 @cvar __KQML__: flag indicating whether KQML communication is supported
40 @type __KQML__: boolean
41 @ivar multiagentClass: the class of the current scenario (default is L{SequentialAgents})
42 @ivar agentClass: the class of individual agents in the scenario (default is L{PsychEntity})
43 """
44 __VERSION__ = '1.8beta'
45 __UNDO__ = False
46 __KQML__ = False
47
48 actionFormat = [('actor',True),('type',True),('object',False)]
49
50 - def __init__(self,
51 scenario=None,
52 classes=None,
53 agentClass=None,
54 multiagentClass=None,
55 debug=0):
139
141 """initialize L{PsychShell} with given L{scenario<teamwork.multiagent.PsychAgents.PsychAgents>}
142 @param agents: the scenario to interact with
143 @type agents: L{MultiagentSimulation<teamwork.multiagent.Simulation.MultiagentSimulation>}
144 @param progress: optional progress display command
145 @type progress: C{lambda}
146 """
147 self.scenario = agents
148 self.entities = self.scenario
149 self.phase = 'run'
150 self.initEntities(progress)
151 if self.__UNDO__:
152 self.lastStep=copy.deepcopy (self.entities)
153 return self.scenario
154
155 - def setupEntities(self,entities=None,progress=None,compileDynamics=True,
156 compilePolicies=None,distill=False):
157 """Creates a scenario instance from a list of entities and applies the default values across the board
158 @param entities: the entities to use in populating the scenario
159 @type entities: L{Agent}[]
160 @param progress: optional C{Queue} argument is used to give progress updates, in the form of C{(label,pct)}, where:
161 - I{label}: a string label to display for the current task
162 - I{pct}: an integer percentage (1-100) representing the amount of progress made since the last call
163 The thread puts C{None} into the queue when it has finished.
164 @type progress: Queue
165 @return: a newly created scenario instance
166 @rtype: L{multiagentClass}
167 @param compileDynamics: Flag indicating whether the scenario dynamics should be compiled as well (default is True)
168 @type compileDynamics: boolean
169 @param compilePolicies: The belief depth at which all agents will have their policies compiled, where 0 is the belief depth of the real agent. If the value of this flag is I{n}, then all agents at belief depthS{>=}I{n} will have their policies compiled, while no agents at belief depth<I{n} will. If omitted, no policies will be compiled
170 @type compilePolicies: int
171 @param distill: If C{True}, then create a distilled version of these agents (default is C{False}
172 @type distill: bool
173 """
174 agents = self.multiagentClass()
175 agents.society = self.classes
176 for entity in entities:
177 agents.addMember(entity)
178
179 if progress is not None:
180 if not isinstance(progress,Queue):
181 raise DeprecationWarning,'"progress" argument should be Queue for proper synchronization.'
182 total = agents.actorCount(compilePolicies)
183 if compileDynamics:
184
185 for entity in agents.members():
186 depth = entity.getDefault('depth')
187 total += pow(len(agents.members()),depth)
188
189 total += agents.actionCount(False)
190 total = float(total)
191
192 agents.applyDefaults(progress=progress,total=total,
193 doBeliefs=not distill)
194 if compileDynamics:
195 if __PROFILE__:
196 filename = '/tmp/stats'
197 prof = hotshot.Profile(filename)
198 prof.start()
199 agents.compileDynamics(progress,total)
200 if __PROFILE__:
201 prof.stop()
202 prof.close()
203 print 'loading stats...'
204 stats = hotshot.stats.load(filename)
205 stats.strip_dirs()
206 stats.sort_stats('time', 'calls')
207 stats.print_stats()
208 agents.compilePolicy(compilePolicies,progress,total)
209 if distill:
210
211 scenario = PWLSimulation(agents)
212
213 attributes = {}
214 vectors = {}
215 keyList = filter(lambda k:isinstance(k,StateKey),
216 agents.getStateKeys().keys())
217 maxAttrs = 8
218 for key in keyList:
219 for agent in scenario.members():
220 for option in agent.actions.getOptions():
221 for action in option:
222 if len(attributes) >= maxAttrs:
223 continue
224 entity = agents[key['entity']]
225 dynamics = entity.getDynamics(action,
226 key['feature'])
227 tree = dynamics.getTree()
228 del entity.dynamics[key['feature']][action]
229 for plane in tree.branches().values():
230 if isinstance(plane.weights,ThresholdRow):
231
232 label = plane.weights.simpleText()
233 try:
234 attributes[label][plane.threshold] = True
235 except KeyError:
236 attributes[label] = {plane.threshold: True}
237 vectors[label] = plane.weights
238
239 keyList = agents.getStateKeys().keys()
240 keyList.sort()
241 del agents
242 for key,vector in vectors.items():
243 vector.fill(keyList)
244
245 for agent in scenario.members():
246 options = agent.actions.getOptions()
247 if len(options) > 0:
248 agent.policy.reset()
249
250 for key,values in attributes.items():
251 values = values.keys()
252 values.sort()
253 agent.policy.attributes.append((vectors[key],values))
254 agent.policy.initialize(choices=options)
255 for index in xrange(len(agent.policy.rules)):
256 agent.policy.rules[index] = options[:]
257 scenario.save('/tmp/pynadath/test%02d_%d.xml' % \
258 (len(scenario),len(agent.policy.rules)))
259 print agent.name
260 print 'done.'
261 sys.exit(0)
262 agents = scenario
263
264 if progress:
265 progress.put(None)
266 return agents
267
269 """A method, to be overridden by subclass, that is invoked whenever there is a new set of entities"""
270 pass
271
273 """Abstract method for getting a command from user.
274 @rtype: C{str}
275 """
276 raise NotImplementedError
277
290
292 """Abstract method for displaying a result to the user
293 @param cmd: the command executed
294 @type cmd: C{str}
295 @param result: the results of the command
296 @type result: C{str[]}
297 """
298 raise NotImplementedError
299
300 - def save(self,filename=None,results=None):
301 """Saves the state of the current simulation to the named file"""
302 if not filename:
303 return 'Usage: save <filename>'
304 self.scenario.save(filename)
305 self.scenarioFile = filename
306 return 1
307
309 """Saves the current generic society to the named file"""
310 if not filename:
311 return 'Usage: savegeneric <filename>'
312 self.classes.save(filename)
313 self.societyFile = filename
314 return 1
315
316 - def load(self,filename=None,results=None):
350
351 - def loadSociety(self,filename=None,overwrite=True,results=None):
352 """Loads in a GenericSociety object from the specified file
353 @param overwrite: flag indicating, when C{True} that the existing society should be erased before loading in the new one
354 @type overwrite: boolean
355 """
356 if not filename:
357 return 'usage: loadgeneric <filename>'
358 if filename[-4:] == '.xml':
359 f = open(filename,'r')
360 else:
361 import bz2
362 f = bz2.BZ2File(filename,'r')
363 data = f.read()
364 f.close()
365 doc = parseString(data)
366 newClasses = GenericSociety()
367 newClasses.parse(doc.documentElement)
368 if overwrite:
369 self.classes = newClasses
370 self.societyFile = filename
371 warnings = []
372 else:
373 warnings = self.classes.merge(newClasses)
374 if not self.societyFile:
375 self.societyFile = filename
376 return warnings
377
378 - def export(self,filename=None,results=None):
379 """Writes a Web page representation of the current scenario"""
380 if filename:
381 f = open(filename,'w')
382 f.write(self.scenario.toHTML())
383 f.close()
384 return 1
385 else:
386 return 'usage: export <filename>'
387
388 - def distill(self,filename=None,results=None):
396
398 """Reverts the current scenario to the last loaded/saved scenario"""
399 if self.scenarioFile:
400 self.load(self.scenarioFile)
401
402 - def step(self,length=1,results=None):
403 """Steps the simulation the specified number of micro-steps
404 into the future (default is 1)"""
405 try:
406 length = int(length)
407 except TypeError:
408 results = length
409 length = 1
410 sequence = []
411 for t in range(int(length)):
412 delta = self.entities.microstep(debug=self.debug)
413 sequence.append(delta)
414 if isinstance(results,list):
415 results.append(dict2str(delta,self.debug.level))
416 return sequence
417
418 - def goals(self,results=None):
424
425 - def send(self,sender,receiver,*content):
443
445 """Returns the action that <entity> will perform, following
446 its policy of behavior"""
447 entity = extractEntity(name,self.entities)
448 if entity:
449 action,explanation = entity.applyPolicy(debug=self.debug)
450 if isinstance(results,list):
451 results.append(entity.name + ' -> ' + `action`)
452 else:
453 if isinstance(results,list):
454 results.append('No such entity: '+name)
455
457 """Sets the debug level to the specified integer"""
458 self.debug.level = int(level)
459 if isinstance(results,list):
460 results.append('Debug level: %d' % (self.debug.level))
461 return self.debug.level
462
464 if isinstance(results,list):
465 results.append(`self.entities`)
466
467 - def help(self,results=None):
468 """Prints descriptions of available commands"""
469 if __DOC__:
470 helpStrings = []
471 for key,value in self.handlers.items():
472 doc = inspect.getdoc(value)
473 if doc:
474 doc = doc.replace('\n','\n\t\t')
475 else:
476 doc = ''
477 helpStrings.append('%-12s\t%s' % (key,doc))
478 helpStrings.sort()
479 if isinstance(results,list):
480 results += helpStrings
481 else:
482 if isinstance(results,list):
483 results.append('Help unavailable under jython')
484
485 - def test(self,label,results=None):
486 try:
487 cmd = self.tests[label]
488 except KeyError:
489 return 'Valid tests: '+self.tests.keys()
490 if isinstance(results,list):
491 results.append('Executing "'+cmd+'"')
492 return self.execute(cmd,results)
493
494 - def undo(self,results=None):
495 if self.lastStep:
496 self.entities = copy.deepcopy (self.lastStep)
497 else:
498 if isinstance(results,list):
499 results.append('No previous state stored!')
500
502 """Returns a string representation of the named entity."""
503 try:
504 entity = self.entities[name]
505 if isinstance(results,list):
506 results.append(`entity`)
507 except KeyError:
508 if isinstance(results,list):
509 results.append('Unknown entity: '+name)
510
511 - def getBelief(self,entityName,beliefName,results=None):
512 """Returns a string representation of the belief that the
513 first entity has about the second."""
514 try:
515 entity = self.entities.agents[entityname]
516 except KeyError:
517 entity = None
518 if isinstance(results,list):
519 results.append('Unknown entity: '+entityName)
520 if entity:
521 if isinstance(results,list):
522 if beliefName in entity.getEntities():
523 results.append(`entity.getEntity(name)`)
524 else:
525 results.append('Unknown entity: '+beliefName)
526
527 - def setModel(self,entityName,beliefName,modelName,results=None):
528 """Sets the mental model that 'entityName' holds in regard to
529 'beliefName' to be 'modelName'"""
530 entity = self.entities[entityName]
531 entity.getEntity(beliefName).setModel(modelName)
532 if isinstance(results,list):
533 results.append('%s now models %s as %s' \
534 % (entityName,beliefName,modelName))
535
548
550 """Performs the actions, provided in dictionary form
551 @param actions: dictionary of actions to be performed, indexed by actor, e.g.:
552 - I{agent1}: [I{act11}, I{act12}, ... ]
553 - I{agent2}: [I{act21}, I{act22}, ... ]
554 - ...
555 @type actions: C{dict:strS{->}L{Action}[]}
556 """
557 turns = []
558 for actor,actList in actions.items():
559 turns.append({'name':actor,'choices':[actList]})
560 delta = self.entities.microstep(turns,hypothetical=False,
561 explain=True,
562 debug=self.debug)
563 if isinstance(results,list):
564 results.append(dict2str(delta))
565 return delta
566
567 - def __act__(self,actList,results):
578
579 - def execute(self,cmd,results=None):
580 if not results:
581 results = []
582 cmd = string.strip(cmd)
583 cmd = string.split(cmd)
584 try:
585 cmd[0] = string.lower(cmd[0])
586 except IndexError:
587 return '?'
588 if cmd[0] == 'quit':
589 return None
590 else:
591 try:
592 handler = self.handlers[cmd[0]]
593 except KeyError:
594 return 'Unknown command: '+cmd[0]
595 apply(handler,cmd[1:]+[results])
596 return '\n'+string.join(results,'\n')
597
601
611
613 self.susceptibility = addr
614
616 if self.susceptibility:
617 if type(entity) is InstanceType:
618 entity = entity.name
619 queryStr = 'request %s %d %s Theme' %\
620 ('127.0.0.1',
621 self.router.server_address[1],
622 entity)
623 print queryStr
624 return self.router.send(self.susceptibility,queryStr)
625 else:
626 return None
627
629 for msg in msgList:
630 print msg
631 if self.susceptibility == msg[0]:
632 exp = re.match('(\S+)\s*(\S+)\s*(Accepted):\s+'+\
633 '(.*)\s+(Rejected):\s+(.*)\s+(Neutral):(.*)',
634 msg[1],re.DOTALL)
635 if exp:
636 themes = {}
637 for index in [3,5,7]:
638 key = exp.group(index)
639 themes[key] = []
640 entries = string.split(exp.group(index+1),'\n')
641 for entry in entries:
642 items = string.split(entry,', ')
643 try:
644 if len(items[0]) > 0:
645 themes[key].append(items)
646 except IndexError:
647 pass
648 self.handleSusceptibility(exp.group(1),themes)
649 else:
650 print 'Unable to parse susceptibility response'
651
653 self.entities[entity].susceptibilities = themes['Accepted']
654
656 if cIndex == len(eList):
657 print modelsUsed
658 entity = self.entities[eList[pIndex]]
659 action,explanation = entity.applyPolicy()
660 print action,explanation['value']
661 for step in explanation['breakdown'][1:]:
662 print '\t',step['decision']
663 print '\t',step['breakdown'][0]
664 print
665 sys.stdout.flush()
666 else:
667 parent = eList[pIndex]
668 child = eList[cIndex]
669 if parent == child:
670 self.iterateModels(eList,pIndex,cIndex+1,modelsUsed)
671 else:
672 parent = self.entities[parent]
673 child = self.entities[child]
674 for model in child.models:
675 parent.getEntity(child).setModel(model)
676 parent.getEntity(parent).getEntity(child).setModel(model)
677 modelsUsed[parent.name][child.name] = model
678 self.iterateModels(eList,pIndex,cIndex+1,modelsUsed)
679
686
687 - def mainloop(self):
688 while not self.done:
689 cmd = self.getCommand()
690 self.executeCommand(cmd)
691 self.stop()
692
697
698 if __name__ == '__main__':
699
700 import getopt
701 import sys
702
703 try:
704 import psyco
705 psyco.full()
706 except ImportError:
707 print 'Unable to find psyco module for maximum speed'
708
709 script = None
710 scenario = None
711 society = None
712 domain = None
713 display = 'tk'
714 debug = 0
715 error = None
716 expert = False
717 dev = False
718 try:
719 optlist,args = getopt.getopt(sys.argv[1:],'hf:s:d:vx',
720 ['file=','shell=','domain=','society=',
721 'debug=','help','version','expert',
722 'dev'])
723 except getopt.error:
724 error = 1
725 optlist = []
726 args = []
727
728 for option in optlist:
729 if option[0] == '--file' or option[0] == '-f':
730 script = option[1]
731 elif option[0] == '--shell' or option[0] == '-s':
732 display = option[1]
733 elif option[0] == '--domain' or option[0] == '-d':
734 domain = option[1]
735 exec('import teamwork.examples.%s as classModule' % (domain))
736
737 if isinstance(classModule.classHierarchy,dict):
738 society = GenericSociety()
739 society.importDict(classModule.classHierarchy)
740 else:
741 society = classModule.classHierarchy
742 elif option[0] == '--society':
743 society = option[1]
744 elif option[0] == '--help' or option[0] == '-h':
745 error = 1
746 elif option[0] == '--debug':
747 debug = int(option[1])
748 elif option[0] == '--version' or option[0] == '-v':
749 print 'PsychSim %s' % (PsychShell.__VERSION__)
750 sys.exit(0)
751 elif option[0] == '--expert' or option[0] == '-x':
752 expert = True
753 elif option[0] == '--dev':
754 dev = True
755 else:
756 error = 1
757
758 if len(args) > 0:
759 if len(args) > 1:
760 error = 1
761 else:
762 scenario = args[0]
763
764 if error:
765 print 'PsychShell.py',\
766 '[--domain|-d <domain>]',\
767 '[--file|-f <script filename>]',\
768 '[--shell|-s tk|terminal]',\
769 '[--society <filename>]',\
770 '[--expert|-x]',\
771 '<scenario filename>'
772 print
773 print '--domain|-d\tIndicates the class path to the generic society definition'
774 print '--society\tIndicates the file name containing the generic society'
775 print '--file|-f\tIndicates the file containing a script of commands to execute'
776 print '--shell|-s\tIf "tk", use GUI; if "terminal", use interactive text (default is "tk")'
777 print '--expert|-x\tTurns on expert mode'
778 print '--version|-v\tPrints out version information'
779 print '--help|-h\tPrints out this message'
780 print
781 sys.exit(-1)
782
783 if display == 'tk':
784 from teamwork.widgets.PsychGUI.Gui import *
785 shell = GuiShell(scenario=scenario,
786 classes=society,
787 expert=expert,dev=dev,
788 debug=debug)
789 else:
790 from teamwork.shell.TerminalShell import TerminalShell
791 shell = TerminalShell(entities=scenario,
792 classes=society,
793 file=script,
794 debug=debug)
795 shell.mainloop()
796