""" A robot that teams with a human to perform a task in a virtual environment. @author: Santosh Shankar, modified by David V. Pynadath @organization: USC ICT @var DISTANCES: A table of travel times between waypoints (asymmetric) @var TEMPLATES: Explanation templates """ import datetime import fileinput import os import random from string import Template import sys import tempfile import time from psychsim.pwl import * from psychsim.world import * from psychsim.agent import Agent from psychsim.reward import * from psychsim.action import * import psychsim.probability from wpRobotWaypoints import WAYPOINTS TEMPLATES = { # TODO: Positive/negative framing # TODO: Sensor model 'positive': { # If a location is safe... True: 'There is a $B_danger_none% chance that the building is safe.', # If a location is not safe... False: 'There is a $B_danger_none% chance that the building is safe.'}, 'negative': { # If a location is safe... True: 'There is a $B_danger_not_none% chance that the building is dangerous.', # If a location is not safe... False: 'There is a $B_danger_not_none% chance that the building is dangerous.'}, 'decision': { # If a location is safe... True: 'I have finished surveying the $B_waypoint. I think the place is safe.', # If a location is not safe... False: 'I have finished surveying the $B_waypoint. I think the place is dangerous.'}, 'confidence': { # If a location is safe... True: 'I am $B_danger_none% confident about this assessment.', # If a location is not safe... False: 'I am $B_danger_not_none% confident about this assessment.'}, 'NBC': { # If I observe NBC... False: 'My sensors have not detected any nuclear, biological or chemical weapons in here.', # If I observe NBC... True: 'My NBC sensors have detected traces of dangerous chemicals.'}, 'armed': { # If I do not observe armed gunmen... False: 'From the image captured by my camera, I have not detected any armed gunmen in the $B_waypoint.', # If I observe armed gunmen... True: 'From the image captured by my camera, I have detected armed gunmen in the $B_waypoint.'}, 'microphone': { # If I do not hear anything... 'nobody': 'My microphone did not pick up anyone talking.', # If I hear friendly conversation... 'friendly': 'My microphone picked up a friendly conversation.', # If I hear suspicious conversation... 'suspicious': 'My microphone picked up a suspicious conversation.'}, 'benevolence': { # If a location is safe... True: 'I don\'t think entering the $B_waypoint without protective gear will pose any danger to you. Without the protective gear, you will be able to search the building a little faster.', # If a location is unsafe... False: 'I think it will be dangerous for you to enter the $B_waypoint without protective gear. The protective gear will slow you down a little.'}, 'acknowledgment': { # False positive True: 'It seems that my assessment of the $B_waypoint was incorrect. I will update my algorithms when we return to base after the mission.', # False negative False: 'It seems that my assessment of the $B_waypoint was incorrect. I will update my algorithms when we return to base after the mission.', None: '', }, } DISTANCES = {'Yellow Mosque': {'Auto Parts Store': 160, 'Furniture Store': 440, 'Suspected Safe House': 600, 'Blue Mosque': 865, 'Potential Sniper Spot': 365, 'Informant\'s House': 240, 'Repair Shop': 290, 'Cafe': 390, 'City Office': 640, 'Farm Supply Store': 535, 'Doctor\'s Office': 575, }, 'Auto Parts Store': {'Furniture Store': 280, 'Suspected Safe House': 440, 'Blue Mosque': 705, 'Potential Sniper Spot': 240, 'Informant\'s House': 400, 'Repair Shop': 450, 'Cafe': 550, 'City Office': 800, 'Farm Supply Store': 340, 'Doctor\'s Office': 405, }, 'Furniture Store': {'Suspected Safe House': 160, 'Blue Mosque': 425, 'Potential Sniper Spot': 135, 'Informant\'s House': 410, 'Repair Shop': 360, 'Cafe': 260, 'City Office': 370, 'Farm Supply Store': 235, 'Doctor\'s Office': 300, }, 'Suspected Safe House': {'Blue Mosque': 265, 'Potential Sniper Spot': 295, 'Informant\'s House': 570, 'Repair Shop': 520, 'Cafe': 420, 'City Office': 370, 'Farm Supply Store': 120, 'Doctor\'s Office': 460, }, 'Blue Mosque': {'Potential Sniper Spot': 490, 'Informant\'s House': 610, 'Repair Shop': 560, 'Cafe': 460, 'City Office': 235, 'Farm Supply Store': 320, 'Doctor\'s Office': 405, }, 'Informant\'s House': {'Repair Shop': 40, 'Cafe': 145, 'City Office': 400, 'Potential Sniper Spot': 425, 'Farm Supply Store': 525, 'Doctor\'s Office': 330, }, 'Repair Shop': {'Cafe': 105, 'City Office': 360, 'Potential Sniper Spot': 385, 'Farm Supply Store': 485, 'Doctor\'s Office': 290, }, 'Cafe': {'City Office': 255, 'Potential Sniper Spot': 270, 'Farm Supply Store': 280, 'Doctor\'s Office': 185, }, 'City Office': {'Potential Sniper Spot': 370, 'Farm Supply Store': 380, 'Doctor\'s Office': 285, }, 'Potential Sniper Spot': {'Farm Supply Store': 170, 'Doctor\'s Office': 300, }, 'Farm Supply Store': {'Doctor\'s Office': 400, }, } CREATE_TAG = 'Created:' MESSAGE_TAG = 'Message:' LOCATION_TAG = 'Location:' USER_TAG = 'Protective:' COMPLETE_TAG = 'Complete' ACK_TAG = 'Acknowledged:' RECOMMEND_TAG = 'Recommend protection:' CODES = {'ability': {'s': 'badSensor','g': 'good','m': 'badModel'}, 'explanation': {'n': 'none','a': 'ability','c': 'confidence'}, 'embodiment': {'r': 'robot','d': 'dog'}, 'acknowledgment': {'n': 'no','y': 'yes'}, } def createWorld(username='anonymous',level=0,ability='good',explanation='none', embodiment='robot',acknowledgment='no',sequence=False, root='.',ext='xml',beliefs=True): """ Creates the initial PsychSim scenario and saves it @param username: name of user ID to use in filenames @param level: robot mission level to use as template @param ability: the level of the robot's ability - good or C{True}: perfect sensors and sensor model - badSensor or C{False}: noisy sensors, but perfect model of noisy sensors - badModel: perfect sensors, but imperfect model of sensors @type ability: bool @param explanation: the type of explanation to use - none: No explanations - ability: Explanation based on robot ability provided. - abilitybenevolence: Explanation based on both robot's ability and benevolence provided. @type explanation: str @param embodiment: the robot's embodiment - robot: The robot looks like a robot - dog: The robot looks like a dog @type embodiment: str @param acknowledgment: the robot's behavior regarding the acknowledgment of errors - no: The robot does not acknowledge its errors - yes: The robot acknowledges its errors @type acknowledgment: str @param root: the root directory to use for files (default is current working directory) @param ext: the file extension for the PsychSim scenario file - xml: Save as uncompressed XML - psy: Save as bzipped XML @type ext: str @param beliefs: if C{True}, store robot's uncertain beliefs in scenario file, rather than compute them on the fly. Storing in scenario file makes the scenario a more complete model, but greatly increases file sixe. Default is C{True} @type beliefs: bool """ print "**************************createWorld***********************" print 'Username:\t%s\nLevel:\t\t%s' % (username,level+1) print 'Ability\t\t%s\nExplanation:\t%s\nEmbodiment:\t%s\nAcknowledge:\t%s' % \ (ability,explanation,embodiment,acknowledgment) # Pre-compute symbols for this level's waypoints for point in WAYPOINTS[level]: if not point.has_key('symbol'): point['symbol'] = point['name'].replace(' ','') world = World() world.defineState(None,'level',int,lo=0,hi=len(WAYPOINTS)-1, description='Static variable indicating what mission level') world.setState(None,'level',level) world.defineState(None,'time',float) world.setState(None,'time',0.) world.defineState(None,'complete',bool) world.setState(None,'complete',False) world.addTermination(makeTree({'if': trueRow('complete'), True: True, False: False})) # Buildings threats = ['none','NBC','armed'] for waypoint in WAYPOINTS[level]: if not waypoint.has_key('symbol'): waypoint['symbol'] = waypoint['name'].replace(' ','') world.addAgent(Agent(waypoint['symbol'])) # Have we visited this waypoint? key = world.defineState(waypoint['symbol'],'visited',bool) world.setFeature(key,False) # Are there dangerous chemicals or armed people here? key = world.defineState(waypoint['symbol'],'danger',list,threats[:]) if waypoint.has_key('NBC') and waypoint['NBC']: world.setFeature(key,'NBC') elif waypoint.has_key('armed') and waypoint['armed']: world.setFeature(key,'armed') else: world.setFeature(key,'none') key = world.defineState(waypoint['symbol'],'recommendation',list, ['none','protected','unprotected']) world.setFeature(key,'none') # Human human = Agent('human') world.addAgent(human) world.defineState(human.name,'alive',bool) human.setState('alive',True) world.defineState(human.name,'deaths',int) human.setState('deaths',0) # Robot robot = Agent('robot') world.addAgent(robot) # Robot states world.defineState(robot.name,'waypoint',list,[point['symbol'] for point in WAYPOINTS[level]]) robot.setState('waypoint',WAYPOINTS[level][getStart(level)]['symbol']) world.defineState(robot.name,'explanation',list,['none','ability','abilitybenevolence','abilityconfidence','confidence']) robot.setState('explanation',explanation) world.defineState(robot.name,'embodiment',list,['robot','dog']) robot.setState('embodiment',embodiment) world.defineState(robot.name,'acknowledgment',list,['no','yes']) robot.setState('acknowledgment',acknowledgment) world.defineState(robot.name,'ability',list,['badSensor','badModel','good']) if ability is True: # Backward compatibility with boolean ability ability = 'good' elif ability is False: ability = 'badSensor' robot.setState('ability',ability) # State of the robot's sensors world.defineState(robot.name,'sensorModel',list,['good','bad']) robot.setState('sensorModel','good') world.defineState(robot.name,'command',list,['none']+[point['symbol'] for point in WAYPOINTS[level]]) robot.setState('command','none') # Actions for end in range(len(WAYPOINTS[level])): symbol = WAYPOINTS[level][end]['symbol'] # Robot movement action = robot.addAction({'verb': 'moveto','object': symbol}) # Legal if no contradictory command tree = makeTree({'if': equalRow(stateKey(robot.name,'command'),'none'), True: True, False: {'if': equalRow(stateKey(robot.name,'command'),symbol), True: True, False: False}}) robot.setLegal(action,tree) # Dynamics of robot's location tree = makeTree(setToConstantMatrix(stateKey(action['subject'],'waypoint'),symbol)) world.setDynamics(stateKey(action['subject'],'waypoint'),action,tree) # Dynamics of visited flag key = stateKey(symbol,'visited') tree = makeTree(setTrueMatrix(key)) world.setDynamics(key,action,tree) # Dynamics of time key = stateKey(None,'time') tree = setToConstantMatrix(key,0.) for start in range(len(WAYPOINTS[level])): if start != end: startsymbol = WAYPOINTS[level][start]['symbol'] if sequence: # Distance is measured by level sequence distance = abs(start-end)*50 else: try: distance = DISTANCES[WAYPOINTS[level][start]['name']][WAYPOINTS[level][end]['name']] except KeyError: try: distance = DISTANCES[WAYPOINTS[level][end]['name']][WAYPOINTS[level][start]['name']] except KeyError: distance = 250 tree = {'if': equalRow(stateKey(action['subject'],'waypoint'),startsymbol), True: setToConstantMatrix(key,float(distance)/1000.), False: tree} world.setDynamics(key,action,makeTree(tree)) # Human entry: Dead or alive if unprotected? key = stateKey(human.name,'alive') action = robot.addAction({'verb': 'recommend unprotected','object': symbol}) tree = makeTree({'if': equalRow(stateKey(symbol,'danger'),'none'), True: setTrueMatrix(key), False: setFalseMatrix(key)}) world.setDynamics(key,action,tree) robot.setLegal(action,makeTree(False)) # Human entry: How much "time" if protected? action = robot.addAction({'verb': 'recommend protected','object': symbol}) key = stateKey(None,'time') world.setDynamics(key,action,makeTree(setToConstantMatrix(key,0.25))) robot.setLegal(action,makeTree(False)) # Robot goals goal = minimizeFeature(stateKey(None,'time')) robot.setReward(goal,2.) goal = maximizeFeature(stateKey(human.name,'alive')) robot.setReward(goal,1.) for point in WAYPOINTS[level]: robot.setReward(maximizeFeature(stateKey(point['symbol'],'visited')),1.) if beliefs: # omega = 'danger' world.defineVariable(robot.name,ActionSet) # Robot beliefs world.setModel(robot.name,True) value = 1./float(len(WAYPOINTS[level])) # tree = KeyedVector({CONSTANT: world.value2float(omega,'none')}) for index in range(len(WAYPOINTS[level])): waypoint = WAYPOINTS[level][index] key = stateKey(waypoint['symbol'],'danger') # if index > 0: # Starting state is safe robot.setBelief(key,psychsim.probability.Distribution({'NBC': value/2., 'armed': value/2.,'none': 1.-value})) # Observation function # tree = {'if': equalRow(stateKey(robot.name,'waypoint'),waypoint['symbol']), # True: generateO(world,key), # False: tree} # robot.defineObservation(omega,makeTree(tree),domain=list,lo=['none','NBC','armed']) robot.defineObservation('microphone',makeTree(None),None,domain=list, lo=['nobody','friendly','suspicious']) robot.defineObservation('NBCsensor',makeTree(None),None,domain=bool) robot.defineObservation('camera',makeTree(None),None,domain=bool) else: robot.defineObservation('microphone',makeTree(None),None,domain=list, lo=['nobody','friendly','suspicious']) robot.defineObservation('NBCsensor',makeTree(None),None,domain=bool) robot.defineObservation('camera',makeTree(None),None,domain=bool) robot.setAttribute('horizon',1) world.setOrder([robot.name]) filename = getFilename(username,level,ext,root) world.save(filename,ext=='psy') WriteLogData('%s user %s, level %d, ability %s, explanation %s' % \ (CREATE_TAG,username,level,ability,explanation),username,level,root=root) return world def generateMicO(world,key): return {'if': equalRow(key,'armed'), True: {'distribution': [(KeyedVector({CONSTANT: world.value2float('microphone','nobody')}),0.04), (KeyedVector({CONSTANT: world.value2float('microphone','friendly')}),0.03), (KeyedVector({CONSTANT: world.value2float('microphone','suspicious')}),0.93)]}, False: {'distribution': [(KeyedVector({CONSTANT: world.value2float('microphone','nobody')}),0.48), (KeyedVector({CONSTANT: world.value2float('microphone','friendly')}),0.49), (KeyedVector({CONSTANT: world.value2float('microphone','suspicious')}),0.03)]}} def generateNBCO(world,key): """ @return: a observation function specification of the robot's NBC sensor @rtype: dict """ return {'if': equalRow(key,'NBC'), True: {'distribution': [(KeyedVector({CONSTANT: world.value2float('NBCsensor',False)}),0.1), (KeyedVector({CONSTANT: world.value2float('NBCsensor',True)}),0.9)]}, False: {'distribution': [(KeyedVector({CONSTANT: world.value2float('NBCsensor',False)}),0.95), (KeyedVector({CONSTANT: world.value2float('NBCsensor',True)}),0.05)]}, } def generateCameraO(world,key,belief=False): """ @return: a observation function specification for use in a PWL function @rtype: dict """ return {'if': equalRow(stateKey('robot','ability'),'badModel'), # Robot's O doesn't match up with its good observations True: {'if': equalRow(key,'armed'), True: {'distribution': [(KeyedVector({CONSTANT: world.value2float('camera',False)}),0.02), (KeyedVector({CONSTANT: world.value2float('camera',True)}),0.98)]}, False: {'distribution': [(KeyedVector({CONSTANT: world.value2float('camera',False)}),0.72), (KeyedVector({CONSTANT: world.value2float('camera',True)}),0.28)]}, }, False: {'if': equalRow(key,'armed'), True: {'distribution': [(KeyedVector({CONSTANT: world.value2float('camera',False)}),0.05), (KeyedVector({CONSTANT: world.value2float('camera',True)}),0.95)]}, False: {'distribution': [(KeyedVector({CONSTANT: world.value2float('camera',False)}),0.95), (KeyedVector({CONSTANT: world.value2float('camera',True)}),0.05)]}, }} def getStart(level): """ @return: the index of the starting waypoint for the given level @rtype: int """ for index in range(len(WAYPOINTS[level])): if WAYPOINTS[level][index].has_key('start'): return index else: return 0 def symbol2index(symbol,level=0): """ @return: the waypoint index corresponding to the given symbol (not full) name in the given level @rtype: int """ for index in range(len(WAYPOINTS[level])): if WAYPOINTS[level][index].has_key('symbol'): if WAYPOINTS[level][index]['symbol'] == symbol: return index elif WAYPOINTS[level][index]['name'].replace(' ','') == symbol: return index else: raise NameError,'Unknown waypoint %s for level %d' % (symbol,level) def index2symbol(index,level=0): """ @return: the symbol (not full) name corresponding to the given waypoint index @rtype: str """ try: return WAYPOINTS[level][index]['symbol'] except KeyError: return WAYPOINTS[level][index]['name'].replace(' ','') def getFilename(session=0,level=0,extension='xml',root='.'): """ @return: the scenario file name for the given session ID and level @rtype: str """ return os.path.join(root,'%s_%d.%s' % (session,level,extension)) def maxLevels(): """ @return: the number of levels defined @rtype: int """ return len(WAYPOINTS) def GetDecision(username,level,parameters,world=None,ext='xml',root='.',sleep=None): """ @param parameters: ignored if request is provided """ print "***********************GetDecision********************"; if sleep: time.sleep(sleep) filename = getFilename(username,level,ext,root) if world is None: # Get the world from the scenario file world = World(filename) oldVector = world.state[None].domain()[0] robot = world.agents['robot'] if 'robotWaypoint' in parameters: # Project world state robotIndex = int(parameters['robotWaypoint']) robotWaypoint = WAYPOINTS[level][robotIndex] if not robotWaypoint.has_key('symbol'): robotWaypoint['symbol'] = robotWaypoint['name'].replace(' ','') world.setState(robot.name,'waypoint',robotWaypoint['symbol']) else: # Read world state robotIndex = symbol2index(world.getState(robot.name,'waypoint').domain()[0],level) robotWaypoint = WAYPOINTS[level][robotIndex] if not robotWaypoint.has_key('symbol'): robotWaypoint['symbol'] = robotWaypoint['name'].replace(' ','') # Process command try: command = int(parameters['commandWaypoint']) if command >= 0: world.setState(robot.name,'command',WAYPOINTS[level][int(command)]['symbol']) else: command = None except KeyError: command = None except TypeError: comand = None if command is None: world.setState(robot.name,'command','none') WriteLogData('Received command: %s' % (command),username,level,root=root) # Find the best action values = [] actions = robot.getActions(oldVector) for action in actions: vector = KeyedVector({CONSTANT: 1.}) key = stateKey(robot.name,'waypoint') vector[key] = world.value2float(key,robotWaypoint['symbol']) vector['time'] = 0. key = stateKey(action['object'],'visited') vector[key] = oldVector[key] key = turnKey(robot.name) vector[key] = oldVector[key] outcome = world.stepFromState(vector,action) ER = robot.reward(outcome['new']) - robot.reward(vector) WriteLogData('ER(%s) = %4.2f' % (action,ER),username,level,root=root) values.append((ER,action,outcome['delta'])) best = max(values) decision = list(best[1])[0] destination = decision['object'] WriteLogData('%s %s' % (LOCATION_TAG,destination),username,level,root=root) index = symbol2index(destination,level) destination = WAYPOINTS[level][index] return index def GetAcknowledgment(user,recommendation,location,danger,username,level,parameters, world=None,ext='xml',root='.'): print "**********************Get Acknowledgment*******************" if world is None: # Get the world from the scenario file filename = getFilename(username,level,ext,root) world = World(filename) oldVector = world.state[None].domain()[0] robotIndex = symbol2index(location,level) beliefs = {'B_waypoint': WAYPOINTS[level][robotIndex]['name']} if recommendation == 'unprotected' and danger != 'none': # Robot mistakenly thought it was safe error = False elif recommendation == 'protected' and danger == 'none': # Robot mistakenly thought it was dangerous error = True elif recommendation != 'none': # Robot was right error = None else: # Robot didn't say anything, so not it's problem error = None if world.getState('robot','acknowledgment').domain()[0] == 'yes': ack = Template(TEMPLATES['acknowledgment'][error]).substitute(beliefs) else: ack = '' # Did the user die? death = not user and danger != 'none' if death: key = stateKey('human','deaths') old = world.getValue(key) world.setFeature(key,old+1) WriteLogData('%s %s %s %s %s %s (%s) (%s)' % \ (USER_TAG,user,location,danger,death, WAYPOINTS[level][robotIndex]['image'], WAYPOINTS[level][robotIndex]['comment'],ack), username,level,root) filename = getFilename(username,level,ext,root) world.save(filename,ext=='psy') return ack def GetRecommendation(username,level,parameters,world=None,ext='xml',root='.',sleep=None): """ Processes incoming observation and makes an assessment """ print "**********************Get Recommendation********************" if sleep: time.sleep(sleep) filename = getFilename(username,level,ext,root) if world is None: # Get the world from the scenario file world = World(filename) oldVector = world.state[None].domain()[0] robot = world.agents['robot'] if 'robotWaypoint' in parameters: # Project world state robotIndex = int(parameters['robotWaypoint']) robotWaypoint = WAYPOINTS[level][robotIndex] if not robotWaypoint.has_key('symbol'): robotWaypoint['symbol'] = robotWaypoint['name'].replace(' ','') world.setState(robot.name,'waypoint',robotWaypoint['symbol']) else: # Read world state robotIndex = symbol2index(world.getState(robot.name,'waypoint').domain()[0],level) robotWaypoint = WAYPOINTS[level][robotIndex] if not robotWaypoint.has_key('symbol'): robotWaypoint['symbol'] = robotWaypoint['name'].replace(' ','') world.setState(robotWaypoint['symbol'],'visited',True) # print robotWaypoint['name'] # Process scripted observations key = stateKey(robotWaypoint['symbol'],'danger') ability = robot.getState('ability').domain()[0] if ability == 'badSensor': sensorCorrect = False else: sensorCorrect = True # Generate individual sensor readings omega = {} danger = world.float2value(key,oldVector[key]) for sensor in robot.omega: try: omega[sensor] = robotWaypoint[sensor][sensorCorrect] except KeyError: # By default, don't sense anything if sensor == 'microphone': omega[sensor] = 'nobody' else: omega[sensor] = False # try: # omega = KeyedVector({'danger': robotWaypoint['observe'][sensorCorrect]}) # except KeyError: # # By default observe true value # omega = KeyedVector({'danger': world.float2value(key,oldVector[key])}) # try: # omega['microphone'] = robotWaypoint['microphone'][sensorCorrect] # except KeyError: # # By default observe nothing # omega['microphone'] = False # omega['NBCsensor'] = (omega['danger'] == 'NBC') # omega['camera'] = (omega['danger'] == 'armed') WriteLogData('NBC sensor: %s' % (omega['NBCsensor']),username,level,root=root) WriteLogData('Camera: %s' % (omega['camera']),username,level,root=root) WriteLogData('Microphone: %s' % (omega['microphone']),username,level,root=root) # Get robot's beliefs loc = stateKey(robot.name,'waypoint') if robot.models[True]['beliefs'] is True: # No explicit beliefs # value = 1./float(len(WAYPOINTS[level])) # oldBeliefs = psychsim.probability.Distribution({'NBC': value/2., 'armed': value/2., # 'none': 1.-value}) oldBeliefs = psychsim.probability.Distribution({'NBC': .2, 'armed': .2, 'none': .6}) # O = VectorDistribution(generateO(world,key)) # O = makeTree(generateO(world,key)).desymbolize(world.symbols) Omic = makeTree(generateMicO(world,key)).desymbolize(world.symbols) ONBC = makeTree(generateNBCO(world,key)).desymbolize(world.symbols) Ocamera = makeTree(generateCameraO(world,key)).desymbolize(world.symbols) assessment = Distribution() for belief in oldBeliefs.domain(): prob = oldBeliefs[belief] vector = KeyedVector({CONSTANT: 1., key: world.value2float(key,belief)}) # joint = world.float2value(key,O*vector) # joint = world.float2value(key,O[vector]*vector) probMic = world.float2value('microphone',Omic[vector]*vector) prob *= probMic[omega['microphone']] probNBC = world.float2value('NBCsensor',ONBC[vector]*vector) prob *= probNBC[omega['NBCsensor']] probCamera = world.float2value('camera',Ocamera[vector]*vector) prob *= probCamera[omega['camera']] assessment.addProb(belief,prob) else: # Use explicit beliefs oldBeliefs = world.getFeature(key,robot.models[True]['beliefs']) assessment = Distribution() for belief in oldBeliefs.domain(): vector = KeyedVector({CONSTANT: 1., key: world.value2float(key,belief), loc: world.value2float(key,robotWaypoint['symbol'])}) joint = robot.observe(vector,ActionSet()) assessment.addProb(belief,oldBeliefs[belief]*joint[omega]) assessment.normalize() for danger in assessment.domain(): WriteLogData('Posterior belief in %s: %d%%' % (danger,assessment[danger]*100.), username,level,root=root) # print assessment with tempfile.NamedTemporaryFile('w',dir=os.path.dirname(filename), delete=False) as tf: tf.write(world.__xml__().toprettyxml()) tempname = tf.name done = False while not done: try: os.remove(filename) os.rename(tempname, filename) done = True except WindowsError: time.sleep(1) # world.save(filename,ext=='psy') # What are my new beliefs? value = {} key = stateKey(robot.name,'waypoint') subBeliefs = VectorDistribution({KeyedVector({CONSTANT: 1.,key: world.value2float(key,robotWaypoint['symbol'])}): 1.}) key = stateKey(robotWaypoint['symbol'],'danger') subBeliefs.join(key,world.value2float(key,assessment)) key = stateKey('human','alive') subBeliefs.join(key,world.state[None].marginal(key)) key = stateKey(robot.name,'embodiment') subBeliefs.join(key,world.state[None].marginal(key)) subBeliefs.join('time',world.state[None].marginal('time')) key = turnKey(robot.name) subBeliefs.join(key,world.state[None].marginal(key)) keyList = subBeliefs.domain()[0].keys() keyList.remove(CONSTANT) # Which recommendation is better? for verb in ['recommend protected','recommend unprotected']: value[verb] = 0. action = Action({'subject': robot.name, 'verb': verb, 'object': robotWaypoint['symbol']}) for newVector in subBeliefs.domain(): result = world.stepFromState(newVector,action) assert len(result['new']) == 1 outcome = result['new'].domain()[0] reward = subBeliefs[newVector]*robot.reward(outcome) value[verb] += reward WriteLogData('Value of %s: %4.2f' % (verb,value[verb]),username,level,root=root) # Package up the separate components of my current model POMDP = {} # Add Omega_t, my latest observation for key,observation in omega.items(): POMDP['omega_%s' % (key)] = observation # Add A_t, my chosen action if value['recommend unprotected'] > value['recommend protected']: POMDP['A'] = 'recommend unprotected' safety = True world.setState(robotWaypoint['symbol'],'recommendation','unprotected') WriteLogData('%s: no' % (RECOMMEND_TAG),username,level,root=root) else: POMDP['A'] = 'recommend protected' safety = False world.setState(robotWaypoint['symbol'],'recommendation','protected') WriteLogData('%s: yes' % (RECOMMEND_TAG),username,level,root=root) # Add B_t, my current beliefs for key in keyList: belief = subBeliefs.marginal(key) feature = state2feature(key) best = belief.max() POMDP['B_%s' % (feature)] = world.float2value(key,best) if feature == 'waypoint': POMDP['B_%s' % (feature)] = WAYPOINTS[level][symbol2index(POMDP['B_%s' % (feature)],level)]['name'] POMDP['B_maxprob'] = belief[best] for value in belief.domain(): pct = round(100.*belief[value]) POMDP['B_%s_%s' % (feature,world.float2value(key,value))] = pct POMDP['B_%s_not_%s' % (feature,world.float2value(key,value))] = 100-pct # Use fixed explanation # TODO: make this a dynamic decision by the robot mode = world.getState(robot.name,'explanation').max() if mode == 'none': mode = '' explanation = ' '.join(explainDecision(safety,POMDP,mode)) WriteLogData('%s %s' % (MESSAGE_TAG,explanation),username,level,root=root) return explanation def explainDecision(decision,beliefs,mode): """ @param decision: the assessment of the safety of the given location (C{True} if safe, C{False} if dangerous) @type decision: bool @param location: the label of the location to which this recommendation applies @type location: str @param beliefs: a table of probabilities for the presence of different features at this location @type beliefs: strS{->}float @param mode: the type of explanation to give @type mode: str @return: a list of sentences @rtype: str[] """ result = [] result.append(Template(TEMPLATES['decision'][decision]).substitute(beliefs)) if 'confidence' in mode: result.append(Template(TEMPLATES['confidence'][decision]).substitute(beliefs)) if 'ability' in mode: result.append(Template(TEMPLATES['NBC'][beliefs['omega_NBCsensor']]).substitute(beliefs)) result.append(Template(TEMPLATES['armed'][beliefs['omega_camera']]).substitute(beliefs)) result.append(Template(TEMPLATES['microphone'][beliefs['omega_microphone']]).substitute(beliefs)) # for sensor in ['NBC','armed','microphone']: # result.append(Template(TEMPLATES[sensor][beliefs[sensor]]).substitute(beliefs)) if 'benevolence' in mode: result.append(Template(TEMPLATES['benevolence'][decision]).substitute(beliefs)) return result def WriteErrorLog(content="",root='.'): f = open(os.path.join(root,'ErrorLog.txt'),'a') print >> f,'[%s] %s' % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), content) f.close() def WriteLogData(content="",username=None,level=None,root='.'): """ (U) Creating logs """ filename = getFilename(username,level,extension='log',root=root) f = open(filename,'a') print >> f,'[%s] %s' % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),content) f.close() def readLogData(username,level,root='.'): """ Extracts key events from a log """ filename = getFilename(username,level,extension='log',root=root) log = [] start = None for line in fileinput.input(filename): elements = line.split() if '%s %s' % (elements[2],elements[3]) == RECOMMEND_TAG: now = datetime.datetime.strptime('%s %s' % (elements[0][1:],elements[1][:-1]),'%Y-%m-%d %H:%M:%S') log.insert(0,{'type': 'message','recommendation': elements[4], 'time': now-start}) elif elements[2] == MESSAGE_TAG: log[0]['content'] = ' '.join(elements[3:]) elif elements[2] == LOCATION_TAG: now = datetime.datetime.strptime('%s %s' % (elements[0][1:],elements[1][:-1]),'%Y-%m-%d %H:%M:%S') index = symbol2index(elements[3],level) waypoint = WAYPOINTS[level][index] log.insert(0,{'type': 'location','destination': waypoint['name'], 'buildingNo': index+1,'buildingTotal': len(WAYPOINTS[level]), 'time': now-start}) elif elements[2] == CREATE_TAG: start = datetime.datetime.strptime('%s %s' % (elements[0][1:],elements[1][:-1]),'%Y-%m-%d %H:%M:%S') log.insert(0,{'type': 'create', 'time': 'Start','start': start, 'ability': elements[8], 'explanation': elements[10]}) elif elements[2] == COMPLETE_TAG: now = datetime.datetime.strptime('%s %s' % (elements[0][1:],elements[1][:-1]),'%Y-%m-%d %H:%M:%S') log.insert(0,{'type': 'complete','success': elements[3] == 'success', 'time': now-start}) elif elements[2] == USER_TAG: log[0]['choice'] = elements[3] log[0]['location'] = WAYPOINTS[level][symbol2index(elements[4],level)]['name'] log[0]['danger'] = elements[5] log[0]['dead'] = elements[6] log[0]['image'] = elements[7] log[0]['content'] = ' '.join(elements[8:])[1:-1] if ') (' in log[0]['content']: log[0]['content'],log[0]['ack'] = log[0]['content'].split(') (') else: log[0]['ack'] = '' fileinput.close() return log def allVisited(world,level): for index in range(len(WAYPOINTS[level])): waypoint = index2symbol(index,level) visited = world.getState(waypoint,'visited').domain()[0] if not visited: return False else: return True def runMission(username,level,ability='good',explanation='none',embodiment='robot', acknowledgment='no',beliefs=False): # Remove any existing log file try: os.remove(getFilename(username,level,extension='log')) except OSError: # Probably didn't exist to begin with pass # Create initial scenario world = createWorld(username,level,ability,explanation,embodiment,acknowledgment, beliefs=beliefs) location = world.getState('robot','waypoint').domain()[0] waypoint = symbol2index(location,level) # Go through all the waypoints while not world.terminated(): parameters = {'robotWaypoint': waypoint, 'level': level} print GetRecommendation(username,level,parameters,world) # Was the robot right? recommendation = world.getState(index2symbol(waypoint,level),'recommendation').domain()[0] danger = world.getState(index2symbol(waypoint,level),'danger').domain()[0] print GetAcknowledgment(None,recommendation,location,danger,username,level,parameters,world) if allVisited(world,level): world.setFeature('complete',True) world.save(getFilename(username,level),False) else: # Continue onward waypoint = GetDecision(username,level,parameters,world) print index2symbol(waypoint,level) if __name__ == '__main__': import argparse parser = argparse.ArgumentParser() parser.add_argument('-b','--beliefs',action='store_true', help='store robot beliefs in scenario file [default: %(default)s]') args = vars(parser.parse_args()) username = 'autotest' sequence = ['scrn','snrn','snry','scry','scdn','sndn','sndy','scdy'] random.shuffle(sequence) start = time.time() for level in range(len(sequence)): config = sequence[level] ability = CODES['ability'][config[0]] explanation = CODES['explanation'][config[1]] embodiment = CODES['embodiment'][config[2]] acknowledgment = CODES['acknowledgment'][config[3]] runMission(username,level,ability,explanation,embodiment,acknowledgment,args['beliefs']) print time.time()-start