def _evaluate_invariants(self, state): dbm_op_seq = DBMOperationSequence() # Get invariant operations inv_operations = [] for inst_name, loc in state.location_state.items(): state.activate_instance_scope(inst_name) for inv in loc.invariants: constr_operation = self._make_constraint_operation_from_ast( constr_ast=inv.ast, state=state) inv_operations.append(constr_operation) # Apply invariant operations dbm_op_seq.extend(inv_operations) for inv in inv_operations: # print(inv) inv.apply(state.dbm_state) # Close DBM if any invariants were applied if len(inv_operations) > 0: close_operation = dbm_op_gen.generate_close() dbm_op_seq.append(close_operation) close_operation.apply(state.dbm_state) return {"dbm_op_seq": dbm_op_seq}
def _evaluate_resets(self, transition: Transition): dbm_op_seq = DBMOperationSequence() state = transition.target_state # Execute "reset" statements reset_operations = [] for inst_name, edge in transition.triggered_edges.items(): if edge is None: continue has_edge_scope = inst_name in transition.edge_scopes if has_edge_scope: state.add_local_scope(name="edge", scope=transition.edge_scopes[inst_name]) state.activate_instance_scope(inst_name) for update in edge.updates: self.c_evaluator.eval_ast(ast=update.ast, state=state) for reset in edge.resets: reset_operation = self._make_reset_operation_from_ast( reset_ast=reset.ast, state=state) reset_operations.append(reset_operation) if has_edge_scope: state.remove_local_scope() # Apply resets dbm_op_seq.extend(reset_operations) for reset in reset_operations: # print(reset) reset.apply(state.dbm_state) return {"dbm_op_seq": dbm_op_seq}
def _evaluate_locations(self, state): all_pot_trans = self._get_all_potential_transitions(state=state) all_urgent_or_committed_trans = list( filter(lambda trans: trans.urgent or trans.committed, all_pot_trans)) dbm_op_seq = DBMOperationSequence() if len(all_urgent_or_committed_trans) == 0: df_operation = dbm_op_gen.generate_delay_future() df_operation.apply(state.dbm_state) dbm_op_seq.append(df_operation) inv_res = self._evaluate_invariants(state) dbm_op_seq.extend(inv_res["dbm_op_seq"]) return {"dbm_op_seq": dbm_op_seq}
def _evaluate_guards(self, transition: Transition): dbm_op_seq = DBMOperationSequence() state = transition.target_state # Get guard operations grd_operations = [] var_guard_res = True for inst_name, edge in transition.triggered_edges.items(): if edge is None: continue state.activate_instance_scope(inst_name) has_edge_scope = inst_name in transition.edge_scopes if has_edge_scope: state.add_local_scope(name="edge", scope=transition.edge_scopes[inst_name]) for guard in edge.clock_guards: # if isinstance(guard, ClockGuard): # TODO: Remove distinguishing clock and variables guards in separate lists, as it affects the order constr_operation = self._make_constraint_operation_from_ast( constr_ast=guard.ast, state=state) grd_operations.append(constr_operation) for guard in edge.variable_guards: ret = self.c_evaluator.eval_ast(ast=guard.ast["expr"], state=state) var_guard_res = var_guard_res and ret if has_edge_scope: state.remove_local_scope() # Apply guards dbm_op_seq.extend(grd_operations) for guard in grd_operations: guard.apply(state.dbm_state) # Close DBM if any guards were applied if len(grd_operations) > 0: close_operation = dbm_op_gen.generate_close() dbm_op_seq.append(close_operation) close_operation.apply(state.dbm_state) return {"dbm_op_seq": dbm_op_seq, "var_guard_res": var_guard_res}
class Simulator: """A simulator for Uppaal model systems.""" def __init__(self): """Initializes UppaalSimulator.""" self.init_system_state = None self.system_state = None self.transitions = None self.recent_transition = None self.transition_trace = [] self.dbm_op_sequence = DBMOperationSequence() self.transition_counts = { "potential": None, "enabled": None, "valid": None } self.system = None self.c_language_parser = UppaalCLanguageParser( semantics=UppaalCLanguageSemantics()) self.c_evaluator = UppaalCEvaluator(do_log_details=False) def load_system(self, system_path): """Loads a system at a given path into the simulator. Args: system_path: The system path. """ with open(system_path) as file: system_xml_str = file.read() self.set_system(system_xml_str) def set_system(self, system): """Sets the system of the simulator. Args: system: The system as string or system object. """ if isinstance(system, str): system = uppaal_xml_to_system(system) self.system = system self.init_system_state = self.generate_init_system_state() self.init_simulator() def generate_init_system_state(self): """Generates the initial system state defined by the declaration and system declaration. Returns: The generated system state. """ system_state = SystemState() # Init const and variable state system_state.init_from_system(self.system) system_state.activate_system_scope(access_instance_scopes=True) return system_state def init_simulator(self): """Initializes the simulator.""" self.transition_trace = [] self.dbm_op_sequence = DBMOperationSequence() init_state = self.init_system_state.copy() initial_transition = Transition(source_state=None, triggered_edges=None, target_state=init_state) loc_res = self._evaluate_locations(initial_transition.target_state) initial_transition.dbm_op_sequence.extend(loc_res["dbm_op_seq"]) self.execute_transition(initial_transition) def get_sequence(self): """Gets the sequence of applied DBM operations. Returns: The DBM operation sequence. """ return self.dbm_op_sequence def set_current_state(self, state): """Sets the current simulator state. Args: state: The new state. """ self.system_state = state @staticmethod def _get_transition_type_from_source_locs(source_locs): urgent = False committed = False for loc in source_locs: urgent = urgent or loc.urgent committed = committed or loc.committed return urgent, committed def _get_all_potential_transitions(self, state): # Get all possible outgoing edges classified by synchronization type all_out_edges = {} for inst_name, loc in state.location_state.items(): state.activate_instance_scope(inst_name) no_sync_edges = [] caller_edges = {} listener_edges = {} for _, edge in loc.out_edges.items(): select_val_combinations = self._get_select_val_combinations( edge=edge, state=state) for select_val_comb in select_val_combinations: edge_scope = { k: UppaalVariable(name=k, val=v) for k, v in select_val_comb.items() } state.add_local_scope(name='edge', scope=edge_scope) if edge.sync is None: no_sync_edges.append((edge_scope, edge)) else: chan_obj: UppaalChan = self.c_evaluator.eval_ast( edge.sync.ast["channel"], state).val if edge.sync.ast["op"] == '!': if chan_obj not in caller_edges: caller_edges[chan_obj] = [] caller_edges[chan_obj].append((edge_scope, edge)) else: if chan_obj not in listener_edges: listener_edges[chan_obj] = [] listener_edges[chan_obj].append((edge_scope, edge)) state.remove_local_scope() all_out_edges[inst_name] = { "no_sync": no_sync_edges, "caller": caller_edges, "listener": listener_edges } # Get all potential transitions (target states do not need to be known at this point) all_pot_trans = [] inst_names = state.location_state.keys() for i, (inst_name, inst_edges) in enumerate(all_out_edges.items()): for edge_data in inst_edges[ "no_sync"]: # Get non-synced transitions triggered_edges_data = dict.fromkeys(inst_names, (None, None)) triggered_edges_data[inst_name] = edge_data edge_scopes = { k: v[0] for (k, v) in triggered_edges_data.items() } triggered_edges = { k: v[1] for (k, v) in triggered_edges_data.items() } source_locs_of_involved_edges = map( lambda e: e.source, filter(lambda e: e is not None, triggered_edges.values())) loc_urgent, loc_committed = self._get_transition_type_from_source_locs( source_locs_of_involved_edges) trans = Transition(source_state=state, triggered_edges=triggered_edges, target_state=None, urgent=loc_urgent, committed=loc_committed, edge_scopes=edge_scopes) all_pot_trans.append(trans) for chan_obj, caller_edges in inst_edges["caller"].items(): if chan_obj.broadcast: # Get broadcast-sync transitions broadcast_listeners = OrderedDict() for j, (other_inst_name, other_inst_edges) in enumerate( all_out_edges.items()): if i == j: # Skip listener edges of caller instance broadcast_listeners[other_inst_name] = [(None, None)] else: listener_edges = other_inst_edges["listener"].get( chan_obj, []) broadcast_listeners[ other_inst_name] = listener_edges broadcast_listener_combinations = product_dict( broadcast_listeners) for broadcast_listener_combination in broadcast_listener_combinations: for caller_edge_data in caller_edges: triggered_edges_data = copy.copy( broadcast_listener_combination) triggered_edges_data[inst_name] = caller_edge_data edge_scopes = { k: v[0] for (k, v) in triggered_edges_data.items() } triggered_edges = { k: v[1] for (k, v) in triggered_edges_data.items() } source_locs_of_involved_edges = map( lambda e: e.source, filter(lambda e: e is not None, triggered_edges.values())) loc_urgent, loc_committed = self._get_transition_type_from_source_locs( source_locs_of_involved_edges) trans = Transition(source_state=state, triggered_edges=triggered_edges, target_state=None, urgent=chan_obj.urgent or loc_urgent, committed=loc_committed, edge_scopes=edge_scopes) all_pot_trans.append(trans) else: # Get binary-sync transitions for caller_edge_data in caller_edges: for j, (other_inst_name, other_inst_edges) in enumerate( all_out_edges.items()): listener_edges = other_inst_edges["listener"].get( chan_obj) if i == j or listener_edges is None: # Skip insts of caller and without listeners on chan continue for listener_edge_data in listener_edges: triggered_edges_data = dict.fromkeys( inst_names, (None, None)) triggered_edges_data[ inst_name] = caller_edge_data triggered_edges_data[ other_inst_name] = listener_edge_data edge_scopes = { k: v[0] for (k, v) in triggered_edges_data.items() } triggered_edges = { k: v[1] for (k, v) in triggered_edges_data.items() } source_locs_of_involved_edges = map( lambda e: e.source, filter(lambda e: e is not None, triggered_edges.values())) loc_urgent, loc_committed = self._get_transition_type_from_source_locs( source_locs_of_involved_edges) trans = Transition( source_state=state, triggered_edges=triggered_edges, target_state=None, urgent=chan_obj.urgent or loc_urgent, committed=loc_committed, edge_scopes=edge_scopes) all_pot_trans.append(trans) # Filter out non-committed transitions if committed current locations exist source_locs = state.location_state.values() has_committed_locs = any(loc.committed for loc in source_locs) if has_committed_locs: all_committed_trans = list( filter(lambda trans_: trans_.committed, all_pot_trans)) all_pot_trans = all_committed_trans if all_committed_trans else all_pot_trans # print(f'Potential transitions: {len(all_pot_trans)}') return all_pot_trans def _get_select_val_combinations(self, edge, state): select_val_iterators = OrderedDict() for select in edge.selects: select_var_name = select.ast["name"] _, select_var_type = self.c_evaluator.eval_ast( ast=select.ast["type"], state=state) key = select_var_name select_val_iterators[key] = select_var_type select_val_combinations = product_dict(select_val_iterators) return select_val_combinations def _get_all_enabled_transitions(self, state, all_pot_trans=None): if all_pot_trans is None: all_pot_trans = self._get_all_potential_transitions(state=state) enabled_transitions = [] for trans in all_pot_trans: # Init target state from source state trans.target_state = trans.source_state.copy() # Update target locations from triggered edges for inst_name, edge in trans.triggered_edges.items(): if edge is None: continue trans.target_state.location_state[inst_name] = edge.target grd_res = self._evaluate_guards(transition=trans) trans.dbm_op_sequence.extend(grd_res["dbm_op_seq"]) if not trans.target_state.dbm_state.is_empty( ) and grd_res["var_guard_res"]: enabled_transitions.append(trans) return enabled_transitions def _get_all_valid_transitions(self, state, all_enabled_trans=None): if all_enabled_trans is None: all_enabled_trans = self._get_all_enabled_transitions( state=state, all_pot_trans=None) valid_transitions = [] for trans in all_enabled_trans: reset_res = self._evaluate_resets(transition=trans) trans.dbm_op_sequence.extend(reset_res["dbm_op_seq"]) loc_res = self._evaluate_locations(state=trans.target_state) trans.dbm_op_sequence.extend(loc_res["dbm_op_seq"]) if not trans.target_state.dbm_state.is_empty(): valid_transitions.append(trans) return valid_transitions def _evaluate_guards(self, transition: Transition): dbm_op_seq = DBMOperationSequence() state = transition.target_state # Get guard operations grd_operations = [] var_guard_res = True for inst_name, edge in transition.triggered_edges.items(): if edge is None: continue state.activate_instance_scope(inst_name) has_edge_scope = inst_name in transition.edge_scopes if has_edge_scope: state.add_local_scope(name="edge", scope=transition.edge_scopes[inst_name]) for guard in edge.clock_guards: # if isinstance(guard, ClockGuard): # TODO: Remove distinguishing clock and variables guards in separate lists, as it affects the order constr_operation = self._make_constraint_operation_from_ast( constr_ast=guard.ast, state=state) grd_operations.append(constr_operation) for guard in edge.variable_guards: ret = self.c_evaluator.eval_ast(ast=guard.ast["expr"], state=state) var_guard_res = var_guard_res and ret if has_edge_scope: state.remove_local_scope() # Apply guards dbm_op_seq.extend(grd_operations) for guard in grd_operations: guard.apply(state.dbm_state) # Close DBM if any guards were applied if len(grd_operations) > 0: close_operation = dbm_op_gen.generate_close() dbm_op_seq.append(close_operation) close_operation.apply(state.dbm_state) return {"dbm_op_seq": dbm_op_seq, "var_guard_res": var_guard_res} def _evaluate_resets(self, transition: Transition): dbm_op_seq = DBMOperationSequence() state = transition.target_state # Execute "reset" statements reset_operations = [] for inst_name, edge in transition.triggered_edges.items(): if edge is None: continue has_edge_scope = inst_name in transition.edge_scopes if has_edge_scope: state.add_local_scope(name="edge", scope=transition.edge_scopes[inst_name]) state.activate_instance_scope(inst_name) for update in edge.updates: self.c_evaluator.eval_ast(ast=update.ast, state=state) for reset in edge.resets: reset_operation = self._make_reset_operation_from_ast( reset_ast=reset.ast, state=state) reset_operations.append(reset_operation) if has_edge_scope: state.remove_local_scope() # Apply resets dbm_op_seq.extend(reset_operations) for reset in reset_operations: # print(reset) reset.apply(state.dbm_state) return {"dbm_op_seq": dbm_op_seq} def _evaluate_locations(self, state): all_pot_trans = self._get_all_potential_transitions(state=state) all_urgent_or_committed_trans = list( filter(lambda trans: trans.urgent or trans.committed, all_pot_trans)) dbm_op_seq = DBMOperationSequence() if len(all_urgent_or_committed_trans) == 0: df_operation = dbm_op_gen.generate_delay_future() df_operation.apply(state.dbm_state) dbm_op_seq.append(df_operation) inv_res = self._evaluate_invariants(state) dbm_op_seq.extend(inv_res["dbm_op_seq"]) return {"dbm_op_seq": dbm_op_seq} def _evaluate_invariants(self, state): dbm_op_seq = DBMOperationSequence() # Get invariant operations inv_operations = [] for inst_name, loc in state.location_state.items(): state.activate_instance_scope(inst_name) for inv in loc.invariants: constr_operation = self._make_constraint_operation_from_ast( constr_ast=inv.ast, state=state) inv_operations.append(constr_operation) # Apply invariant operations dbm_op_seq.extend(inv_operations) for inv in inv_operations: # print(inv) inv.apply(state.dbm_state) # Close DBM if any invariants were applied if len(inv_operations) > 0: close_operation = dbm_op_gen.generate_close() dbm_op_seq.append(close_operation) close_operation.apply(state.dbm_state) return {"dbm_op_seq": dbm_op_seq} def get_transitions(self): """Gets all valid transitions for the current state. Returns: The list of valid transitions. """ return self._get_all_valid_transitions(state=self.system_state) def _make_constraint_operation_from_ast(self, constr_ast, state): dbm_constr_ast = adapt_dbm_constraint_ast(constr_ast) # if dbm_constr_ast["clock1"] is not None: clock1 = self.c_evaluator.eval_ast(ast=dbm_constr_ast["clock1"], state=state) clock1_name = clock1.name # else: # clock1_name = "T0_REF" # Note: Cannot occur, as "-t2 (<|<=|>=|>) c" is not supported by Uppaal if dbm_constr_ast["clock2"] is not None: clock2 = self.c_evaluator.eval_ast(ast=dbm_constr_ast["clock2"], state=state) clock2_name = clock2.name else: clock2_name = "T0_REF" rel = relation_from_ast_op[dbm_constr_ast["rel"]] val = self.c_evaluator.eval_ast(dbm_constr_ast["val"], state) constr_operation = dbm_op_gen.generate_constraint(clock1=clock1_name, clock2=clock2_name, rel=rel, val=val) return constr_operation def _make_reset_operation_from_ast(self, reset_ast, state): dbm_reset_ast = adapt_dbm_reset_ast(reset_ast) clock = self.c_evaluator.eval_ast(ast=dbm_reset_ast["clock"], state=state) clock_name = clock.name val = self.c_evaluator.eval_ast(dbm_reset_ast["val"], state) reset_operation = dbm_op_gen.generate_reset(clock=clock_name, val=val) return reset_operation def execute_transition(self, transition): """Executes a given transition from the current state. Args: transition: The transition that is executed. """ self.system_state = transition.target_state potential_transitions = self._get_all_potential_transitions( state=self.system_state) enabled_transitions = self._get_all_enabled_transitions( state=self.system_state, all_pot_trans=potential_transitions) valid_transitions = self._get_all_valid_transitions( state=self.system_state, all_enabled_trans=enabled_transitions) self.system_state.transitions = valid_transitions self.transition_counts = { "potential": len(potential_transitions), "enabled": len(enabled_transitions), "valid": len(valid_transitions) } self.transitions = valid_transitions self.transition_trace.append(transition) self.dbm_op_sequence.extend(transition.dbm_op_sequence) def simulate_step(self): """Performs a single random simulation step. Returns: The executed transition. """ if not self.transitions: print(f'No transitions possible from current state.') return None random_transition_id = random.randint(0, len(self.transitions) - 1) transition = self.transitions[random_transition_id] self.execute_transition(transition) return transition def simulate(self, time_scope=None, max_steps=None ): # TODO: Add "T_GLOBAL" to system if it does not exist """Simulates the system up to a given time value of step count. Args: time_scope: The maximum time scope of the simulation. max_steps: The maximum number of simulation steps. """ if time_scope is None and max_steps is None: raise TypeError( f'Either of parameters "time_scope" or "steps" need to be set.' ) global_time_interval = self.system_state.dbm_state.get_interval( "T_GLOBAL") step = 0 while ((time_scope is None or global_time_interval.lower.val <= time_scope) and (max_steps is None or step < max_steps)): self.simulate_step() global_time_interval = self.system_state.dbm_state.get_interval( "T_GLOBAL") step += 1 print(global_time_interval) def revert_to_state_by_index(self, idx): """Reverts the simulation to the state at given index. Args: idx: The targeted state index. """ trans = self.transition_trace[idx] self.transition_trace = self.transition_trace[:idx + 1] self.system_state = trans.target_state valid_transitions = self._get_all_valid_transitions( state=self.system_state, all_enabled_trans=None) self.transitions = valid_transitions