def ensure_tau(collect, events): """ Ensure that the C{tau} event exists and is part of L{events}. @param collect: Collection to get the events from. @type collect: L{collection.Collection} @param events: Events. @type events: A C{set} of L{Event} @return: Possibly updated events. @rtype: A C{set} of L{Event} """ if 'tau' not in collect.events: raise exceptions.ModelError("Event 'tau' does not exist.") tau = collect.events['tau'] if tau not in events: common.print_line("Information: Adding event 'tau' to events.\n") evts = events.copy() evts.add(tau) return evts return events
def remove_tau(aut): """ Modify automaton in-place, removing the 'tau' event. Will fail if there is more than one otgoing 'tau' event. @param aut: Existing automaton. @type aut: L{BaseAutomaton} """ coll = aut.collection tau_event = coll.events['tau'] # Count edges from initial state tau_edge_count = 0 #: Number of 'tau' edges. tau_edge_dest = None #: A destination of a 'tau' edge. for edge in aut.initial.get_outgoing(): assert edge.label is tau_event tau_edge_count = tau_edge_count + 1 tau_edge_dest = edge.succ if tau_edge_count > 1: msg = "Cannot remove 'tau' event, there are %d 'tau' edges from " \ "initial state while expected exactly one." % tau_edge_count raise exceptions.ModelError(msg) assert tau_edge_dest != aut.initial aut.remove_state(aut.initial) aut.set_initial(tau_edge_dest) # Check there are no other 'tau' events in the automaton. for state in aut.get_states(): for edge in state.get_outgoing(): assert edge.label is not tau_event aut.alphabet.remove(tau_event)
def get_edge_dict(state): edges = list(state.get_outgoing()) edict = dict(((edge.label, edge.weight), edge) for edge in edges) if len(edges) != len(edict): msg = "Non-deterministic weighted automata are not supported " \ "by this check." raise exceptions.ModelError(msg) return edict
def load_automaton_file(collect, fname, loader_class, flags): """ Load an automaton file (generic routine). Aborts execution with an error if loading fails in some way. @param collect: Collection to store the events of the automaton. @type collect: L{collection.Collection} @param fname: Filename of the file to load. @type fname: C{str} @param flags: Tests that should be applied after loading. @type flags: C{int} @return: Loaded automaton. @rtype: L{Automaton} @todo: Check and report event name/property clashes while loading. """ fname = fname.strip() if not os.path.isfile(fname): raise exceptions.InputError("Automaton file %r does not exist." % fname) loader = loader_class(collect) aut = loader.load(fname) if aut is None: raise exceptions.ModelError("Load of file %r failed." % fname) if (flags & MUST_HAVE_MARKER_STATE) and len(loader.marker_states) == 0: raise exceptions.ModelError("Automaton in file %r has no marker states." % fname) if (flags & TEST_STANDARDIZED) and not aut.is_standardized(): sys.stderr.write("Warning: Automaton %r is not standardized.\n" % fname) sys.stderr.write(" Results may be meaningless.\n") return aut
def get_events(collect, evt_names): """ Retrieve events listed in L{evt_names} from L{collection.Collection}. Function aborts if retrieval fails. @param collect: Collection to get the events from. @type collect: L{collection.Collection} @param evt_names: Comma seperated list of event names. @type evt_names: C{str} @return: Events from the collection. @rtype: A C{set} of L{Event} """ events = set() for name in evt_names.split(","): evt_name = name.strip() if evt_name not in collect.events: raise exceptions.ModelError("Event '%s' does not exist." % evt_name) events.add(collect.events[evt_name]) return events
def observer_check(aut, observable_events): """ Verify whether the natural projection from the alphabet of the automaton to the preserved alphabet is an observer w.r.t. the marked behaviour Lm(aut). @param aut: Automaton to abstract. @type aut: L{Automaton} @param observable_events: Set of observable events. @type observable_events: C{set} of L{Event} @return: The set of events that break the observer property. @rtype: C{set} of L{Event} """ # Reduce automaton to only the reachable and co-reachable part. coreachables = aut.coreachable_states_set(None, None) if len(coreachables) != aut.get_num_states(): msg = "Automaton is not coreachable, please trim it first." raise exceptions.ModelError(msg) partitions = bisimilarity_partition_for_observer_check(aut, observable_events) bad_events = set() allowed_events = aut.alphabet.difference(observable_events) for partition in partitions: for state in partition: for edge in state.get_outgoing(): if edge.label not in allowed_events: continue if edge.succ not in partition: bad_events.add(edge.label) return bad_events
def compute_products(plants, specs, verbose=True): """ Perform the product for the purpose of supervisor synthesis by sequentially computing products of all L{plants} and L{specs}. @param plants: Plant automata. @type plants: C{list} of L{BaseAutomaton} @param specs: Requirement automata. @type specs: C{list} of L{BaseAutomaton} """ assert len(plants) > 0 # Need at least one plant component. plants = set(plants) specs = set(specs) # Check alphabets plant_alphabet = set() #: Alphabet of the merged components. for plant in plants: plant_alphabet.update(plant.alphabet) for spec in specs: bad_events = spec.alphabet.difference(plant_alphabet) if len(bad_events) == 1: msg = "Event %r from the specification is not in the alphabet " \ "of the plant." % bad_events.pop().name raise exceptions.ModelError(msg) elif len(bad_events) > 1: msg = "Events %s from the specification are not in the " \ "alphabet of the plant." % ", ".join(repr(event.name) for event in bad_events) raise exceptions.ModelError(msg) # else len(bad_events) == 0 -> ok ordered_plants = order_plants(plants) fixated_plants = add_fixated_events(ordered_plants) del plants del ordered_plants #: Order to compute the supervisor product. prod_order = insert_requirements(fixated_plants, specs) del fixated_plants del specs if DBG: for comp in prod_order: aut = comp[0] aut_type = "Plant" if comp[1]: aut_type = "Specification" print "%s %s" % (aut_type, aut.name) print "\t#states :", aut.get_num_states() print "\t#trans :", aut.get_num_edges() print "\talphabet:", sorted(evt.name for evt in aut.alphabet) print "\tFixated events:", sorted(evt.name for evt in comp[2]) result = None idx = 1 preserve_result = True for aut, aut_is_spec, fixated in prod_order: aut_type = "requirement" if aut_is_spec else "plant" if result is None: if verbose: common.print_line( "(%d of %d) Starting with %s %s (%d states)" % (idx, len(prod_order), aut_type, aut.name, aut.get_num_states())) result = aut preserve_result = True bad_states = set() else: if verbose: common.print_line("(%d of %d) Adding %s %s (%d states)" % (idx, len(prod_order), aut_type, aut.name, aut.get_num_states())) result2, bad_states2 = supervisor_product.supervisor_product( result, bad_states, aut, aut_is_spec, fixated, verbose) bad_states.clear() bad_states = bad_states2 del bad_states2 if not preserve_result: result.clear() result = result2 del result2 preserve_result = False if result.get_num_states() == 0: msg = "Supervisor product is empty (no states in the product)." raise exceptions.ModelError(msg) idx = idx + 1 return result, bad_states
def read_ads_file(fhandle): """ Load an ADS file, and return its data. @param fhandle: File handle of the ADS file. @type fhandle: C{file} @return: The data of the ADS file, (name, n_state_size, marker_lines, vocal_states, trans) @rtype: (C{str}, C{int}, either C{['*']} or a list of integers, C{dict} of C{int}-state to C{int}-event, list of (C{int}-srcstate, C{int}-event, C{int}-deststate)) @note: States are numbered from C{0} (initial state) upto C{n_state_size-1}. Odd event numbers are considered to be controllable. Even event numbers are considered to be uncontrollable. C{['*']} for marker states means all states are marker state. @note: Code written by J.Hamer, edited by A.T.Hofkamp. @note: Copied from svctools/trunk/svctools/bin/ads2stm r368 (21-04-2009). """ header = '' linenum = 0 vocal_states = {} marker_lines = [] trans = [] name = None for rawline in fhandle.readlines(): line = rawline.strip() linenum = linenum + 1 if line == '': header = '' elif line[0] == '#': pass elif line == 'State size (State set will be (0,1....,size-1)):': header = 'Size' elif line == 'Marker states:': header = 'Marker' elif line == 'Vocal states:': header = 'Vocal' elif line == 'Transitions:': header = 'Trans' elif line == 'Forcible events:': header = 'Force' elif header == 'Size': n_state_size = int(line.strip()) elif header == 'Marker': marker_lines.append(line) # either a number or * elif header == 'Vocal': vocal = line.split() # state number, event number vocal_states[int(vocal[0])] = int(vocal[1]) elif header == 'Force': pass elif header == 'Trans': trans.append([int(txt) for txt in line.split()]) elif name is None: if line[-4:].lower() == '.ads': name = line[:-4] else: name = line else: raise exceptions.ModelError('Line %d has unknown format.' % linenum) if '*' in marker_lines: marker_lines = ['*'] else: marker_lines = [ int(m) for m in marker_lines ] return name, n_state_size, marker_lines, vocal_states, trans
def supervisor_product(comp, comp_bad, compreq, compreq_is_requirement, usable_events, verbose=True): """ Perform a product calculation for the purpose of supervisor synthesis. @param comp: First automaton, always a component (possbily the result of a previous call). @type comp: L{BaseAutomaton} @param comp_bad: Known bad states of L{comp}. @type comp_bad: C{set} of L{BaseState} @param compreq: Second automaton, either a component or a requirement. @type compreq: L{BaseAutomaton} @param compreq_is_requirement: Second automaton is a requirement automaton. @type compreq_is_requirement: C{bool} @param usable_events: Set of events that may be traversed. @type usable_events: C{set} of L{Event} @return: Resulting automaton, and its bad state set. @rtype: L{BaseAutomaton}, C{set} of L{BaseState} @note: The alphabet of the resulting automaton is inserted into the properties by the function. @precond: If L{compreq_is_requirement}, the alphabet of L{compreq} must be a subset of L{comp}. """ # Generate progress message. if compreq_is_requirement: compreq_text = "spec" else: compreq_text = "plant" msg = "Start supervisor product %d states (%d bad) with %s %d states" \ % (comp.get_num_states(), len(comp_bad), compreq_text, compreq.get_num_states()) if verbose: common.print_line(msg) props = algorithm.ManagerProperties(comp.collection) props.aut_type = algorithm.UNWEIGHTED_AUT props.marker_func = algorithm.MARKED_ALL props.explore_mgr = algorithm.ORIGINAL_STATE props.edge_calc = algorithm.COPY_LABEL result_alphabet = comp.alphabet.union(compreq.alphabet) props.alphabet = result_alphabet compreq_only_alphabet = compreq.alphabet.difference(comp.alphabet) # Either compreq is not a requirement, or it has no edges of its own. assert not compreq_is_requirement or len(compreq_only_alphabet) == 0 bad_states = set() #: New bad states, list of original state combinations. mgr = algorithm.Manager(props) mgr.set_initial((comp.initial, compreq.initial)) while True: orig_state = mgr.get_next() if orig_state is None: break # If it was a bad state previously, it will be again. if orig_state[0] in comp_bad: # Reset marker property of bad state. state = mgr.state_mgr.mapping[orig_state] state.marked = False bad_states.add(state) continue # Pick the next one. #: Available compreq edges ordered by event-name. compreq_event_edges = {} for edge in orig_state[1].get_outgoing(): edges = compreq_event_edges.get(edge.label) if edges is None: edges = [] compreq_event_edges[edge.label] = edges edges.append(edge) if compreq_is_requirement: # If compreq is a requirement, look whether we are at a bad state. # Those happen when the event is enabled in comp, disabled in # compreq, and the event is uncontrollable (ie from the current # state, we disable an uncontrollable event by a spec). In that # case, the current state in the product is bad (it violates the # controllability property). Add the product state to the bad # states. # Decide whether it is a bad state. bad_state = False for edge in orig_state[0].get_outgoing(): # The edge label is controllable, or # compreq also has an outgoing edge for this event, or # the label is not in the compreq alphabet. if edge.label.controllable or \ edge.label in compreq_event_edges or \ edge.label not in compreq.alphabet: continue bad_state = True break if bad_state: # Reset marker property of bad state. state = mgr.state_mgr.mapping[orig_state] state.marked = False bad_states.add(state) continue # Do not expand current state, pick the next one. # A good state, expand to new states. # Expand edges of first automaton. for edge in orig_state[0].get_outgoing(): if edge.label not in compreq.alphabet: # comp only. mgr.add_edge(orig_state, (edge.succ, orig_state[1]), [edge]) continue compreq_edges = compreq_event_edges.get(edge.label) if compreq_edges is None: # Disabled by compreq, but not in a bad way. continue for edge2 in compreq_edges: mgr.add_edge(orig_state, (edge.succ, edge2.succ), [edge, edge2]) # Perform compreq only for evt in compreq_only_alphabet: compreq_edges = compreq_event_edges.get(evt) if compreq_edges is not None: for edge2 in compreq_edges: mgr.add_edge(orig_state, (orig_state[0], edge2.succ), [edge2]) # Finished with the product. prod_aut = mgr.get_automaton() prod_aut.aut_kind = 'supervisor' mgr.get_mapping().clear() # Do a co-reachability search, and trim out all states AFTER the initial # non-coreachable state. The initial non-coreachable states must be added # to the bad states as well. # Compute co-reachable set of the product. coreachables = set() not_done = [] for state in prod_aut.get_states(): if state.marked: # Bad states are never marked. coreachables.add(state) not_done.append(state) if len(not_done) == 0: # No marker states at all in the product msg = "Supervisor product is empty (no marker states in the product)." raise exceptions.ModelError(msg) while len(not_done) > 0: state = not_done.pop() for edge in state.get_incoming(): if edge.pred not in coreachables: coreachables.add(edge.pred) not_done.append(edge.pred) # Finished, all states are coreacahable. # Will probably not happen often due to bad states. if len(coreachables) == prod_aut.get_num_states(): assert len(bad_states) == 0 if DBG: common.print_line("Finished, %d states, no bad states" % prod_aut.get_num_states()) coreachables.clear() return prod_aut, bad_states # Non-coreachables that have a co-reachable predecessor are bad too. non_coreachables = [] for state in prod_aut.get_states(): if state in coreachables: continue if state in bad_states: continue pred_coreachable = False for edge in state.get_incoming(): if edge.pred in coreachables: pred_coreachable = True break if pred_coreachable: bad_states.add(state) # Reset marker property of bad state. state.marked = False # Remove all outgoing edges of the new bad state. for edge in list(state.get_outgoing()): prod_aut.remove_edge(edge) else: non_coreachables.append(state) coreachables.clear() # Remove states that are not bad and not co-reachable. for state in non_coreachables: if state not in bad_states: prod_aut.remove_state(state) del non_coreachables # Extend the number of bad states by walking backwards over # 'usable_events'. illegal_states = coreachable_bad_states(prod_aut, bad_states, usable_events) if illegal_states == bad_states: if DBG: common.print_line("Finished, %d states (%d bad)" % (prod_aut.get_num_states(), len(bad_states))) return prod_aut, bad_states # Found new illegal states. assert bad_states.issubset(illegal_states) bad_states = set() for state in illegal_states: # Check whether 'state' has an ancestor state which is good. found_good_state = False for edge in state.get_incoming(): if edge.pred not in illegal_states: found_good_state = True break if found_good_state: bad_states.add(state) # Reset marker property of bad state. state.marked = False # Remove all outgoing edges of the new bad state. for edge in list(state.get_outgoing()): prod_aut.remove_edge(edge) else: prod_aut.remove_state(state) illegal_states.clear() if DBG: common.print_line("Finished, %d states (%d bad)" % (prod_aut.get_num_states(), len(bad_states))) prod_aut.save_as_dot("test.dot") return prod_aut, bad_states