diff --git a/CHANGELOG.md b/CHANGELOG.md index 54a5dfccff..bfd9b6a801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). [Full changelog](https://github.com/LearnLib/learnlib/compare/learnlib-0.16.0...HEAD) +### Added + +* Added the OSTIA passive learning algorithm, thanks to [Aleksander Mendoza-Drosik](https://github.com/aleksander-mendoza). + ### Changed * `PassiveLearningAlgorithm#comuteModel` did not specify whether repeated calls to the method should yield identical models. It is now explicitly left open to the respective implementation to support this behavior. `BlueFringeRPNI{DFA,Mealy}` explicitly does not support this behavior, as the internal prefix-tree acceptors is now constructed on-the-fly as samples are added via the `addSample` methods. This allows to drop the previously redundant caching of samples and reduce memory pressure. `BlueFringeEDSMDFA` and `BlueFringeMDLDFA` still have to cache the samples internally and therefore still support repeated model construction. diff --git a/README.md b/README.md index 972365d202..42e67c01cf 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,10 @@ Currently the following learning algorithms with respective target models are su Algorithm (active) | Target models || Algorithm (passive) | Models --- | --- | --- | --- | --- -ADT | `Mealy` || RPNI | `DFA` `Mealy` -DHC | `Mealy` || RPNI (EDSM) | `DFA` -Discrimination Tree | `DFA` `Mealy` `VPDA` || RPNI (MDL) | `DFA` -Kearns & Vazirani | `DFA` `Mealy` +ADT | `Mealy` || OSTIA | `SST` +DHC | `Mealy` || RPNI | `DFA` +Discrimination Tree | `DFA` `Mealy` `VPDA` || RPNI (EDSM) | `DFA` +Kearns & Vazirani | `DFA` `Mealy` || RPNI (MDL) | `DFA` L* (incl. variants) | `DFA` `Mealy` NL* | `NFA` TTT | `DFA` `Mealy` `VPDA` diff --git a/algorithms/passive/ostia/pom.xml b/algorithms/passive/ostia/pom.xml new file mode 100644 index 0000000000..26a67075ce --- /dev/null +++ b/algorithms/passive/ostia/pom.xml @@ -0,0 +1,88 @@ + + + + 4.0.0 + + + de.learnlib + learnlib-algorithms-passive-parent + 0.17.0-SNAPSHOT + + + learnlib-ostia + + LearnLib :: Algorithms :: OSTIA + The OSTIA passive learning algorithm + + + + de.learnlib + learnlib-api + + + + net.automatalib + automata-api + + + net.automatalib + automata-commons-smartcollections + + + net.automatalib + automata-commons-util + + + net.automatalib + automata-core + + + + org.checkerframework + checker-qual + + + + + com.google.guava + guava + test + + + + de.learnlib.testsupport + learnlib-learner-it-support + + + + net.automatalib + automata-serialization-dot + test + + + net.automatalib + automata-util + test + + + + org.testng + testng + + + diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/Blue.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/Blue.java new file mode 100644 index 0000000000..5ff88883e5 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/Blue.java @@ -0,0 +1,43 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.algorithms.ostia; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * @author Aleksander Mendoza-Drosik + */ +class Blue { + + final State parent; + final int symbol; + + Blue(State parent, int symbol) { + this.symbol = symbol; + this.parent = parent; + } + + @Nullable State state() { + final @Nullable Edge edge = parent.transitions[symbol]; + assert edge != null; + return edge.target; + } + + @Override + public String toString() { + return String.valueOf(state()); + } +} diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/Edge.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/Edge.java new file mode 100644 index 0000000000..e2f9ce1d86 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/Edge.java @@ -0,0 +1,39 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.algorithms.ostia; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * @author Aleksander Mendoza-Drosik + */ +class Edge { + + @Nullable IntQueue out; + State target; + + Edge() {} + + Edge(Edge edge) { + out = IntQueue.copyAndConcat(edge.out, null); + target = edge.target; + } + + @Override + public String toString() { + return String.valueOf(target); + } +} diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntQueue.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntQueue.java new file mode 100644 index 0000000000..fc91f11339 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntQueue.java @@ -0,0 +1,102 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.algorithms.ostia; + +import java.util.HashSet; +import java.util.Set; +import java.util.StringJoiner; + +import net.automatalib.commons.smartcollections.IntSeq; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.PolyNull; + +/** + * @author Aleksander Mendoza-Drosik + */ +class IntQueue { + + int value; + @Nullable IntQueue next; + + @Override + public String toString() { + final StringJoiner sj = new StringJoiner(", ", "[", "]"); + + IntQueue iter = this; + while (iter != null) { + sj.add(Integer.toString(iter.value)); + iter = iter.next; + } + return sj.toString(); + } + + static @Nullable IntQueue asQueue(IntSeq str) { + IntQueue q = null; + for (int i = str.size() - 1; i >= 0; i--) { + IntQueue next = new IntQueue(); + next.value = str.get(i); + next.next = q; + q = next; + } + assert !IntQueue.hasCycle(q); + return q; + } + + static boolean eq(@Nullable IntQueue a, @Nullable IntQueue b) { + IntQueue aIter = a; + IntQueue bIter = b; + while (aIter != null && bIter != null) { + if (aIter.value != bIter.value) { + return false; + } + aIter = aIter.next; + bIter = bIter.next; + } + return aIter == null && bIter == null; + } + + static boolean hasCycle(@Nullable IntQueue q) { + final Set elements = new HashSet<>(); + IntQueue iter = q; + while (iter != null) { + if (!elements.add(iter)) { + return true; + } + iter = iter.next; + } + return false; + } + + static @PolyNull IntQueue copyAndConcat(@Nullable IntQueue q, @PolyNull IntQueue tail) { + assert !hasCycle(q) && !hasCycle(tail); + if (q == null) { + return tail; + } + final IntQueue root = new IntQueue(); + root.value = q.value; + IntQueue curr = root; + IntQueue iter = q.next; + while (iter != null) { + curr.next = new IntQueue(); + curr = curr.next; + curr.value = iter.value; + iter = iter.next; + } + curr.next = tail; + assert !hasCycle(root); + return root; + } +} diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSSTWrapper.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSSTWrapper.java new file mode 100644 index 0000000000..faca344824 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSSTWrapper.java @@ -0,0 +1,117 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.algorithms.ostia; + +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Queue; +import java.util.Set; + +import net.automatalib.automata.transducers.SubsequentialTransducer; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; +import net.automatalib.words.WordBuilder; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * @author frohme + */ +class OSSTWrapper implements SubsequentialTransducer { + + private final State root; + private final Alphabet inputAlphabet; + private final Alphabet outputAlphabet; + + OSSTWrapper(State root, Alphabet inputAlphabet, Alphabet outputAlphabet) { + this.root = root; + this.inputAlphabet = inputAlphabet; + this.outputAlphabet = outputAlphabet; + } + + @Override + public Collection getStates() { + final Set cache = new LinkedHashSet<>(); + final Queue queue = new ArrayDeque<>(); + + queue.add(root); + + while (!queue.isEmpty()) { + @SuppressWarnings("nullness") // false positive https://github.com/typetools/checker-framework/issues/399 + @NonNull State s = queue.poll(); + cache.add(s); + + for (Edge transition : s.transitions) { + if (transition != null) { + State succ = transition.target; + + if (succ != null && !cache.contains(succ)) { + queue.add(succ); + cache.add(succ); + } + } + } + } + + return cache; + } + + @Override + public @Nullable Edge getTransition(State state, I input) { + return state.transitions[inputAlphabet.getSymbolIndex(input)]; + } + + @Override + public State getInitialState() { + return root; + } + + @Override + public Word getStateProperty(State state) { + return outToWord(state.out); + } + + @Override + public Word getTransitionProperty(Edge transition) { + return outToWord(transition.out); + } + + @Override + public State getSuccessor(Edge transition) { + return transition.target; + } + + private Word outToWord(@Nullable Out out) { + return outToWord(out == null ? null : out.str); + } + + private Word outToWord(@Nullable IntQueue out) { + if (out == null) { + return Word.epsilon(); + } + + final WordBuilder wb = new WordBuilder<>(); + + IntQueue outIter = out; + while (outIter != null) { + wb.add(outputAlphabet.getSymbol(outIter.value)); + outIter = outIter.next; + } + + return wb.toWord(); + } +} diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSTIA.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSTIA.java new file mode 100644 index 0000000000..be3b161d4a --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSTIA.java @@ -0,0 +1,380 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.algorithms.ostia; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; + +import de.learnlib.api.algorithm.PassiveLearningAlgorithm; +import de.learnlib.api.query.DefaultQuery; +import net.automatalib.automata.transducers.SubsequentialTransducer; +import net.automatalib.commons.smartcollections.IntSeq; +import net.automatalib.commons.util.Pair; +import net.automatalib.words.Alphabet; +import net.automatalib.words.GrowingAlphabet; +import net.automatalib.words.Word; +import net.automatalib.words.impl.GrowingMapAlphabet; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Implementation of the "onward subsequential transducer inference algorithm" (OSTIA) from the paper Learning Subsequential Transducers for Pattern Recognition Interpretation + * Tasks by Oncina, García and Vidal. + * + * @author Aleksander Mendoza-Drosik + * @author frohme + */ +public class OSTIA implements PassiveLearningAlgorithm, I, Word> { + + private final Alphabet inputAlphabet; + private final GrowingAlphabet outputAlphabet; + private final State root; + private boolean hasBeenComputed; + + public OSTIA(Alphabet inputAlphabet) { + this.inputAlphabet = inputAlphabet; + this.outputAlphabet = new GrowingMapAlphabet<>(); + this.root = new State(inputAlphabet.size()); + this.hasBeenComputed = false; + } + + @Override + public void addSamples(Collection>> samples) { + for (DefaultQuery> sample : samples) { + final Word output = sample.getOutput(); + this.outputAlphabet.addAll(output.asList()); + buildPttOnward(root, + sample.getInput().asIntSeq(inputAlphabet), + IntQueue.asQueue(output.asIntSeq(outputAlphabet))); + } + } + + @Override + public SubsequentialTransducer computeModel() { + if (!hasBeenComputed) { + hasBeenComputed = true; + ostia(root); + } + return new OSSTWrapper<>(root, inputAlphabet, outputAlphabet); + } + + public static State buildPtt(int alphabetSize, Iterator> informant) { + final State root = new State(alphabetSize); + while (informant.hasNext()) { + Pair inout = informant.next(); + buildPttOnward(root, inout.getFirst(), IntQueue.asQueue(inout.getSecond())); + } + return root; + } + + private static void buildPttOnward(State ptt, IntSeq input, @Nullable IntQueue output) { + State pttIter = ptt; + @Nullable IntQueue outputIter = output; + + for (int i = 0; i < input.size(); i++) {//input index + final int symbol = input.get(i); + final Edge edge; + if (pttIter.transitions[symbol] == null) { + edge = new Edge(); + edge.out = outputIter; + edge.target = new State(pttIter.transitions.length); + pttIter.transitions[symbol] = edge; + outputIter = null; + } else { + edge = pttIter.transitions[symbol]; + IntQueue commonPrefixEdge = edge.out; + IntQueue commonPrefixEdgePrev = null; + IntQueue commonPrefixInformant = outputIter; + while (commonPrefixEdge != null && commonPrefixInformant != null && + commonPrefixEdge.value == commonPrefixInformant.value) { + commonPrefixInformant = commonPrefixInformant.next; + commonPrefixEdgePrev = commonPrefixEdge; + commonPrefixEdge = commonPrefixEdge.next; + } + /* + informant=x + edge.out=y + -> + informant=lcp(x,y)^-1 x + edge=lcp(x,y) + pushback=lcp(x,y)^-1 y + */ + if (commonPrefixEdgePrev == null) { + edge.out = null; + } else { + commonPrefixEdgePrev.next = null; + } + edge.target.prependButIgnoreMissingStateOutput(commonPrefixEdge); + outputIter = commonPrefixInformant; + } + pttIter = edge.target; + } + if (pttIter.out != null && !IntQueue.eq(pttIter.out.str, outputIter)) { + throw new IllegalArgumentException("For input '" + input + "' the state output is '" + pttIter.out + + "' but training sample has remaining suffix '" + outputIter + '\''); + } + pttIter.out = new Out(outputIter); + } + + private static void addBlueStates(State parent, Queue blue) { + for (int i = 0; i < parent.transitions.length; i++) { + final Edge transition = parent.transitions[i]; + if (transition != null) { + assert !contains(blue, transition.target); + assert transition.target != parent; + blue.add(new Blue(parent, i)); + } + } + } + + public static void ostia(State transducer) { + final Queue blue = new LinkedList<>(); + final Set red = new LinkedHashSet<>(); + assert isTree(transducer, new HashSet<>()); + red.add(transducer); + addBlueStates(transducer, blue); + assert uniqueItems(blue); + assert disjoint(blue, red); + assert validateBlueAndRed(transducer, red, blue); + blue: + while (!blue.isEmpty()) { + @SuppressWarnings("nullness") // false positive https://github.com/typetools/checker-framework/issues/399 + final @NonNull Blue next = blue.poll(); + final @Nullable State blueState = next.state(); + assert blueState != null; + assert isTree(blueState, new HashSet<>()); + assert uniqueItems(blue); + assert !contains(blue, blueState); + assert disjoint(blue, red); + for (State redState : red) { + if (ostiaMerge(next, redState, blue, red)) { + assert disjoint(blue, red); + assert uniqueItems(blue); + continue blue; + } + } + assert isTree(blueState, new HashSet<>()); + assert uniqueItems(blue); + addBlueStates(blueState, blue); + assert uniqueItems(blue); + assert !contains(blue, blueState); + assert disjoint(blue, red); + red.add(blueState); + assert disjoint(blue, red); + assert validateBlueAndRed(transducer, red, blue); + } + } + + private static boolean ostiaMerge(Blue blue, State redState, Queue blueToVisit, Set red) { + final Map merged = new HashMap<>(); + final List reachedBlueStates = new ArrayList<>(); + if (ostiaFold(redState, null, blue.parent, blue.symbol, merged, reachedBlueStates)) { + for (Map.Entry mergedRedState : merged.entrySet()) { + assert mergedRedState.getKey() == mergedRedState.getValue().original; + mergedRedState.getValue().assign(); + } + for (Blue reachedBlueCandidate : reachedBlueStates) { + if (red.contains(reachedBlueCandidate.parent)) { + assert !contains(blueToVisit, reachedBlueCandidate.state()); + blueToVisit.add(reachedBlueCandidate); + } + } + return true; + } + return false; + } + + private static boolean ostiaFold(State red, + @Nullable IntQueue pushedBack, + State blueParent, + int symbolIncomingToBlue, + Map mergedStates, + List reachedBlueStates) { + final Edge incomingTransition = blueParent.transitions[symbolIncomingToBlue]; + assert incomingTransition != null; + final State blueState = incomingTransition.target; + assert red != blueState; + assert !mergedStates.containsKey(blueState); + + final StateCopy mergedRedState = mergedStates.computeIfAbsent(red, StateCopy::new); + final StateCopy mergedBlueState = new StateCopy(blueState); + final Edge mergedIncomingTransition = + mergedStates.computeIfAbsent(blueParent, StateCopy::new).transitions[symbolIncomingToBlue]; + assert mergedIncomingTransition != null; + mergedIncomingTransition.target = red; + + final StateCopy prevBlue = mergedStates.put(blueState, mergedBlueState); + assert prevBlue == null; + + mergedBlueState.prepend(pushedBack); + if (mergedBlueState.out != null) { + if (mergedRedState.out == null) { + mergedRedState.out = mergedBlueState.out; + } else if (!IntQueue.eq(mergedRedState.out.str, mergedBlueState.out.str)) { + return false; + } + } + for (int i = 0; i < mergedRedState.transitions.length; i++) { + final Edge transitionBlue = mergedBlueState.transitions[i]; + if (transitionBlue != null) { + final Edge transitionRed = mergedRedState.transitions[i]; + if (transitionRed == null) { + mergedRedState.transitions[i] = new Edge(transitionBlue); + reachedBlueStates.add(new Blue(red, i)); + } else { + IntQueue commonPrefixRed = transitionRed.out; + IntQueue commonPrefixBlue = transitionBlue.out; + IntQueue commonPrefixBluePrev = null; + while (commonPrefixBlue != null && commonPrefixRed != null && + commonPrefixBlue.value == commonPrefixRed.value) { + commonPrefixBluePrev = commonPrefixBlue; + commonPrefixBlue = commonPrefixBlue.next; + commonPrefixRed = commonPrefixRed.next; + } + assert commonPrefixBluePrev == null || commonPrefixBluePrev.next == commonPrefixBlue; + if (commonPrefixRed == null) { + if (commonPrefixBluePrev == null) { + transitionBlue.out = null; + } else { + commonPrefixBluePrev.next = null; + } + assert Objects.equals(Optional.ofNullable(mergedBlueState.transitions[i]).map(e -> e.target), + Optional.ofNullable(blueState.transitions[i]).map(e -> e.target)); + if (!ostiaFold(transitionRed.target, + commonPrefixBlue, + blueState, + i, + mergedStates, + reachedBlueStates)) { + return false; + } + } else { + return false; + } + } + } + } + return true; + } + + public static @Nullable IntSeq run(State init, IntSeq input) { + final List output = new ArrayList<>(); + State iter = init; + for (int i = 0; i < input.size(); i++) { + final Edge edge = iter.transitions[input.get(i)]; + if (edge == null) { + return null; + } + iter = edge.target; + IntQueue q = edge.out; + while (q != null) { + output.add(q.value); + q = q.next; + } + } + if (iter.out == null) { + return null; + } + IntQueue q = iter.out.str; + while (q != null) { + output.add(q.value); + q = q.next; + } + return IntSeq.of(output); + } + + // Assertion methods + + private static boolean disjoint(Queue blue, Set red) { + for (Blue b : blue) { + if (red.contains(b.state())) { + return false; + } + } + return true; + } + + private static boolean contains(Queue blue, @Nullable State state) { + for (Blue b : blue) { + if (Objects.equals(state, b.state())) { + return true; + } + } + return false; + } + + private static boolean uniqueItems(Queue blue) { + final Set<@Nullable State> unique = new HashSet<>(); + for (Blue b : blue) { + if (!unique.add(b.state())) { + return false; + } + } + return true; + } + + private static boolean validateBlueAndRed(State root, Set red, Queue blue) { + final Set reachable = new HashSet<>(); + isTree(root, reachable); + for (State r : red) { + for (Edge edge : r.transitions) { + assert edge == null || contains(blue, edge.target) ^ red.contains(edge.target); + } + assert reachable.contains(r); + } + for (Blue b : blue) { + assert red.contains(b.parent); + assert reachable.contains(b.state()); + } + return true; + } + + private static boolean isTree(State root, Set nodes) { + final Queue toVisit = new ArrayDeque<>(); + toVisit.add(root); + boolean isTree = true; + while (!toVisit.isEmpty()) { + @SuppressWarnings("nullness") // false positive https://github.com/typetools/checker-framework/issues/399 + final @NonNull State s = toVisit.poll(); + if (nodes.add(s)) { + for (Edge edge : s.transitions) { + if (edge != null) { + toVisit.add(edge.target); + } + } + } else { + isTree = false; + } + + } + return isTree; + } + +} + diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/Out.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/Out.java new file mode 100644 index 0000000000..d3fcb26642 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/Out.java @@ -0,0 +1,35 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.algorithms.ostia; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * @author Aleksander Mendoza-Drosik + */ +class Out { + + @Nullable IntQueue str; + + Out(@Nullable IntQueue str) { + this.str = str; + } + + @Override + public String toString() { + return String.valueOf(str); + } +} diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/State.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/State.java new file mode 100644 index 0000000000..28e19de774 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/State.java @@ -0,0 +1,43 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.algorithms.ostia; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * @author Aleksander Mendoza-Drosik + */ +public class State extends StateParent { + + State(int alphabetSize) { + super.out = null; + super.transitions = new Edge[alphabetSize]; + } + + /** + * The IntQueue is consumed and should not be reused after calling this method. + */ + void prependButIgnoreMissingStateOutput(@Nullable IntQueue prefix) { + for (@Nullable Edge edge : transitions) { + if (edge != null) { + edge.out = IntQueue.copyAndConcat(prefix, edge.out); + } + } + if (out != null) { + out.str = IntQueue.copyAndConcat(prefix, out.str); + } + } +} diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/StateCopy.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/StateCopy.java new file mode 100644 index 0000000000..c1daf1b9f0 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/StateCopy.java @@ -0,0 +1,62 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.algorithms.ostia; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * @author Aleksander Mendoza-Drosik + */ +class StateCopy extends StateParent { + + final State original; + + StateCopy(State original) { + super.out = original.out == null ? null : new Out(IntQueue.copyAndConcat(original.out.str, null)); + super.transitions = copyTransitions(original.transitions); + this.original = original; + } + + private static @Nullable Edge[] copyTransitions(@Nullable Edge[] transitions) { + final @Nullable Edge[] copy = new Edge[transitions.length]; + for (int i = 0; i < copy.length; i++) { + @Nullable Edge edge = transitions[i]; + copy[i] = edge == null ? null : new Edge(edge); + } + return copy; + } + + void assign() { + original.out = out; + original.transitions = transitions; + } + + /** + * The IntQueue is consumed and should not be reused after calling this method. + */ + void prepend(@Nullable IntQueue prefix) { + for (@Nullable Edge edge : transitions) { + if (edge != null) { + edge.out = IntQueue.copyAndConcat(prefix, edge.out); + } + } + if (out == null) { + out = new Out(prefix); + } else { + out.str = IntQueue.copyAndConcat(prefix, out.str); + } + } +} diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/StateParent.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/StateParent.java new file mode 100644 index 0000000000..0ec6e4c218 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/StateParent.java @@ -0,0 +1,32 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.algorithms.ostia; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * @author Aleksander Mendoza-Drosik + */ +class StateParent { + + @Nullable Out out; + @Nullable Edge[] transitions; + + @Override + public String toString() { + return String.valueOf(out); + } +} diff --git a/algorithms/passive/ostia/src/test/java/de/learnlib/algorithms/ostia/OSTIAIT.java b/algorithms/passive/ostia/src/test/java/de/learnlib/algorithms/ostia/OSTIAIT.java new file mode 100644 index 0000000000..1384389687 --- /dev/null +++ b/algorithms/passive/ostia/src/test/java/de/learnlib/algorithms/ostia/OSTIAIT.java @@ -0,0 +1,31 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.algorithms.ostia; + +import de.learnlib.testsupport.it.learner.AbstractSSTPassiveLearnerIT; +import de.learnlib.testsupport.it.learner.PassiveLearnerVariantList; +import net.automatalib.automata.transducers.SubsequentialTransducer; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; + +public class OSTIAIT extends AbstractSSTPassiveLearnerIT { + + @Override + protected void addLearnerVariants(Alphabet alphabet, + PassiveLearnerVariantList, I, Word> variants) { + variants.addLearnerVariant("OSTIA", new OSTIA<>(alphabet)); + } +} diff --git a/algorithms/passive/ostia/src/test/java/de/learnlib/algorithms/ostia/OSTIATest.java b/algorithms/passive/ostia/src/test/java/de/learnlib/algorithms/ostia/OSTIATest.java new file mode 100644 index 0000000000..4d752b8f8c --- /dev/null +++ b/algorithms/passive/ostia/src/test/java/de/learnlib/algorithms/ostia/OSTIATest.java @@ -0,0 +1,167 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.algorithms.ostia; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Random; + +import com.google.common.collect.Iterables; +import com.google.common.io.CharStreams; +import net.automatalib.automata.transducers.SubsequentialTransducer; +import net.automatalib.automata.transducers.impl.compact.CompactSST; +import net.automatalib.commons.smartcollections.IntSeq; +import net.automatalib.commons.util.IOUtil; +import net.automatalib.commons.util.Pair; +import net.automatalib.commons.util.collections.CollectionsUtil; +import net.automatalib.serialization.dot.GraphDOT; +import net.automatalib.util.automata.Automata; +import net.automatalib.util.automata.conformance.WMethodTestsIterator; +import net.automatalib.util.automata.random.RandomAutomata; +import net.automatalib.util.automata.transducers.SubsequentialTransducers; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; +import net.automatalib.words.WordBuilder; +import net.automatalib.words.impl.Alphabets; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class OSTIATest { + + private static final Alphabet INPUTS = Alphabets.characters('a', 'c'); + private static final Collection OUTPUTS = Arrays.asList("o1", "o2", "o3"); + private static final long SEED = 1337L; + + @DataProvider(name = "sizes") + public static Object[][] sizes() { + return new Object[][] {{10}, {25}, {50}, {100}}; + } + + /** + * Returns the examples from Section 18.3.4 of Colin de la Higuera's book "Grammatical Inference" with a's encoded + * as 0 and b's encoded as 1. + */ + public List> getExampleSamples() { + return Arrays.asList(Pair.of(IntSeq.of(0), IntSeq.of(1)), + Pair.of(IntSeq.of(1), IntSeq.of(1)), + Pair.of(IntSeq.of(0, 0), IntSeq.of(0, 1)), + Pair.of(IntSeq.of(0, 0, 0), IntSeq.of(0, 0, 1)), + Pair.of(IntSeq.of(0, 1, 0, 1), IntSeq.of(0, 1, 0, 1))); + } + + @Test + public void testStaticInvocation() { + final List> samples = getExampleSamples(); + + final State root = OSTIA.buildPtt(2, samples.iterator()); + OSTIA.ostia(root); + + Assert.assertEquals(OSTIA.run(root, IntSeq.of(1)), IntSeq.of(1)); + Assert.assertEquals(OSTIA.run(root, IntSeq.of(1, 0)), IntSeq.of(1, 1)); + Assert.assertEquals(OSTIA.run(root, IntSeq.of(1, 0, 0)), IntSeq.of(1, 0, 1)); + Assert.assertEquals(OSTIA.run(root, IntSeq.of(1, 0, 0, 1)), IntSeq.of(1, 0, 0, 1, 0, 1)); + Assert.assertEquals(OSTIA.run(root, IntSeq.of(1, 0, 0, 1, 0)), IntSeq.of(1, 0, 0, 1, 0, 1)); + Assert.assertEquals(OSTIA.run(root, IntSeq.of(1, 0, 0, 1, 0, 1)), IntSeq.of(1, 0, 0, 1, 0, 1)); + + Assert.assertEquals(OSTIA.run(root, IntSeq.of(0, 1, 0, 1, 0)), IntSeq.of(0, 1, 0, 1, 1)); + Assert.assertEquals(OSTIA.run(root, IntSeq.of(0, 1, 0, 1, 1)), IntSeq.of(0, 1, 0, 1, 1)); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testInconsistentSamples() { + final List> samples = new ArrayList<>(getExampleSamples()); + + samples.add(Pair.of(IntSeq.of(0, 1, 0, 1), IntSeq.of(0, 1, 0, 0))); + + OSTIA.buildPtt(2, samples.iterator()); + } + + @Test + public void testVisualization() throws IOException { + final Alphabet alphabet = Alphabets.integers(0, 1); + final OSTIA learner = new OSTIA<>(alphabet); + + final WordBuilder wb = new WordBuilder<>(); + for (Pair p : getExampleSamples()) { + Iterables.addAll(wb, p.getFirst()); + final Word input = wb.toWord(); + wb.clear(); + + Iterables.addAll(wb, p.getSecond()); + final Word output = wb.toWord(); + wb.clear(); + + learner.addSample(input, output); + } + + final SubsequentialTransducer model = learner.computeModel(); + final String expectedHyp; + + try (InputStream is = getClass().getResourceAsStream("/hyp.dot")) { + assert is != null; + expectedHyp = CharStreams.toString(IOUtil.asBufferedUTF8Reader(is)); + } + + final StringWriter actualHyp = new StringWriter(); + GraphDOT.write(model, alphabet, actualHyp); + + Assert.assertEquals(actualHyp.toString(), expectedHyp); + } + + @Test(dataProvider = "sizes") + public void testEquivalence(int size) { + + final Random random = new Random(SEED); + final CompactSST sst = new CompactSST<>(INPUTS); + + final List> words = new ArrayList<>(); + for (List t : CollectionsUtil.allTuples(OUTPUTS, 0, 3)) { + words.add(Word.fromList(t)); + } + + Collections.shuffle(words, random); + final int midpoint = words.size() / 2; + final Collection> stateProps = words.subList(0, midpoint); + final Collection> transProps = words.subList(midpoint, words.size()); + + RandomAutomata.randomDeterministic(random, size, INPUTS, stateProps, transProps, sst); + final SubsequentialTransducer osst = + SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS)); + Assert.assertTrue(SubsequentialTransducers.isOnwardSST(osst, INPUTS)); + + final OSTIA learner = new OSTIA<>(INPUTS); + + final int lookAhead = 2; + final Iterator> testIterator = new WMethodTestsIterator<>(sst, INPUTS, lookAhead); + + learner.addSample(Word.epsilon(), sst.computeOutput(Word.epsilon())); + while (testIterator.hasNext()) { + final Word test = testIterator.next(); + learner.addSample(test, sst.computeOutput(test)); + } + + final SubsequentialTransducer model = learner.computeModel(); + Assert.assertTrue(Automata.testEquivalence(osst, model, INPUTS)); + } +} diff --git a/algorithms/passive/ostia/src/test/resources/hyp.dot b/algorithms/passive/ostia/src/test/resources/hyp.dot new file mode 100644 index 0000000000..2fe49a9d5d --- /dev/null +++ b/algorithms/passive/ostia/src/test/resources/hyp.dot @@ -0,0 +1,16 @@ +digraph g { + + s0 [shape="circle" label="null / ε"]; + s1 [shape="circle" label="[0] / 1"]; + s2 [shape="circle" label="null / ε"]; + s0 -> s1 [label="0 / ε"]; + s0 -> s0 [label="1 / 1"]; + s1 -> s1 [label="0 / 0"]; + s1 -> s2 [label="1 / 0 1 0 1"]; + s2 -> s2 [label="0 / ε"]; + s2 -> s0 [label="1 / ε"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; + +} diff --git a/algorithms/passive/pom.xml b/algorithms/passive/pom.xml index dcdb717fee..cf73d412f8 100644 --- a/algorithms/passive/pom.xml +++ b/algorithms/passive/pom.xml @@ -32,6 +32,7 @@ limitations under the License. Parent module for (passive) automata learning algorithms shipped with LearnLib + ostia rpni rpni-edsm rpni-mdl diff --git a/distribution/pom.xml b/distribution/pom.xml index b16d2f0763..44b02b4f18 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -92,6 +92,11 @@ limitations under the License. learnlib-ttt-vpda + + de.learnlib + learnlib-ostia + + de.learnlib learnlib-rpni diff --git a/pom.xml b/pom.xml index 3582876bbf..bafe4c9512 100644 --- a/pom.xml +++ b/pom.xml @@ -99,6 +99,13 @@ limitations under the License. Developer + + Aleksander Mendoza-Drosik + aleksander.mendoza.drosik@gmail.com + + Developer + + @@ -312,6 +319,11 @@ limitations under the License. ${project.version} pom + + de.learnlib + learnlib-ostia + ${project.version} + de.learnlib learnlib-rpni diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractSSTPassiveLearnerIT.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractSSTPassiveLearnerIT.java new file mode 100644 index 0000000000..aa33614f93 --- /dev/null +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractSSTPassiveLearnerIT.java @@ -0,0 +1,90 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.testsupport.it.learner; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import de.learnlib.api.query.DefaultQuery; +import de.learnlib.examples.DefaultPassiveLearningExample; +import de.learnlib.examples.LearningExample; +import de.learnlib.examples.LearningExample.MealyLearningExample; +import de.learnlib.examples.LearningExample.SSTLearningExample; +import de.learnlib.examples.LearningExamples; +import de.learnlib.examples.PassiveLearningExample; +import net.automatalib.automata.UniversalDeterministicAutomaton; +import net.automatalib.automata.concepts.SuffixOutput; +import net.automatalib.automata.transducers.SubsequentialTransducer; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; +import org.testng.annotations.Factory; + +/** + * Abstract integration test for passive {@link SubsequentialTransducer}s learning algorithms. + *

+ * SST learning algorithms tested by this integration test are expected to assume membership queries yield the full + * output word corresponding to the suffix part of the query. + * + * @author frohme + */ +public abstract class AbstractSSTPassiveLearnerIT { + + @Factory + public Object[] createExampleITCases() { + final List> result = new ArrayList<>(); + + for (MealyLearningExample example : LearningExamples.createMealyExamples()) { + result.addAll(createAllVariantsITCase(example)); + } + + for (SSTLearningExample example : LearningExamples.createSSTExamples()) { + result.addAll(createAllVariantsITCase(example)); + } + + return result.toArray(); + } + + private & SuffixOutput>> List, SubsequentialTransducer>> createAllVariantsITCase( + LearningExample example) { + + final Alphabet alphabet = example.getAlphabet(); + final A reference = example.getReferenceAutomaton(); + + Collection>> queries = LearnerITUtil.generateSamples(alphabet, reference); + + final PassiveLearnerVariantListImpl, I, Word> variants = + new PassiveLearnerVariantListImpl<>(); + addLearnerVariants(alphabet, variants); + + final PassiveLearningExample> effectiveExample = + new DefaultPassiveLearningExample<>(queries, alphabet); + + return LearnerITUtil.createPassiveExampleITCases(effectiveExample, variants); + } + + /** + * Adds, for a given setup, all the variants of the DFA learner to be tested to the specified {@link + * PassiveLearnerVariantList variant list}. + * + * @param alphabet + * the input alphabet + * @param variants + * list to add the learner variants to + */ + protected abstract void addLearnerVariants(Alphabet alphabet, + PassiveLearnerVariantList, I, Word> variants); +} \ No newline at end of file diff --git a/test-support/learning-examples/src/main/java/de/learnlib/examples/DefaultLearningExample.java b/test-support/learning-examples/src/main/java/de/learnlib/examples/DefaultLearningExample.java index d819572346..2f9345e90a 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/examples/DefaultLearningExample.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/examples/DefaultLearningExample.java @@ -20,6 +20,7 @@ import net.automatalib.automata.concepts.SuffixOutput; import net.automatalib.automata.fsa.DFA; import net.automatalib.automata.transducers.MealyMachine; +import net.automatalib.automata.transducers.SubsequentialTransducer; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; @@ -81,4 +82,17 @@ public DefaultMealyLearningExample(Alphabet alphabet, MealyMachine + extends DefaultLearningExample, SubsequentialTransducer> + implements SSTLearningExample { + + public & InputAlphabetHolder> DefaultSSTLearningExample(A automaton) { + this(automaton.getInputAlphabet(), automaton); + } + + public DefaultSSTLearningExample(Alphabet alphabet, SubsequentialTransducer referenceAutomaton) { + super(alphabet, referenceAutomaton); + } + } + } diff --git a/test-support/learning-examples/src/main/java/de/learnlib/examples/LearningExample.java b/test-support/learning-examples/src/main/java/de/learnlib/examples/LearningExample.java index d4f6f8eb8f..fba1430398 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/examples/LearningExample.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/examples/LearningExample.java @@ -19,6 +19,7 @@ import net.automatalib.automata.fsa.DFA; import net.automatalib.automata.transducers.MealyMachine; import net.automatalib.automata.transducers.StateLocalInputMealyMachine; +import net.automatalib.automata.transducers.SubsequentialTransducer; import net.automatalib.words.Alphabet; public interface LearningExample> { @@ -31,6 +32,8 @@ interface DFALearningExample extends LearningExample> {} interface MealyLearningExample extends LearningExample> {} + interface SSTLearningExample extends LearningExample> {} + /** * A {@link LearningExample} refinement for {@link StateLocalInputMealyMachine}. *

diff --git a/test-support/learning-examples/src/main/java/de/learnlib/examples/LearningExamples.java b/test-support/learning-examples/src/main/java/de/learnlib/examples/LearningExamples.java index 1a4fb3ef82..54fef25a22 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/examples/LearningExamples.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/examples/LearningExamples.java @@ -16,12 +16,14 @@ package de.learnlib.examples; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Random; import de.learnlib.examples.LearningExample.DFALearningExample; import de.learnlib.examples.LearningExample.MealyLearningExample; +import de.learnlib.examples.LearningExample.SSTLearningExample; import de.learnlib.examples.LearningExample.StateLocalInputMealyLearningExample; import de.learnlib.examples.dfa.ExampleAngluin; import de.learnlib.examples.dfa.ExampleKeylock; @@ -34,7 +36,9 @@ import de.learnlib.examples.mealy.ExampleShahbazGroz; import de.learnlib.examples.mealy.ExampleStack; import de.learnlib.examples.mealy.ExampleTinyMealy; +import de.learnlib.examples.sst.ExampleRandomSST; import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; import net.automatalib.words.impl.Alphabets; public final class LearningExamples { @@ -44,6 +48,8 @@ public final class LearningExamples { private static final int GRID_XSIZE = 5; private static final int GRID_YSIZE = 5; private static final String[] RANDOM_MEALY_OUTPUTS = {"o1", "o2", "o3"}; + private static final Collection> RANDOM_SST_PROPS = + Arrays.asList(Word.fromCharSequence("ab"), Word.fromCharSequence("bc"), Word.fromCharSequence("ca")); private static final String UNDEFINED_MEALY_OUTPUT = "undefined"; private static final int KEYLOCK_SIZE = 100; private static final long RANDOM_SEED = 1337L; @@ -80,4 +86,12 @@ public static List> createDFAExamples() { RANDOM_MEALY_OUTPUTS)); } + public static List> createSSTExamples() { + return Collections.singletonList(ExampleRandomSST.createExample(new Random(RANDOM_SEED), + RANDOM_ALPHABET, + RANDOM_SIZE, + RANDOM_SST_PROPS, + RANDOM_SST_PROPS)); + } + } diff --git a/test-support/learning-examples/src/main/java/de/learnlib/examples/sst/ExampleRandomSST.java b/test-support/learning-examples/src/main/java/de/learnlib/examples/sst/ExampleRandomSST.java new file mode 100644 index 0000000000..5c69d93d26 --- /dev/null +++ b/test-support/learning-examples/src/main/java/de/learnlib/examples/sst/ExampleRandomSST.java @@ -0,0 +1,57 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.examples.sst; + +import java.util.Collection; +import java.util.Random; + +import de.learnlib.examples.DefaultLearningExample.DefaultSSTLearningExample; +import net.automatalib.automata.transducers.impl.compact.CompactSST; +import net.automatalib.util.automata.random.RandomAutomata; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; + +public class ExampleRandomSST extends DefaultSSTLearningExample { + + public ExampleRandomSST(Alphabet alphabet, + int size, + Collection> stateProperties, + Collection> transitionProperties) { + this(new Random(), alphabet, size, stateProperties, transitionProperties); + } + + public ExampleRandomSST(Random random, + Alphabet alphabet, + int size, + Collection> stateProperties, + Collection> transitionProperties) { + super(alphabet, + RandomAutomata.randomDeterministic(random, + size, + alphabet, + stateProperties, + transitionProperties, + new CompactSST<>(alphabet))); + } + + public static ExampleRandomSST createExample(Random random, + Alphabet alphabet, + int size, + Collection> stateProperties, + Collection> transitionProperties) { + return new ExampleRandomSST<>(random, alphabet, size, stateProperties, transitionProperties); + } +}