|
| 1 | +package de.learnlib.eqtests.basic; |
| 2 | + |
| 3 | +import de.learnlib.api.EquivalenceOracle; |
| 4 | +import de.learnlib.api.MembershipOracle; |
| 5 | +import de.learnlib.oracles.DefaultQuery; |
| 6 | +import net.automatalib.automata.UniversalDeterministicAutomaton; |
| 7 | +import net.automatalib.automata.concepts.Output; |
| 8 | +import net.automatalib.commons.util.mappings.MutableMapping; |
| 9 | +import net.automatalib.util.automata.Automata; |
| 10 | +import net.automatalib.words.Word; |
| 11 | +import net.automatalib.words.WordBuilder; |
| 12 | + |
| 13 | +import javax.annotation.ParametersAreNonnullByDefault; |
| 14 | +import java.util.*; |
| 15 | + |
| 16 | +/** |
| 17 | + * Implements an equivalence test by applying the Wp-method test on the given hypothesis automaton, |
| 18 | + * as described in "Test Selection Based on Finite State Models" by S. Fujiwara et al. |
| 19 | + * Instead of enumerating the test suite in order, this is a sampling implementation: |
| 20 | + * 1. sample uniformly from the states for a prefix |
| 21 | + * 2. sample geometrically a random word |
| 22 | + * 3. sample a word from the set of suffixes / state identifiers (either local or global) |
| 23 | + * There are two parameters: minimalSize determines the minimal size of the random word, this is |
| 24 | + * useful when one first performs a W(p)-method with some depth and continue with this randomized |
| 25 | + * tester from that depth onward. The second parameter rndLength determines the expected length |
| 26 | + * of the random word. (The expected length in effect is minimalSize + rndLength.) |
| 27 | + * In the unbounded case it will not terminate for a correct hypothesis. |
| 28 | + * |
| 29 | + * @param <A> automaton type |
| 30 | + * @param <I> input symbol type |
| 31 | + * @param <D> output domain type |
| 32 | + * @author Joshua Moerman |
| 33 | + */ |
| 34 | +public class RandomWpMethodEQOracle<A extends UniversalDeterministicAutomaton<?, I, ?, ?, ?> & Output<I, D>, I, D> |
| 35 | + implements EquivalenceOracle<A, I, D> { |
| 36 | + private final MembershipOracle<I, D> sulOracle; |
| 37 | + private final int minimalSize; |
| 38 | + private final int rndLength; |
| 39 | + private final int bound; |
| 40 | + |
| 41 | + /** |
| 42 | + * Constructor for an unbounded testing oracle |
| 43 | + * |
| 44 | + * @param sulOracle oracle which answers tests. |
| 45 | + * @param minimalSize minimal size of the random word |
| 46 | + * @param rndLength expected length (in addition to minimalSize) of random word |
| 47 | + */ |
| 48 | + public RandomWpMethodEQOracle(MembershipOracle<I, D> sulOracle, int minimalSize, int rndLength) { |
| 49 | + this.sulOracle = sulOracle; |
| 50 | + this.minimalSize = minimalSize; |
| 51 | + this.rndLength = rndLength; |
| 52 | + this.bound = 0; |
| 53 | + } |
| 54 | + |
| 55 | + /** |
| 56 | + * Constructor for a bounded testing oracle |
| 57 | + * |
| 58 | + * @param sulOracle oracle which answers tests. |
| 59 | + * @param minimalSize minimal size of the random word |
| 60 | + * @param rndLength expected length (in addition to minimalSize) of random word |
| 61 | + * @param bound specifies the bound (set to 0 for unbounded). |
| 62 | + */ |
| 63 | + public RandomWpMethodEQOracle(MembershipOracle<I, D> sulOracle, int minimalSize, int rndLength, int bound) { |
| 64 | + this.sulOracle = sulOracle; |
| 65 | + this.minimalSize = minimalSize; |
| 66 | + this.rndLength = rndLength; |
| 67 | + this.bound = bound; |
| 68 | + } |
| 69 | + |
| 70 | + /* |
| 71 | + * (non-Javadoc) |
| 72 | + * @see de.learnlib.api.EquivalenceOracle#findCounterExample(java.lang.Object, java.util.Collection) |
| 73 | + */ |
| 74 | + @Override |
| 75 | + @ParametersAreNonnullByDefault |
| 76 | + public DefaultQuery<I, D> findCounterExample(A hypothesis, Collection<? extends I> inputs) { |
| 77 | + UniversalDeterministicAutomaton<?, I, ?, ?, ?> aut = hypothesis; |
| 78 | + Output<I, D> out = hypothesis; |
| 79 | + return doFindCounterExample(aut, out, inputs); |
| 80 | + } |
| 81 | + |
| 82 | + /* |
| 83 | + * Delegate target, used to bind the state-parameter of the automaton |
| 84 | + */ |
| 85 | + private <S> DefaultQuery<I, D> doFindCounterExample(UniversalDeterministicAutomaton<S, I, ?, ?, ?> hypothesis, |
| 86 | + Output<I, D> output, Collection<? extends I> inputs) { |
| 87 | + // Note that we want to use ArrayLists because we want constant time random access |
| 88 | + // We will sample from this for a prefix |
| 89 | + ArrayList<Word<I>> stateCover = new ArrayList<>(hypothesis.size()); |
| 90 | + Automata.cover(hypothesis, inputs, stateCover, null); |
| 91 | + |
| 92 | + // Then repeatedly from this for a random word |
| 93 | + ArrayList<I> arrayAlphabet = new ArrayList<>(inputs); |
| 94 | + |
| 95 | + // Finally we test the state with a suffix, sometimes a global one, sometimes local |
| 96 | + ArrayList<Word<I>> globalSuffixes = new ArrayList<>(); |
| 97 | + Automata.characterizingSet(hypothesis, inputs, globalSuffixes); |
| 98 | + |
| 99 | + MutableMapping<S, ArrayList<Word<I>>> localSuffixSets = hypothesis.createStaticStateMapping(); |
| 100 | + for (S state : hypothesis.getStates()) { |
| 101 | + ArrayList<Word<I>> suffixSet = new ArrayList<>(); |
| 102 | + Automata.stateCharacterizingSet(hypothesis, inputs, state, suffixSet); |
| 103 | + localSuffixSets.put(state, suffixSet); |
| 104 | + } |
| 105 | + |
| 106 | + Random rand = new Random(); |
| 107 | + int currentBound = bound; |
| 108 | + while (bound == 0 || currentBound-- > 0) { |
| 109 | + WordBuilder<I> wb = new WordBuilder<>(minimalSize + rndLength + 1); |
| 110 | + |
| 111 | + // pick a random state |
| 112 | + wb.append(stateCover.get(rand.nextInt(stateCover.size()))); |
| 113 | + |
| 114 | + // construct random middle part (of some expected length) |
| 115 | + int size = minimalSize; |
| 116 | + while ((size > 0) || (rand.nextDouble() > 1 / (rndLength + 1.0))) { |
| 117 | + wb.append(arrayAlphabet.get(rand.nextInt(arrayAlphabet.size()))); |
| 118 | + if (size > 0) size--; |
| 119 | + } |
| 120 | + |
| 121 | + // pick a random suffix for this state |
| 122 | + // 50% chance for state testing, 50% chance for transition testing |
| 123 | + if (rand.nextBoolean()) { |
| 124 | + // global |
| 125 | + if (!globalSuffixes.isEmpty()) { |
| 126 | + wb.append(globalSuffixes.get(rand.nextInt(globalSuffixes.size()))); |
| 127 | + } |
| 128 | + } else { |
| 129 | + // local |
| 130 | + S state2 = hypothesis.getState(wb); |
| 131 | + ArrayList<Word<I>> localSuffixes = localSuffixSets.get(state2); |
| 132 | + if (!localSuffixes.isEmpty()) { |
| 133 | + wb.append(localSuffixes.get(rand.nextInt(localSuffixes.size()))); |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + Word<I> queryWord = wb.toWord(); |
| 138 | + DefaultQuery<I, D> query = new DefaultQuery<>(queryWord); |
| 139 | + D hypOutput = output.computeOutput(queryWord); |
| 140 | + sulOracle.processQueries(Collections.singleton(query)); |
| 141 | + if (!Objects.equals(hypOutput, query.getOutput())) |
| 142 | + return query; |
| 143 | + } |
| 144 | + |
| 145 | + // no counter example found within the bound |
| 146 | + return null; |
| 147 | + } |
| 148 | +} |
0 commit comments