Esempio n. 1
0
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
Esempio n. 2
0
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)
Esempio n. 3
0
 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
Esempio n. 4
0
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
Esempio n. 5
0
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
Esempio n. 6
0
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
Esempio n. 7
0
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
Esempio n. 9
0
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