class Replayer(object): """ Base class for replaying log files :ivar files: List of names of the log files to process :type files: List(str) :ivar rddl_file: Name of file containing the RDDL specification of the domain :type rddl_file: str :ivar action_file: Name of CSV file containing the mapping between JSON messages and PsychSim actions :type action_file: str """ OBSERVER = 'ATOMIC' COUNT_ACTIONS_ARGS = [ ('Event:dialogue_event', {}), ('Event:VictimPickedUp', {}), ('Event:VictimPlaced', {}), ('Event:ToolUsed', {}), ('Event:Triage', {'triage_state': 'SUCCESSFUL'}), ('Event:RoleSelected', {}) ] def __init__(self, files=[], maps=None, processor=None, rddl_file=None, action_file=None, feature_output=None, logger=logging): # Extract files to process self.files = accumulate_files(files) self.processor = processor self.logger = logger self.rddl_file = rddl_file self.action_file = action_file if self.action_file: Msg2ActionEntry.read_psysim_msg_conversion(self.action_file) self.msg_types = Msg2ActionEntry.get_msg_types() else: self.msg_types = None self.rddl_converter = None # Feature count bookkeeping self.derived_features = [] self.feature_output = feature_output self.feature_data = [] self.condition_fields = None # information for each log file # TODO maybe encapsulate in an object and send as arg in post_replay()? self.world = None self.triage_agent = None self.observer = None self.victims = None self.world_map = None self.map_table = None self.parser = None self.conditions = None self.file_name = None # Extract maps self.maps = get_default_maps(logger) if maps is None else maps def get_map(self, logger=logging): # try to get map name directly from conditions dictionary try: map_name = self.conditions['CondWin'] map_table = self.maps[map_name] return map_name, map_table except KeyError: # Maybe Phase 1 filename scheme? map_name = self.conditions['CondWin'][0] map_table = self.maps[map_name] return map_name, map_table # todo to be retro-compatible would have to determine the map some other way.. logger.error('Unable to find matching map') return None, None def process_files(self, num_steps=0, fname=None): """ :param num_steps: if nonzero, the maximum number of steps to replay from each log (default is 0) :type num_steps: int :param fname: Name of log file to process (default is all of them) :type fname: str """ if fname is None: files = self.files else: files = [fname] # Get to work for fname in files: self.file_name = fname logger = self.logger.getLogger(os.path.splitext(os.path.basename(fname))[0]) logger.debug('Full path: {}'.format(fname)) self.conditions = filename_to_condition(os.path.splitext(os.path.basename(fname))[0]) # Parse events from log file logger_name = type(self.processor).__name__ if self.processor is not None else '' _, ext = os.path.splitext(fname) ext = ext.lower() if ext == '.csv' or ext == '.xlsx': map_name, self.map_table = self.get_map(logger) if map_name is None or self.map_table is None: # could not determine map continue self.parser = ProcessCSV(fname, self.processor, logger.getChild(logger_name)) elif ext == '.metadata': try: self.parser = MsgQCreator(fname, self.processor, logger=logger.getChild(logger_name)) except: logger.error('Unable to extract actions/events') logger.error(traceback.format_exc()) continue self.derived_features = _get_derived_features(self.parser) else: raise ValueError('Unable to parse log file: {}, unknown extension.'.format(fname)) # set parser to processor if self.processor is not None: self.processor.parser = self.parser if not self.pre_replay(logger=logger.getChild('pre_replay')): # Failure in creating world continue # Replay actions from log file try: self.parser.getActionsAndEvents(self.victims, self.world_map) except: logger.error(traceback.format_exc()) logger.error('Unable to extract actions/events') continue if num_steps == 0: last = len(self.parser.actions) else: last = num_steps + 1 try: self.replay(last, logger) except: logger.error(traceback.format_exc()) logger.error('Unable to complete re-simulation') self.post_replay() if self.world_map: self.world_map.clear() if self.feature_output: assert self.condition_fields is not None, 'Never extracted condition fields from filename' with open(self.feature_output, 'w') as csvfile: cumulative_fields = [set(row.keys()) for row in self.feature_data] fields = self.condition_fields + sorted(set.union(*cumulative_fields)-{'File'}) fields.append('File') writer = csv.DictWriter(csvfile, fields, extrasaction='ignore') writer.writeheader() for row in self.feature_data: row.update(filename_to_condition(row['File'])) writer.writerow(row) def pre_replay(self, logger=logging): # Create PsychSim model logger.info('Creating world') try: self.parser.startProcessing(self.derived_features, self.msg_types) except: logger.error('Unable to start parser') logger.error(traceback.format_exc()) return False if self.feature_output: # processes data to extract features depending on type of count features = {'File': os.path.splitext(os.path.basename(self.file_name))[0]} if self.condition_fields is None: self.condition_fields = list(filename_to_condition(features['File']).keys()) for feature in self.derived_features: features.update(_get_feature_values(feature)) self.feature_data.append(features) try: if self.rddl_file: # Team mission self.rddl_converter = Converter() self.rddl_converter.convert_file(self.rddl_file) self.world = self.rddl_converter.world players = set(self.parser.agentToPlayer.keys()) zero_models = {name: self.world.agents[name].zero_level() for name in players} for name in players: agent = self.world.agents[name] agent.create_belief_state() agent.create_belief_state(model=zero_models[name]) agent.setAttribute('selection', 'distribution', zero_models[name]) for other_name in players-{name}: other_agent = self.world.agents[other_name] self.world.setModel(name, zero_models[name], other_name, other_agent.get_true_model()) agent.set_observations() else: # Solo mission self.world, self.triage_agent, _, self.victims, self.world_map = \ make_single_player_world(self.parser.player_name(), self.map_table.init_loc, self.map_table.adjacency, self.map_table.victims, False, True, False, logger.getChild('make_single_player_world')) except: logger.error('Unable to create world') exc_type, exc_value, exc_traceback = sys.exc_info() logger.error(traceback.format_exc()) return False return True def replay(self, duration, logger): if isinstance(self.parser, MsgQCreator): num = len(self.parser.actions) for i, msgs in enumerate(self.parser.actions): logger.info(f'Message {i} out of {num}') debug = {ag_name: {} for ag_name in self.rddl_converter.actions} actions = {} any_none = False for player_name, msg in msgs.items(): action_name = Msg2ActionEntry.get_action(msg) if action_name not in self.rddl_converter.actions[player_name]: any_none = True logger.warning(f'Msg {msg} has no associated action ({action_name} missing from {", ".join(sorted(self.rddl_converter.actions[player_name]))})') else: logger.info(f'Msg {msg} becomes {action_name}') action = self.rddl_converter.actions[player_name][action_name] actions[player_name] = action if any_none: continue self.pre_step() self.world.step(actions, debug=debug) self.post_step(actions, debug) else: self.parser.runTimeless(self.world, 0, duration, duration, permissive=True) def post_replay(self): pass def pre_step(self): pass def post_step(self, debug): pass def read_filename(self, fname): raise DeprecationWarning('Use filename_to_condition function (in this module) instead') def parameterized_replay(self, args): if args['profile']: return cProfile.run('self.process_files(args["number"])', sort=1) elif args['1']: return self.process_files(args['number'], replayer.files[0]) else: return self.process_files(args['number'])
help='Whether to select an outcome if dynamics are stochastic.') parser.add_argument( '--log-actions', action='store_true', help='Whether to log agents\' actions in addition to current state.') parser.add_argument( '--log-rewards', action='store_true', help= 'Whether to log agents\' rewards wrt chosen actions in addition to current state.' ) args = parser.parse_args() args.log_rewards = True conv = Converter() conv.convert_file(RDDL_FILE, verbose=True) ################ J S O N M S G T O P S Y C H S I M A C T I O N N A M E fname = '../data/rddl_psim/rddl2actions_small.csv' Msg2ActionEntry.read_psysim_msg_conversion(fname) ################# F A K E M S G S all_msgs = [] #all_msgs.append({'p1': {'type':'Marker Block 1', 'playername':'p1', 'sub_type':'Event:MarkerPlaced'}, # 'p2':{'room_name':'loc12', 'playername':'p2', 'sub_type':'Event:location'}}) #all_msgs.append({'p1': {'room_name':'loc12', 'playername':'p1', 'sub_type':'Event:location'}, # 'p2':{'room_name':'loc11', 'playername':'p2', 'sub_type':'Event:location'}}) #all_msgs.append({'p1': {'room_name':'tkt_4', 'playername':'p1', 'sub_type':'Event:Location'}, # 'p2':{'room_name':'tkt_5', 'playername':'p2', 'sub_type':'Event:Location'},
action='store_true', help= 'Whether to log agents\' rewards wrt chosen actions in addition to current state.' ) args = parser.parse_args() # prepare log to screen logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s', datefmt='%H:%M:%S') # parse and convert RDDL file if not os.path.isfile(args.input): raise ValueError(f'Could not find RDDL file: {args.input}') conv = Converter() conv.convert_file(args.input, verbose=True) logging.info('') logging.info('==================================================') logging.info('Starting simulation...') conv.log_state() for i in tqdm(range(args.steps), ): logging.info('\n__________________________________________________') logging.info(f'Step {i}:') debug = {ag_name: {} for ag_name in conv.actions.keys() } if args.log_rewards else set() conv.world.step(debug=debug, threshold=args.threshold, select=args.select) conv.log_state(log_actions=args.log_actions)