Package teamwork :: Package examples :: Package Games :: Module PublicGood
[hide private]
[frames] | no frames]

Source Code for Module teamwork.examples.Games.PublicGood

   1  import copy 
   2  import random 
   3  import sys 
   4   
   5  from teamwork.multiagent.sequential import * 
   6  from teamwork.agent.Entities import * 
   7  from teamwork.agent.AgentClasses import * 
   8  from teamwork.dynamics.pwlDynamics import * 
   9  from teamwork.math.Interval import * 
  10  from teamwork.math.KeyedMatrix import * 
  11  from teamwork.math.KeyedTree import * 
  12  from teamwork.action.PsychActions import * 
  13  from teamwork.policy.StochasticPolicy import * 
  14  from teamwork.shell.TerminalShell import * 
  15   
  16  __GOOD__ = None 
  17  __PUNISH__ = 1 
  18   
  19  population = {'OptOptDonor':0, 
  20                'OptPesDonor':0, 
  21                'PesOptDonor':0, 
  22                'PesPesDonor':0 
  23                } 
  24  donations = [.001,.003,.005,.007,.01] 
  25  ## numDonations = 5 
  26  ## donations = map(lambda x:float(x)/1000,range(1,11,10/numDonations)) 
  27  fines = [.01] 
  28  namePrefix = 'agent' 
  29  neighborSpacing = 1 
  30  # Static elements of the class hierarchy 
  31   
  32  classHierarchy['PublicGood'] = { 
  33      'parent': [], 
  34      'state':{'wealth':0.5}, 
  35      'beliefs':{None:{'wealth':None}}, 
  36      'dynamics':{'wealth':{}}, 
  37      'depth':1 
  38      } 
  39   
  40  classHierarchy['Donee'] = { 
  41      'parent':['PublicGood'], 
  42      'state':{'wealth':0., 
  43               'goodExists':0.}, 
  44      'goals':[], 
  45      'models':{'normal':{'goals':[]}}, 
  46      'model':'normal', 
  47      'beliefs':{'Donor':{'model':'altruistic'}} 
  48      } 
  49   
  50  classHierarchy['Donor'] = { 
  51      'parent':['PublicGood'], 
  52      'relationships': {'donee':'Donee', 
  53                        'neighbor':'Donor'}, 
  54      'actions': [], 
  55      'state':{'wealth':0.9}, # This gets re-set dynamically 
  56      # Label models by Donation style (Altruistic vs. Egoistic) followed by 
  57      # Punishment Style (A vs. E) 
  58      'models':{'AA': {}, 'AE': {}, 'EA':{}, 'EE': {}, 
  59                'deliberate':{'policy':['observation depth 1 type disburse -> '\ 
  60                                        +'{"type":"lookahead"}']}}, 
  61      'beliefs':{'Donee':{'model':'normal'}}, 
  62      'model':'deliberate' 
  63      } 
  64   
  65  classHierarchy['OptOptDonor'] = { 
  66      'parent':['Donor'], 
  67      'beliefs':{'Donor':{'model':'AA'}} 
  68      } 
  69   
  70  classHierarchy['OptPesDonor'] = { 
  71      'parent':['Donor'], 
  72      'beliefs':{'Donor':{'model':'AE'}} 
  73      } 
  74   
  75  classHierarchy['PesOptDonor'] = { 
  76      'parent':['Donor'], 
  77      'beliefs':{'Donor':{'model':'EA'}} 
  78      } 
  79   
  80  classHierarchy['PesPesDonor'] = { 
  81      'parent':['Donor'], 
  82      'beliefs':{'Donor':{'model':'EE'}} 
  83      } 
  84   
  85  __count__ = -1 
86 -def agentCount():
87 """Returns the number of agents in the total population""" 88 global __count__ 89 if __count__ < 0: 90 __count__ = 0 91 for count in population.values(): 92 __count__ += count 93 if __count__ == 0: 94 raise UserWarning,'No agents selected!' 95 return __count__
96
97 -class DonateAction(Action):
98 """Domain-specific class that includes an 'amount' field.""" 99 format = ['actor','type','object','amount'] 100
101 - def __init__(self,arg={}):
102 self.fields['amount'] = {'range':donations} 103 Action.__init__(self,arg)
104
105 - def __eq__(self,other):
106 for key in self.fields.keys(): 107 if not self[key] is None and not other[key] is None \ 108 and self[key] != other[key]: 109 return None 110 return 1
111
112 -class PublicGoodAgents(TurnBasedAgents):
113 """Domain-specific class that sets up the game stages""" 114 115 fixedNetwork = 1 116
117 - def mapActions(self,actions):
118 if self.fixedNetwork: 119 return None 120 # Map neighbors to previously randomized agents 121 for act in actions: 122 if act['object'] and act['object'] != 'Public': 123 act['object'] = act['actor'].neighbors[act['object']] 124 return 1
125
126 - def updateTurn(self,actionList,debug=None):
127 TurnBasedAgents.updateTurn(self,actionList,debug) 128 if not self.fixedNetwork: 129 self.randomizeNeighbors(debug)
130
131 - def randomizeNeighbors(self,debug=None):
132 # Find new random mapping (works if you have only 1 neighbor!) 133 remaining = self.keys() 134 try: 135 remaining.remove('Public') 136 except ValueError: 137 # Not in the punishment phase 138 return None 139 for entity in self.members(): 140 if entity.name == 'Public' or entity.parent: 141 continue 142 if len(remaining) > 1: 143 # Choose out of the list remaining 144 index = int(random.random()*len(remaining)) 145 if remaining[index] == entity.name: 146 index += 1 147 if index == len(remaining): 148 index = 0 149 else: 150 # Only one agent left to choose 151 index = 0 152 if remaining[index] == entity.name: 153 # All that's left is myself! 154 flag = 1 155 done = None 156 while not done: 157 # Keep searching for an agent to swap with 158 other = self.members()[int(random.random()*len(self))] 159 if other.name != 'Public' and \ 160 other.name != entity.name: 161 # Swap neighbors with this agent 162 neighbor = other.neighbors.keys()[0] 163 remaining[index] = other.neighbors[neighbor] 164 other.neighbors[neighbor] = entity.name 165 done = 1 166 # Save random mapping for next call 167 try: 168 entity.neighbors[entity.neighbors.keys()[0]] = remaining[index] 169 except AttributeError,e: 170 print entity.ancestry() 171 raise AttributeError,e 172 del remaining[index] 173 if debug: 174 for entity in self.members(): 175 debug.message(8,'New neighbor of %s: %s' % \ 176 (entity.name,entity.neighbors.values()[0])) 177 return 1
178
179 - def generateOrder(self):
180 """Orders the entities so that there is a first parallel 181 donation stage and then a second parallel 182 punishment/disbursement stage""" 183 # Figure out which agents are donors and which ones not 184 donors = [] 185 donees = [] 186 for name in self.keys(): 187 entity = self[name] 188 if entity.instanceof('Donor'): 189 donors.append(name) 190 else: 191 donees.append(name) 192 self.keyOrder = [] 193 # Generate turn sequence for game 194 round = [] 195 for agent in donors: 196 choices = [] 197 for act in self[agent].actions: 198 # Restrict donors to either donate or wait 199 if act['type'] in ['wait','donate']: 200 choices.append(act) 201 round.append({'name':agent,'choices':choices}) 202 self.keyOrder.append(round) 203 # Generate turn sequence for disbursement + punishment 204 round = [] 205 for agent in donees: 206 for act in self[agent].actions: 207 if act['type'] == 'disburse': 208 round.append({'name':agent,'choices':[act]}) 209 for agent in donors: 210 choices = [] 211 for act in self[agent].actions: 212 # Restrict donors to either punish or wait 213 if act['type'] in ['wait','punish']: 214 choices.append(act) 215 round.append({'name':agent,'choices':choices}) 216 self.keyOrder.append(round) 217 # Prime for first round 218 self.order = copy.deepcopy(self.keyOrder) 219 # Domain-specific patch time! 220 try: 221 self['Public'].freezeModels() 222 except KeyError: 223 pass
224 225
226 -class PublicGoodAgent(PsychEntity):
227 beliefClass = PublicGoodAgents 228 actionClass = DonateAction 229 # Comment out the following for deterministic policies 230 ## policyClass = StochasticLookupAhead 231 # Comment out the following to eliminate model change 232 modelChange = 1 233 learningRate = 0.2 234 valueType = 'final' 235 mentalType = 'aggregate' 236
237 - def freeze(self):
238 """These agents don't really do any backward projection, so 239 let's shortcut through the annoying copying that is required 240 when freezing the initial version of myself""" 241 self.initial = self
242
243 - def preComStateEstimator(self,beliefs,actions,epoch=-1,debug=Debugger()):
244 """Updates beliefs in response to observation 245 (Within this model, the agent and its belief state are one and 246 the same)""" 247 self.saveObservations(actions) 248 delta = {} 249 if len(self.getEntities()) == 0: 250 return beliefs,delta 251 # Aggregate actions 252 aggActList = [] 253 pubAct = None 254 toDistribute = 0. 255 total = 0. 256 for act in actions: 257 # Get the actor name 258 if isinstance(act['actor'],str): 259 actor = act['actor'] 260 else: 261 actor = act['actor'].name 262 if actor == self.name: 263 # Set aside for later 264 myAct = act 265 elif actor == 'Public': 266 # Set aside for later 267 pubAct = act 268 elif act['type'] == 'punish': 269 if act['object'] == self.name: 270 # We care about only those punishments of our ourself 271 if actor in self.relationships['neighbor']: 272 aggActList.append(copy.copy(act)) 273 total += act['amount'] 274 else: 275 # Store this amount for re-distribution 276 toDistribute += act['amount'] 277 elif actor in self.relationships['neighbor']: 278 aggActList.append(self.actionClass({'type':'wait', 279 'actor':actor})) 280 elif act['type'] == 'donate': 281 if actor in self.relationships['neighbor']: 282 aggActList.append(copy.copy(act)) 283 total += act['amount'] 284 else: 285 # Store this amount for re-distribution 286 toDistribute += act['amount'] 287 elif actor in self.relationships['neighbor']: 288 # For wait actions 289 aggActList.append(copy.copy(act)) 290 else: 291 # Wait (I hope) 292 pass 293 # Re-distribute actions of non-neighbors among neighbors 294 if toDistribute > 0.: 295 for act in aggActList: 296 if total > 0.: 297 if act['type'] != 'wait': 298 act['amount'] += toDistribute*act['amount']/total 299 else: 300 if act['type'] == 'wait': 301 if pubAct: # i.e., punish stage 302 act['object'] = self.name 303 act['type'] = 'punish' 304 else: # donation stage 305 act['object'] = 'Public' 306 act['type'] = 'donate' 307 elif act['type'] == 'punish': 308 act['object'] = self.name 309 act['amount'] = toDistribute/float(len(aggActList)) 310 if act['amount'] > 1.: 311 print obs 312 raise ValueError,'Illegal amount: %s' % `act` 313 aggActList.append(myAct) 314 if pubAct: 315 aggActList.append(pubAct) 316 # Update beliefs 317 for act in aggActList: 318 delta[`act`] = beliefs.updateBeliefs(act,debug) 319 # Update any mental models 320 result = Stereotyper.preComStateEstimator(self,beliefs,aggActList, 321 epoch,debug) 322 for key,value in delta.items(): 323 try: 324 value.update(result[key]) 325 except KeyError: 326 # That's OK...no model change here 327 pass 328 # Update beliefs about entities' beliefs 329 for name in beliefs.getEntities(): 330 entity = beliefs.getEntity(name) 331 entity,changes = entity.preComStateEstimator(entity,aggActList, 332 epoch,debug) 333 # Merge these nested changes into the overall delta 334 for obsType in changes.keys(): 335 if not delta[obsType].has_key(name): 336 delta[obsType][name] = {} 337 for key in changes[obsType].keys(): 338 delta[obsType][name][key] = changes[obsType][key] 339 if len(delta[obsType][name].keys()) == 0: 340 del delta[obsType][name] 341 self.invalidateCache() 342 return beliefs,delta
343
344 - def initEntities(self,entityList,depth=1):
345 """Sets the entities known to be the list provided, and makes 346 the appropriate updates to goals, policy depth, etc.""" 347 # Fill out recursive beliefs 348 maxDepth = self.getDefault('depth') 349 if depth <= maxDepth: 350 newList = [] 351 # First, generate entity objects for my beliefs 352 for entity in entityList: 353 # Beliefs for only our neighbors 354 if 'Donee' in self.classes or \ 355 ('Donor' in entity.classes and \ 356 entity.name != self.name and \ 357 not entity.name in self.relationships['neighbor']): 358 continue 359 # Stick this entity object into my beliefs 360 newEntity = copy.copy(entity) 361 self.setEntity(newEntity) 362 newList.append(newEntity) 363 # I am the only neighbor for my Donor models 364 if self.mentalType == 'aggregate' and \ 365 'Donor' in newEntity.classes and \ 366 newEntity.name != self.name: 367 newEntity.relationships = {'donee':newEntity.relationships['donee'], 368 'neighbor': [self.name]} 369 # Assume correct beliefs about states 370 for feature in entity.getStateFeatures(): 371 try: 372 value = entity.getState(feature) 373 newEntity.setState(feature,value) 374 except KeyError: 375 pass 376 # Finally, fill in specific beliefs according to my class 377 # defaults, and go to the next recursive level 378 for entity in newList: 379 entity.models = copyModels(entity.getDefault('models')) 380 entity.initModels(newList) 381 self.initBeliefs(entity) 382 if entity.name != self.name: 383 for entry in entity.policy.entries[:]: 384 if not entry['action']['object'] in \ 385 ['Public',self.name,None]: 386 entity.policy.entries.remove(entry) 387 elif entry.has_key('actor') and \ 388 entry['actor'] != self.name: 389 entity.policy.entries.remove(entry) 390 elif entry['class'] == 'conjunction': 391 clause = entry['clauses'][0] 392 if clause['label'] == 'ifWait' and \ 393 clause['actor'] != self.name: 394 entity.policy.entries.remove(entry) 395 entity.initEntities(newList,depth+1) 396 # Add any goals related to the new entities 397 self.initGoals(entityList) 398 # Set the depth of lookahead 399 horizon = self.getDefault('horizon')#*len(entityList)-depth+1 400 if not self.policy: 401 self.policy = self.policyClass(entity=self.name, 402 actions=self.actions, 403 relationships=self.relationships, 404 size=1, 405 depth=horizon) 406 # Look for a simple model for top-level agents as well 407 if not self.parent: 408 try: 409 self.setModel(self.getDefault('model')) 410 except KeyError: 411 pass 412 # Make sure that we have mental models all the way through 413 if not self.model: 414 raise UserWarning , 'No model for %s' % (self.ancestry()) 415 self.entities.initializeOrder()
416
417 - def initRelationships(self,entityList):
418 """Instantiates the relationships of this entity regarding the 419 provided list of entities""" 420 GenericEntity.initRelationships(self,entityList) 421 if self.mentalType != 'individual': 422 # Reduce our relationships to only a subset of neighbors 423 try: 424 myIndex = int(self.name[len(namePrefix):]) 425 except ValueError: 426 # Public 427 return 428 neighbor = myIndex + neighborSpacing 429 while neighbor >= agentCount(): 430 neighbor -= agentCount() 431 neighbor = '%s%d' % (namePrefix,neighbor) 432 try: 433 for name in self.relationships['neighbor'][:]: 434 if name != neighbor: 435 self.relationships['neighbor'].remove(name) 436 except KeyError: 437 pass 438 # initialize neighbor mapping 439 self.neighbors = {} 440 if self.name != 'Public': 441 for neighbor in self.relationships['neighbor']: 442 self.neighbors[neighbor] = neighbor
443
444 - def updateModels(self,actions,debug):
445 delta = {} 446 stage = 'donate' 447 total = 0. 448 count = 0 449 actDict = {} 450 debug.message(8,'Updating mental models held by %s' % self.ancestry()) 451 for act in actions: 452 if act['type'] in ['disburse','punish']: 453 stage = 'punish' 454 try: 455 actDict[act['actor'].name] = act 456 except AttributeError: 457 actDict[act['actor']] = act 458 # Find my last action 459 try: 460 obsList = self.getObservations()[1]['content'] 461 myLast = None 462 except IndexError: 463 myLast = self.actionClass({'type':'wait', 464 'actor':self.name}) 465 if not myLast: 466 for act in obsList: 467 if act['actor'] == self.name: 468 if stage == 'punish' and act['type'] == 'wait': 469 # Waiting is equivalent to a donation of zero 470 myLast = self.actionClass({'type':'donate', 471 'amount':0., 472 'object':'Public', 473 'actor':self.name}) 474 else: 475 myLast = act 476 break 477 if stage == 'donate': 478 lastDonation = {} 479 # Find last donation received 480 try: 481 obsList = self.getObservations()[2]['content'] 482 except IndexError: 483 obsList = [] 484 for name in self.neighbors.values(): 485 lastDonation[name] = 0. 486 for act in obsList: 487 if act['actor'] in self.neighbors.values(): 488 if act['type'] == 'wait': 489 lastDonation[act['actor'].name] = 0. 490 else: 491 lastDonation[act['actor'].name] = act['amount'] 492 break 493 models = self.extractModels() 494 # Find model to update 495 for entity in self.getEntityBeliefs(): 496 if not entity.name in [self.name,'Public']: 497 curModel = models[entity.name] 498 debug.message(8,'Examining model of %s' % entity.name) 499 debug.message(7,'My current model is %s' % `curModel`) 500 debug.message(7,'My last action was %s' % `myLast`) 501 debug.message(7,'I observed %s' % `actDict[entity.name]`) 502 relevant = [] 503 # Compare the policy to find the applicable entry 504 if stage == 'punish': 505 relevant = entity.policy.entries[:3] 506 interval = str2Interval(relevant[0]['amount']) 507 if myLast['amount'] in interval: 508 size = 'Big' 509 else: 510 size = 'Small' 511 else: 512 entry = entity.policy.entries[5] 513 interval = str2Interval(entry['amount']) 514 if lastDonation[self.neighbors[entity.name]] in interval: 515 size = 'Big' 516 relevant = [entity.policy.entries[3]] 517 relevant.append(entity.policy.entries[5]) 518 else: 519 size = 'Small' 520 relevant = [entity.policy.entries[4]] 521 relevant.append(entity.policy.entries[6]) 522 # Policy RHS actions have no actor field, so let's prep 523 act = copy.copy(actDict[entity.name]) 524 act['actor'] = None 525 if stage == 'donate': 526 debug.message(7,'The last donation was %s (%6.4f)' % 527 (size,lastDonation[self.neighbors[entity.name]])) 528 for entry in relevant: 529 debug.message(4,'Examining policy entry: %s' % \ 530 `entry`) 531 # Update our model of donation amounts 532 if myLast['type'] == 'punish': 533 key = 'donateIfPun'+size 534 index = 0 535 else: 536 key = 'donateIfNotPun'+size 537 index = 1 538 debug.message(4,'Modifying entry: %s' \ 539 % `relevant[index]`) 540 # Check whether we must modify the RHS of this entry 541 if act != relevant[index]['action']: 542 if act['amount']: 543 donation = act['amount'] 544 else: 545 donation = 0. 546 amt = (1.-self.learningRate)*curModel[key]\ 547 +self.learningRate*donation 548 debug.message(7,'New amount = %6.4f' % (amt)) 549 relevant[index]['action'] = copy.copy(act) 550 if amt > 0.: 551 relevant[index]['action']['type'] = stage 552 relevant[index]['action']['amount'] = amt 553 relevant[index]['action']['object'] = 'Public' 554 else: 555 relevant[index]['action']['type'] = 'wait' 556 relevant[index]['action']['object'] = None 557 relevant[index]['action']['amount'] = None 558 delta[entity.name] = {key:amt} 559 debug.message(8,"New model: %s" % `relevant`) 560 else: 561 # Update our belief about the punishment threshold 562 for entry in relevant: 563 debug.message(4,'Examining policy entry: %s' % \ 564 `entry`) 565 hiInterval = str2Interval(relevant[0]['amount']) 566 loInterval = str2Interval(relevant[1]['amount']) 567 # Find out where my last donation lies 568 if myLast['amount'] in hiInterval: 569 myIndex = 0 570 else: 571 myIndex = 1 572 try: 573 donIndex = donations.index(myLast['amount']) 574 except ValueError: 575 donIndex = -1 576 # Check what kind of update is needed 577 if act['type'] != relevant[myIndex]['action']['type']: 578 if act['type'] == 'punish': 579 # Punished unexpectedly 580 try: 581 threshold = donations[donIndex+1] 582 except IndexError: 583 threshold = 2.*myLast['amount'] 584 threshold -= Interval.QUANTUM 585 else: 586 # Got away with it unexpectedly 587 if donIndex > 0: 588 threshold = donations[donIndex-1] 589 else: 590 threshold = -Interval.QUANTUM 591 threshold += Interval.QUANTUM 592 else: 593 if act['type'] == 'wait': 594 # Got away with it unexpectedly 595 if donIndex > 2: 596 threshold = donations[donIndex-2] \ 597 + Interval.QUANTUM 598 else: 599 threshold = -Interval.QUANTUM 600 else: 601 threshold = curModel['punishIfDon<'] 602 # Update threshold 603 threshold = (1.-self.learningRate)\ 604 *curModel['punishIfDon<']\ 605 +self.learningRate*threshold 606 hiInterval['lo'] = threshold 607 loInterval['hi'] = threshold 608 relevant[0]['amount'] = `hiInterval` 609 relevant[1]['amount'] = `loInterval` 610 if 0. in hiInterval: 611 relevant[2]['action'] = self.actionClass({'type': 612 'wait'}) 613 else: 614 relevant[2]['action'] = copy.deepcopy(relevant[1]['action']) 615 delta[entity.name] = {'punishIfDon<':threshold} 616 debug.message(8,"New model: %s" % `relevant`) 617 return delta
618
619 - def extractModels(self):
620 models = {} 621 for belief in self.getEntityBeliefs(): 622 if not belief.name in [self.name,'Public']: 623 # Analyze agent's mental models 624 model = {} 625 # Punishment threshold 626 entry = belief.policy.entries[1] 627 interval = str2Interval(entry['amount']) 628 model['punishIfDon<'] = interval['hi'] 629 # Donation if punished for big donation 630 entry = belief.policy.entries[3] 631 RHS = entry['action'] 632 model['donateIfPunBig'] = RHS['amount'] 633 if not RHS['amount']: 634 model['donateIfPunBig'] = 0. 635 # Donation if punished for small donation 636 entry = belief.policy.entries[4] 637 RHS = entry['action'] 638 model['donateIfPunSmall'] = RHS['amount'] 639 if not RHS['amount']: 640 model['donateIfPunSmall'] = 0. 641 # Donation if not punished for big donation 642 entry = belief.policy.entries[5] 643 RHS = entry['action'] 644 model['donateIfNotPunBig'] = RHS['amount'] 645 if not RHS['amount']: 646 model['donateIfNotPunBig'] = 0. 647 # Donation if not punished for small donation 648 entry = belief.policy.entries[6] 649 RHS = entry['action'] 650 model['donateIfNotPunSmall'] = RHS['amount'] 651 if not RHS['amount']: 652 model['donateIfNotPunSmall'] = 0. 653 if len(model.values()) < 5: 654 raise ValueError,'%s has incomplete model' \ 655 % (belief.ancestry()) 656 models[belief.name] = model 657 return models
658 659 ## def __copy__(self): 660 ## newEntity = PsychEntity.__copy__(self) 661 ## for feature in self.getStateFeatures(): 662 ## value = self.getState(feature) 663 ## newEntity.setState(feature,value) 664 ## return newEntity 665 666 ## def __deepcopy__(self): 667 ## newEntity = copy.copy(self) 668 ## for otherName,other in self.entities.items(): 669 ## if isinstance(other,str): 670 ## newEntity.entities[otherName] = other 671 ## else: 672 ## newOther = copy.copy(other) 673 ## newEntity.setEntity(newOther) 674 ## newEntity.entities.initializeOrder() 675 ## newEntity.entities.order = copy.deepcopy(self.entities.order) 676 ## return newEntity 677 678
679 -class PublicGoodShell:
680 agentClass = PublicGoodAgent 681 multiagentClass = PublicGoodAgents 682 actionFormat = [('actor',1),('type',1),('object',None),('amount',None)] 683
684 - def createEntities(self):
685 """Interactive creation of entities for initial scenario""" 686 entityList = [] 687 count = 0 688 for key,value in population.items(): 689 for i in range(value): 690 entity = createEntity(key,'%s%d' % (namePrefix,count), 691 self.classes,self.agentClass) 692 count += 1 693 entityList.append(entity) 694 # Exactly one Donee instance, named "Public" 695 entityList.append(createEntity('Donee','Public',self.classes, 696 self.agentClass)) 697 return entityList
698
699 - def performAct(self,name,actType,obj,amt,results=[]):
700 """Performs the action of the specified type by the named 701 entity on the specified object (use 'nil' if no object or 702 amount)""" 703 actList = [name,actType] 704 if obj != 'nil': 705 actList.append(obj) 706 if amt != 'nil': 707 actList.append(amt) 708 self.__act__(actList,results)
709
710 -class PublicGoodTerminal(PublicGoodShell,TerminalShell):
711 pass
712
713 -def genWealthDyn(amountLost=-1.):
714 if amountLost < 0.: 715 # Donation 716 actorGets = '-amount' 717 threshold = 'amount' 718 objectGets = 'amount' 719 else: 720 # Punishment 721 threshold = amountLost 722 actorGets = -amountLost 723 objectGets = '-amount' 724 725 keyStruct = {'amObject': # Am I object (recipient)? 726 makeIdentityKey('object'), 727 'amActor': # Am I actor (donor)? 728 makeIdentityKey('actor'), 729 'actorValue': # How much does actor have to give ? 730 makeStateKey('actor','wealth'), 731 'myValue': # How much do I have ? 732 makeStateKey('self','wealth') 733 } 734 735 # Support unchanged 736 unchangedTree = createNodeTree(KeyedMatrix()) 737 738 # Increase value 739 weights = {keyConstant: objectGets} 740 objTree = createDynamicNode(keyStruct['myValue'],weights) 741 # Decrease value 742 weights = {keyConstant: actorGets} 743 actorTree = createDynamicNode(keyStruct['myValue'],weights) 744 745 ## # Branch on having enough wealth 746 ## weights = {`keyStruct['actorValue']`: 1.} 747 ## enoughDonorTree = createBranchTree(KeyedPlane(KeyedRow(weights), 748 ## threshold), 749 ## unchangedTree,actorTree) 750 ## enoughDoneeTree = createBranchTree(KeyedPlane(KeyedRow(weights), 751 ## threshold), 752 ## unchangedTree,objTree) 753 754 # Branch on being donor 755 weights = {`keyStruct['amActor']`: 1.} 756 donorTree = createBranchTree(KeyedPlane(KeyedRow(weights),0.5), 757 unchangedTree,actorTree) 758 ## unchangedTree,enoughDonorTree) 759 760 # Branch on being donee 761 weights = {`keyStruct['amObject']`: 1.} 762 doneeTree = createBranchTree(KeyedPlane(KeyedRow(weights),0.5), 763 donorTree,objTree) 764 ## donorTree,enoughDoneeTree) 765 766 return {'tree':doneeTree}
767
768 -def genDisburseDyn(scale):
769 """Generates the wealth dynamics for the disbursement of the 770 public pool of wealth""" 771 key = makeStateKey('actor','wealth') 772 # Donor receives a portion of the total pool 773 donorTree = createDynamicNode(key,{key: scale/float(agentCount())}) 774 # Donee loses all wealth 775 doneeTree = createDynamicNode(key,{key:-1.}) 776 tree = createBranchTree(KeyedPlane({makeIdentityKey('actor'):1.},0.5), 777 donorTree,doneeTree) 778 return {'tree':tree}
779
780 -def genGoodDyn():
781 weights = {keyConstant: 1.} 782 return {'tree':createDynamicNode({'entity':'self', 783 'feature':'goodExists'},weights)}
784
785 -def createGoals(uWealth):
786 if not __GOOD__: 787 uWealth = 1. 788 goalList = [{'entity':'self', 789 'direction':'max', 790 'type':'state', 791 'feature':'wealth', 792 'weight':uWealth}] 793 if __GOOD__: 794 goalList.append({'entity':'Donee', 795 'direction':'max', 796 'type':'state', 797 'feature':'goodExists', 798 'weight':1.-uWealth}) 799 return goalList
800
801 -def makeDonateEntry(lo,hi,label='',depth=1):
802 entry = 'observation depth %d actor neighbor type donate amount [%f,%f]'\ 803 % (depth,lo,hi) 804 if len(label) > 0: 805 entry += ' label %s' % label 806 return entry
807
808 -def makePunishPolicy(punishmentThreshold):
809 punish = '{"type":"punish","object":"neighbor",'\ 810 + '"amount":%f}' % (fines[len(fines)-1]) 811 wait = '{"type":"wait"}' 812 policy = [] 813 # If neighbor donated enough, then do nothing 814 entry = makeDonateEntry(punishmentThreshold-Interval.QUANTUM, 815 Interval.CEILING,'ifBig') 816 entry += '-> %s' % wait 817 policy.append(entry) 818 # If neighbor donated too small an amount, then punish 819 # by an appropriate amount 820 entry = makeDonateEntry(Interval.FLOOR, 821 punishmentThreshold-Interval.QUANTUM,'ifSmall') 822 entry += ' -> %s' % punish 823 policy.append(entry) 824 if punishmentThreshold > 0.: 825 # If neighbor did nothing before, then punish the max amount 826 act = punish 827 else: 828 # We're not ever punishing 829 act = wait 830 policy.append('conjunction observation depth 1 actor neighbor type wait '\ 831 +'label ifWait & negation observation depth 1 type disburse'\ 832 +' -> %s' % act) 833 return policy
834
835 -def makeDonatePolicy(actions,threshold=0.):
836 policy = [] 837 punishEntry = 'observation depth 1 type punish object self label ifPun' 838 if len(actions) == 2: 839 # This is how I play the game if I've just been punished 840 policy.append('%s -> %s' % (punishEntry,actions['ifPun'])) 841 # This is how I play the game if I have not been punished 842 policy.append('default -> %s' % (actions['default'])) 843 else: 844 ifBig = makeDonateEntry(threshold-Interval.QUANTUM, 845 Interval.CEILING,'ifBig',2) 846 ifSmall = makeDonateEntry(Interval.FLOOR, 847 threshold-Interval.QUANTUM,'ifBig',2) 848 # This is how I play the game if I've been punished after donating 849 policy.append('conjunction %s & %s -> %s' % \ 850 (punishEntry,ifBig,actions['ifPunBig'])) 851 # This is how I play the game if I've been punished after not 852 # donating 853 policy.append('%s -> %s' % (punishEntry,actions['ifPunSmall'])) 854 # This is how I play the game if I've not been punished after 855 # donating 856 policy.append('%s -> %s' % (ifBig,actions['ifNotPunBig'])) 857 # This is how I play the game if I've not been punished after 858 # not donating 859 policy.append('default -> %s' % (actions['ifNotPunSmall'])) 860 861 return policy
862
863 -def createPolicy(threshold):
864 """Creates a policy for the pooler/disburser, with the threshold the cost 865 of the public good (ignored if there is no explicit public good""" 866 policy = [] 867 if __GOOD__: 868 # Don't buy the good if already bought 869 policy.append('belief entities self state goodExists 0.5 1. -> '\ 870 +'{"type":"wait"}') 871 # Always buy the good if have more than the threshold 872 policy.append('belief entities self state wealth %4.2f 1.0 -> '\ 873 +'{"type":"buyGood"}' % (threshold-0.01)) 874 # Otherwise, do nothing 875 policy.append('default -> {"type":"wait"}') 876 else: 877 # Give wealth away 878 policy.append('default -> {"type":"disburse"}') 879 return policy
880 881
882 -def initialize(args={}):
883 """Set the dynamic parameters of the class hierarchy.""" 884 default = classHierarchy['PublicGood'] 885 donor = classHierarchy['Donor'] 886 donee = classHierarchy['Donee'] 887 default['horizon'] = args['horizon'] 888 # Set the initial wealth 889 donor['state']['wealth'] = args['wealth']/float(agentCount()) 890 # Set the utility function 891 donor['goals'] = createGoals(0.2) 892 # Set the space of actions to match the space of possible donation sizes 893 actList = [{'type':'donate','object':['Donee']}] 894 donor['actions'] += actList 895 # Set the dynamics for each of these donation sizes 896 dynamics = default['dynamics']['wealth'] 897 dynamics['donate'] = {'class':PWLDynamics,'args':genWealthDyn()} 898 if __PUNISH__: 899 # Add actions and dynamics for fines 900 actList = [{'type':'punish','object':['neighbor'], 901 'amount':fines}] 902 donor['actions'] += actList 903 # Don't need to charge for punishment if using a fixed policy 904 if args['punish'] != 'lookahead': 905 args['cost'] = 0. 906 dynFun = {'class':PWLDynamics,'args':genWealthDyn(args['cost'])} 907 dynamics['punish'] = dynFun 908 909 if __GOOD__: 910 # Parameters required for a separate, fixed-price public good 911 default['dynamics']['goodExists'] = {'buyGood':{'class':PWLDynamics, 912 'args':genGoodDyn()}} 913 default['beliefs'][None]['goodExists'] = None 914 donee['actions'] = [{'type':'buyGood','object':[]}] 915 else: 916 # Parameters required for a general pool division 917 donee['actions'] = [{'type':'disburse','object':[],'amount':[]}] 918 dynamics['disburse'] = {'class':PWLDynamics, 919 'args':genDisburseDyn(args['scale'])} 920 921 if args['aggregate']: 922 ideal = {'type':'donate','object':'donee', 923 'amount':args['ideal']*(agentCount()-1)} 924 else: 925 ideal = {'type':'donate','object':'donee', 926 'amount':args['ideal']} 927 wait = {'type':'wait'} 928 for name,model in donor['models'].items(): 929 if name == 'deliberate': 930 model['goals'] = createGoals(1.) 931 if args['punish'] == 'lookahead': 932 ## if args['aggregate']: 933 ## model['policy'].insert(0,'observation depth 1 type donate actor neighbor amount [%f,1.] -> {"type":"wait"}' % (donations[len(donations)-1]-Interval.QUANTUM)) 934 ## else: 935 ## raise TypeError,'Unable to avoid punishing max donators under individual model' 936 model['policy'].append('default -> {"type":"lookahead",'\ 937 +'"amount":3}') 938 else: 939 model['policy'] += makePunishPolicy(args['punishmentThreshold']) 940 else: 941 if name[1] == 'A': 942 # Altruistic punisher 943 policy = makePunishPolicy(args['punishmentThreshold']) 944 else: 945 # Egoistic punisher 946 policy = makePunishPolicy(0.) 947 if name[0] == 'A': 948 # Altruistic donor 949 punished = copy.copy(ideal) 950 punished['amount'] /= 2. 951 punished['amount'] = min(punished['amount'],Interval.CEILING) 952 policy += makeDonatePolicy({'ifPunBig':wait, 953 'ifPunSmall':ideal, 954 'ifNotPunSmall':punished, 955 'ifNotPunBig':ideal}, 956 args['ideal']) 957 else: 958 # Egoistic donor 959 policy += makeDonatePolicy({'ifPunBig':wait, 960 'ifPunSmall':ideal, 961 'ifNotPunSmall':wait, 962 'ifNotPunBig':ideal}, 963 args['ideal']) 964 model['policy'] = policy 965 # Goals are always the same for now 966 model['goals'] = createGoals(1.) 967 968 donee['models']['normal']['policy'] = createPolicy(0.8) 969 # Set the type of policy 970 if args['beta'] != 'deterministic': 971 StochasticLookupAhead.beta = float(args['beta']) 972 PublicGoodAgent.policyClass = StochasticLookupAhead 973 # Set the type of network 974 if args['network'] == 'random': 975 PublicGoodAgents.fixedNetwork = None 976 # Set type of mental models 977 if args['aggregate']: 978 PublicGoodAgent.mentalType = 'aggregate' 979 else: 980 PublicGoodAgent.mentalType = 'individual'
981
982 -def usage(value=-1):
983 sys.stderr.write('Supported arguments:\n') 984 sys.stderr.write('-c|--cost <amt>\t\tPunishing another agent costs <amt>\n') 985 sys.stderr.write('--horizon <T>\t\tAgents compute expected values over <T> games\n') 986 sys.stderr.write('-l|--length <n>\t\tThe agents play <n> iterations of the game\n') 987 sys.stderr.write('-p|--punish <policy>\tPunishment policy is either "fixed" or "lookahead"\n') 988 sys.stderr.write('-s|--scale <num>\tDonations are scaled by <num> before disbursement\n') 989 sys.stderr.write('-w|--wealth <amt>\tThe total wealth among agents is <amt> at start\n') 990 sys.stderr.write('\n') 991 sys.stderr.write('--beta <b>\t\tAgents use a stochastic lokahead policy with beta=<b>\n') 992 sys.stderr.write('--beta deterministic\tAgents use a deterministic lokahead policy\n') 993 sys.stderr.write('\n') 994 sys.stderr.write('--network fixed\t\tUse a static assignment of neighbors\n') 995 sys.stderr.write('--network random\tUse a dynamic, random assignment of neighbors\n') 996 sys.stderr.write('\n') 997 sys.stderr.write('--directory <name>\tSaves the data files in the directory <name>\n') 998 sys.stderr.write('--save <name>\t\tSaves the *initial* game in file <name>\n') 999 sys.stderr.write('\n') 1000 sys.stderr.write('-d|--debug <level>\tSets the output level of detail\n') 1001 sys.stderr.write('-h|--help\t\tPrints this message\n') 1002 sys.exit(value)
1003 1004 if __name__ == '__main__': 1005 import getopt 1006 import os 1007 import profile 1008 1009 args = {'cost': 0.001, 1010 'debug': 1, 1011 'filename': '', 1012 'horizon':2, 1013 'punishmentThreshold': .003, 1014 'ideal':.005, 1015 'scale' : 2., 1016 'steps': 1, 1017 'punish':'lookahead', 1018 'wealth':.5, 1019 'beta':'deterministic', 1020 'network':'fixed', 1021 'aggregate':1, 1022 'profile':None, 1023 'directory':os.environ['HOME']+'/python/teamwork/examples/games/' 1024 } 1025 1026 try: 1027 optlist,cmdargs = getopt.getopt(sys.argv[1:],'gd:l:s:w:h:p:c:ai', 1028 ['debug=','OO=','PP=','OP=','PO=', 1029 'steps=','length=','wealth=', 1030 'horizon=','help','save=', 1031 'punish=','scale=','directory=', 1032 'beta=','network=','cost=', 1033 'aggregate','individual','profile']) 1034 except getopt.error: 1035 usage() 1036 for option in optlist: 1037 if option[0] == '-g': 1038 __GOOD__ = 1 1039 elif option[0] == '-c' or option[0] == '--cost': 1040 args['cost'] = float(option[1]) 1041 elif option[0] == '-d' or option[0] == '--debug': 1042 args['debug'] = int(option[1]) 1043 elif option[0] == '-p' or option[0] == '--punish': 1044 args['punish'] = int(option[1]) 1045 elif option[0] == '--OO': 1046 population['OptOptDonor'] = int(option[1]) 1047 elif option[0] == '--OP': 1048 population['OptPesDonor'] = int(option[1]) 1049 elif option[0] == '--PO': 1050 population['PesOptDonor'] = int(option[1]) 1051 elif option[0] == '--PP': 1052 population['PesPesDonor'] = int(option[1]) 1053 elif option[0] == '-l' or option[0] == '--length': 1054 args['steps'] = int(option[1]) 1055 elif option[0] == '-s' or option[0] == '--scale': 1056 args['scale'] = float(option[1]) 1057 elif option[0] == '-h' or option[0] == '--help': 1058 usage(0) 1059 elif option[0] == '--horizon': 1060 args['horizon'] = int(option[1]) 1061 elif option[0] == '--save': 1062 args['filename'] = option[1] 1063 elif option[0] == '--directory': 1064 args['directory'] = option[1] 1065 elif option[0] == '-w' or option[0] == '--wealth': 1066 args['wealth'] = float(option[1]) 1067 elif option[0] == '--beta': 1068 args['beta'] = option[1] 1069 elif option[0] == '--network': 1070 args['network'] = option[1] 1071 elif option[0] == '-a' or option[0] == '--aggregate': 1072 args['aggregate'] = 1 1073 elif option[0] == '-i' or option[0] == '--individual': 1074 args['aggregate'] = None 1075 elif option[0] == '--profile': 1076 args['profile'] = 1 1077 else: 1078 usage() 1079 initialize(args) 1080 dynamics = classHierarchy['PublicGood']['dynamics'] 1081 if args['profile']: 1082 import hotshot 1083 prof = hotshot.Profile('intialization.prof') 1084 prof.runcall(PublicGoodTerminal,entities=None, 1085 classes=classHierarchy,dynamics=dynamics, 1086 file=None,debug=args['debug']) 1087 prof.close() 1088 else: 1089 shell = PublicGoodTerminal(None,classHierarchy,dynamics, 1090 None,args['debug']) 1091 sys.stderr.write('Initialization complete.\n') 1092 script = [] 1093 results = [] 1094 if len(args['filename']) > 0: 1095 shell.save(args['filename']) 1096 # Set up log file 1097 fileroot = args['directory']+'/OO%dOP%dPO%dPP%d' \ 1098 % (population['OptOptDonor'],population['OptPesDonor'], 1099 population['PesOptDonor'],population['PesPesDonor']) 1100 fields = ['amount','amount','mean','wealth','punishIfDon<', 1101 'donateIfPunBig','donateIfNotPunBig', 1102 'donateIfPunSmall','donateIfNotPunSmall', 1103 'donateIfSmallDiff','donateIfBigDiff'] 1104 # Iterate game 1105 output = [] 1106 for t in range(2*args['steps']+1): 1107 results = [] 1108 if t > 0: 1109 sys.stderr.write('Iteration %d' % ((t+1)/2)) 1110 if t%2 == 1: 1111 sys.stderr.write(': Donate\n') 1112 else: 1113 sys.stderr.write(': Punish\n') 1114 if args['profile']: 1115 prof = hotshot.Profile('step.prof') 1116 prof.run('data = shell.step(1,results)[0]') 1117 prof.close() 1118 else: 1119 data = shell.step(1,results)[0] 1120 del data['delta'] 1121 for result in data.values(): 1122 for key in result.keys(): 1123 if key == 'decision': 1124 if result[key]['type'] == 'wait': 1125 result[key] = 0. 1126 else: 1127 result[key] = result[key]['amount'] 1128 else: 1129 del result[key] 1130 else: 1131 data = {} 1132 # Let's save some interesting results 1133 for agent in shell.entities.members(): 1134 if agent.name != 'Public': 1135 if not data.has_key(agent.name): 1136 data[agent.name] = {} 1137 data[agent.name]['wealth'] = agent.getState('wealth').mean() 1138 models = agent.extractModels().values() 1139 data[agent.name].update(models[0]) 1140 output.append(data) 1141 ## shell.displayResult('step',string.join(results,'\n')) 1142 shell.executeCommand('quit') 1143 ## script = [ 1144 #### 'save %s/python/teamwork/examples/games/publicgood.scn' \ 1145 #### % (os.environ['HOME']), 1146 ## 'step %d' % (2*steps), 1147 ## 'quit' 1148 ## ] 1149 ## for cmd in script: 1150 ## shell.executeCommand(cmd) 1151 sys.stderr.write('Saving data...') 1152 shell.mainloop() 1153 for field in fields: 1154 if field == 'amount': 1155 donFile = open(fileroot+'D','w') 1156 punFile = open(fileroot+'P','w') 1157 for index in range(agentCount()): 1158 name = '%s%d' % (namePrefix,index) 1159 stage = None 1160 for step in output[1:]: 1161 content = '\t%6.4f' % (step[name]['decision']) 1162 if stage: 1163 punFile.write(content) 1164 else: 1165 donFile.write(content) 1166 stage = not stage 1167 donFile.write('\n') 1168 punFile.write('\n') 1169 donFile.close() 1170 punFile.close() 1171 elif field == 'mean': 1172 donFile = open(fileroot+'DMean','w') 1173 punFile = open(fileroot+'PMean','w') 1174 stage = None 1175 for step in output[1:]: 1176 totalP = 0. 1177 totalD = 0. 1178 for index in range(agentCount()): 1179 if stage: 1180 totalP += step[name]['decision'] 1181 else: 1182 totalD += step[name]['decision'] 1183 if stage: 1184 punFile.write('%6.4f\n' % (totalP/float(agentCount()))) 1185 else: 1186 donFile.write('%6.4f\n' % (totalD/float(agentCount()))) 1187 stage = not stage 1188 donFile.close() 1189 punFile.close() 1190 else: 1191 donFile = open(fileroot+field,'w') 1192 for index in range(agentCount()): 1193 name = '%s%d' % (namePrefix,index) 1194 for step in output: 1195 if field[-4:] == 'Diff': 1196 root = field[:-4] 1197 if root[-3:] == 'Big': 1198 donation = root[-3:] 1199 root = root[:-3] 1200 else: 1201 donation = root[-5:] 1202 root = root[:-5] 1203 value = step[name][root+'Pun'+donation]\ 1204 -step[name][root+'NotPun'+donation] 1205 else: 1206 value = step[name][field] 1207 try: 1208 content = '\t%6.4f' % (value) 1209 except TypeError: 1210 raise TypeError,'%s = %s' % (value) 1211 donFile.write(content) 1212 donFile.write('\n') 1213 donFile.close() 1214 1215 sys.stderr.write('Done.\n') 1216 if args['profile']: 1217 stats = hotshot.stats.load('step.prof') 1218 stats.print_stats() 1219