Package teamwork :: Package policy :: Module LookupPolicy
[hide private]
[frames] | no frames]

Source Code for Module teamwork.policy.LookupPolicy

  1  """Reactive policies as conditionS{->}action rules""" 
  2  import copy 
  3  import re 
  4  import string 
  5   
  6  from generic import * 
  7   
  8  from teamwork.action.PsychActions import * 
  9  from teamwork.math.Interval import * 
 10  from teamwork.utils.Debugger import * 
 11  from teamwork.agent.Agent import Agent 
 12  #from teamwork.examples.TactLang.TactLangMessage import TactLangMessage 
 13   
 14  ##def testObservation(act,entry): 
 15  ##    """Returns true iff the action matches the LHS entry""" 
 16  ##    for key in act.fields: 
 17  ##        if entry.has_key(key) and entry[key]: 
 18  ##            if isinstance(act[key],str): 
 19  ##                if entry[key] != act[key]: 
 20  ##                    # Mismatch 
 21  ##                    break 
 22  ##            elif isinstance(act[key],Agent): 
 23  ##                if entry[key] != act[key].name: 
 24  ##                    # Mismatch 
 25  ##                    break 
 26  ##            elif act[key]: 
 27  ##                # HACK! HACK! HACK! 
 28  ##                if key == 'amount': 
 29  ##                    if not act[key] in str2Interval(entry[key]): 
 30  ##                        break 
 31  ##                else: 
 32  ##                    break 
 33  ##    else: 
 34  ##        # Successful match 
 35  ##        return 1 
 36  ##    return 0 
 37   
 38  ## mei 09/27/05 
39 -def testObservation(act,entry):
40 """Returns true iff the action matches the LHS entry""" 41 42 for key in entry.keys(): 43 44 debug=0 45 try: 46 if entry['sact_type'] == 'accept': 47 pass 48 ## debug=1 49 except: 50 pass 51 52 ## debug = 1 53 if key in ['depth' ,'action','class','value',\ 54 'attitude','command','performative','force','_observed','_unobserved']: 55 continue 56 57 if entry[key] == 'any': 58 continue 59 60 if not act.has_key(key): 61 if key in ['lhs','rhs'] and act['factors']: 62 pass 63 elif key in ['sender','actor'] and (act.has_key('sender') or act.has_key('actor')): 64 pass 65 elif key in ['receiver','object'] and (act.has_key('receiver') or act.has_key('object')): 66 pass 67 else: 68 if debug ==1: 69 print 'act does not have the key ', key 70 break 71 72 73 if key == 'lhs': 74 res = 0 75 for factor in act['factors']: 76 ## print 'lhs factor ',factor 77 try: 78 if isinstance(entry[key],str): 79 tmp = factor['lhs'][1]+'_'+factor['lhs'][3] 80 if string.strip(entry[key]) == string.strip(tmp): 81 res =1 82 else: 83 if debug ==1: 84 print 'lhs not match ' 85 print string.strip(tmp), type(string.strip(tmp)) 86 print string.strip(entry[key]), type(string.strip(entry[key])) 87 ## print entry[key] == tmp 88 ## 89 ## for i in range(len(string.strip(entry[key]))): 90 ## if string.strip(entry[key])[i] == string.strip(tmp)[i]: 91 ## pass 92 ## else: 93 ## print string.strip(entry[key])[i], string.strip(tmp)[i] 94 95 96 97 pass 98 elif type (entry[key]) == ListType: 99 tmp = factor['lhs'] 100 if entry[key] == tmp: 101 res =1 102 else: 103 if debug ==1: 104 print 'lhs not match ' 105 print string.strip(tmp), type(string.strip(tmp)) 106 print string.strip(entry[key]), type(string.strip(entry[key])) 107 print entry[key] == tmp 108 pass 109 110 except: 111 pass 112 if res == 0: 113 break 114 115 elif key == 'rhs': 116 res = 0 117 for factor in act['factors']: 118 ## print 'rhs factor ',factor 119 try: 120 if isinstance(entry[key],str): 121 if len (factor['rhs']) == 1: 122 tmp = float(factor['rhs'][0]) 123 else: 124 tmp = (float(factor['rhs'][0])+float(factor['rhs'][1]))/2 125 if float(entry[key]) == tmp: 126 res =1 127 ## print 'rhs matched' 128 else: 129 if debug ==1: 130 print 'rhs not match' 131 print tmp, float(entry[key]) 132 pass 133 134 except: 135 pass 136 if res == 0: 137 break 138 139 elif key == 'sender': 140 if act.has_key('sender'): 141 if entry['sender'] == act['sender']: 142 continue 143 elif act.has_key('actor'): 144 if entry['sender'] == act['actor']: 145 continue 146 if debug ==1: 147 print 'sender doe not match ' 148 break 149 150 elif key == 'actor': 151 if act.has_key('sender'): 152 if entry['actor'] == act['sender']: 153 continue 154 elif act.has_key('actor'): 155 if entry['actor'] == act['actor']: 156 continue 157 if debug ==1: 158 print 'sender does not match ' 159 break 160 161 elif key in ['addressee']: 162 if type(entry['addressee']) == ListType: 163 if act['addressee'] == entry['addressee'] or act['addressee'] in entry['addressee']: 164 continue 165 else: 166 if debug ==1: 167 print act['addressee'], entry['addressee'] 168 break 169 elif type(act['addressee']) == ListType: 170 if entry['addressee'] in act['addressee']: 171 continue 172 else: 173 break 174 else: 175 if debug ==1: 176 print 'addressee doe not match ' 177 break 178 179 180 elif isinstance(act[key],str): 181 182 if entry[key] != act[key]: 183 if debug ==1: 184 print 'Mismatch ', key, entry[key], act[key] 185 # Mismatch 186 break 187 elif isinstance(act[key],Agent): 188 if entry[key] != act[key].name: 189 # Mismatch 190 break 191 else: 192 break 193 194 195 else: 196 # Successful match 197 return 1 198 return 0
199
200 -class LookupPolicy(Policy):
201 """Class for representing a strictly reactive policy 202 203 Each entry is represented as a dictionary: 204 >>> {'class':<cls>, 'action':<action>, ... } 205 The action can be passed in as a dictionary, which is then 206 converted into the appropriate Action subclass. The remaining 207 structure of the entry depends on the value of <cls>, as follows: 208 209 - I{default}: no other entries. Rules of this class always fire 210 """
211 - def __init__(self,entity,actions=[],span=1):
212 Policy.__init__(self,actions) 213 self.entries = [] 214 self.entity = entity 215 self.span = span
216
217 - def matchAnswer(self,state,action):
218 obsList = state.getObservations() 219 for index in range(len(obsList)): 220 lastObs = obsList[index] 221 for actor,actList in lastObs.items(): 222 for act in actList: 223 if act['sact_type']=='enquiry': 224 action['factors']=[] 225 action['factors'].append(act['factors'][0]) 226 227 try: 228 action['factors'][0]['value']=state.getBelief(act['factors'][0]['lhs'][1],act['factors'][0]['lhs'][3]) 229 except: 230 action['factors'][0]['value'][0]=-1 231 action['factors'][0]['value'][1]=1 232 try: 233 action['factors'][0]['rhs'][0]=str(action['factors'][0]['value']['lo']) 234 action['factors'][0]['rhs'][1]=str(action['factors'][0]['value']['hi']) 235 except: 236 ## print type(msg['factors'][0]['value']) 237 action['factors'][0]['rhs']=str(action['factors'][0]['value']) 238 239 return
240 241
242 - def execute(self,state,choices=[],history=None,debug=Debugger(),explain=None):
243 # The basic explanation structure for a lookup-specified 244 # action is always the following 245 explanation = {'value':None, 246 'decision':None, 247 'actor':self.entity.name, 248 'effect':{}, 249 'breakdown':[], 250 } 251 # Test against each of the entries in the lookup table 252 for entry in self.entries: 253 debug.message(1,'\tConsidering entry: '+`entry`) 254 result = self.testCondition(state,entry,debug) 255 if result: 256 # We match the trigger of this particular entry 257 ## if len(entry['action']) == 0 or \ 258 ## isinstance(entry['action'][0], TactLangMessage) or \ 259 ## len(choices) == 0 or entry['action'] in choices: 260 # The RHS is one of our available options 261 debug.message(5,'Matched.') 262 decision = copy.deepcopy(entry['action']) 263 264 ## if decision[0]['sact_type'] == 'inform' : 265 ## self.matchAnswer (state, decision[0]) 266 267 explanation['breakdown'].append({'entry':entry, 268 'actor':self.entity.name, 269 'decision':decision, 270 'result':result}) 271 explanation['decision'] = decision 272 break 273 debug.message(8,'Lookup table match on: %s' \ 274 % (`explanation['decision']`)) 275 return explanation['decision'],explanation
276
277 - def actionValue(self,state,actStruct,debug=Debugger()):
278 """Return some quantified value of performing action""" 279 actStr = str(actStruct) 280 myAction,explanation = LookupPolicy.execute(self,state=state, 281 debug=debug) 282 if `myAction` == actStr: 283 # This is the exact action generated by the policy 284 debug.message(3,'Exact match') 285 return 1.0,explanation 286 # Otherwise, identify how many entries generate this action 287 value = 0.0 288 explanation['breakdown'] = [] 289 for entry in self.entries: 290 if `entry['action']` == actStr: 291 explanation['breakdown'].append(entry) 292 value = value + 1.0 293 debug.message(3,'Number of entries with action '+actStr+': '+`int(value)`) 294 try: 295 value = value / float(len(self.entries)) 296 except ZeroDivisionError: 297 value = 0.0 298 return value,explanation
299
300 - def testCondition(self,state,entry,debug=Debugger):
301 if entry['class'] == 'observation': 302 # This entry pertains to a specific observation, so 303 # identify most recent observations and check whether 304 # they match the specified condition (LHS) 305 debug.message(3,'\t\tTesting condition: %s' % `entry`) 306 try: 307 depth = entry['depth'] 308 except KeyError: 309 depth = 1 310 obsList = state.getObservations() 311 for index in range(len(obsList)): 312 lastObs = obsList[index] 313 for actor,actList in lastObs.items(): 314 for act in actList: 315 debug.message(3,'\t\t\tObservation: %s' % `act`) 316 if testObservation(act,entry): 317 return 1 318 319 if depth == -1: 320 if entry['sender'] == act ['sender'] or entry['sender'] == act ['actor'] : 321 if act['sact_type'] != 'wait': 322 depth = 0 323 break 324 325 326 if depth >= 1: 327 depth = depth - 1 328 else: 329 # A received message 330 pass 331 if depth == 0: 332 # We have now examined the maximum number of 333 # observations specified 334 debug.message(1,'\t\tNo such observation') 335 break 336 elif entry['class'] == 'belief': 337 # This entry pertains to some (possibly nested) 338 # belief, so we dig down into the beliefs until we 339 # find the specified belief and then verify whether 340 # its value matches to the specified condition (LHS) 341 try: 342 currentBelief = state.getNestedBelief(entry['keys']) 343 except KeyError: 344 # No such belief 345 debug.message(1,'\t\tNo relevant belief value') 346 return None 347 # OK, we've found the specified belief, so let's check 348 # its value 349 debug.message(1,'\t\tRelevant belief value: '+`currentBelief`) 350 return currentBelief in entry['range'] 351 elif entry['class'] == 'combine': 352 try: 353 lBelief = state.getNestedBelief(entry['left keys']) 354 rBelief = state.getNestedBelief(entry['right keys']) 355 except KeyError: 356 # No matching beliefs 357 return None 358 cmd = 'Interval('+`lBelief['lo']`+','+`lBelief['hi']`+')' + \ 359 entry['operator'] + \ 360 'Interval('+`rBelief['lo']`+','+`rBelief['hi']`+')' 361 try: 362 value = eval(cmd,{'Interval':Interval},{}) 363 except: 364 raise TypeError,"Unable to perform combination: %s" % cmd 365 debug.message(1,'\t\tRelevant belief value: '+`value`) 366 return value in entry['range'] 367 elif entry['class'] == 'conjunction': 368 # For a conjunction, each and every clause must hold for the 369 # overall rule to hold 370 for clause in entry['clauses']: 371 if not self.testCondition(state,clause,debug): 372 debug.message(1,'\t\tClause failure: '+`clause`) 373 return None 374 else: 375 debug.message(1,'\t\tSuccessful match') 376 return 1 377 elif entry['class'] == 'negation': 378 # For a negation, return the opposite of the contained clause 379 if self.testCondition(state,entry['clause'],debug): 380 debug.message(1,'\t\tNegation of successful match') 381 return None 382 else: 383 debug.message(1,'\t\tNegation of failed match') 384 return 1 385 elif entry['class'] == 'default': 386 # Default rules always match 387 return 1 388 else: 389 raise TypeError,'illegal entry class: '+entry['class'] 390 return None
391
392 - def extend(self,entry,actionClass=None,entity=None):
393 """Extends the current policy table to include the given entry 394 395 The entry can be either a string or a list of policy entry 396 dictionaries or a single policy entry dictionary. The 397 optional actionClass argument (default: Action) specifies the 398 base class for the RHS""" 399 if isinstance(entry,list): 400 newEntries = entry 401 elif isinstance(entry,dict): 402 newEntries = [entry] 403 else: 404 # Assume to be string 405 ## print 'WARNING: Use dictionary specification of policies' 406 newEntries = self.parseEntry(entry,actionClass) 407 # Convert RHS entries to the given actionClass 408 for entry in newEntries[:]: 409 if not isinstance(entry['action'],list): 410 entry['action'] = [entry['action']] 411 for index in range(len(entry['action'])): 412 action = entry['action'][index] 413 414 if not isinstance (action, Action): 415 action = actionClass(action) 416 417 action['actor'] = entity.name 418 obj = action['object'] 419 if entity: 420 objList = entity.instantiateName(obj) 421 if len(objList) > 0: 422 action['object'] = objList[0] 423 entry['action'][index] = action 424 # Add new entries to end of table 425 self.entries += newEntries 426 return newEntries
427
428 - def parseEntry(self,entry,actionClass=None):
429 """Takes a string representation of a lookup entry and returns 430 the corresponding policy entry structure""" 431 try: 432 lhs,rhs = string.split(entry,'->') 433 except ValueError: 434 print entry 435 return 436 lhs = string.strip(lhs) 437 if not actionClass: 438 actionClass = Action 439 ## mei to comply with using messages as actions 440 if rhs.find('{')>-1: 441 rhs = actionClass(eval(string.strip(rhs),{})) 442 else: 443 rhs = TactLangMessage(string.strip(rhs)) 444 445 keys = string.split(lhs) 446 lhs = self.parseLHS(keys[1:],keys[0]) 447 entries = [] 448 for (entry,substitution) in lhs: 449 entry['action'] = copy.deepcopy(rhs) 450 for key in rhs.keys(): 451 if type (rhs[key]) == ListType: 452 rhs[key] =rhs[key][0] 453 if key == 'command': 454 for subkey in rhs[key].keys(): 455 if isinstance(rhs[key][subkey],str): 456 if substitution.has_key(rhs[key][subkey]): 457 value = substitution[rhs[key][subkey]] 458 entry['action'][key][subkey] = value 459 elif self.entity.relationships.has_key(rhs[key][subkey]): 460 value = self.entity.relationships[rhs[key][subkey]][0] 461 entry['action'][key][subkey] = value 462 elif rhs[key][subkey] == 'self': 463 entry['action'][key][subkey] = self.entity.name 464 elif isinstance(rhs[key],str): 465 if substitution.has_key(rhs[key]): 466 value = substitution[rhs[key]] 467 entry['action'][key] = value 468 elif self.entity.relationships.has_key(rhs[key]): 469 value = self.entity.relationships[rhs[key]][0] 470 entry['action'][key] = value 471 elif rhs[key] == 'self': 472 entry['action'][key] = self.entity.name 473 entries.append(entry) 474 return entries
475
476 - def parseLHS(self,keys,entryType):
477 entries = [({'class':entryType},{})] 478 if entryType == 'observation': 479 # Sample observation policy string: 480 # "observation depth 3 actor Paramilitary type violence 481 # object UrbanPoor -> violence-against-Paramilitary" 482 # (i.e., triggered if, within past 3 steps, the 483 # paramilitary have committed violence against UrbanPoor) 484 for index in range(0,len(keys),2): 485 key = keys[index] 486 value = keys[index+1] 487 for entry,sub in entries[:]: 488 entries.remove((entry,sub)) 489 try: 490 entry[key] = int(value) 491 if key == 'depth': 492 entry[key] = entry[key] * self.span 493 entries.append((entry,sub)) 494 except ValueError: 495 if value == 'self': 496 entry[key] = self.entity.name 497 entries.append((entry,sub)) 498 elif self.entity.relationships.has_key(value): 499 for other in self.entity.relationships[value]: 500 newSub = copy.copy(sub) 501 newSub[value] = other 502 newEntry = copy.deepcopy(entry) 503 newEntry[key] = other 504 entries.append((newEntry,newSub)) 505 elif key == 'command': 506 command = self.choices[0].__class__(value) 507 if self.entity.relationships.has_key(command.object): 508 for other in self.entity.relationships[command.object]: 509 newSub = copy.copy(sub) 510 newSub[command.object] = other 511 newEntry = copy.deepcopy(entry) 512 newEntry[key] = copy.copy(command) 513 newEntry[key].object = other 514 entries.append((newEntry,newSub)) 515 else: 516 entry[key] = value 517 entries.append((entry,sub)) 518 elif entryType == 'message': 519 # Sample message policy string: 520 # "message depth 3 sender CortinaGov content command;... 521 # -> violence-against-Paramilitary" 522 # (i.e., triggered if, within past 3 steps, the CortinaGov 523 # have issued a command) 524 for index in range(0,len(keys),2): 525 key = keys[index] 526 value = keys[index+1] 527 for entry in entries[:]: 528 entries.remove(entry) 529 try: 530 entry[key] = int(value) 531 entries.append(entry) 532 except ValueError: 533 if self.entity.relationships.has_key(value): 534 for other in self.entity.relationships[value]: 535 entry[key] = other 536 entries.append(copy.copy(entry)) 537 else: 538 entry[key] = value 539 entries.append(entry) 540 elif entryType == 'belief': 541 # Sample belief policy string: 542 # "belief entities Psyops state power 0.0 0.4 543 # -> violence-against-Psyops" 544 # (i.e., triggered if beliefs about Psyops' power in [0.0,0.4]) 545 keyList = [] 546 for key in keys[:len(keys)-2]: 547 if key == 'self': 548 keyList.append(self.entity.name) 549 else: 550 keyList.append(key) 551 entries[0][0]['keys'] = keyList 552 553 entries[0][0]['range'] = Interval(float(keys[len(keys)-2]), 554 float(keys[len(keys)-1])) 555 entry,sub = entries.pop() 556 for other,keySub in self.instantiateKeys(entry['keys']): 557 newSub = copy.copy(sub) 558 for key in keySub.keys(): 559 newSub[key] = keySub[key] 560 entry['keys'] = other 561 entries.append((copy.copy(entry),newSub)) 562 elif entryType == 'combine': 563 # Sample combination policy string: 564 # "combine entities Psyops state power - entities 565 # Paramilitary state power 0.0 0.4 -> 566 # violence-against-Psyops" 567 # (i.e., triggered if beliefs about Psyops' power - 568 # paramilitary's power in [0.0,0.4]) 569 try: 570 index = keys.index('-') 571 entries[0][0]['operator'] = '-' 572 except ValueError: 573 index = keys.index('+') 574 entries[0][0]['operator'] = '+' 575 entries[0][0]['left keys'] = keys[:index] 576 entries[0][0]['range'] = Interval(float(keys[len(keys)-2]), 577 float(keys[len(keys)-1])) 578 for entry,sub in entries[:]: 579 entries.remove((entry,sub)) 580 for keySet,sub in \ 581 self.instantiateKeys(entry['left keys']): 582 entry['left keys'] = keySet 583 entries.append((copy.deepcopy(entry),sub)) 584 for entry,oldsub in entries[:]: 585 entries.remove((entry,oldsub)) 586 entry['right keys'] = keys[index+1:len(keys)-2] 587 for keySet,sub in \ 588 self.instantiateKeys(entry['right keys']): 589 # Check for conflict with left key substitution 590 for key,val in oldsub.items(): 591 if sub.has_key(key) and sub[key] != val: 592 break 593 else: 594 sub[key] = val 595 else: 596 entry['right keys'] = keySet 597 entries.append((copy.deepcopy(entry),sub)) 598 elif entryType == 'conjunction': 599 # Sample conjunction policy string: 600 # "conjunction observation depth 3 actor Paramilitary 601 # type violence object UrbanPoor & belief entities Psyops 602 # state power 0.0 0.4 -> violence-against-Psyops" 603 clauses = string.split(string.join(keys[:]),'&') 604 entries[0][0]['clauses'] = [] 605 for str in clauses: 606 keys = string.split(str) 607 for entry,sub in entries[:]: 608 entries.remove((entry,sub)) 609 for clause,clauseSub in \ 610 self.parseLHS(keys[1:],keys[0]): 611 newSub = copy.copy(sub) 612 # First, check whether this clause's 613 # substitution is consistent with substitution 614 # so far 615 for key in clauseSub.keys(): 616 if sub.has_key(key) and \ 617 sub[key] != clauseSub[key]: 618 break 619 else: 620 newSub[key] = clauseSub[key] 621 else: 622 # Consistent substitution 623 newEntry = copy.deepcopy(entry) 624 newEntry['clauses'].append(clause) 625 entries.append((newEntry,newSub)) 626 elif entryType == 'negation': 627 # Sample negation policy string: 628 # "negation observation depth 3 actor Paramilitary 629 # type violence object UrbanPoor -> violence-against-Psyops" 630 for entry,sub in entries[:]: 631 entries.remove((entry,sub)) 632 for clause,clauseSub in self.parseLHS(keys[1:],keys[0]): 633 newSub = copy.copy(sub) 634 # First, check whether this clause's 635 # substitution is consistent with substitution 636 # so far 637 for key in clauseSub.keys(): 638 if sub.has_key(key) and sub[key] != clauseSub[key]: 639 break 640 else: 641 newSub[key] = clauseSub[key] 642 else: 643 # Consistent substitution 644 newEntry = copy.deepcopy(entry) 645 newEntry['clause'] = clause 646 entries.append((newEntry,newSub)) 647 elif entryType == 'default': 648 # Sample default string: 649 # "default -> wait" 650 pass 651 else: 652 raise TypeError,'illegal entry class: '+entryType 653 return entries
654
655 - def instantiateKeys(self,keys):
656 """Takes a list of strings and substitutes any specific 657 entities into generic relation labels""" 658 keyList = [([],{})] 659 for key in keys: 660 if self.entity.relationships.has_key(key): 661 for keySet,sub in keyList[:]: 662 keyList.remove((keySet,sub)) 663 for entity in self.entity.relationships[key]: 664 newKeys = copy.copy(keySet) 665 newSub = copy.copy(sub) 666 newKeys.append(entity) 667 newSub[key] = entity 668 keyList.append((newKeys,newSub)) 669 else: 670 for keySet,sub in keyList: 671 keySet.append(key) 672 return keyList
673
674 - def __str__(self):
675 return str(self.entries)
676
677 - def __contains__(self,value):
678 """Returns true if the specified value matches an entry in 679 this policy""" 680 entries = self.parseEntry(value) 681 for entry in entries: 682 if not entry in self.entries: 683 return False 684 return True
685