def gen_hypothesis(self) -> Onfsm: """ Generate automaton based on the values found in the abstracted observation table. Returns: Current abstracted hypothesis """ state_distinguish = dict() states_dict = dict() initial = None unified_S = self.S + self.S_dot_A stateCounter = 0 for prefix in self.S: state_id = f's{stateCounter}' states_dict[prefix] = OnfsmState(state_id) states_dict[prefix].prefix = prefix state_distinguish[self.row_to_hashable( prefix)] = states_dict[prefix] if prefix == self.S[0]: initial = states_dict[prefix] stateCounter += 1 for prefix in self.S: similar_rows = [] for row in unified_S: if self.row_to_hashable(row) == self.row_to_hashable(prefix): similar_rows.append(row) for row in similar_rows: for a in self.A: for t in self.observation_table.T[row][a]: if (row[0] + a, row[1] + tuple([t])) in unified_S: state_in_S = state_distinguish[ self.row_to_hashable( (row[0] + a, row[1] + tuple([t])))] if (t, state_in_S ) not in states_dict[prefix].transitions[a[0]]: states_dict[prefix].transitions[a[0]].append( (t, state_in_S)) assert initial automaton = Onfsm(initial, [s for s in states_dict.values()]) automaton.characterization_set = self.E return automaton
def get_benchmark_ONFSM(): """ Returns ONFSM presented in 'Learning Finite State Models of Observable Nondeterministic Systems in a Testing Context'. """ from aalpy.automata import Onfsm, OnfsmState a = OnfsmState('q0') b = OnfsmState('q1') c = OnfsmState('g2') d = OnfsmState('q3') a.transitions['a'].append((0, b)) a.transitions['b'].append((2, a)) a.transitions['b'].append((0, c)) b.transitions['a'].append((2, a)) b.transitions['b'].append((3, b)) c.transitions['a'].append((2, d)) c.transitions['b'].append((0, c)) c.transitions['b'].append((3, c)) d.transitions['a'].append((2, b)) d.transitions['b'].append((3, d)) return Onfsm(a, [a, b, c, d])
def get_benchmark_ONFSM(): """ Returns ONFSM presented in 'Learning Finite State Models of Observable Nondeterministic Systems in a Testing Context'. """ a = OnfsmState('a') b = OnfsmState('b') c = OnfsmState('c') d = OnfsmState('d') a.transitions['a'].append((0, b)) a.transitions['b'].append((2, a)) a.transitions['b'].append((0, c)) b.transitions['a'].append((2, a)) b.transitions['b'].append((3, b)) c.transitions['a'].append((2, d)) c.transitions['b'].append((0, c)) c.transitions['b'].append((3, c)) d.transitions['a'].append((2, b)) d.transitions['b'].append((3, d)) return Onfsm(a, [a, b, c, d])
def gen_hypothesis(self) -> Automaton: """ Generate automaton based on the values found in the observation table. Returns: Current hypothesis """ state_distinguish = dict() states_dict = dict() initial = None stateCounter = 0 for prefix in self.S: state_id = f's{stateCounter}' states_dict[prefix] = OnfsmState(state_id) states_dict[prefix].prefix = prefix state_distinguish[self.row_to_hashable( prefix)] = states_dict[prefix] if prefix == self.S[0]: initial = states_dict[prefix] stateCounter += 1 for prefix in self.S: curr_node = self.sul.pta.get_to_node(prefix[0], prefix[1]) for a in self.A: trace = self.sul.pta.get_all_traces(curr_node, a) for t in trace: reached_row = (prefix[0] + a, prefix[1] + (t[-1], )) if self.row_to_hashable( reached_row) not in state_distinguish.keys(): print('reeee') state_in_S = state_distinguish[self.row_to_hashable( reached_row)] assert state_in_S # shouldn't be necessary because of the if condition states_dict[prefix].transitions[a[0]].append( (t[-1], state_in_S)) assert initial automaton = Onfsm(initial, [s for s in states_dict.values()]) automaton.characterization_set = self.E return automaton
def cex_processing(self, cex: tuple, hypothesis: Onfsm): """ Add counterexample to the observation table. If the counterexample leads to a state where an output of the same equivalence class already exists, the prefixes of the counterexample are added to S.A. Otherwise, the postfixes of counterexample are added to E. Args: cex: counterexample that should be added to the observation table hypothesis: onfsm that implements the counterexample """ cex_len = len(cex[0]) hypothesis.reset_to_initial() for step in range(0, cex_len - 1): hypothesis.step_to(cex[0][step], cex[1][step]) possible_outputs = hypothesis.outputs_on_input(cex[0][cex_len - 1]) equivalent_output = False for out in possible_outputs: abstracted_out = self.get_abstraction(out) abstracted_out_cex = self.get_abstraction(cex[1][cex_len - 1]) if abstracted_out_cex == abstracted_out: equivalent_output = True break if equivalent_output: # add prefixes of cex to S_dot_A cex_prefixes = [(tuple(cex[0][0:i + 1]), tuple(cex[1][0:i + 1])) for i in range(0, len(cex[0]))] prefixes_to_extend = self.extend_S_dot_A(cex_prefixes) # CHANGED: REMOVED # self.observation_table.S_dot_A.extend(prefixes_to_extend) self.update_obs_table(s_set=prefixes_to_extend) else: # add distinguishing suffixes of cex to E # CHANGED CEX PROX # TODO: this will now not work as cex processing was changed # cex_suffixes = non_det_longest_prefix_cex_processing(self.observation_table, cex) cex_suffixes = all_suffixes(cex[0]) added_suffixes = extend_set(self.observation_table.E, cex_suffixes) self.update_obs_table(e_set=added_suffixes)
def gen_hypothesis(self) -> Automaton: """ Generate automaton based on the values found in the observation table. Returns: Current hypothesis """ state_distinguish = dict() states_dict = dict() initial = None stateCounter = 0 for prefix in self.S: state_id = f's{stateCounter}' states_dict[prefix] = OnfsmState(state_id) states_dict[prefix].prefix = prefix state_distinguish[self.row_to_hashable( prefix)] = states_dict[prefix] if prefix == self.S[0]: initial = states_dict[prefix] stateCounter += 1 for prefix in self.S: for a in self.A: for t in self.T[prefix][a]: state_in_S = state_distinguish[self.row_to_hashable( (prefix[0] + a, prefix[1] + tuple([t])))] assert state_in_S states_dict[prefix].transitions[a[0]].append( (t, state_in_S)) assert initial automaton = Onfsm(initial, [s for s in states_dict.values()]) automaton.characterization_set = self.E return automaton
def generate_random_ONFSM(num_states, num_inputs, num_outputs, multiple_out_prob=0.1): """ Randomly generate an observable non-deterministic finite-state machine. Args: num_states: number of states num_inputs: number of inputs num_outputs: number of outputs multiple_out_prob: probability that state will have multiple outputs (Default value = 0.1) Returns: randomly generated ONFSM """ inputs = [ random_string_generator(random.randint(1, 3)) for _ in range(num_inputs) ] outputs = [ random_string_generator(random.randint(3, 7)) for _ in range(num_outputs) ] states = [] for i in range(num_states): state = OnfsmState(f's{i}') states.append(state) for state in states: for i in inputs: state_outputs = 1 if random.random() <= multiple_out_prob and num_outputs > 1: state_outputs = random.randint(2, num_outputs) random_out = random.sample(outputs, state_outputs) for index in range(state_outputs): state.transitions[i].append( (random_out[index], random.choice(states))) return Onfsm(states[0], states)
def generate_random_ONFSM(num_states, num_inputs, num_outputs, multiple_out_prob=0.1): """ Randomly generate an observable non-deterministic finite-state machine. Args: num_states: number of states num_inputs: number of inputs num_outputs: number of outputs multiple_out_prob: probability that state will have multiple outputs (Default value = 0.5) Returns: randomly generated ONFSM """ inputs = [f'i{i+1}' for i in range(num_inputs)] outputs = [f'o{i+1}' for i in range(num_outputs)] states = [] for i in range(num_states): state = OnfsmState(f's{i}') states.append(state) state_buffer = states.copy() for state in states: for i in inputs: state_outputs = 1 if random.random() <= multiple_out_prob and num_outputs >= 2: state_outputs = random.randint(2, num_outputs) random_out = random.sample(outputs, state_outputs) for index in range(state_outputs): if state_buffer: new_state = random.choice(state_buffer) state_buffer.remove(new_state) else: new_state = random.choice(states) state.transitions[i].append((random_out[index], new_state)) return Onfsm(states[0], states)
def get_ONFSM(): """ Returns example of an ONFSM. """ from aalpy.automata import Onfsm, OnfsmState q0 = OnfsmState('q0') q1 = OnfsmState('q1') q2 = OnfsmState('q2') q3 = OnfsmState('q3') q4 = OnfsmState('q4') q5 = OnfsmState('q5') q6 = OnfsmState('q6') q7 = OnfsmState('q7') q8 = OnfsmState('q8') q0.transitions['a'].append((2, q1)) q0.transitions['b'].append((0, q0)) q1.transitions['a'].append((2, q0)) q1.transitions['b'].append((0, q2)) q2.transitions['a'].append((1, q2)) q2.transitions['b'].append((0, q3)) q3.transitions['a'].append((2, q8)) q3.transitions['b'].append((0, q4)) q4.transitions['a'].append((1, q4)) q4.transitions['b'].append((0, q5)) q5.transitions['a'].append((2, q6)) q5.transitions['b'].append((0, q7)) q6.transitions['a'].append((2, q5)) q6.transitions['b'].append((0, q6)) q7.transitions['a'].append((1, q7)) q7.transitions['b'].append(('O', q0)) q8.transitions['a'].append((2, q3)) q8.transitions['b'].append((0, q8)) return Onfsm(q0, [q0, q1, q2, q3, q4, q5, q6, q7, q8])
def load_automaton_from_file(path, automaton_type, compute_prefixes=False): """ Loads the automaton from the file. Standard of the automatas strictly follows syntax found at: https://automata.cs.ru.nl/Syntax/Overview. Args: path: path to the file automaton_type: type of the automaton, if not specified it will be automatically determined according, one of ['dfa', 'mealy', 'moore', 'mdp', 'smm', 'onfsm'] compute_prefixes: it True, shortest path to reach every state will be computed and saved in the prefix of the state. Useful when loading the model to use them as a equivalence oracle. (Default value = False) Returns: automaton """ graph = graph_from_dot_file(path)[0] assert automaton_type in automaton_types node = MealyState if automaton_type == 'mealy' else DfaState if automaton_type == 'dfa' else MooreState node = MdpState if automaton_type == 'mdp' else StochasticMealyState if automaton_type == 'smm' else node node = OnfsmState if automaton_type == 'onfsm' else node assert node is not None node_label_dict = dict() for n in graph.get_node_list(): if n.get_name() == '__start0' or n.get_name() == '': continue label = None if 'label' in n.get_attributes().keys(): label = n.get_attributes()['label'] label = _process_label(label) node_name = n.get_name() output = None if automaton_type == 'moore' and label != "": label_output = _process_label(label) label = label_output.split('|')[0] output = label_output.split('|')[1] if automaton_type == 'mdp': node_label_dict[node_name] = node(node_name, label) else: node_label_dict[node_name] = node( label, output) if automaton_type == 'moore' else node(label) if 'shape' in n.get_attributes().keys( ) and 'doublecircle' in n.get_attributes()['shape']: node_label_dict[node_name].is_accepting = True initial_node = None for edge in graph.get_edge_list(): if edge.get_source() == '__start0': initial_node = node_label_dict[edge.get_destination()] continue source = node_label_dict[edge.get_source()] destination = node_label_dict[edge.get_destination()] label = edge.get_attributes()['label'] label = _process_label(label) if automaton_type == 'mealy': inp = label.split('/')[0] out = label.split('/')[1] inp = int(inp) if inp.isdigit() else inp out = int(out) if out.isdigit() else out source.transitions[inp] = destination source.output_fun[inp] = out elif automaton_type == 'onfsm': inp = label.split('/')[0] out = label.split('/')[1] inp = int(inp) if inp.isdigit() else inp out = int(out) if out.isdigit() else out source.transitions[inp].append((out, destination)) elif automaton_type == 'smm': inp = label.split('/')[0] out_prob = label.split('/')[1] out = out_prob.split(':')[0] prob = out_prob.split(':')[1] inp = int(inp) if inp.isdigit() else inp out = int(out) if out.isdigit() else out source.transitions[inp].append((destination, out, float(prob))) elif automaton_type == 'mdp': inp = label.split(':')[0] prob = label.split(':')[1] inp = int(inp) if inp.isdigit() else inp prob = float(prob) source.transitions[inp].append((destination, prob)) else: source.transitions[int(label) if label.isdigit( ) else label] = destination if automaton_type == 'dfa': automaton = Dfa(initial_node, list(node_label_dict.values())) elif automaton_type == 'moore': automaton = MooreMachine(initial_node, list(node_label_dict.values())) elif automaton_type == 'mdp': automaton = Mdp(initial_node, list(node_label_dict.values())) elif automaton_type == 'smm': automaton = StochasticMealyMachine(initial_node, list(node_label_dict.values())) elif automaton_type == 'onfsm': automaton = Onfsm(initial_node, list(node_label_dict.values())) else: automaton = MealyMachine(initial_node, list(node_label_dict.values())) assert automaton.is_input_complete() if compute_prefixes: for state in automaton.states: state.prefix = automaton.get_shortest_path(automaton.initial_state, state) return automaton