All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.automatalib.util.automata.cover.Covers Maven / Gradle / Ivy

Go to download

This artifact provides various common utility operations for analyzing and manipulating automata and graphs, such as traversal, minimization and copying.

There is a newer version: 0.11.0
Show newest version
/* Copyright (C) 2013-2019 TU Dortmund
 * This file is part of AutomataLib, http://www.automatalib.net/.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.automatalib.util.automata.cover;

import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Queue;
import java.util.function.BiFunction;
import java.util.function.Consumer;

import javax.annotation.ParametersAreNonnullByDefault;

import com.google.common.collect.Sets;
import net.automatalib.automata.DeterministicAutomaton;
import net.automatalib.commons.util.mappings.MutableMapping;
import net.automatalib.words.Word;

/**
 * @author Malte Isberner
 * @author frohme
 */
@ParametersAreNonnullByDefault
public final class Covers {

    private Covers() {
    }

    /**
     * Computes a state cover for a given automaton.
     * 

* A state cover is a set C of input sequences, such that for each state s of an automaton, there * exists an input sequence in C that transitions the automaton from its initial state to state s. *

* Note: if restrictions on the {@code inputs} parameter do not allow to reach certain states, the computed cover is * not complete. * * @param automaton * the automaton for which the cover should be computed * @param inputs * the set of input symbols allowed in the cover sequences * @param states * the collection in which the sequences will be stored * @param * input symbol type */ public static void stateCover(DeterministicAutomaton automaton, Collection inputs, Collection> states) { cover(automaton, inputs, states::add, w -> {}); } /** * Returns an iterator for the sequences of a state cover. Sequences are computed lazily (i.e. as requested by the * iterators {@link Iterator#next() next} method. * * @param automaton * the automaton for which the cover should be computed * @param inputs * the set of input symbols allowed in the cover sequences * @param * input symbol type * * @return an iterator for the input sequences of the cover. * * @see #stateCover(DeterministicAutomaton, Collection, Collection) */ public static Iterator> stateCoverIterator(DeterministicAutomaton automaton, Collection inputs) { return new IncrementalStateCoverIterator<>(automaton, inputs, Collections.emptyList()); } /** * Computes a transition cover for a given automaton. *

* A transition cover is a set C of input sequences, such that for each state s and each input symbol * i of an automaton, there exists an input sequence in C that starts from the initial state of the * automaton and ends with the transition that applies i to state s. *

* Note: if restrictions on the {@code inputs} parameter do not allow to reach certain transitions, the computed * cover is not complete. * * @param automaton * the automaton for which the cover should be computed * @param inputs * the set of input symbols allowed in the cover sequences * @param transitions * the collection in which the sequences will be stored * @param * input symbol type */ public static void transitionCover(DeterministicAutomaton automaton, Collection inputs, Collection> transitions) { cover(automaton, inputs, w -> {}, transitions::add); } /** * Returns an iterator for the sequences of a transition cover. Sequences are computed lazily (i.e. as requested by * the iterators {@link Iterator#next() next} method. * * @param automaton * the automaton for which the cover should be computed * @param inputs * the set of input symbols allowed in the cover sequences * @param * input symbol type * * @return an iterator for the input sequences of the cover. * * @see #transitionCover(DeterministicAutomaton, Collection, Collection) */ public static Iterator> transitionCoverIterator(DeterministicAutomaton automaton, Collection inputs) { return new TransitionCoverIterator<>(automaton, inputs); } /** * Computes a structural cover for a given automaton. *

* A structural cover is the union of a state cover and a transition cover * * @param automaton * the automaton for which the cover should be computed * @param inputs * the set of input symbols allowed in the cover sequences * @param cover * the collection in which the sequences will be stored * @param * input symbol type * * @see #stateCover(DeterministicAutomaton, Collection, Collection) * @see #transitionCover(DeterministicAutomaton, Collection, Collection) */ public static void structuralCover(DeterministicAutomaton automaton, Collection inputs, Collection> cover) { cover(automaton, inputs, cover::add, cover::add); } /** * Utility method that allows to compute a state and transition cover simultaneously. * * @param automaton * the automaton for which the covers should be computed * @param inputs * the set of input symbols allowed in the cover sequences * @param states * the collection in which the state cover sequences will be stored * @param transitions * the collection in which the transition cover sequences will be stored * @param * input symbol type * * @see #stateCover(DeterministicAutomaton, Collection, Collection) * @see #transitionCover(DeterministicAutomaton, Collection, Collection) */ public static void cover(DeterministicAutomaton automaton, Collection inputs, Collection> states, Collection> transitions) { cover(automaton, inputs, states::add, transitions::add); } private static void cover(DeterministicAutomaton automaton, Collection inputs, Consumer> states, Consumer> transitions) { MutableMapping> reach = automaton.createStaticStateMapping(); Queue bfsQueue = new ArrayDeque<>(); S init = automaton.getInitialState(); reach.put(init, Word.epsilon()); bfsQueue.add(init); states.accept(Word.epsilon()); S curr; while ((curr = bfsQueue.poll()) != null) { Word as = reach.get(curr); for (I in : inputs) { S succ = automaton.getSuccessor(curr, in); if (succ == null) { continue; } final Word succAs = as.append(in); if (reach.get(succ) == null) { reach.put(succ, succAs); states.accept(succAs); bfsQueue.add(succ); } transitions.accept(succAs); } } } /** * Computes an incremental state cover for a given automaton, i.e. a cover that only contains the missing sequences * for obtaining a complete state cover. * * @param automaton * the automaton for which the cover should be computed * @param inputs * the set of input symbols allowed in the cover sequences * @param oldStates * the collection containing the already existing sequences of the state cover * @param newStates * the collection in which the missing sequences will be stored * @param * input symbol type * * @return {@code true} if new sequences have been added to the state cover, {@code false} otherwise. * * @see #stateCover(DeterministicAutomaton, Collection, Collection) */ public static boolean incrementalStateCover(DeterministicAutomaton automaton, Collection inputs, Collection> oldStates, Collection> newStates) { MutableMapping> reach = automaton.createStaticStateMapping(); boolean augmented = false; Queue> bfsQueue = new ArrayDeque<>(); buildReachFromStateCover(reach, bfsQueue, automaton, oldStates, Record::new); S init = automaton.getInitialState(); if (reach.get(init) == null) { // apparently the initial state was not yet covered Record rec = new Record<>(init, Word.epsilon()); reach.put(init, rec); bfsQueue.add(rec); newStates.add(Word.epsilon()); augmented = true; } Record curr; while ((curr = bfsQueue.poll()) != null) { S state = curr.state; Word as = curr.accessSequence; for (I in : inputs) { S succ = automaton.getSuccessor(state, in); if (succ == null) { continue; } if (reach.get(succ) == null) { Word succAs = as.append(in); Record succRec = new Record<>(succ, succAs); reach.put(succ, succRec); bfsQueue.add(succRec); newStates.add(succAs); augmented = true; } } } return augmented; } /** * Returns an iterator for the remaining sequences of a state cover. Sequences are computed lazily (i.e. as * requested by the iterators {@link Iterator#next() next} method. * * @param automaton * the automaton for which the cover should be computed * @param inputs * the set of input symbols allowed in the cover sequences * @param stateCover * the collection containing the already existing sequences of the state cover * @param * input symbol type * * @return an iterator for the remaining input sequences of the cover. * * @see #incrementalStateCover(DeterministicAutomaton, Collection, Collection, Collection) */ public static Iterator> incrementalStateCoverIterator(DeterministicAutomaton automaton, Collection inputs, Collection> stateCover) { return new IncrementalStateCoverIterator<>(automaton, inputs, stateCover); } /** * Computes an incremental transition cover for a given automaton, i.e. a cover that only contains the missing * sequences for obtaining a complete transition cover. * * @param automaton * the automaton for which the cover should be computed * @param inputs * the set of input symbols allowed in the cover sequences * @param oldTransCover * the collection containing the already existing sequences of the transition cover * @param newTransCover * the collection in which the missing sequences will be stored * @param * input symbol type * * @return {@code true} if new sequences have been added to the state cover, {@code false} otherwise. * * @see #transitionCover(DeterministicAutomaton, Collection, Collection) */ public static boolean incrementalTransitionCover(DeterministicAutomaton automaton, Collection inputs, Collection> oldTransCover, Collection> newTransCover) { final int oldTransSize = newTransCover.size(); incrementalCover(automaton, inputs, Collections.emptySet(), oldTransCover, w -> {}, newTransCover::add); return oldTransSize < newTransCover.size(); } /** * Returns an iterator for the remaining sequences of a transition cover. Sequences are computed lazily (i.e. as * requested by the iterators {@link Iterator#next() next} method. * * @param automaton * the automaton for which the cover should be computed * @param inputs * the set of input symbols allowed in the cover sequences * @param transitionCover * the collection containing the already existing sequences of the transition cover * @param * input symbol type * * @return an iterator for the remaining input sequences of the cover. * * @see #incrementalStateCover(DeterministicAutomaton, Collection, Collection, Collection) */ public static Iterator> incrementalTransitionCoverIterator(DeterministicAutomaton automaton, Collection inputs, Collection> transitionCover) { return new IncrementalTransitionCoverIterator<>(automaton, inputs, transitionCover); } /** * Computes an incremental structural cover for a given automaton, i.e. a cover that only contains the missing * sequences for obtaining a complete structural cover. * * @param automaton * the automaton for which the cover should be computed * @param inputs * the set of input symbols allowed in the cover sequences * @param oldCover * the collection containing the already existing sequences of the structural cover * @param newCover * the collection in which the missing sequences will be stored * @param * input symbol type * * @return {@code true} if new sequences have been added to the structural cover, {@code false} otherwise. * * @see #structuralCover(DeterministicAutomaton, Collection, Collection) */ public static boolean incrementalStructuralCover(DeterministicAutomaton automaton, Collection inputs, Collection> oldCover, Collection> newCover) { final int oldCoverSize = newCover.size(); incrementalCover(automaton, inputs, oldCover, Collections.emptySet(), newCover::add, newCover::add); return oldCoverSize < newCover.size(); } /** * Utility method that allows to compute an incremental state and transition cover simultaneously. * * @param automaton * the automaton for which the covers should be computed * @param inputs * the set of input symbols allowed in the cover sequences * @param oldStateCover * the collection containing the already existing sequences of the state cover * @param oldTransCover * the collection containing the already existing sequences of the transition cover * @param newStateCover * the collection in which the missing state cover sequences will be stored * @param newTransCover * the collection in which the missing transition cover sequences will be stored * @param * input symbol type * * @return {@code true} if new sequences have been added to the structural cover, {@code false} otherwise. * * @see #incrementalStateCover(DeterministicAutomaton, Collection, Collection, Collection) * @see #incrementalStateCover(DeterministicAutomaton, Collection, Collection, Collection) */ public static boolean incrementalCover(DeterministicAutomaton automaton, Collection inputs, Collection> oldStateCover, Collection> oldTransCover, Collection> newStateCover, Collection> newTransCover) { final int oldStateSize = newStateCover.size(); final int oldTransSize = newTransCover.size(); incrementalCover(automaton, inputs, oldStateCover, oldTransCover, newStateCover::add, newTransCover::add); return oldStateSize < newStateCover.size() || oldTransSize < newTransCover.size(); } private static void incrementalCover(DeterministicAutomaton automaton, Collection inputs, Collection> oldStateCover, Collection> oldTransCover, Consumer> newStateCover, Consumer> newTransCover) { MutableMapping> reach = automaton.createStaticStateMapping(); Queue> bfsQueue = new ArrayDeque<>(); // We enforce that the initial state *always* is covered by the empty word, // regardless of whether other sequence in oldCover cover it S init = automaton.getInitialState(); Record initRec = new Record<>(init, Word.epsilon(), Sets.newHashSetWithExpectedSize(inputs.size())); bfsQueue.add(initRec); reach.put(init, initRec); boolean hasEpsilon = buildReachFromStateCover(reach, bfsQueue, automaton, oldStateCover, (s, as) -> new Record<>(s, as, Sets.newHashSetWithExpectedSize(inputs.size()))); // Add transition cover information from *state covers* for (Word oldStateAs : oldStateCover) { if (oldStateAs.isEmpty()) { continue; } Word asPrefix = oldStateAs.prefix(oldStateAs.length() - 1); S pred = automaton.getState(asPrefix); assert pred != null; Record predRec = reach.get(pred); if (predRec == null) { throw new IllegalArgumentException( "State cover was not prefix-closed: prefix of " + oldStateAs + " not in set"); } I lastSym = oldStateAs.lastSymbol(); predRec.coveredInputs.add(lastSym); } // Till now, we haven't augmented any set. if (!hasEpsilon) { newStateCover.accept(Word.epsilon()); } // Add transition covers buildReachFromTransitionCover(reach, bfsQueue, automaton, oldTransCover, (s, as) -> new Record<>(s, as, Sets.newHashSetWithExpectedSize(inputs.size())), newStateCover); Record curr; while ((curr = bfsQueue.poll()) != null) { for (I input : inputs) { if (curr.coveredInputs.add(input)) { S succ = automaton.getSuccessor(curr.state, input); Word newAs = curr.accessSequence.append(input); if (succ != null) { Record succRec = reach.get(succ); if (succRec == null) { // new state! succRec = new Record<>(succ, newAs, Sets.newHashSetWithExpectedSize(inputs.size())); bfsQueue.add(succRec); reach.put(succ, succRec); newStateCover.accept(newAs); } // new transition newTransCover.accept(newAs); } } } } } static boolean buildReachFromStateCover(MutableMapping> reach, Queue> bfsQueue, DeterministicAutomaton automaton, Collection> oldStateCover, BiFunction, Record> recordBuilder) { boolean hasEpsilon = false; for (Word oldStateAs : oldStateCover) { S state = automaton.getState(oldStateAs); if (state == null || reach.get(state) != null) { if (oldStateAs.isEmpty()) { hasEpsilon = true; } continue; // strange, but we'll ignore it } Record rec = recordBuilder.apply(state, oldStateAs); bfsQueue.add(rec); reach.put(state, rec); } return hasEpsilon; } static void buildReachFromTransitionCover(MutableMapping> reach, Queue> bfsQueue, DeterministicAutomaton automaton, Collection> oldTransCover, BiFunction, Record> recordBuilder, Consumer> newStateCallback) { for (Word oldTransAs : oldTransCover) { // Check if this transition now leads to a new state S state = automaton.getState(oldTransAs); if (state != null) { Record rec = reach.get(state); if (rec == null) { // if so, add it to the state cover and to the queue rec = recordBuilder.apply(state, oldTransAs); bfsQueue.add(rec); reach.put(state, rec); newStateCallback.accept(oldTransAs); } } // In any case, mark the transition as covered Word predAs = oldTransAs.prefix(oldTransAs.length() - 1); S pred = automaton.getState(predAs); if (pred == null) { throw new IllegalArgumentException( "Invalid transition: prefix of transition " + oldTransAs + " not covered by state cover"); } I lastSym = oldTransAs.lastSymbol(); Record predRec = reach.get(pred); if (predRec == null) { predRec = recordBuilder.apply(state, oldTransAs); bfsQueue.add(predRec); reach.put(pred, predRec); newStateCallback.accept(oldTransAs); } predRec.coveredInputs.add(lastSym); } } }