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
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
49 except:
50 pass
51
52
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
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
88
89
90
91
92
93
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
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
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
186 break
187 elif isinstance(act[key],Agent):
188 if entry[key] != act[key].name:
189
190 break
191 else:
192 break
193
194
195 else:
196
197 return 1
198 return 0
199
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
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
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
244
245 explanation = {'value':None,
246 'decision':None,
247 'actor':self.entity.name,
248 'effect':{},
249 'breakdown':[],
250 }
251
252 for entry in self.entries:
253 debug.message(1,'\tConsidering entry: '+`entry`)
254 result = self.testCondition(state,entry,debug)
255 if result:
256
257
258
259
260
261 debug.message(5,'Matched.')
262 decision = copy.deepcopy(entry['action'])
263
264
265
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
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
284 debug.message(3,'Exact match')
285 return 1.0,explanation
286
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
301 if entry['class'] == 'observation':
302
303
304
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
330 pass
331 if depth == 0:
332
333
334 debug.message(1,'\t\tNo such observation')
335 break
336 elif entry['class'] == 'belief':
337
338
339
340
341 try:
342 currentBelief = state.getNestedBelief(entry['keys'])
343 except KeyError:
344
345 debug.message(1,'\t\tNo relevant belief value')
346 return None
347
348
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
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
369
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
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
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
405
406 newEntries = self.parseEntry(entry,actionClass)
407
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
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
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
477 entries = [({'class':entryType},{})]
478 if entryType == 'observation':
479
480
481
482
483
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
520
521
522
523
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
542
543
544
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
564
565
566
567
568
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
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
600
601
602
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
613
614
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
623 newEntry = copy.deepcopy(entry)
624 newEntry['clauses'].append(clause)
625 entries.append((newEntry,newSub))
626 elif entryType == 'negation':
627
628
629
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
635
636
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
644 newEntry = copy.deepcopy(entry)
645 newEntry['clause'] = clause
646 entries.append((newEntry,newSub))
647 elif entryType == 'default':
648
649
650 pass
651 else:
652 raise TypeError,'illegal entry class: '+entryType
653 return entries
654
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
675 return str(self.entries)
676
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