1 """Class definition of messages
2 @author: David V. Pynadath <pynadath@isi.edu>
3 """
4
5 import string
6 from types import *
7 from xml.dom.minidom import *
8
9 from teamwork.math.Interval import Interval
10 from teamwork.math.KeyedMatrix import KeyedMatrix
11 from teamwork.math.probability import Distribution
12 from teamwork.action.PsychActions import *
13
15 """Subclass of L{Action} corresponding to messages, which are realized as attempts to modify the beliefs of others
16 @cvar acceptString: the flag indicating that a hearer should be forced to believe this message
17 @cvar rejectString: the flag indicating that a hearer should be forced to disbelieve this message
18 @type acceptString,rejectString: C{str}
19 @cvar fields: the available keys for messages of this class (unless otherwise noted, the value under each key is a string)
20 - I{sender}: the agent sending the message (i.e., the I{actor})
21 - I{receiver}: the I{sender}'s intended hearer (i.e., the I{object})
22 - I{type}: the action type (default is C{_message})
23 - I{performative}: as defined by ACL conventions (default is C{tell})
24 - I{command}: the action that the I{sender} is commanding the I{receiver} to perform (probably doesn't work)
25 - I{force}: dictionary (over receivers) of flags indicating whether acceptance/rejection of the message should be forced. For each name key, if the value is C{None}, then the agent decides for itself; if L{acceptString}, then theagent must believe the message; if L{rejectString}, then the agent must reject the message. You should not set this field's value directly; rather, use the L{forceAccept}, L{forceReject}, L{mustAccept}, and L{mustReject} methods as appropriate
26 - I{factors}: the content of the message, in the form of a list of dictionaries, specifying the intended change to individual beliefs
27 """
28 fields = {
29
30 'actor':{},'sender':{},
31 'object':{},'receiver':{},
32
33 'type':{},'command':{},
34
35 'factors':{},'performative':{},'force':{},
36 '_observed':{},'_unobserved':{},
37
38 'matrix':{},
39
40 }
41 acceptString = 'accept'
42 rejectString = 'reject'
43
45 if isinstance(arg,str):
46 Action.__init__(self)
47 self.extractFromString(arg)
48 else:
49 Action.__init__(self,arg)
50 if not self['type']:
51 self['type'] = '_message'
52
53
54 if not self.has_key('force'):
55 self['force'] = {}
56 if not self.has_key('performative'):
57 self['performative'] = 'tell'
58
60 Action.__setitem__(self,index,value)
61 if index == 'actor':
62 Action.__setitem__(self,'sender',value)
63 elif index == 'sender':
64 Action.__setitem__(self,'actor',value)
65 elif index == 'object':
66 Action.__setitem__(self,'receiver',value)
67 elif index == 'receiver':
68 Action.__setitem__(self,'object',value)
69
71 """The string representation of a non-command message is:
72 <key11>:<key12>:...:<key1n>=<value1>;<key21>:<key22>:...:<key2m>=<value2>;...
73 e.g., entities:Psyops:state:power=0.7;entities:Paramilitary:entities:Psyops:state:power=0.7
74 e.g., message Psyops Paramilitary entities:Psyops:policy:observation depth 3 actor Paramilitary type violence object UrbanPoor violence-to-Paramilitary
75 For commands, the syntax is
76 'command;<key1>=<val1>;<key2>=<val2>;...', where the usual keys
77 are 'object' (e.g., 'opponent') and 'type' (e.g., 'violence').
78 This same format is expected by the constructor and returned by
79 the string converter."""
80 print 'WARNING: You should migrate to dictionary spec of messages'
81 self['factors'] = []
82 factors = string.split(str,';')
83 if factors[0] == 'command':
84 self['command'] = {}
85 for factor in factors[1:]:
86 key,value = string.split(factor,'=')
87 self['command'][key] = value
88 else:
89 self['command'] = None
90 for factor in factors:
91 factor = string.strip(factor)
92 if factor == self.acceptString:
93 self.forceAccept()
94 elif factor == self.rejectString:
95 self.forceReject()
96 else:
97 relation = '='
98 pair = string.split(factor,relation)
99 if len(pair) == 1:
100 relation = '>>'
101 pair = string.split(factor,relation)
102 lhs = string.split(string.strip(pair[0]),':')
103 rhs = string.split(string.strip(pair[1]),':')
104 factor = {'lhs':lhs,'rhs':rhs,'relation':relation,
105 'topic':'state'}
106 if len(rhs) == 1:
107 try:
108 value = float(pair[1])
109 except ValueError:
110 value = pair[1]
111 factor['value'] = value
112 elif len(rhs) == 2:
113 try:
114 lo = float(rhs[0])
115 hi = float(rhs[1])
116 factor['value'] = Interval(lo,hi)
117 except ValueError:
118 pass
119 self['factors'].append(factor)
120
121 - def force(self,agent='',value=None):
122 """
123 @param value: if a positive number or equals the 'acceptString'
124 attribute, then sets the message to force acceptance; if value
125 is a negative number of equals the 'rejectString' attribute,
126 then sets the message to force rejection; otherwise, the
127 acceptance of this message is left up to the receiver
128 @param agent: the agent whose acceptance is being forced. If the empty string, then all agents are forced (the default)
129 @type agent: str
130 """
131 if value:
132 if type(value) is StringType:
133 if value == self.acceptString:
134 self['force'][agent] = self.acceptString
135 elif value == self.rejectString:
136 self['force'][agent] = self.rejectString
137 else:
138 raise TypeError,'Unknown forced value: '+`value`
139 elif value > 0:
140 self['force'][agent] = self.acceptString
141 else:
142 self['force'][agent] = self.rejectString
143 else:
144 self['force'][agent] = None
145
147 """Sets the message to force acceptance by the receiver
148 @param agent: the agent whose acceptance is being forced. If the empty string, then all agents are forced (the default)
149 @type agent: str
150 """
151 self.force(agent,self.acceptString)
152
154 """Sets the message to force rejection by the receiver
155 @param agent: the agent whose acceptance is being forced. If the empty string, then all agents are forced (the default)
156 @type agent: str
157 """
158 self.force(agent,self.rejectString)
159
161 """
162 @param agent: the agent whose forcing is being tested. If the empty string, then the test is over all agents (the default)
163 @type agent: str
164 @return: true iff this message has been set to force
165 acceptance by the specified receiver
166 @rtype: boolean
167 """
168 try:
169 return self['force'][agent] == self.acceptString
170 except KeyError:
171 try:
172 return self['force'][''] == self.acceptString
173 except KeyError:
174 return False
175
177 """
178 @param agent: the agent whose forcing is being tested. If the empty string, then the test is over all agents (the default)
179 @type agent: str
180 @return: true iff this message has been set to force
181 rejection by the receiver
182 @rtype: boolean"""
183 try:
184 return self['force'][agent] == self.rejectString
185 except KeyError:
186 try:
187 return self['force'][''] == self.rejectString
188 except KeyError:
189 return False
190
192 if len(self['factors']) == len(other['factors']):
193 for factor in self['factors']:
194 if not factor in other['factors']:
195 return -1
196 else:
197 return 0
198 else:
199 return -1
200
203
205 doc = Action.__xml__(self)
206
207 node = doc.createElement('force')
208 doc.documentElement.appendChild(node)
209 for name in self['force'].keys():
210 if self.mustAccept(name):
211 child = doc.createElement(self.acceptString)
212 node.appendChild(child)
213 child.setAttribute('agent',name)
214 elif self.mustReject(name):
215 child = doc.createElement(self.rejectString)
216 node.appendChild(child)
217 child.setAttribute('agent',name)
218
219 for factor in self['factors']:
220 node = doc.createElement('factor')
221 doc.documentElement.appendChild(node)
222 node.setAttribute('topic',factor['topic'])
223 if factor.has_key('matrix'):
224 node.appendChild(factor['matrix'].__xml__().documentElement)
225 return doc
226
228 Action.parse(self,doc)
229 if doc.nodeType == doc.DOCUMENT_NODE:
230 element = doc.documentElement
231 else:
232 element = doc
233 self['factors'] = []
234 child = element.firstChild
235 while child:
236 if child.nodeType == child.ELEMENT_NODE:
237 if child.tagName == 'factor':
238 factor = {'topic':str(child.getAttribute('topic'))}
239 if child.firstChild:
240 distribution = Distribution()
241 distribution.parse(child.firstChild,KeyedMatrix)
242 factor['matrix'] = distribution
243 self['factors'].append(factor)
244 child = child.nextSibling
245 return self
246
249
251 """
252 @return: a more user-friendly string rep of this message"""
253 rep = ''
254 for factor in self['factors']:
255 if factor['topic'] == 'state':
256 substr = ''
257 if factor['lhs'][0] == 'entities':
258 entity = factor['lhs'][1]
259 if factor['lhs'][2] == 'state':
260 feature = factor['lhs'][3]
261 if query:
262 substr = 'What is the'
263 else:
264 substr = 'The'
265 substr += ' %s of %s' % (feature,entity)
266 if isinstance(factor['value'],Distribution):
267 if len(factor['value']) == 1:
268 value = str(factor['value'].domain()[0])
269 else:
270 value = str(factor['value'])
271 else:
272 try:
273 value = '%4.2f' % (float(factor['rhs'][0]))
274 except KeyError:
275 value = str(factor['value'])
276 if len(substr) == 0:
277
278 substr = string.join(factor['lhs'],':')
279 value = string.join(factor['rhs'],':')
280 if query:
281 substr += '?'
282 rep += '; %s' % (substr)
283 if not query:
284 rep += ' %s %s' % (factor['relation'],value)
285 elif factor['topic'] == 'observation':
286 factors = map(lambda a:'%s to %s' % (a['type'],a['object']),
287 factor['action'])
288 actType = string.join(factors,', ')
289 if query:
290 rep += '; Who did %s?' % (actType)
291 else:
292 rep += '; %s chose to %s' % (factor['actor'],actType)
293 elif factor['topic'] == 'model':
294 substr = ' is %s' % (factor['value'])
295 entity = string.join(factor['entity'],' believes ')
296 rep += '; %s %s' % (entity,substr)
297 else:
298 raise NotImplementedError,'No pretty printing for messages of type %s' % (factor['topic'])
299 if self.mustAccept():
300 rep += ' (force to accept)'
301 elif self.mustReject():
302 rep += ' (force to reject)'
303 return rep[2:]
304
305 if __name__ == '__main__':
306 from teamwork.examples.PsychSim.PsychUtils import load
307
308
309
310
311
312
313
314 msg1 = Message('entities:Coalition:entities:CortinaGov:state:support=0.7:1.0')
315
316
317
318 from xml.marshal.generic import *
319
320 xmlStr = dumps(msg1)
321
322 msg2 = loads(xmlStr)
323
324 if msg1 == msg2:
325 print msg2
326