From 3c254c0a4dc9c90a54fbe1d54d311ff5d2980f8f Mon Sep 17 00:00:00 2001 From: Alagris Date: Sat, 28 Nov 2020 15:58:35 +0100 Subject: [PATCH 01/21] OSTIA sketch --- .../de/learnlib/algorithms/ostia/OSTIA.java | 421 ++++++++++++++++++ 1 file changed, 421 insertions(+) create mode 100644 algorithms/passive/rpni/src/main/java/de/learnlib/algorithms/ostia/OSTIA.java diff --git a/algorithms/passive/rpni/src/main/java/de/learnlib/algorithms/ostia/OSTIA.java b/algorithms/passive/rpni/src/main/java/de/learnlib/algorithms/ostia/OSTIA.java new file mode 100644 index 0000000000..42bb0f9a63 --- /dev/null +++ b/algorithms/passive/rpni/src/main/java/de/learnlib/algorithms/ostia/OSTIA.java @@ -0,0 +1,421 @@ +/* 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 net.automatalib.commons.util.Pair; + +import java.util.*; + +public class OSTIA { + + + public static final IntSeq Epsilon = seq(); + + + interface IntSeq { + int size(); + + int get(int index); + } + private static IntSeq seq(int... ints) { + return new IntSeq() { + @Override + public int size() { + return ints.length; + } + + @Override + public int get(int index) { + return ints[index]; + } + + @Override + public String toString() { + return Arrays.toString(ints); + } + }; + } + + + static class IntQueue { + int value; + IntQueue next; + + @Override + public String toString() { + return OSTIA.toString(this); + } + + + public static int len(IntQueue q){ + int len =0; + while(q!=null){ + len++; + q = q.next; + } + return len; + } + public static int[] arr(IntQueue q){ + final int[] arr = new int[len(q)]; + for(int i=0;i elements = new HashSet<>(); + while(q!=null){ + if(!elements.add(q)){ + return true; + } + q = q.next; + } + return false; + } + static class Out { + IntQueue str; + + public Out(IntQueue str) { + this.str = str; + } + + @Override + public String toString() { + return OSTIA.toString(str); + } + } + + static IntQueue concat(IntQueue q, IntQueue tail) { + assert !hasCycle(q) && !hasCycle(tail); + if (q == null) return tail; + final IntQueue first = q; + while (q.next != null) { + q = q.next; + } + q.next = tail; + assert !hasCycle(first); + return first; + } + + static IntQueue copyAndConcat(IntQueue q, IntQueue tail) { + assert !hasCycle(q) && !hasCycle(tail); + if (q == null) return tail; + final IntQueue root = new IntQueue(); + root.value = q.value; + IntQueue curr = root; + q = q.next; + while (q != null) { + curr.next = new IntQueue(); + curr = curr.next; + curr.value = q.value; + q = q.next; + } + curr.next = tail; + assert !hasCycle(root); + return root; + } + + + /** + * builds onward prefix tree transducer + */ + private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { + for (int i = 0; i < input.size(); i++) {//input index + final int symbol = input.get(i); + if (ptt.transitions[symbol] == null) { + final State.Edge edge = ptt.transitions[symbol] = new State.Edge(); + edge.out = output; + output = null; + ptt = edge.target = new State(ptt.transitions.length); + } else { + final State.Edge edge = ptt.transitions[symbol]; + IntQueue commonPrefixEdge = edge.out; + IntQueue commonPrefixEdgePrev = null; + IntQueue commonPrefixInformant = output; + 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.prepend(commonPrefixEdge); + output = commonPrefixInformant; + ptt = edge.target; + } + } + if (ptt.out != null && !eq(ptt.out.str, output)) throw new IllegalArgumentException(); + ptt.out = new Out(output); + } + + private static boolean eq(IntQueue a, IntQueue b) { + while (a != null && b != null) { + if (a.value != b.value) return false; + a = a.next; + b = b.next; + } + return a == null && b == null; + } + + private static IntQueue asQueue(IntSeq str, int offset) { + IntQueue q = null; + for (int i = str.size() - 1; i >= offset; i--) { + IntQueue next = new IntQueue(); + next.value = str.get(i); + next.next = q; + q = next; + } + assert !hasCycle(q); + return q; + } + + + 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(), asQueue(inout.getSecond(), 0)); + } + return root; + } + + static class State { + public void assign(State other) { + out = other.out; + transitions = other.transitions; + } + + static class Edge { + IntQueue out; + State target; + + public Edge() { + + } + + public Edge(Edge edge) { + out = copyAndConcat(edge.out,null); + target = edge.target; + } + + @Override + public String toString() { + return target.toString(); + } + } + + public Out out; + public Edge[] transitions; + + State(int alphabetSize) { + transitions = new Edge[alphabetSize]; + } + + State(State copy) { + transitions = copyTransitions(copy.transitions); + out = copy.out == null ? null : new Out(copyAndConcat(copy.out.str, null)); + } + + /** + * The IntQueue is consumed and should not be reused after calling this method + */ + void prepend(IntQueue prefix) { + for (Edge edge : transitions) { + if (edge != null) { + edge.out = copyAndConcat(prefix, edge.out); + } + } + if (out == null) { + out = new Out(prefix); + } else { + out.str = copyAndConcat(prefix, out.str); + } + } + + } + + static class Blue { + State parent; + int symbol; + + State state() { + return parent.transitions[symbol].target; + } + + public Blue(State parent, int symbol) { + this.symbol = symbol; + this.parent = parent; + } + + @Override + public String toString() { + return state().toString(); + } + } + + static void addBlueStates(State parent, java.util.Queue blue) { + for (int i = 0; i < parent.transitions.length; i++) + if (parent.transitions[i] != null) + blue.add(new Blue(parent, i)); + } + + static State.Edge[] copyTransitions(State.Edge[] transitions) { + final State.Edge[] copy = new State.Edge[transitions.length]; + for (int i = 0; i < copy.length; i++) { + copy[i] = transitions[i] == null ? null : new State.Edge(transitions[i]); + } + return copy; + } + static class VV{ + + } + + + public static void ostia(State transducer) { + final java.util.Queue blue = new LinkedList<>(); + final ArrayList red = new ArrayList<>(); + red.add(transducer); + addBlueStates(transducer, blue); + blue:while (!blue.isEmpty()) { + final Blue next = blue.poll(); + final State blueState = next.state(); + for (State redState : red) { + if (ostiaMerge(next, redState, blue)){ + continue blue; + } + } + addBlueStates(blueState,blue); + red.add(blueState); + } + } + + private static boolean ostiaMerge(Blue blue, State redState, java.util.Queue blueToVisit) { + final HashMap merged = new HashMap<>(); + final ArrayList reachedBlueStates = new ArrayList<>(); + if (ostiaFold(redState,null,blue.parent,blue.symbol , merged, reachedBlueStates)) { + for (Map.Entry mergedRedState : merged.entrySet()) { + mergedRedState.getKey().assign(mergedRedState.getValue()); + } + blueToVisit.addAll(reachedBlueStates); + return true; + } + return false; + } + + private static boolean ostiaFold(State red, IntQueue pushedBack,State blueParent,int symbolIncomingToBlue, HashMap mergedStates, ArrayList reachedBlueStates) { + final State mergedRedState = mergedStates.computeIfAbsent(red, State::new); + final State blueState = blueParent.transitions[symbolIncomingToBlue].target; + final State mergedBlueState = new State(blueState); + assert !mergedStates.containsKey(blueState); + mergedStates.computeIfAbsent(blueParent, State::new).transitions[symbolIncomingToBlue].target = red; + final State 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 (!eq(mergedRedState.out.str, mergedBlueState.out.str)){ + return false; + } + } + for (int i = 0; i < mergedRedState.transitions.length; i++) { + final State.Edge transitionBlue = mergedBlueState.transitions[i]; + if (transitionBlue != null) { + final State.Edge transitionRed = mergedRedState.transitions[i]; + if (transitionRed == null) { + mergedRedState.transitions[i] = new State.Edge(transitionBlue); + reachedBlueStates.add(new Blue(blueState, 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? + commonPrefixBlue==transitionBlue.out: + commonPrefixBluePrev.next==commonPrefixBlue; + if (commonPrefixRed == null) { + if (commonPrefixBluePrev == null) { + transitionBlue.out = null; + } else { + commonPrefixBluePrev.next = null; + } + if (!ostiaFold(transitionRed.target, commonPrefixBlue, mergedBlueState,i, mergedStates, reachedBlueStates)) { + return false; + } + + } else { + return false; + } + } + } + } + return true; + } + + static ArrayList run(State init, Iterator input) { + ArrayList output = new ArrayList<>(); + while (input.hasNext()) { + final State.Edge edge = init.transitions[input.next()]; + if (edge == null) return null; + init = edge.target; + IntQueue q = edge.out; + while (q != null) { + output.add(q.value); + q = q.next; + } + } + if (init.out == null) return null; + IntQueue q = init.out.str; + while (q != null) { + output.add(q.value); + q = q.next; + } + return output; + } + + private static String toString(IntQueue ints) { + if (ints == null) return ""; + StringBuilder sb = new StringBuilder(); + sb.append(ints.value); + ints = ints.next; + while (ints != null) { + sb.append(" ").append(ints.value); + ints = ints.next; + } + return sb.toString(); + } + +} + From ad5513c8c4346020f4581e0f8316d3d028d70ee0 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Sun, 6 Dec 2020 00:32:19 +0100 Subject: [PATCH 02/21] initial refactoring into separate module --- algorithms/passive/ostia/pom.xml | 45 +++ .../de/learnlib/algorithms/ostia/Blue.java | 21 ++ .../de/learnlib/algorithms/ostia/Edge.java | 21 ++ .../learnlib/algorithms/ostia/IntQueue.java | 51 ++++ .../de/learnlib/algorithms/ostia/IntSeq.java | 32 +++ .../de/learnlib/algorithms/ostia/OSTIA.java | 256 ++++-------------- .../de/learnlib/algorithms/ostia/Out.java | 15 + .../de/learnlib/algorithms/ostia/State.java | 38 +++ .../de/learnlib/algorithms/ostia/OSTIAIT.java | 112 ++++++++ algorithms/passive/pom.xml | 3 +- distribution/pom.xml | 5 + pom.xml | 5 + 12 files changed, 403 insertions(+), 201 deletions(-) create mode 100644 algorithms/passive/ostia/pom.xml create mode 100644 algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/Blue.java create mode 100644 algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/Edge.java create mode 100644 algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntQueue.java create mode 100644 algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java rename algorithms/passive/{rpni => ostia}/src/main/java/de/learnlib/algorithms/ostia/OSTIA.java (56%) create mode 100644 algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/Out.java create mode 100644 algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/State.java create mode 100644 algorithms/passive/ostia/src/test/java/de/learnlib/algorithms/ostia/OSTIAIT.java diff --git a/algorithms/passive/ostia/pom.xml b/algorithms/passive/ostia/pom.xml new file mode 100644 index 0000000000..c3affd89eb --- /dev/null +++ b/algorithms/passive/ostia/pom.xml @@ -0,0 +1,45 @@ + + + + 4.0.0 + + + learnlib-algorithms-passive-parent + de.learnlib + 0.17.0-SNAPSHOT + + + learnlib-ostia + + LearnLib :: Algorithms :: OSTIA + The OSTIA passive learning algorithm + + + + de.learnlib + learnlib-api + + + + + de.learnlib.testsupport + learnlib-learner-it-support + + + + 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..b0f3930ebd --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/Blue.java @@ -0,0 +1,21 @@ +package de.learnlib.algorithms.ostia; + +class Blue { + + State parent; + int symbol; + + State state() { + return parent.transitions[symbol].target; + } + + public Blue(State parent, int symbol) { + this.symbol = symbol; + this.parent = parent; + } + + @Override + public String toString() { + return state().toString(); + } +} 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..3064f38427 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/Edge.java @@ -0,0 +1,21 @@ +package de.learnlib.algorithms.ostia; + +class Edge { + + IntQueue out; + State target; + + public Edge() { + + } + + public Edge(Edge edge) { + out = OSTIA.copyAndConcat(edge.out, null); + target = edge.target; + } + + @Override + public String toString() { + return target.toString(); + } +} 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..261d5a575d --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntQueue.java @@ -0,0 +1,51 @@ +package de.learnlib.algorithms.ostia; + +import java.util.HashSet; + +class IntQueue { + + int value; + IntQueue next; + + @Override + public String toString() { + + StringBuilder sb = new StringBuilder(); + sb.append(value); + IntQueue next = this.next; + while (next != null) { + sb.append(" ").append(next.value); + next = next.next; + } + return sb.toString(); + } + + public static int len(IntQueue q) { + int len = 0; + while (q != null) { + len++; + q = q.next; + } + return len; + } + + public static int[] arr(IntQueue q) { + final int[] arr = new int[len(q)]; + for (int i = 0; i < arr.length; i++) { + arr[i] = q.value; + q = q.next; + } + return arr; + } + + static boolean hasCycle(IntQueue q) { + final HashSet elements = new HashSet<>(); + while (q != null) { + if (!elements.add(q)) { + return true; + } + q = q.next; + } + return false; + } +} diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java new file mode 100644 index 0000000000..23c1b02bd4 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java @@ -0,0 +1,32 @@ +package de.learnlib.algorithms.ostia; + +import java.util.Arrays; + +interface IntSeq { + + int size(); + + int get(int index); + + public static final IntSeq Epsilon = seq(); + + static IntSeq seq(int... ints) { + return new IntSeq() { + + @Override + public int size() { + return ints.length; + } + + @Override + public int get(int index) { + return ints[index]; + } + + @Override + public String toString() { + return Arrays.toString(ints); + } + }; + } +} diff --git a/algorithms/passive/rpni/src/main/java/de/learnlib/algorithms/ostia/OSTIA.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSTIA.java similarity index 56% rename from algorithms/passive/rpni/src/main/java/de/learnlib/algorithms/ostia/OSTIA.java rename to algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSTIA.java index 42bb0f9a63..fbe8a63d10 100644 --- a/algorithms/passive/rpni/src/main/java/de/learnlib/algorithms/ostia/OSTIA.java +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSTIA.java @@ -15,94 +15,31 @@ */ package de.learnlib.algorithms.ostia; -import net.automatalib.commons.util.Pair; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; -import java.util.*; +import net.automatalib.commons.util.Pair; public class OSTIA { - - public static final IntSeq Epsilon = seq(); - - - interface IntSeq { - int size(); - - int get(int index); - } - private static IntSeq seq(int... ints) { - return new IntSeq() { - @Override - public int size() { - return ints.length; - } - - @Override - public int get(int index) { - return ints[index]; - } - - @Override - public String toString() { - return Arrays.toString(ints); - } - }; - } - - - static class IntQueue { - int value; - IntQueue next; - - @Override - public String toString() { - return OSTIA.toString(this); - } - - - public static int len(IntQueue q){ - int len =0; - while(q!=null){ - len++; - q = q.next; - } - return len; - } - public static int[] arr(IntQueue q){ - final int[] arr = new int[len(q)]; - for(int i=0;i elements = new HashSet<>(); - while(q!=null){ - if(!elements.add(q)){ + while (q != null) { + if (!elements.add(q)) { return true; } q = q.next; } return false; } - static class Out { - IntQueue str; - - public Out(IntQueue str) { - this.str = str; - } - - @Override - public String toString() { - return OSTIA.toString(str); - } - } static IntQueue concat(IntQueue q, IntQueue tail) { assert !hasCycle(q) && !hasCycle(tail); - if (q == null) return tail; + if (q == null) { return tail; } final IntQueue first = q; while (q.next != null) { q = q.next; @@ -114,7 +51,7 @@ static IntQueue concat(IntQueue q, IntQueue tail) { static IntQueue copyAndConcat(IntQueue q, IntQueue tail) { assert !hasCycle(q) && !hasCycle(tail); - if (q == null) return tail; + if (q == null) { return tail; } final IntQueue root = new IntQueue(); root.value = q.value; IntQueue curr = root; @@ -130,7 +67,6 @@ static IntQueue copyAndConcat(IntQueue q, IntQueue tail) { return root; } - /** * builds onward prefix tree transducer */ @@ -138,17 +74,17 @@ private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { for (int i = 0; i < input.size(); i++) {//input index final int symbol = input.get(i); if (ptt.transitions[symbol] == null) { - final State.Edge edge = ptt.transitions[symbol] = new State.Edge(); + final Edge edge = ptt.transitions[symbol] = new Edge(); edge.out = output; output = null; ptt = edge.target = new State(ptt.transitions.length); } else { - final State.Edge edge = ptt.transitions[symbol]; + final Edge edge = ptt.transitions[symbol]; IntQueue commonPrefixEdge = edge.out; IntQueue commonPrefixEdgePrev = null; IntQueue commonPrefixInformant = output; - while (commonPrefixEdge != null && commonPrefixInformant != null - && commonPrefixEdge.value == commonPrefixInformant.value) { + while (commonPrefixEdge != null && commonPrefixInformant != null && + commonPrefixEdge.value == commonPrefixInformant.value) { commonPrefixInformant = commonPrefixInformant.next; commonPrefixEdgePrev = commonPrefixEdge; commonPrefixEdge = commonPrefixEdge.next; @@ -163,7 +99,7 @@ private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { */ if (commonPrefixEdgePrev == null) { edge.out = null; - }else{ + } else { commonPrefixEdgePrev.next = null; } edge.target.prepend(commonPrefixEdge); @@ -171,13 +107,13 @@ private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { ptt = edge.target; } } - if (ptt.out != null && !eq(ptt.out.str, output)) throw new IllegalArgumentException(); + if (ptt.out != null && !eq(ptt.out.str, output)) { throw new IllegalArgumentException(); } ptt.out = new Out(output); } private static boolean eq(IntQueue a, IntQueue b) { while (a != null && b != null) { - if (a.value != b.value) return false; + if (a.value != b.value) { return false; } a = a.next; b = b.next; } @@ -196,7 +132,6 @@ private static IntQueue asQueue(IntSeq str, int offset) { return q; } - public static State buildPtt(int alphabetSize, Iterator> informant) { final State root = new State(alphabetSize); while (informant.hasNext()) { @@ -206,112 +141,35 @@ public static State buildPtt(int alphabetSize, Iterator> in return root; } - static class State { - public void assign(State other) { - out = other.out; - transitions = other.transitions; - } - - static class Edge { - IntQueue out; - State target; - - public Edge() { - - } - - public Edge(Edge edge) { - out = copyAndConcat(edge.out,null); - target = edge.target; - } - - @Override - public String toString() { - return target.toString(); - } - } - - public Out out; - public Edge[] transitions; - - State(int alphabetSize) { - transitions = new Edge[alphabetSize]; - } - - State(State copy) { - transitions = copyTransitions(copy.transitions); - out = copy.out == null ? null : new Out(copyAndConcat(copy.out.str, null)); - } - - /** - * The IntQueue is consumed and should not be reused after calling this method - */ - void prepend(IntQueue prefix) { - for (Edge edge : transitions) { - if (edge != null) { - edge.out = copyAndConcat(prefix, edge.out); - } - } - if (out == null) { - out = new Out(prefix); - } else { - out.str = copyAndConcat(prefix, out.str); - } - } - - } - - static class Blue { - State parent; - int symbol; - - State state() { - return parent.transitions[symbol].target; - } - - public Blue(State parent, int symbol) { - this.symbol = symbol; - this.parent = parent; - } - - @Override - public String toString() { - return state().toString(); - } - } - static void addBlueStates(State parent, java.util.Queue blue) { - for (int i = 0; i < parent.transitions.length; i++) - if (parent.transitions[i] != null) - blue.add(new Blue(parent, i)); + for (int i = 0; i < parent.transitions.length; i++) { + if (parent.transitions[i] != null) { blue.add(new Blue(parent, i)); } + } } - static State.Edge[] copyTransitions(State.Edge[] transitions) { - final State.Edge[] copy = new State.Edge[transitions.length]; + static Edge[] copyTransitions(Edge[] transitions) { + final Edge[] copy = new Edge[transitions.length]; for (int i = 0; i < copy.length; i++) { - copy[i] = transitions[i] == null ? null : new State.Edge(transitions[i]); + copy[i] = transitions[i] == null ? null : new Edge(transitions[i]); } return copy; } - static class VV{ - - } - public static void ostia(State transducer) { final java.util.Queue blue = new LinkedList<>(); final ArrayList red = new ArrayList<>(); red.add(transducer); addBlueStates(transducer, blue); - blue:while (!blue.isEmpty()) { + blue: + while (!blue.isEmpty()) { final Blue next = blue.poll(); final State blueState = next.state(); for (State redState : red) { - if (ostiaMerge(next, redState, blue)){ + if (ostiaMerge(next, redState, blue)) { continue blue; } } - addBlueStates(blueState,blue); + addBlueStates(blueState, blue); red.add(blueState); } } @@ -319,7 +177,7 @@ public static void ostia(State transducer) { private static boolean ostiaMerge(Blue blue, State redState, java.util.Queue blueToVisit) { final HashMap merged = new HashMap<>(); final ArrayList reachedBlueStates = new ArrayList<>(); - if (ostiaFold(redState,null,blue.parent,blue.symbol , merged, reachedBlueStates)) { + if (ostiaFold(redState, null, blue.parent, blue.symbol, merged, reachedBlueStates)) { for (Map.Entry mergedRedState : merged.entrySet()) { mergedRedState.getKey().assign(mergedRedState.getValue()); } @@ -329,49 +187,59 @@ private static boolean ostiaMerge(Blue blue, State redState, java.util.Queue mergedStates, ArrayList reachedBlueStates) { + private static boolean ostiaFold(State red, + IntQueue pushedBack, + State blueParent, + int symbolIncomingToBlue, + HashMap mergedStates, + ArrayList reachedBlueStates) { final State mergedRedState = mergedStates.computeIfAbsent(red, State::new); final State blueState = blueParent.transitions[symbolIncomingToBlue].target; final State mergedBlueState = new State(blueState); assert !mergedStates.containsKey(blueState); mergedStates.computeIfAbsent(blueParent, State::new).transitions[symbolIncomingToBlue].target = red; - final State prevBlue = mergedStates.put(blueState,mergedBlueState); + final State prevBlue = mergedStates.put(blueState, mergedBlueState); assert prevBlue == null; mergedBlueState.prepend(pushedBack); - if(mergedBlueState.out!=null) { + if (mergedBlueState.out != null) { if (mergedRedState.out == null) { mergedRedState.out = mergedBlueState.out; - } else if (!eq(mergedRedState.out.str, mergedBlueState.out.str)){ + } else if (!eq(mergedRedState.out.str, mergedBlueState.out.str)) { return false; } } for (int i = 0; i < mergedRedState.transitions.length; i++) { - final State.Edge transitionBlue = mergedBlueState.transitions[i]; + final Edge transitionBlue = mergedBlueState.transitions[i]; if (transitionBlue != null) { - final State.Edge transitionRed = mergedRedState.transitions[i]; + final Edge transitionRed = mergedRedState.transitions[i]; if (transitionRed == null) { - mergedRedState.transitions[i] = new State.Edge(transitionBlue); + mergedRedState.transitions[i] = new Edge(transitionBlue); reachedBlueStates.add(new Blue(blueState, i)); } else { IntQueue commonPrefixRed = transitionRed.out; IntQueue commonPrefixBlue = transitionBlue.out; IntQueue commonPrefixBluePrev = null; - while (commonPrefixBlue != null && commonPrefixRed != null - && commonPrefixBlue.value == commonPrefixRed.value) { + while (commonPrefixBlue != null && commonPrefixRed != null && + commonPrefixBlue.value == commonPrefixRed.value) { commonPrefixBluePrev = commonPrefixBlue; commonPrefixBlue = commonPrefixBlue.next; commonPrefixRed = commonPrefixRed.next; } - assert commonPrefixBluePrev==null? - commonPrefixBlue==transitionBlue.out: - commonPrefixBluePrev.next==commonPrefixBlue; + assert commonPrefixBluePrev == null ? + commonPrefixBlue == transitionBlue.out : + commonPrefixBluePrev.next == commonPrefixBlue; if (commonPrefixRed == null) { if (commonPrefixBluePrev == null) { transitionBlue.out = null; } else { commonPrefixBluePrev.next = null; } - if (!ostiaFold(transitionRed.target, commonPrefixBlue, mergedBlueState,i, mergedStates, reachedBlueStates)) { + if (!ostiaFold(transitionRed.target, + commonPrefixBlue, + mergedBlueState, + i, + mergedStates, + reachedBlueStates)) { return false; } @@ -387,8 +255,8 @@ private static boolean ostiaFold(State red, IntQueue pushedBack,State blueParent static ArrayList run(State init, Iterator input) { ArrayList output = new ArrayList<>(); while (input.hasNext()) { - final State.Edge edge = init.transitions[input.next()]; - if (edge == null) return null; + final Edge edge = init.transitions[input.next()]; + if (edge == null) { return null; } init = edge.target; IntQueue q = edge.out; while (q != null) { @@ -396,7 +264,7 @@ static ArrayList run(State init, Iterator input) { q = q.next; } } - if (init.out == null) return null; + if (init.out == null) { return null; } IntQueue q = init.out.str; while (q != null) { output.add(q.value); @@ -405,17 +273,5 @@ static ArrayList run(State init, Iterator input) { return output; } - private static String toString(IntQueue ints) { - if (ints == null) return ""; - StringBuilder sb = new StringBuilder(); - sb.append(ints.value); - ints = ints.next; - while (ints != null) { - sb.append(" ").append(ints.value); - ints = ints.next; - } - return sb.toString(); - } - } 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..58c7d311fb --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/Out.java @@ -0,0 +1,15 @@ +package de.learnlib.algorithms.ostia; + +class Out { + + IntQueue str; + + public Out(IntQueue str) { + this.str = str; + } + + @Override + public String toString() { + return str.toString(); + } +} 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..8cf19056cb --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/State.java @@ -0,0 +1,38 @@ +package de.learnlib.algorithms.ostia; + +class State { + + public void assign(State other) { + out = other.out; + transitions = other.transitions; + } + + public Out out; + public Edge[] transitions; + + State(int alphabetSize) { + transitions = new Edge[alphabetSize]; + } + + State(State copy) { + transitions = OSTIA.copyTransitions(copy.transitions); + out = copy.out == null ? null : new Out(OSTIA.copyAndConcat(copy.out.str, null)); + } + + /** + * The IntQueue is consumed and should not be reused after calling this method + */ + void prepend(IntQueue prefix) { + for (Edge edge : transitions) { + if (edge != null) { + edge.out = OSTIA.copyAndConcat(prefix, edge.out); + } + } + if (out == null) { + out = new Out(prefix); + } else { + out.str = OSTIA.copyAndConcat(prefix, out.str); + } + } + +} 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..0973c0b2f3 --- /dev/null +++ b/algorithms/passive/ostia/src/test/java/de/learnlib/algorithms/ostia/OSTIAIT.java @@ -0,0 +1,112 @@ +/* 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.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; + +import de.learnlib.examples.mealy.ExampleRandomMealy; +import net.automatalib.automata.transducers.MealyMachine; +import net.automatalib.commons.util.Pair; +import net.automatalib.util.automata.Automata; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; +import net.automatalib.words.impl.Alphabets; +import org.testng.Assert; +import org.testng.ITest; +import org.testng.annotations.Factory; +import org.testng.annotations.Test; + +public class OSTIAIT { + + private static final Alphabet RANDOM_ALPHABET = Alphabets.characters('a', 'c'); + private static final int RANDOM_SIZE = 100; + private static final String[] RANDOM_MEALY_OUTPUTS = {"o1", "o2", "o3"}; + private static final long RANDOM_SEED = 1337L; + + @Factory + public Object[] createITCases() { + + final List> result = new ArrayList<>(); + + // for (MealyLearningExample example : LearningExamples.createMealyExamples()) { + // result.add(new TestCase(example.getReferenceAutomaton(), example.getAlphabet())); + // } + + final ExampleRandomMealy example = ExampleRandomMealy.createExample(new Random(RANDOM_SEED), + RANDOM_ALPHABET, + RANDOM_SIZE, + RANDOM_MEALY_OUTPUTS); + + result.add(new TestCase<>(example.getReferenceAutomaton(), + RANDOM_ALPHABET, + Alphabets.fromArray(RANDOM_MEALY_OUTPUTS))); + + return result.toArray(); + } + + static class TestCase implements ITest { + + private final MealyMachine automaton; + private final Alphabet inputAlphabet; + private final Alphabet outputAlphabet; + + public TestCase(MealyMachine automaton, Alphabet inputAlphabet, Alphabet outputAlphabet) { + this.automaton = automaton; + this.inputAlphabet = inputAlphabet; + this.outputAlphabet = outputAlphabet; + } + + @Test + public void test() { + + final List> characterizingSet = Automata.characterizingSet(automaton, inputAlphabet); + + final List> informant = new ArrayList<>(characterizingSet.size()); + + for (Word input : characterizingSet) { + final Word output = automaton.computeOutput(input); + + final IntSeq inSeq = IntSeq.seq(input.stream().mapToInt(inputAlphabet).toArray()); + final IntSeq outSeq = IntSeq.seq(output.stream().mapToInt(outputAlphabet).toArray()); + informant.add(Pair.of(inSeq, outSeq)); + } + + State root = OSTIA.buildPtt(inputAlphabet.size(), informant.iterator()); + OSTIA.ostia(root); + + for (Word input : characterizingSet) { + final Iterator inIter = input.stream().mapToInt(inputAlphabet).iterator(); + + final ArrayList output = OSTIA.run(root, inIter); + final List expectedOutput = automaton.computeOutput(input) + .stream() + .map(outputAlphabet::applyAsInt) + .collect(Collectors.toList()); + + Assert.assertEquals(output, expectedOutput); + } + } + + @Override + public String getTestName() { + return "OSTIA"; + } + } +} diff --git a/algorithms/passive/pom.xml b/algorithms/passive/pom.xml index dcdb717fee..790b9b059f 100644 --- a/algorithms/passive/pom.xml +++ b/algorithms/passive/pom.xml @@ -32,8 +32,9 @@ limitations under the License. Parent module for (passive) automata learning algorithms shipped with LearnLib + ostia rpni rpni-edsm rpni-mdl - + \ No newline at end of file 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 0e5a35ad9a..845cbab949 100644 --- a/pom.xml +++ b/pom.xml @@ -312,6 +312,11 @@ limitations under the License. ${project.version} pom + + de.learnlib + learnlib-ostia + ${project.version} + de.learnlib learnlib-rpni From d4e2d0cfb8214e0277d78ffd90217f43a3cff4f9 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Tue, 8 Dec 2020 01:56:27 +0100 Subject: [PATCH 03/21] initial integration of LearnLib/AutomataLib interfaces --- algorithms/passive/ostia/pom.xml | 4 + .../de/learnlib/algorithms/ostia/IntSeq.java | 2 - .../de/learnlib/algorithms/ostia/OSTIA.java | 67 ++++++++- .../learnlib/algorithms/ostia/OSTWrapper.java | 110 +++++++++++++++ .../transducers/impl/compact/CompactOST.java | 12 ++ .../impl/compact/SequentialTransducer.java | 43 ++++++ .../impl/compact/UniversalCompactDet.java | 129 ++++++++++++++++++ .../de/learnlib/algorithms/ostia/OSTIAIT.java | 112 --------------- .../learnlib/algorithms/ostia/OSTIATest.java | 112 +++++++++++++++ 9 files changed, 470 insertions(+), 121 deletions(-) create mode 100644 algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSTWrapper.java create mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOST.java create mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SequentialTransducer.java create mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java delete mode 100644 algorithms/passive/ostia/src/test/java/de/learnlib/algorithms/ostia/OSTIAIT.java create mode 100644 algorithms/passive/ostia/src/test/java/de/learnlib/algorithms/ostia/OSTIATest.java diff --git a/algorithms/passive/ostia/pom.xml b/algorithms/passive/ostia/pom.xml index c3affd89eb..faa4898d1b 100644 --- a/algorithms/passive/ostia/pom.xml +++ b/algorithms/passive/ostia/pom.xml @@ -34,6 +34,10 @@ limitations under the License. de.learnlib learnlib-api + + net.automatalib + automata-core + diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java index 23c1b02bd4..5e9d615e71 100644 --- a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java @@ -8,8 +8,6 @@ interface IntSeq { int get(int index); - public static final IntSeq Epsilon = seq(); - static IntSeq seq(int... ints) { return new IntSeq() { 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 index fbe8a63d10..7d433e1f75 100644 --- 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 @@ -16,15 +16,56 @@ package de.learnlib.algorithms.ostia; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; +import java.util.List; import java.util.Map; +import de.learnlib.api.algorithm.PassiveLearningAlgorithm; +import de.learnlib.api.query.DefaultQuery; +import net.automatalib.automata.transducers.impl.compact.SequentialTransducer; import net.automatalib.commons.util.Pair; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; -public class OSTIA { +public class OSTIA implements PassiveLearningAlgorithm, I, Word> { + + private final Alphabet inputAlphabet; + private final Alphabet outputAlphabet; + private final int alphabetSize; + + private final List>> samples; + + public OSTIA(Alphabet inputAlphabet, Alphabet outputAlphabet) { + this.inputAlphabet = inputAlphabet; + this.outputAlphabet = outputAlphabet; + this.alphabetSize = inputAlphabet.size(); + this.samples = new ArrayList<>(); + } + + @Override + public void addSamples(Collection>> samples) { + this.samples.addAll(samples); + } + + @Override + public SequentialTransducer computeModel() { + final List> informant = new ArrayList<>(this.samples.size()); + + for (DefaultQuery> sample : this.samples) { + final IntSeq inSeq = IntSeq.seq(sample.getInput().stream().mapToInt(inputAlphabet).toArray()); + final IntSeq outSeq = IntSeq.seq(sample.getOutput().stream().mapToInt(outputAlphabet).toArray()); + informant.add(Pair.of(inSeq, outSeq)); + } + + final State root = buildPtt(this.alphabetSize, informant.iterator()); + ostia(root); + return new OSTWrapper<>(root, inputAlphabet, outputAlphabet); + + } public static boolean hasCycle(IntQueue q) { final HashSet elements = new HashSet<>(); @@ -39,7 +80,9 @@ public static boolean hasCycle(IntQueue q) { static IntQueue concat(IntQueue q, IntQueue tail) { assert !hasCycle(q) && !hasCycle(tail); - if (q == null) { return tail; } + if (q == null) { + return tail; + } final IntQueue first = q; while (q.next != null) { q = q.next; @@ -51,7 +94,9 @@ static IntQueue concat(IntQueue q, IntQueue tail) { static IntQueue copyAndConcat(IntQueue q, IntQueue tail) { assert !hasCycle(q) && !hasCycle(tail); - if (q == null) { return tail; } + if (q == null) { + return tail; + } final IntQueue root = new IntQueue(); root.value = q.value; IntQueue curr = root; @@ -107,13 +152,17 @@ private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { ptt = edge.target; } } - if (ptt.out != null && !eq(ptt.out.str, output)) { throw new IllegalArgumentException(); } + if (ptt.out != null && !eq(ptt.out.str, output)) { + throw new IllegalArgumentException(); + } ptt.out = new Out(output); } private static boolean eq(IntQueue a, IntQueue b) { while (a != null && b != null) { - if (a.value != b.value) { return false; } + if (a.value != b.value) { + return false; + } a = a.next; b = b.next; } @@ -256,7 +305,9 @@ static ArrayList run(State init, Iterator input) { ArrayList output = new ArrayList<>(); while (input.hasNext()) { final Edge edge = init.transitions[input.next()]; - if (edge == null) { return null; } + if (edge == null) { + return null; + } init = edge.target; IntQueue q = edge.out; while (q != null) { @@ -264,7 +315,9 @@ static ArrayList run(State init, Iterator input) { q = q.next; } } - if (init.out == null) { return null; } + if (init.out == null) { + return null; + } IntQueue q = init.out.str; while (q != null) { output.add(q.value); diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSTWrapper.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSTWrapper.java new file mode 100644 index 0000000000..a37b11613f --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSTWrapper.java @@ -0,0 +1,110 @@ +package de.learnlib.algorithms.ostia; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Queue; +import java.util.Set; +import java.util.stream.StreamSupport; + +import net.automatalib.automata.transducers.impl.compact.SequentialTransducer; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; +import net.automatalib.words.WordBuilder; +import org.checkerframework.checker.nullness.qual.Nullable; + +class OSTWrapper implements SequentialTransducer { + + private final State root; + private final Alphabet inputAlphabet; + private final Alphabet outputAlphabet; + + public OSTWrapper(State root, Alphabet inputAlphabet, Alphabet outputAlphabet) { + this.root = root; + this.inputAlphabet = inputAlphabet; + this.outputAlphabet = outputAlphabet; + } + + @Override + public Word computeOutput(Iterable input) { + final Iterator transformedInput = + StreamSupport.stream(input.spliterator(), false).mapToInt(inputAlphabet).iterator(); + final ArrayList output = OSTIA.run(root, transformedInput); + + return output != null ? + output.stream().map(outputAlphabet::getSymbol).collect(Word.collector()) : + Word.epsilon(); + } + + @Override + public Collection getStates() { + final Set cache = new HashSet<>(); + final Queue queue = new ArrayDeque<>(); + + queue.add(root); + + while (!queue.isEmpty()) { + 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(Out out) { + return outToWord(out == null ? null : out.str); + } + + private Word outToWord(IntQueue out) { + if (out == null) { + return Word.epsilon(); + } + + final WordBuilder wb = new WordBuilder<>(); + + while (out != null) { + wb.add(outputAlphabet.getSymbol(out.value)); + out = out.next; + } + + return wb.toWord(); + } +} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOST.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOST.java new file mode 100644 index 0000000000..0a173b51ad --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOST.java @@ -0,0 +1,12 @@ +package net.automatalib.automata.transducers.impl.compact; + +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; + +public class CompactOST extends UniversalCompactDet, Word> + implements SequentialTransducer>, O> { + + public CompactOST(Alphabet alphabet) { + super(alphabet); + } +} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SequentialTransducer.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SequentialTransducer.java new file mode 100644 index 0000000000..4aecfe42b6 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SequentialTransducer.java @@ -0,0 +1,43 @@ +package net.automatalib.automata.transducers.impl.compact; + +import java.util.Collection; +import java.util.Iterator; + +import net.automatalib.automata.UniversalDeterministicAutomaton; +import net.automatalib.automata.concepts.DetSuffixOutputAutomaton; +import net.automatalib.words.Word; +import net.automatalib.words.WordBuilder; + +public interface SequentialTransducer + extends DetSuffixOutputAutomaton>, UniversalDeterministicAutomaton, Word> { + + @Override + default Word computeStateOutput(S state, Iterable input) { + final WordBuilder result; + if (input instanceof Word) { + result = new WordBuilder<>(((Word) input).length()); + } else if (input instanceof Collection) { + result = new WordBuilder<>(((Collection) input).size()); + } else { + result = new WordBuilder<>(); + } + + final Iterator inputIter = input.iterator(); + S stateIter = state; + + while (inputIter.hasNext()) { + final I i = inputIter.next(); + final T t = getTransition(stateIter, i); + + if (t != null) { + result.append(getTransitionProperty(t)); + stateIter = getSuccessor(t); + } + } + + result.append(getStateProperty(stateIter)); + + return result.toWord(); + } + +} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java new file mode 100644 index 0000000000..f82ee606e6 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java @@ -0,0 +1,129 @@ +package net.automatalib.automata.transducers.impl.compact; + +import java.util.Arrays; + +import net.automatalib.automata.base.compact.AbstractCompact; +import net.automatalib.automata.base.compact.AbstractCompactDeterministic; +import net.automatalib.words.Alphabet; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class UniversalCompactDet + extends AbstractCompactDeterministic, SP, TP> { + + private int[] transitions; + private @Nullable Object[] stateProperties; + private @Nullable Object[] transitionProperties; + + public UniversalCompactDet(Alphabet alphabet) { + this(alphabet, DEFAULT_INIT_CAPACITY, DEFAULT_RESIZE_FACTOR); + } + + public UniversalCompactDet(Alphabet alphabet, int stateCapacity, float resizeFactor) { + super(alphabet, stateCapacity, resizeFactor); + + final int numTrans = stateCapacity * numInputs(); + this.transitions = new int[numTrans]; + this.stateProperties = new Object[stateCapacity]; + this.transitionProperties = new Object[numTrans]; + + Arrays.fill(this.transitions, AbstractCompact.INVALID_STATE); + } + + @Override + public @Nullable CompactMealyTransition getTransition(int state, int input) { + final int idx = toMemoryIndex(state, input); + final int succ = transitions[idx]; + + if (succ == AbstractCompact.INVALID_STATE) { + return null; + } + + @SuppressWarnings("unchecked") + final TP output = (TP) transitionProperties[idx]; + + return new CompactMealyTransition<>(idx, succ, output); + } + + @Override + public int getIntSuccessor(CompactMealyTransition transition) { + return transition.getSuccId(); + } + + @Override + public void setStateProperty(int state, @Nullable SP property) { + this.stateProperties[state] = property; + } + + @Override + public void setTransitionProperty(CompactMealyTransition transition, TP property) { + transition.setOutput(property); + + if (transition.isAutomatonTransition()) { + transitionProperties[transition.getMemoryIdx()] = property; + } + } + + @Override + public CompactMealyTransition createTransition(int successor, TP property) { + return new CompactMealyTransition<>(successor, property); + } + + @Override + public void removeAllTransitions(Integer state) { + final int lower = state * numInputs(); + final int upper = lower + numInputs(); + Arrays.fill(transitions, lower, upper, AbstractCompact.INVALID_STATE); + Arrays.fill(transitionProperties, lower, upper, null); + + } + + @Override + public void setTransition(int state, int input, @Nullable CompactMealyTransition transition) { + if (transition == null) { + setTransition(state, input, AbstractCompact.INVALID_STATE, null); + } else { + setTransition(state, input, transition.getSuccId(), transition.getOutput()); + transition.setMemoryIdx(toMemoryIndex(state, input)); + } + } + + @Override + public void setTransition(int state, int input, int successor, TP property) { + final int idx = toMemoryIndex(state, input); + transitions[idx] = successor; + transitionProperties[idx] = property; + } + + @Override + @SuppressWarnings("unchecked") + public SP getStateProperty(int state) { + return (SP) stateProperties[state]; + } + + @Override + public TP getTransitionProperty(CompactMealyTransition transition) { + return transition.getOutput(); + } + + @Override + public void clear() { + int endIdx = size() * numInputs(); + Arrays.fill(stateProperties, 0, size(), null); + Arrays.fill(transitions, 0, endIdx, AbstractCompact.INVALID_STATE); + Arrays.fill(transitionProperties, 0, endIdx, null); + + super.clear(); + } + + @Override + protected void updateStateStorage(Payload payload) { + this.stateProperties = updateStateStorage(this.stateProperties, null, payload); + super.updateStateStorage(payload); + } + + @Override + protected void updateTransitionStorage(Payload payload) { + this.transitions = updateTransitionStorage(this.transitions, AbstractCompact.INVALID_STATE, payload); + this.transitionProperties = updateTransitionStorage(this.transitionProperties, null, payload); + } +} 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 deleted file mode 100644 index 0973c0b2f3..0000000000 --- a/algorithms/passive/ostia/src/test/java/de/learnlib/algorithms/ostia/OSTIAIT.java +++ /dev/null @@ -1,112 +0,0 @@ -/* 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.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Random; -import java.util.stream.Collectors; - -import de.learnlib.examples.mealy.ExampleRandomMealy; -import net.automatalib.automata.transducers.MealyMachine; -import net.automatalib.commons.util.Pair; -import net.automatalib.util.automata.Automata; -import net.automatalib.words.Alphabet; -import net.automatalib.words.Word; -import net.automatalib.words.impl.Alphabets; -import org.testng.Assert; -import org.testng.ITest; -import org.testng.annotations.Factory; -import org.testng.annotations.Test; - -public class OSTIAIT { - - private static final Alphabet RANDOM_ALPHABET = Alphabets.characters('a', 'c'); - private static final int RANDOM_SIZE = 100; - private static final String[] RANDOM_MEALY_OUTPUTS = {"o1", "o2", "o3"}; - private static final long RANDOM_SEED = 1337L; - - @Factory - public Object[] createITCases() { - - final List> result = new ArrayList<>(); - - // for (MealyLearningExample example : LearningExamples.createMealyExamples()) { - // result.add(new TestCase(example.getReferenceAutomaton(), example.getAlphabet())); - // } - - final ExampleRandomMealy example = ExampleRandomMealy.createExample(new Random(RANDOM_SEED), - RANDOM_ALPHABET, - RANDOM_SIZE, - RANDOM_MEALY_OUTPUTS); - - result.add(new TestCase<>(example.getReferenceAutomaton(), - RANDOM_ALPHABET, - Alphabets.fromArray(RANDOM_MEALY_OUTPUTS))); - - return result.toArray(); - } - - static class TestCase implements ITest { - - private final MealyMachine automaton; - private final Alphabet inputAlphabet; - private final Alphabet outputAlphabet; - - public TestCase(MealyMachine automaton, Alphabet inputAlphabet, Alphabet outputAlphabet) { - this.automaton = automaton; - this.inputAlphabet = inputAlphabet; - this.outputAlphabet = outputAlphabet; - } - - @Test - public void test() { - - final List> characterizingSet = Automata.characterizingSet(automaton, inputAlphabet); - - final List> informant = new ArrayList<>(characterizingSet.size()); - - for (Word input : characterizingSet) { - final Word output = automaton.computeOutput(input); - - final IntSeq inSeq = IntSeq.seq(input.stream().mapToInt(inputAlphabet).toArray()); - final IntSeq outSeq = IntSeq.seq(output.stream().mapToInt(outputAlphabet).toArray()); - informant.add(Pair.of(inSeq, outSeq)); - } - - State root = OSTIA.buildPtt(inputAlphabet.size(), informant.iterator()); - OSTIA.ostia(root); - - for (Word input : characterizingSet) { - final Iterator inIter = input.stream().mapToInt(inputAlphabet).iterator(); - - final ArrayList output = OSTIA.run(root, inIter); - final List expectedOutput = automaton.computeOutput(input) - .stream() - .map(outputAlphabet::applyAsInt) - .collect(Collectors.toList()); - - Assert.assertEquals(output, expectedOutput); - } - } - - @Override - public String getTestName() { - return "OSTIA"; - } - } -} 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..c781d8745c --- /dev/null +++ b/algorithms/passive/ostia/src/test/java/de/learnlib/algorithms/ostia/OSTIATest.java @@ -0,0 +1,112 @@ +/* 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.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import net.automatalib.automata.transducers.MealyMachine; +import net.automatalib.automata.transducers.impl.compact.CompactOST; +import net.automatalib.automata.transducers.impl.compact.SequentialTransducer; +import net.automatalib.commons.util.collections.CollectionsUtil; +import net.automatalib.util.automata.Automata; +import net.automatalib.util.automata.random.RandomAutomata; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; +import net.automatalib.words.impl.Alphabets; +import org.testng.Assert; +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 int SIZE = 100; + private static final long SEED = 1337L; + + @Test + public void testMealySamples() { + + final MealyMachine automaton = + RandomAutomata.randomMealy(new Random(SEED), SIZE, INPUTS, OUTPUTS); + + final OSTIA learner = new OSTIA<>(INPUTS, Alphabets.fromCollection(OUTPUTS)); + + final List> characterizingSet = Automata.characterizingSet(automaton, INPUTS); + + for (Word input : characterizingSet) { + learner.addSample(input, automaton.computeOutput(input)); + } + + final SequentialTransducer model = learner.computeModel(); + + for (Word input : characterizingSet) { + final Word output = model.computeOutput(input); + final Word expectedOutput = automaton.computeOutput(input); + + Assert.assertEquals(output, expectedOutput); + } + } + + @Test(enabled = false) + public void testEquivalence() { + + final Random random = new Random(SEED); + final CompactOST automaton = new CompactOST<>(INPUTS); + + final List> words = new ArrayList<>(); + for (List t : CollectionsUtil.allTuples(OUTPUTS, 1, 3)) { + words.add(Word.fromList(t)); + } + + Collections.shuffle(words, random); + final int midpoint = words.size() / 2; + Collection> stateProps = words.subList(0, midpoint); + Collection> transProps = words.subList(midpoint, words.size()); + + RandomAutomata.randomDeterministic(random, + SIZE, + INPUTS, + stateProps, + transProps, + automaton); + + final OSTIA learner = new OSTIA<>(INPUTS, Alphabets.fromCollection(OUTPUTS)); + + final List> characterizingSet = Automata.characterizingSet(automaton, INPUTS); + + for (Word input : characterizingSet) { + learner.addSample(input, automaton.computeOutput(input)); + } + + final SequentialTransducer model = learner.computeModel(); + + Word sepWord = Automata.findSeparatingWord(automaton, model, INPUTS); + Word autOut = automaton.computeOutput(sepWord); + Word modelOut = model.computeOutput(sepWord); + + System.err.println("sepWord: " + sepWord); + System.err.println("automaton: " + autOut); + System.err.println("model: " + modelOut); + + Assert.assertTrue(Automata.testEquivalence(automaton, model, INPUTS)); + } + +} From 6d99d247cf54a609fd54661341ea33e0fb6da726 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Wed, 9 Dec 2020 10:08:43 +0100 Subject: [PATCH 04/21] small adjustment in tests --- .../learnlib/algorithms/ostia/OSTIATest.java | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) 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 index c781d8745c..05db57836a 100644 --- 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 @@ -19,14 +19,18 @@ 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.Iterators; import net.automatalib.automata.transducers.MealyMachine; import net.automatalib.automata.transducers.impl.compact.CompactOST; import net.automatalib.automata.transducers.impl.compact.SequentialTransducer; import net.automatalib.commons.util.collections.CollectionsUtil; import net.automatalib.util.automata.Automata; +import net.automatalib.util.automata.conformance.WMethodTestsIterator; +import net.automatalib.util.automata.conformance.WpMethodTestsIterator; import net.automatalib.util.automata.random.RandomAutomata; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; @@ -49,15 +53,17 @@ public void testMealySamples() { final OSTIA learner = new OSTIA<>(INPUTS, Alphabets.fromCollection(OUTPUTS)); - final List> characterizingSet = Automata.characterizingSet(automaton, INPUTS); + final List> trainingWords = new ArrayList<>(); + final Iterator> testIterator = new WMethodTestsIterator<>(automaton, INPUTS, 0); + Iterators.addAll(trainingWords, testIterator); - for (Word input : characterizingSet) { + for (Word input : trainingWords) { learner.addSample(input, automaton.computeOutput(input)); } final SequentialTransducer model = learner.computeModel(); - for (Word input : characterizingSet) { + for (Word input : trainingWords) { final Word output = model.computeOutput(input); final Word expectedOutput = automaton.computeOutput(input); @@ -81,30 +87,26 @@ public void testEquivalence() { Collection> stateProps = words.subList(0, midpoint); Collection> transProps = words.subList(midpoint, words.size()); - RandomAutomata.randomDeterministic(random, - SIZE, - INPUTS, - stateProps, - transProps, - automaton); + RandomAutomata.randomDeterministic(random, SIZE, INPUTS, stateProps, transProps, automaton); final OSTIA learner = new OSTIA<>(INPUTS, Alphabets.fromCollection(OUTPUTS)); - final List> characterizingSet = Automata.characterizingSet(automaton, INPUTS); + final WpMethodTestsIterator wpIterator = new WpMethodTestsIterator<>(automaton, INPUTS, 0); - for (Word input : characterizingSet) { + while (wpIterator.hasNext()) { + final Word input = wpIterator.next(); learner.addSample(input, automaton.computeOutput(input)); } final SequentialTransducer model = learner.computeModel(); - Word sepWord = Automata.findSeparatingWord(automaton, model, INPUTS); - Word autOut = automaton.computeOutput(sepWord); - Word modelOut = model.computeOutput(sepWord); - - System.err.println("sepWord: " + sepWord); - System.err.println("automaton: " + autOut); - System.err.println("model: " + modelOut); +// Word sepWord = Automata.findSeparatingWord(automaton, model, INPUTS); +// Word autOut = automaton.computeOutput(sepWord); +// Word modelOut = model.computeOutput(sepWord); +// +// System.err.println("sepWord: " + sepWord); +// System.err.println("automaton: " + autOut); +// System.err.println("model: " + modelOut); Assert.assertTrue(Automata.testEquivalence(automaton, model, INPUTS)); } From f888be30b8bb5cc16178fb11a563be4a5982f2f1 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Wed, 9 Dec 2020 12:22:30 +0100 Subject: [PATCH 05/21] renaming (Sub)SequentialTransducer --- .../ostia/{OSTWrapper.java => OSSTWrapper.java} | 6 +++--- .../java/de/learnlib/algorithms/ostia/OSTIA.java | 8 ++++---- .../transducers/impl/compact/CompactOST.java | 12 ------------ .../transducers/impl/compact/CompactSST.java | 12 ++++++++++++ ...ansducer.java => SubsequentialTransducer.java} | 2 +- .../de/learnlib/algorithms/ostia/OSTIATest.java | 15 ++++++++------- 6 files changed, 28 insertions(+), 27 deletions(-) rename algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/{OSTWrapper.java => OSSTWrapper.java} (91%) delete mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOST.java create mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java rename algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/{SequentialTransducer.java => SubsequentialTransducer.java} (96%) diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSTWrapper.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSSTWrapper.java similarity index 91% rename from algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSTWrapper.java rename to algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSSTWrapper.java index a37b11613f..4d32ad679c 100644 --- a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSTWrapper.java +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/OSSTWrapper.java @@ -9,19 +9,19 @@ import java.util.Set; import java.util.stream.StreamSupport; -import net.automatalib.automata.transducers.impl.compact.SequentialTransducer; +import net.automatalib.automata.transducers.impl.compact.SubsequentialTransducer; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; import net.automatalib.words.WordBuilder; import org.checkerframework.checker.nullness.qual.Nullable; -class OSTWrapper implements SequentialTransducer { +class OSSTWrapper implements SubsequentialTransducer { private final State root; private final Alphabet inputAlphabet; private final Alphabet outputAlphabet; - public OSTWrapper(State root, Alphabet inputAlphabet, Alphabet outputAlphabet) { + public OSSTWrapper(State root, Alphabet inputAlphabet, Alphabet outputAlphabet) { this.root = root; this.inputAlphabet = inputAlphabet; this.outputAlphabet = outputAlphabet; 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 index 7d433e1f75..dcbd938afa 100644 --- 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 @@ -26,12 +26,12 @@ import de.learnlib.api.algorithm.PassiveLearningAlgorithm; import de.learnlib.api.query.DefaultQuery; -import net.automatalib.automata.transducers.impl.compact.SequentialTransducer; +import net.automatalib.automata.transducers.impl.compact.SubsequentialTransducer; import net.automatalib.commons.util.Pair; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; -public class OSTIA implements PassiveLearningAlgorithm, I, Word> { +public class OSTIA implements PassiveLearningAlgorithm, I, Word> { private final Alphabet inputAlphabet; private final Alphabet outputAlphabet; @@ -52,7 +52,7 @@ public void addSamples(Collection>> samples) { } @Override - public SequentialTransducer computeModel() { + public SubsequentialTransducer computeModel() { final List> informant = new ArrayList<>(this.samples.size()); for (DefaultQuery> sample : this.samples) { @@ -63,7 +63,7 @@ public void addSamples(Collection>> samples) { final State root = buildPtt(this.alphabetSize, informant.iterator()); ostia(root); - return new OSTWrapper<>(root, inputAlphabet, outputAlphabet); + return new OSSTWrapper<>(root, inputAlphabet, outputAlphabet); } diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOST.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOST.java deleted file mode 100644 index 0a173b51ad..0000000000 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOST.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.automatalib.automata.transducers.impl.compact; - -import net.automatalib.words.Alphabet; -import net.automatalib.words.Word; - -public class CompactOST extends UniversalCompactDet, Word> - implements SequentialTransducer>, O> { - - public CompactOST(Alphabet alphabet) { - super(alphabet); - } -} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java new file mode 100644 index 0000000000..623bddf85d --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java @@ -0,0 +1,12 @@ +package net.automatalib.automata.transducers.impl.compact; + +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; + +public class CompactSST extends UniversalCompactDet, Word> + implements SubsequentialTransducer>, O> { + + public CompactSST(Alphabet alphabet) { + super(alphabet); + } +} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SequentialTransducer.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducer.java similarity index 96% rename from algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SequentialTransducer.java rename to algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducer.java index 4aecfe42b6..626acf5b29 100644 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SequentialTransducer.java +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducer.java @@ -8,7 +8,7 @@ import net.automatalib.words.Word; import net.automatalib.words.WordBuilder; -public interface SequentialTransducer +public interface SubsequentialTransducer extends DetSuffixOutputAutomaton>, UniversalDeterministicAutomaton, Word> { @Override 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 index 05db57836a..719c99cddc 100644 --- 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 @@ -25,13 +25,14 @@ import com.google.common.collect.Iterators; import net.automatalib.automata.transducers.MealyMachine; -import net.automatalib.automata.transducers.impl.compact.CompactOST; -import net.automatalib.automata.transducers.impl.compact.SequentialTransducer; +import net.automatalib.automata.transducers.impl.compact.CompactSST; +import net.automatalib.automata.transducers.impl.compact.SubsequentialTransducer; import net.automatalib.commons.util.collections.CollectionsUtil; import net.automatalib.util.automata.Automata; import net.automatalib.util.automata.conformance.WMethodTestsIterator; import net.automatalib.util.automata.conformance.WpMethodTestsIterator; import net.automatalib.util.automata.random.RandomAutomata; +import net.automatalib.visualization.Visualization; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; import net.automatalib.words.impl.Alphabets; @@ -61,7 +62,7 @@ public void testMealySamples() { learner.addSample(input, automaton.computeOutput(input)); } - final SequentialTransducer model = learner.computeModel(); + final SubsequentialTransducer model = learner.computeModel(); for (Word input : trainingWords) { final Word output = model.computeOutput(input); @@ -75,7 +76,7 @@ public void testMealySamples() { public void testEquivalence() { final Random random = new Random(SEED); - final CompactOST automaton = new CompactOST<>(INPUTS); + final CompactSST automaton = new CompactSST<>(INPUTS); final List> words = new ArrayList<>(); for (List t : CollectionsUtil.allTuples(OUTPUTS, 1, 3)) { @@ -84,8 +85,8 @@ public void testEquivalence() { Collections.shuffle(words, random); final int midpoint = words.size() / 2; - Collection> stateProps = words.subList(0, midpoint); - Collection> transProps = words.subList(midpoint, words.size()); + final Collection> stateProps = words.subList(0, midpoint); + final Collection> transProps = words.subList(midpoint, words.size()); RandomAutomata.randomDeterministic(random, SIZE, INPUTS, stateProps, transProps, automaton); @@ -98,7 +99,7 @@ public void testEquivalence() { learner.addSample(input, automaton.computeOutput(input)); } - final SequentialTransducer model = learner.computeModel(); + final SubsequentialTransducer model = learner.computeModel(); // Word sepWord = Automata.findSeparatingWord(automaton, model, INPUTS); // Word autOut = automaton.computeOutput(sepWord); From 258d2ef6c289e3d53a6a80b8ebe62f5293d08046 Mon Sep 17 00:00:00 2001 From: Alagris Date: Sat, 12 Dec 2020 13:02:59 +0100 Subject: [PATCH 06/21] fixed IllegalArgumentException, onwardForm algorithm, minor refactorization --- .../de/learnlib/algorithms/ostia/Edge.java | 2 +- .../learnlib/algorithms/ostia/IntQueue.java | 67 +++++++ .../de/learnlib/algorithms/ostia/OSTIA.java | 181 +++++++++++++----- .../de/learnlib/algorithms/ostia/State.java | 36 +++- .../learnlib/algorithms/ostia/OSTIATest.java | 7 +- 5 files changed, 240 insertions(+), 53 deletions(-) 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 index 3064f38427..3a15f33856 100644 --- 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 @@ -10,7 +10,7 @@ public Edge() { } public Edge(Edge edge) { - out = OSTIA.copyAndConcat(edge.out, null); + out = IntQueue.copyAndConcat(edge.out, null); target = edge.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 index 261d5a575d..6e974c05e4 100644 --- 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 @@ -7,6 +7,35 @@ class IntQueue { int value; IntQueue next; + public static String str(IntQueue q) { + if(q==null)return "[]"; + StringBuilder sb = new StringBuilder("[").append(q.value); + while(q.next!=null){ + q = q.next; + sb.append(", ").append(q.value); + } + return sb.append("]").toString(); + } + + public static int lcpLen(IntQueue a, IntQueue b) { + int len = 0; + while (a != null && b != null && a.value==b.value) { + len++; + a = a.next; + b = b.next; + } + return len; + } + + /**offset(q,0) returns q, offset(q,1) returns q.next and so on*/ + public static IntQueue offset(IntQueue queue,int len) { + while(len-->0){//it looks sort of like, "len approaches 0" which is neat + queue = queue.next; + } + return queue; + } + + @Override public String toString() { @@ -48,4 +77,42 @@ static boolean hasCycle(IntQueue q) { } return false; } + + static IntQueue copyAndConcat(IntQueue q, IntQueue tail) { + assert !hasCycle(q) && !hasCycle(tail); + if (q == null) { + return tail; + } + final IntQueue root = new IntQueue(); + root.value = q.value; + IntQueue curr = root; + q = q.next; + while (q != null) { + curr.next = new IntQueue(); + curr = curr.next; + curr.value = q.value; + q = q.next; + } + curr.next = tail; + assert !hasCycle(root); + return root; + } + + static IntQueue concat(IntQueue q, IntQueue tail) { + assert !hasCycle(q) && !hasCycle(tail); + if (q == null) { + return tail; + } + final IntQueue first = q; + while (q.next != null) { + q = q.next; + } + q.next = tail; + assert !hasCycle(first); + return first; + } + + static IntQueue concatAndCopy(IntQueue q, IntQueue tail) { + return concat(q,copyAndConcat(tail,null)); + } } 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 index dcbd938afa..861be31812 100644 --- 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 @@ -15,21 +15,18 @@ */ package de.learnlib.algorithms.ostia; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; import de.learnlib.api.algorithm.PassiveLearningAlgorithm; import de.learnlib.api.query.DefaultQuery; +import net.automatalib.automata.transducers.impl.compact.CompactMealyTransition; +import net.automatalib.automata.transducers.impl.compact.CompactSST; import net.automatalib.automata.transducers.impl.compact.SubsequentialTransducer; import net.automatalib.commons.util.Pair; +import net.automatalib.visualization.Visualization; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; +import org.checkerframework.checker.nullness.qual.Nullable; public class OSTIA implements PassiveLearningAlgorithm, I, Word> { @@ -46,6 +43,7 @@ public OSTIA(Alphabet inputAlphabet, Alphabet outputAlphabet) { this.samples = new ArrayList<>(); } + @Override public void addSamples(Collection>> samples) { this.samples.addAll(samples); @@ -53,20 +51,20 @@ public void addSamples(Collection>> samples) { @Override public SubsequentialTransducer computeModel() { - final List> informant = new ArrayList<>(this.samples.size()); - + final State root = new State(alphabetSize); + this.samples.sort(Comparator.comparingInt(a -> a.getInput().length()));//these samples must be sorted + //with respect to length or otherwise building prefix-tree-transducer won't work for (DefaultQuery> sample : this.samples) { final IntSeq inSeq = IntSeq.seq(sample.getInput().stream().mapToInt(inputAlphabet).toArray()); final IntSeq outSeq = IntSeq.seq(sample.getOutput().stream().mapToInt(outputAlphabet).toArray()); - informant.add(Pair.of(inSeq, outSeq)); + buildPttOnward(root, inSeq, asQueue(outSeq, 0)); } - - final State root = buildPtt(this.alphabetSize, informant.iterator()); ostia(root); return new OSSTWrapper<>(root, inputAlphabet, outputAlphabet); } + public static boolean hasCycle(IntQueue q) { final HashSet elements = new HashSet<>(); while (q != null) { @@ -78,39 +76,6 @@ public static boolean hasCycle(IntQueue q) { return false; } - static IntQueue concat(IntQueue q, IntQueue tail) { - assert !hasCycle(q) && !hasCycle(tail); - if (q == null) { - return tail; - } - final IntQueue first = q; - while (q.next != null) { - q = q.next; - } - q.next = tail; - assert !hasCycle(first); - return first; - } - - static IntQueue copyAndConcat(IntQueue q, IntQueue tail) { - assert !hasCycle(q) && !hasCycle(tail); - if (q == null) { - return tail; - } - final IntQueue root = new IntQueue(); - root.value = q.value; - IntQueue curr = root; - q = q.next; - while (q != null) { - curr.next = new IntQueue(); - curr = curr.next; - curr.value = q.value; - q = q.next; - } - curr.next = tail; - assert !hasCycle(root); - return root; - } /** * builds onward prefix tree transducer @@ -153,7 +118,7 @@ private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { } } if (ptt.out != null && !eq(ptt.out.str, output)) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("For input "+input.toString()+" the state output is "+IntQueue.str(ptt.out.str)+" but training sample has remaining suffix "+IntQueue.str(output)); } ptt.out = new Out(output); } @@ -204,6 +169,128 @@ static Edge[] copyTransitions(Edge[] transitions) { return copy; } + public static Word dequeueLongestCommonPrefix(CompactSST automaton,int stateIdx) { + Word lcp = automaton.getStateProperty(stateIdx); + for (int symbol=0;symbol> outgoing = automaton.getTransition(stateIdx,symbol); + if(outgoing==null)continue; + if (lcp == null){ + lcp = outgoing.getOutput(); + }else{ + lcp = outgoing.getOutput().longestCommonPrefix(lcp); + } + } + if(lcp==null||lcp.length()==0)return null; + final Word stateOut = automaton.getStateProperty(stateIdx); + if(stateOut!=null){ + automaton.setStateProperty(stateIdx,stateOut.subWord(lcp.length())); + } + for (int symbol=0;symbol> outgoing = automaton.getTransition(stateIdx, symbol); + if (outgoing != null) { + automaton.setStateProperty(stateIdx,outgoing.getOutput().subWord(lcp.length())); + } + } + return lcp; + } + + + public static void onwardForm(CompactSST automaton) { + final HashMap>> allStates = new HashMap<>(); + for(int source:automaton.getStates()){ + allStates.put(source,new ArrayList<>()); + } + for(int source:automaton.getStates()){ + for(int symbol=0;symbol> outgoing = automaton.getTransition(source,symbol); + if(outgoing!=null){ + allStates.get(outgoing.getSuccId()).add(Pair.of(symbol,source)); + } + } + } + final Queue modified = new LinkedList<>(); + final HashSet modifiedLookup = new HashSet<>(); + for(Map.Entry>> stateAndReversedTransitions:allStates.entrySet()){ + final int target = stateAndReversedTransitions.getKey(); + final Word dequeued = dequeueLongestCommonPrefix(automaton,target); + if(dequeued!=null){ + for(Pair symbolAndSource: stateAndReversedTransitions.getValue()){ + final int symbol = symbolAndSource.getFirst(); + final int source = symbolAndSource.getSecond(); + final @Nullable CompactMealyTransition> incoming = automaton.getTransition(source,symbol); + assert incoming!=null; + automaton.setTransitionProperty(incoming,incoming.getOutput().concat(dequeued)); + if(modifiedLookup.add(source))modified.add(source); + } + } + } + while(!modified.isEmpty()){ + final int target = modified.poll(); + modifiedLookup.remove(target); + final Word dequeued = dequeueLongestCommonPrefix(automaton,target); + if(dequeued!=null){ + for(Pair symbolAndSource: allStates.get(target)){ + final int symbol = symbolAndSource.getFirst(); + final int source = symbolAndSource.getSecond(); + final @Nullable CompactMealyTransition> incoming = automaton.getTransition(source,symbol); + assert incoming!=null; + automaton.setTransitionProperty(incoming,incoming.getOutput().concat(dequeued)); + if(modifiedLookup.add(source))modified.add(source); + } + } + } + } + + public static void onwardForm(State transducer) { + final HashMap>> allStates = new HashMap<>(); + final Stack toVisit = new Stack<>(); + toVisit.push(transducer); + allStates.put(transducer,new ArrayList<>()); + while(!toVisit.isEmpty()){ + final State s = toVisit.pop(); + for(Edge outgoing:s.transitions){ + if(outgoing!=null && !allStates.containsKey(outgoing.target)){ + allStates.put(outgoing.target,new ArrayList<>()); + toVisit.push(outgoing.target); + } + } + } + for(State source:allStates.keySet()){ + for(int symbol=0;symbol modified = new LinkedList<>(); + final HashSet modifiedLookup = new HashSet<>(); + for(Map.Entry>> stateAndReversedTransitions:allStates.entrySet()){ + final State target = stateAndReversedTransitions.getKey(); + final IntQueue dequeued = target.dequeueLongestCommonPrefix(); + if(dequeued!=null){ + for(Pair symbolAndSource: stateAndReversedTransitions.getValue()){ + final int symbol = symbolAndSource.getFirst(); + final State source = symbolAndSource.getSecond(); + source.transitions[symbol].out = IntQueue.concatAndCopy(source.transitions[symbol].out,dequeued); + if(modifiedLookup.add(source))modified.add(source); + } + } + } + while(!modified.isEmpty()){ + final State target = modified.poll(); + modifiedLookup.remove(target); + final IntQueue dequeued = target.dequeueLongestCommonPrefix(); + if(dequeued!=null) { + for (Pair symbolAndSource : allStates.get(target)) { + final int symbol = symbolAndSource.getFirst(); + final State source = symbolAndSource.getSecond(); + source.transitions[symbol].out = IntQueue.concatAndCopy(source.transitions[symbol].out, dequeued); + if (modifiedLookup.add(source)) modified.add(source); + } + } + } + } public static void ostia(State transducer) { final java.util.Queue blue = new LinkedList<>(); final ArrayList red = new ArrayList<>(); 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 index 8cf19056cb..83a46f4677 100644 --- 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 @@ -1,5 +1,7 @@ package de.learnlib.algorithms.ostia; +import net.automatalib.commons.util.Pair; + class State { public void assign(State other) { @@ -16,7 +18,7 @@ public void assign(State other) { State(State copy) { transitions = OSTIA.copyTransitions(copy.transitions); - out = copy.out == null ? null : new Out(OSTIA.copyAndConcat(copy.out.str, null)); + out = copy.out == null ? null : new Out(IntQueue.copyAndConcat(copy.out.str, null)); } /** @@ -25,14 +27,42 @@ public void assign(State other) { void prepend(IntQueue prefix) { for (Edge edge : transitions) { if (edge != null) { - edge.out = OSTIA.copyAndConcat(prefix, edge.out); + edge.out = IntQueue.copyAndConcat(prefix, edge.out); } } if (out == null) { out = new Out(prefix); } else { - out.str = OSTIA.copyAndConcat(prefix, out.str); + out.str = IntQueue.copyAndConcat(prefix, out.str); + } + } + + + IntQueue dequeueLongestCommonPrefix() { + Out lcp = out; + int len = out==null?-1:IntQueue.len(out.str); + for (Edge outgoing : transitions) { + if(outgoing==null)continue; + if (lcp == null){ + lcp = new Out(outgoing.out); + len = IntQueue.len(outgoing.out); + }else{ + len = Math.min(len,IntQueue.lcpLen(lcp.str,outgoing.out)); + } + } + if(lcp==null||len==0)return null; + assert len>0; + IntQueue dequeuedLcp = lcp.str; + if(out!=null){ + out.str = IntQueue.offset(out.str,len); + } + for (Edge outgoing : transitions) { + if (outgoing != null){ + outgoing.out = IntQueue.offset(outgoing.out,len); + } } + IntQueue.offset(dequeuedLcp,len-1).next = null; + return dequeuedLcp; } } 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 index 719c99cddc..9521631f71 100644 --- 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 @@ -96,8 +96,11 @@ public void testEquivalence() { while (wpIterator.hasNext()) { final Word input = wpIterator.next(); - learner.addSample(input, automaton.computeOutput(input)); + final Word out = automaton.computeOutput(input); + System.out.println(input+" "+out); + learner.addSample(input, out); } + // For input [2, 2] the state output is [0, 1, 0, 0, 0, 1, 2, 0, 1, 0, 0] but training sample has remaining suffix [2, 0, 2] final SubsequentialTransducer model = learner.computeModel(); @@ -108,7 +111,7 @@ public void testEquivalence() { // System.err.println("sepWord: " + sepWord); // System.err.println("automaton: " + autOut); // System.err.println("model: " + modelOut); - + OSTIA.onwardForm(automaton); Assert.assertTrue(Automata.testEquivalence(automaton, model, INPUTS)); } From eb36136309123f36d8ac9acd9160da53e3f0ae26 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Sun, 13 Dec 2020 12:03:09 +0100 Subject: [PATCH 07/21] add utility classes and provide alternative onward computation --- algorithms/passive/ostia/pom.xml | 9 ++ .../transducers/impl/compact/CompactOSST.java | 22 ++++ .../transducers/impl/compact/CompactSST.java | 2 +- .../MutableOnwardSubsequentialTransducer.java | 7 ++ .../MutableSubsequentialTransducer.java | 7 ++ .../OnwardSubsequentialTransducer.java | 3 + .../impl/compact/SSTVisualizationHelper.java | 41 +++++++ .../impl/compact/SubsequentialTransducer.java | 22 ++++ .../compact/SubsequentialTransducers.java | 105 ++++++++++++++++++ .../learnlib/algorithms/ostia/OSTIATest.java | 56 ++++++---- 10 files changed, 253 insertions(+), 21 deletions(-) create mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOSST.java create mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableOnwardSubsequentialTransducer.java create mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableSubsequentialTransducer.java create mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/OnwardSubsequentialTransducer.java create mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SSTVisualizationHelper.java create mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducers.java diff --git a/algorithms/passive/ostia/pom.xml b/algorithms/passive/ostia/pom.xml index faa4898d1b..f23e71e3e9 100644 --- a/algorithms/passive/ostia/pom.xml +++ b/algorithms/passive/ostia/pom.xml @@ -38,12 +38,21 @@ limitations under the License. net.automatalib automata-core + + net.automatalib + automata-util + de.learnlib.testsupport learnlib-learner-it-support + + net.automatalib + automata-dot-visualizer + test + diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOSST.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOSST.java new file mode 100644 index 0000000000..7dc5879351 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOSST.java @@ -0,0 +1,22 @@ +package net.automatalib.automata.transducers.impl.compact; + +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; + +public class CompactOSST extends UniversalCompactDet, Word> + implements MutableOnwardSubsequentialTransducer>, O> { + + public CompactOSST(Alphabet alphabet) { + super(alphabet); + } + +// @Override +// public void setTransitionProperty(CompactMealyTransition> transition, Word property) { +// super.setTransitionProperty(transition, property); +// } +// +// @Override +// public void setTransition(int state, int input, int successor, Word property) { +// super.setTransition(state, input, successor, property); +// } +} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java index 623bddf85d..548b49cf3c 100644 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java @@ -4,7 +4,7 @@ import net.automatalib.words.Word; public class CompactSST extends UniversalCompactDet, Word> - implements SubsequentialTransducer>, O> { + implements MutableSubsequentialTransducer>, O> { public CompactSST(Alphabet alphabet) { super(alphabet); diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableOnwardSubsequentialTransducer.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableOnwardSubsequentialTransducer.java new file mode 100644 index 0000000000..f1ae11a72a --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableOnwardSubsequentialTransducer.java @@ -0,0 +1,7 @@ +package net.automatalib.automata.transducers.impl.compact; + +import net.automatalib.automata.MutableDeterministic; +import net.automatalib.words.Word; + +public interface MutableOnwardSubsequentialTransducer extends OnwardSubsequentialTransducer, + MutableDeterministic, Word> {} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableSubsequentialTransducer.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableSubsequentialTransducer.java new file mode 100644 index 0000000000..dd06159125 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableSubsequentialTransducer.java @@ -0,0 +1,7 @@ +package net.automatalib.automata.transducers.impl.compact; + +import net.automatalib.automata.MutableDeterministic; +import net.automatalib.words.Word; + +public interface MutableSubsequentialTransducer + extends SubsequentialTransducer, MutableDeterministic, Word> {} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/OnwardSubsequentialTransducer.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/OnwardSubsequentialTransducer.java new file mode 100644 index 0000000000..c00675e2b9 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/OnwardSubsequentialTransducer.java @@ -0,0 +1,3 @@ +package net.automatalib.automata.transducers.impl.compact; + +public interface OnwardSubsequentialTransducer extends SubsequentialTransducer {} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SSTVisualizationHelper.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SSTVisualizationHelper.java new file mode 100644 index 0000000000..7c2c4dc15d --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SSTVisualizationHelper.java @@ -0,0 +1,41 @@ +package net.automatalib.automata.transducers.impl.compact; + +import java.util.Map; + +import net.automatalib.automata.graphs.TransitionEdge; +import net.automatalib.automata.visualization.AutomatonVisualizationHelper; +import net.automatalib.words.Word; + +public class SSTVisualizationHelper + extends AutomatonVisualizationHelper> { + + public SSTVisualizationHelper(SubsequentialTransducer automaton) { + super(automaton); + } + + @Override + public boolean getEdgeProperties(S src, TransitionEdge edge, S tgt, Map properties) { + if (!super.getEdgeProperties(src, edge, tgt, properties)) { + return false; + } + + final StringBuilder labelBuilder = new StringBuilder(); + labelBuilder.append(edge.getInput()).append(" / "); + Word output = automaton.getTransitionProperty(edge.getTransition()); + if (output != null) { + labelBuilder.append(output); + } + properties.put(EdgeAttrs.LABEL, labelBuilder.toString()); + return true; + } + + @Override + public boolean getNodeProperties(S node, Map properties) { + if (!super.getNodeProperties(node, properties)) { + return false; + } + + properties.put(NodeAttrs.LABEL, automaton.getStateProperty(node).toString()); + return true; + } +} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducer.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducer.java index 626acf5b29..d40d5f454c 100644 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducer.java +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducer.java @@ -5,6 +5,11 @@ import net.automatalib.automata.UniversalDeterministicAutomaton; import net.automatalib.automata.concepts.DetSuffixOutputAutomaton; +import net.automatalib.automata.graphs.TransitionEdge; +import net.automatalib.automata.graphs.TransitionEdge.Property; +import net.automatalib.automata.graphs.UniversalAutomatonGraphView; +import net.automatalib.graphs.UniversalGraph; +import net.automatalib.visualization.VisualizationHelper; import net.automatalib.words.Word; import net.automatalib.words.WordBuilder; @@ -40,4 +45,21 @@ default Word computeStateOutput(S state, Iterable input) { return result.toWord(); } + @Override + default UniversalGraph, Word, Property>> transitionGraphView(Collection inputs) { + return new SSTGraphView<>(this, inputs); + } + + class SSTGraphView> + extends UniversalAutomatonGraphView, Word, A> { + + public SSTGraphView(A automaton, Collection inputs) { + super(automaton, inputs); + } + + @Override + public VisualizationHelper> getVisualizationHelper() { + return new SSTVisualizationHelper<>(automaton); + } + } } diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducers.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducers.java new file mode 100644 index 0000000000..779fe12938 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducers.java @@ -0,0 +1,105 @@ +package net.automatalib.automata.transducers.impl.compact; + +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.HashSet; +import java.util.Set; + +import net.automatalib.commons.util.Pair; +import net.automatalib.commons.util.mappings.Mapping; +import net.automatalib.commons.util.mappings.MutableMapping; +import net.automatalib.util.automata.copy.AutomatonCopyMethod; +import net.automatalib.util.automata.copy.AutomatonLowLevelCopy; +import net.automatalib.words.Word; + +public final class SubsequentialTransducers { + + private SubsequentialTransducers() { + // prevent initialization + } + + public static > A toOSST( + SubsequentialTransducer sst, + Collection inputs, + A out) { + + AutomatonLowLevelCopy.copy(AutomatonCopyMethod.STATE_BY_STATE, sst, inputs, out); + + final Mapping>> incomingTransitions = getIncomingTransitions(out, inputs); + final Deque queue = new ArrayDeque<>(out.getStates()); + + while (!queue.isEmpty()) { + final S2 s = queue.pop(); + final Word lcp = computeLCP(out, inputs, s); + + System.err.println("lcp: " + lcp); + + if (!lcp.isEmpty()) { + final Word oldStateProperty = out.getStateProperty(s); + final Word newStateProperty = oldStateProperty.subWord(lcp.length()); + + out.setStateProperty(s, newStateProperty); + + for (I i : inputs) { + final T2 t = out.getTransition(s, i); + if (t != null) { + final Word oldTransitionProperty = out.getTransitionProperty(t); + final Word newTransitionProperty = oldTransitionProperty.subWord(lcp.length()); + + out.setTransitionProperty(t, newTransitionProperty); + } + } + + for (Pair incoming : incomingTransitions.get(s)) { + final T2 t = out.getTransition(incoming.getFirst(), incoming.getSecond()); + + final Word oldTransitionProperty = out.getTransitionProperty(t); + final Word newTransitionProperty = oldTransitionProperty.concat(lcp); + + out.setTransitionProperty(t, newTransitionProperty); + queue.add(incoming.getFirst()); + } + } + } + + return out; + } + + private static Mapping>> getIncomingTransitions(SubsequentialTransducer sst, + Collection inputs) { + + final MutableMapping>> result = sst.createStaticStateMapping(); + + for (S s : sst) { + result.put(s, new HashSet<>()); + } + + for (S s : sst) { + for (I i : inputs) { + final T t = sst.getTransition(s, i); + + if (t != null) { + final S succ = sst.getSuccessor(t); + result.get(succ).add(Pair.of(s, i)); + } + } + } + + return result; + } + + private static Word computeLCP(SubsequentialTransducer sst, + Collection inputs, + S s) { + + Word lcp = sst.getStateProperty(s); + + for (I i : inputs) { + lcp = lcp.longestCommonPrefix(sst.getTransitionProperty(s, i)); + } + + return lcp; + } + +} 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 index 9521631f71..4328671316 100644 --- 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 @@ -25,14 +25,15 @@ import com.google.common.collect.Iterators; import net.automatalib.automata.transducers.MealyMachine; +import net.automatalib.automata.transducers.impl.compact.CompactOSST; import net.automatalib.automata.transducers.impl.compact.CompactSST; +import net.automatalib.automata.transducers.impl.compact.OnwardSubsequentialTransducer; import net.automatalib.automata.transducers.impl.compact.SubsequentialTransducer; +import net.automatalib.automata.transducers.impl.compact.SubsequentialTransducers; import net.automatalib.commons.util.collections.CollectionsUtil; import net.automatalib.util.automata.Automata; import net.automatalib.util.automata.conformance.WMethodTestsIterator; -import net.automatalib.util.automata.conformance.WpMethodTestsIterator; import net.automatalib.util.automata.random.RandomAutomata; -import net.automatalib.visualization.Visualization; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; import net.automatalib.words.impl.Alphabets; @@ -43,7 +44,7 @@ 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 int SIZE = 100; + private static final int SIZE = 10; private static final long SEED = 1337L; @Test @@ -76,7 +77,7 @@ public void testMealySamples() { public void testEquivalence() { final Random random = new Random(SEED); - final CompactSST automaton = new CompactSST<>(INPUTS); + final CompactSST sst = new CompactSST<>(INPUTS); final List> words = new ArrayList<>(); for (List t : CollectionsUtil.allTuples(OUTPUTS, 1, 3)) { @@ -88,31 +89,46 @@ public void testEquivalence() { final Collection> stateProps = words.subList(0, midpoint); final Collection> transProps = words.subList(midpoint, words.size()); - RandomAutomata.randomDeterministic(random, SIZE, INPUTS, stateProps, transProps, automaton); + RandomAutomata.randomDeterministic(random, SIZE, INPUTS, stateProps, transProps, sst); final OSTIA learner = new OSTIA<>(INPUTS, Alphabets.fromCollection(OUTPUTS)); - final WpMethodTestsIterator wpIterator = new WpMethodTestsIterator<>(automaton, INPUTS, 0); + final Iterator> testIterator = new WMethodTestsIterator<>(sst, INPUTS, 0); - while (wpIterator.hasNext()) { - final Word input = wpIterator.next(); - final Word out = automaton.computeOutput(input); - System.out.println(input+" "+out); + while (testIterator.hasNext()) { + final Word input = testIterator.next(); + final Word out = sst.computeOutput(input); + System.out.println(input + "|" + out); learner.addSample(input, out); } - // For input [2, 2] the state output is [0, 1, 0, 0, 0, 1, 2, 0, 1, 0, 0] but training sample has remaining suffix [2, 0, 2] final SubsequentialTransducer model = learner.computeModel(); + final OnwardSubsequentialTransducer osst = + SubsequentialTransducers.toOSST(sst, INPUTS, new CompactOSST<>(INPUTS)); -// Word sepWord = Automata.findSeparatingWord(automaton, model, INPUTS); -// Word autOut = automaton.computeOutput(sepWord); -// Word modelOut = model.computeOutput(sepWord); -// -// System.err.println("sepWord: " + sepWord); -// System.err.println("automaton: " + autOut); -// System.err.println("model: " + modelOut); - OSTIA.onwardForm(automaton); - Assert.assertTrue(Automata.testEquivalence(automaton, model, INPUTS)); + System.err.println(osst.size()); + printStateProperties(osst); + System.err.println("---"); + System.err.println(model.size()); + printStateProperties(model); + + Word sepWord = Automata.findSeparatingWord(osst, model, INPUTS); + Word autOut = osst.computeOutput(sepWord); + Word modelOut = model.computeOutput(sepWord); + + System.err.println("sepWord: " + sepWord); + System.err.println("osst: " + autOut); + System.err.println("model: " + modelOut); + + // Visualization.visualize(sst); + + Assert.assertTrue(Automata.testEquivalence(osst, model, INPUTS)); + } + + private static void printStateProperties(SubsequentialTransducer sst) { + for (S s : sst) { + System.err.println(sst.getStateProperty(s)); + } } } From b2482fd4bb9329fc2f1aef7804ef8f8e574f0ee2 Mon Sep 17 00:00:00 2001 From: Alagris Date: Sat, 19 Dec 2020 15:05:22 +0100 Subject: [PATCH 08/21] fixed OSTIA buildPtt procedure to allow for non-sorted informants, minor improvements and optimizations --- .../de/learnlib/algorithms/ostia/IntSeq.java | 17 +++++ .../de/learnlib/algorithms/ostia/OSTIA.java | 67 +++---------------- .../de/learnlib/algorithms/ostia/State.java | 14 ++++ .../compact/SubsequentialTransducers.java | 3 +- .../learnlib/algorithms/ostia/OSTIATest.java | 8 ++- 5 files changed, 47 insertions(+), 62 deletions(-) diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java index 5e9d615e71..c966cb0c92 100644 --- a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java @@ -1,9 +1,26 @@ package de.learnlib.algorithms.ostia; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; + import java.util.Arrays; interface IntSeq { + static IntSeq of(Word word, Alphabet alphabet) { + return new IntSeq() { + @Override + public int size() { + return word.size(); + } + + @Override + public int get(int index) { + return alphabet.applyAsInt(word.getSymbol(index)); + } + }; + } + int size(); int get(int index); 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 index 861be31812..3971adb563 100644 --- 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 @@ -52,12 +52,13 @@ public void addSamples(Collection>> samples) { @Override public SubsequentialTransducer computeModel() { final State root = new State(alphabetSize); - this.samples.sort(Comparator.comparingInt(a -> a.getInput().length()));//these samples must be sorted - //with respect to length or otherwise building prefix-tree-transducer won't work for (DefaultQuery> sample : this.samples) { - final IntSeq inSeq = IntSeq.seq(sample.getInput().stream().mapToInt(inputAlphabet).toArray()); - final IntSeq outSeq = IntSeq.seq(sample.getOutput().stream().mapToInt(outputAlphabet).toArray()); - buildPttOnward(root, inSeq, asQueue(outSeq, 0)); + //The beauty of duck-typing is that we don't have to do this: +// final IntSeq inSeq = IntSeq.seq(sample.getInput().stream().mapToInt(inputAlphabet).toArray()); +// final IntSeq outSeq = IntSeq.seq(sample.getOutput().stream().mapToInt(outputAlphabet).toArray()); +// buildPttOnward(root, inSeq, asQueue(outSeq, 0)); + //Instead we can do it like this: + buildPttOnward(root, IntSeq.of(sample.getInput(),inputAlphabet), asQueue(IntSeq.of(sample.getOutput(),outputAlphabet), 0)); } ostia(root); return new OSSTWrapper<>(root, inputAlphabet, outputAlphabet); @@ -112,7 +113,7 @@ private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { } else { commonPrefixEdgePrev.next = null; } - edge.target.prepend(commonPrefixEdge); + edge.target.prependButIgnoreMissingStateOutput(commonPrefixEdge); output = commonPrefixInformant; ptt = edge.target; } @@ -194,7 +195,7 @@ public static Word dequeueLongestCommonPrefix(CompactSST automaton) { final HashMap>> allStates = new HashMap<>(); for(int source:automaton.getStates()){ @@ -240,57 +241,7 @@ public static void onwardForm(CompactSST automaton) { } } } - - public static void onwardForm(State transducer) { - final HashMap>> allStates = new HashMap<>(); - final Stack toVisit = new Stack<>(); - toVisit.push(transducer); - allStates.put(transducer,new ArrayList<>()); - while(!toVisit.isEmpty()){ - final State s = toVisit.pop(); - for(Edge outgoing:s.transitions){ - if(outgoing!=null && !allStates.containsKey(outgoing.target)){ - allStates.put(outgoing.target,new ArrayList<>()); - toVisit.push(outgoing.target); - } - } - } - for(State source:allStates.keySet()){ - for(int symbol=0;symbol modified = new LinkedList<>(); - final HashSet modifiedLookup = new HashSet<>(); - for(Map.Entry>> stateAndReversedTransitions:allStates.entrySet()){ - final State target = stateAndReversedTransitions.getKey(); - final IntQueue dequeued = target.dequeueLongestCommonPrefix(); - if(dequeued!=null){ - for(Pair symbolAndSource: stateAndReversedTransitions.getValue()){ - final int symbol = symbolAndSource.getFirst(); - final State source = symbolAndSource.getSecond(); - source.transitions[symbol].out = IntQueue.concatAndCopy(source.transitions[symbol].out,dequeued); - if(modifiedLookup.add(source))modified.add(source); - } - } - } - while(!modified.isEmpty()){ - final State target = modified.poll(); - modifiedLookup.remove(target); - final IntQueue dequeued = target.dequeueLongestCommonPrefix(); - if(dequeued!=null) { - for (Pair symbolAndSource : allStates.get(target)) { - final int symbol = symbolAndSource.getFirst(); - final State source = symbolAndSource.getSecond(); - source.transitions[symbol].out = IntQueue.concatAndCopy(source.transitions[symbol].out, dequeued); - if (modifiedLookup.add(source)) modified.add(source); - } - } - } - } +*/ public static void ostia(State transducer) { final java.util.Queue blue = new LinkedList<>(); final ArrayList red = new ArrayList<>(); 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 index 83a46f4677..d3a339ac66 100644 --- 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 @@ -37,6 +37,20 @@ void prepend(IntQueue prefix) { } } + /** + * The IntQueue is consumed and should not be reused after calling this method + */ + void prependButIgnoreMissingStateOutput(IntQueue prefix) { + for (Edge edge : transitions) { + if (edge != null) { + edge.out = IntQueue.copyAndConcat(prefix, edge.out); + } + } + if (out != null) { + out.str = IntQueue.copyAndConcat(prefix, out.str); + } + } + IntQueue dequeueLongestCommonPrefix() { Out lcp = out; diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducers.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducers.java index 779fe12938..ff0d09935c 100644 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducers.java +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducers.java @@ -58,7 +58,8 @@ public static newTransitionProperty = oldTransitionProperty.concat(lcp); out.setTransitionProperty(t, newTransitionProperty); - queue.add(incoming.getFirst()); + if(!queue.contains(incoming.getFirst()))//this if can improve performance a little + queue.add(incoming.getFirst()); } } } 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 index 4328671316..980bbf1fda 100644 --- 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 @@ -34,6 +34,7 @@ import net.automatalib.util.automata.Automata; import net.automatalib.util.automata.conformance.WMethodTestsIterator; import net.automatalib.util.automata.random.RandomAutomata; +import net.automatalib.visualization.Visualization; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; import net.automatalib.words.impl.Alphabets; @@ -73,7 +74,7 @@ public void testMealySamples() { } } - @Test(enabled = false) + @Test() public void testEquivalence() { final Random random = new Random(SEED); @@ -120,8 +121,9 @@ public void testEquivalence() { System.err.println("osst: " + autOut); System.err.println("model: " + modelOut); - // Visualization.visualize(sst); - +// Visualization.visualize(sst); +// Visualization.visualize(osst.transitionGraphView(INPUTS)); +// Visualization.visualize(model.transitionGraphView(INPUTS)); Assert.assertTrue(Automata.testEquivalence(osst, model, INPUTS)); } From fad3e79916ebbc7b15d3797b0ad731a83bc953f4 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Sun, 20 Dec 2020 02:53:32 +0100 Subject: [PATCH 09/21] cleanups and refactorings * removed dead code * refactored some interfaces/classes * added new test cases * applied style guidelines --- CHANGELOG.md | 3 + README.md | 8 +- algorithms/passive/ostia/pom.xml | 29 ++- .../de/learnlib/algorithms/ostia/Blue.java | 31 ++- .../de/learnlib/algorithms/ostia/Edge.java | 23 +- .../learnlib/algorithms/ostia/IntQueue.java | 135 +++++------ .../de/learnlib/algorithms/ostia/IntSeq.java | 33 ++- .../algorithms/ostia/OSSTWrapper.java | 34 ++- .../de/learnlib/algorithms/ostia/OSTIA.java | 229 +++++------------- .../de/learnlib/algorithms/ostia/Out.java | 19 +- .../de/learnlib/algorithms/ostia/State.java | 63 ++--- .../MutableSubsequentialTransducer.java | 36 +++ .../compact => }/SSTVisualizationHelper.java | 20 +- .../transducers/SubsequentialTransducer.java | 109 +++++++++ .../SubsequentialTransducers.java | 31 ++- .../transducers/impl/compact/CompactOSST.java | 22 -- .../transducers/impl/compact/CompactSST.java | 16 ++ .../MutableOnwardSubsequentialTransducer.java | 7 - .../MutableSubsequentialTransducer.java | 7 - .../OnwardSubsequentialTransducer.java | 3 - .../impl/compact/SubsequentialTransducer.java | 65 ----- .../impl/compact/UniversalCompactDet.java | 15 ++ .../learnlib/algorithms/ostia/OSTIATest.java | 49 +++- .../SubsequentialTransducersTest.java | 138 +++++++++++ pom.xml | 6 + 25 files changed, 686 insertions(+), 445 deletions(-) create mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/MutableSubsequentialTransducer.java rename algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/{impl/compact => }/SSTVisualizationHelper.java (59%) create mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducer.java rename algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/{impl/compact => }/SubsequentialTransducers.java (73%) delete mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOSST.java delete mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableOnwardSubsequentialTransducer.java delete mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableSubsequentialTransducer.java delete mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/OnwardSubsequentialTransducer.java delete mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducer.java create mode 100644 algorithms/passive/ostia/src/test/java/net/automatalib/automata/transducers/SubsequentialTransducersTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 95495838ec..d89c30b6cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ 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). ## [0.16.0](https://github.com/LearnLib/learnlib/releases/tag/learnlib-0.16.0) - 2020-10-12 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 index f23e71e3e9..354cd36cb8 100644 --- a/algorithms/passive/ostia/pom.xml +++ b/algorithms/passive/ostia/pom.xml @@ -19,8 +19,8 @@ limitations under the License. 4.0.0 - learnlib-algorithms-passive-parent de.learnlib + learnlib-algorithms-passive-parent 0.17.0-SNAPSHOT @@ -34,6 +34,15 @@ limitations under the License. de.learnlib learnlib-api + + + net.automatalib + automata-api + + + net.automatalib + automata-commons-util + net.automatalib automata-core @@ -43,16 +52,32 @@ limitations under the License. automata-util + + org.checkerframework + checker-qual + + + + com.google.guava + guava + test + + de.learnlib.testsupport learnlib-learner-it-support + net.automatalib automata-dot-visualizer 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 index b0f3930ebd..c34693c877 100644 --- 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 @@ -1,21 +1,36 @@ +/* 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; class Blue { - State parent; - int symbol; + final State parent; + final int symbol; - State state() { - return parent.transitions[symbol].target; - } - - public Blue(State parent, int symbol) { + Blue(State parent, int symbol) { this.symbol = symbol; this.parent = parent; } + State state() { + return parent.transitions[symbol].target; + } + @Override public String toString() { - return state().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 index 3a15f33856..2c7b4e5e28 100644 --- 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 @@ -1,3 +1,18 @@ +/* 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; class Edge { @@ -5,17 +20,15 @@ class Edge { IntQueue out; State target; - public Edge() { + Edge() {} - } - - public Edge(Edge edge) { + Edge(Edge edge) { out = IntQueue.copyAndConcat(edge.out, null); target = edge.target; } @Override public String toString() { - return target.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 index 6e974c05e4..a0f5fe3b0c 100644 --- 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 @@ -1,84 +1,85 @@ +/* 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 org.checkerframework.checker.nullness.qual.Nullable; class IntQueue { int value; IntQueue next; - public static String str(IntQueue q) { - if(q==null)return "[]"; - StringBuilder sb = new StringBuilder("[").append(q.value); - while(q.next!=null){ - q = q.next; - sb.append(", ").append(q.value); - } - return sb.append("]").toString(); - } - - public static int lcpLen(IntQueue a, IntQueue b) { - int len = 0; - while (a != null && b != null && a.value==b.value) { - len++; - a = a.next; - b = b.next; - } - return len; - } - - /**offset(q,0) returns q, offset(q,1) returns q.next and so on*/ - public static IntQueue offset(IntQueue queue,int len) { - while(len-->0){//it looks sort of like, "len approaches 0" which is neat - queue = queue.next; - } - return queue; - } - - @Override public String toString() { + return str(this); + } - StringBuilder sb = new StringBuilder(); - sb.append(value); - IntQueue next = this.next; - while (next != null) { - sb.append(" ").append(next.value); - next = next.next; + 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; } - return sb.toString(); + assert !IntQueue.hasCycle(q); + return q; } - public static int len(IntQueue q) { - int len = 0; - while (q != null) { - len++; - q = q.next; + static String str(@Nullable IntQueue q) { + final StringJoiner sj = new StringJoiner(", ", "[", "]"); + + IntQueue iter = q; + while (iter != null) { + sj.add(Integer.toString(iter.value)); + iter = iter.next; } - return len; + return sj.toString(); } - public static int[] arr(IntQueue q) { - final int[] arr = new int[len(q)]; - for (int i = 0; i < arr.length; i++) { - arr[i] = q.value; - q = q.next; + 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 arr; + return aIter == null && bIter == null; } - static boolean hasCycle(IntQueue q) { - final HashSet elements = new HashSet<>(); - while (q != null) { - if (!elements.add(q)) { + static boolean hasCycle(@Nullable IntQueue q) { + final Set elements = new HashSet<>(); + IntQueue iter = q; + while (iter != null) { + if (!elements.add(iter)) { return true; } - q = q.next; + iter = iter.next; } return false; } - static IntQueue copyAndConcat(IntQueue q, IntQueue tail) { + static IntQueue copyAndConcat(@Nullable IntQueue q, @Nullable IntQueue tail) { assert !hasCycle(q) && !hasCycle(tail); if (q == null) { return tail; @@ -86,33 +87,15 @@ static IntQueue copyAndConcat(IntQueue q, IntQueue tail) { final IntQueue root = new IntQueue(); root.value = q.value; IntQueue curr = root; - q = q.next; - while (q != null) { + IntQueue iter = q.next; + while (iter != null) { curr.next = new IntQueue(); curr = curr.next; - curr.value = q.value; - q = q.next; + curr.value = iter.value; + iter = iter.next; } curr.next = tail; assert !hasCycle(root); return root; } - - static IntQueue concat(IntQueue q, IntQueue tail) { - assert !hasCycle(q) && !hasCycle(tail); - if (q == null) { - return tail; - } - final IntQueue first = q; - while (q.next != null) { - q = q.next; - } - q.next = tail; - assert !hasCycle(first); - return first; - } - - static IntQueue concatAndCopy(IntQueue q, IntQueue tail) { - return concat(q,copyAndConcat(tail,null)); - } } diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java index c966cb0c92..d13524acac 100644 --- a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java @@ -1,14 +1,34 @@ +/* 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.Arrays; + import net.automatalib.words.Alphabet; import net.automatalib.words.Word; -import java.util.Arrays; - interface IntSeq { + int size(); + + int get(int index); + static IntSeq of(Word word, Alphabet alphabet) { return new IntSeq() { + @Override public int size() { return word.size(); @@ -18,13 +38,14 @@ public int size() { public int get(int index) { return alphabet.applyAsInt(word.getSymbol(index)); } + + @Override + public String toString() { + return word.toString(); + } }; } - int size(); - - int get(int index); - static IntSeq seq(int... ints) { return new IntSeq() { 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 index 4d32ad679c..ac9f3425cd 100644 --- 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 @@ -1,15 +1,30 @@ +/* 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.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Queue; import java.util.Set; import java.util.stream.StreamSupport; -import net.automatalib.automata.transducers.impl.compact.SubsequentialTransducer; +import net.automatalib.automata.transducers.SubsequentialTransducer; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; import net.automatalib.words.WordBuilder; @@ -21,7 +36,7 @@ class OSSTWrapper implements SubsequentialTransducer { private final Alphabet inputAlphabet; private final Alphabet outputAlphabet; - public OSSTWrapper(State root, Alphabet inputAlphabet, Alphabet outputAlphabet) { + OSSTWrapper(State root, Alphabet inputAlphabet, Alphabet outputAlphabet) { this.root = root; this.inputAlphabet = inputAlphabet; this.outputAlphabet = outputAlphabet; @@ -31,7 +46,7 @@ public OSSTWrapper(State root, Alphabet inputAlphabet, Alphabet outputAlph public Word computeOutput(Iterable input) { final Iterator transformedInput = StreamSupport.stream(input.spliterator(), false).mapToInt(inputAlphabet).iterator(); - final ArrayList output = OSTIA.run(root, transformedInput); + final List output = OSTIA.run(root, transformedInput); return output != null ? output.stream().map(outputAlphabet::getSymbol).collect(Word.collector()) : @@ -89,20 +104,21 @@ public State getSuccessor(Edge transition) { return transition.target; } - private Word outToWord(Out out) { + private Word outToWord(@Nullable Out out) { return outToWord(out == null ? null : out.str); } - private Word outToWord(IntQueue out) { + private Word outToWord(@Nullable IntQueue out) { if (out == null) { return Word.epsilon(); } final WordBuilder wb = new WordBuilder<>(); - while (out != null) { - wb.add(outputAlphabet.getSymbol(out.value)); - out = out.next; + 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 index 3971adb563..615d7f08cc 100644 --- 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 @@ -15,15 +15,19 @@ */ package de.learnlib.algorithms.ostia; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; import de.learnlib.api.algorithm.PassiveLearningAlgorithm; import de.learnlib.api.query.DefaultQuery; -import net.automatalib.automata.transducers.impl.compact.CompactMealyTransition; -import net.automatalib.automata.transducers.impl.compact.CompactSST; -import net.automatalib.automata.transducers.impl.compact.SubsequentialTransducer; +import net.automatalib.automata.transducers.SubsequentialTransducer; import net.automatalib.commons.util.Pair; -import net.automatalib.visualization.Visualization; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; import org.checkerframework.checker.nullness.qual.Nullable; @@ -32,68 +36,56 @@ public class OSTIA implements PassiveLearningAlgorithm inputAlphabet; private final Alphabet outputAlphabet; - private final int alphabetSize; - - private final List>> samples; + private final State root; public OSTIA(Alphabet inputAlphabet, Alphabet outputAlphabet) { this.inputAlphabet = inputAlphabet; this.outputAlphabet = outputAlphabet; - this.alphabetSize = inputAlphabet.size(); - this.samples = new ArrayList<>(); + this.root = new State(inputAlphabet.size()); } - @Override public void addSamples(Collection>> samples) { - this.samples.addAll(samples); + for (DefaultQuery> sample : samples) { + buildPttOnward(root, + IntSeq.of(sample.getInput(), inputAlphabet), + IntQueue.asQueue(IntSeq.of(sample.getOutput(), outputAlphabet))); + } } @Override public SubsequentialTransducer computeModel() { - final State root = new State(alphabetSize); - for (DefaultQuery> sample : this.samples) { - //The beauty of duck-typing is that we don't have to do this: -// final IntSeq inSeq = IntSeq.seq(sample.getInput().stream().mapToInt(inputAlphabet).toArray()); -// final IntSeq outSeq = IntSeq.seq(sample.getOutput().stream().mapToInt(outputAlphabet).toArray()); -// buildPttOnward(root, inSeq, asQueue(outSeq, 0)); - //Instead we can do it like this: - buildPttOnward(root, IntSeq.of(sample.getInput(),inputAlphabet), asQueue(IntSeq.of(sample.getOutput(),outputAlphabet), 0)); - } ostia(root); return new OSSTWrapper<>(root, inputAlphabet, outputAlphabet); - } - - public static boolean hasCycle(IntQueue q) { - final HashSet elements = new HashSet<>(); - while (q != null) { - if (!elements.add(q)) { - return true; - } - q = q.next; + 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 false; + return root; } - - /** - * builds onward prefix tree transducer - */ private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { + State pttIter = ptt; + IntQueue outputIter = output; + for (int i = 0; i < input.size(); i++) {//input index final int symbol = input.get(i); - if (ptt.transitions[symbol] == null) { - final Edge edge = ptt.transitions[symbol] = new Edge(); - edge.out = output; - output = null; - ptt = edge.target = new State(ptt.transitions.length); + 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 { - final Edge edge = ptt.transitions[symbol]; + edge = pttIter.transitions[symbol]; IntQueue commonPrefixEdge = edge.out; IntQueue commonPrefixEdgePrev = null; - IntQueue commonPrefixInformant = output; + IntQueue commonPrefixInformant = outputIter; while (commonPrefixEdge != null && commonPrefixInformant != null && commonPrefixEdge.value == commonPrefixInformant.value) { commonPrefixInformant = commonPrefixInformant.next; @@ -114,51 +106,22 @@ private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { commonPrefixEdgePrev.next = null; } edge.target.prependButIgnoreMissingStateOutput(commonPrefixEdge); - output = commonPrefixInformant; - ptt = edge.target; + outputIter = commonPrefixInformant; } + pttIter = edge.target; } - if (ptt.out != null && !eq(ptt.out.str, output)) { - throw new IllegalArgumentException("For input "+input.toString()+" the state output is "+IntQueue.str(ptt.out.str)+" but training sample has remaining suffix "+IntQueue.str(output)); - } - ptt.out = new Out(output); - } - - private static boolean eq(IntQueue a, IntQueue b) { - while (a != null && b != null) { - if (a.value != b.value) { - return false; - } - a = a.next; - b = b.next; - } - return a == null && b == null; - } - - private static IntQueue asQueue(IntSeq str, int offset) { - IntQueue q = null; - for (int i = str.size() - 1; i >= offset; i--) { - IntQueue next = new IntQueue(); - next.value = str.get(i); - next.next = q; - q = next; - } - assert !hasCycle(q); - return q; - } - - 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(), asQueue(inout.getSecond(), 0)); + if (pttIter.out != null && !IntQueue.eq(pttIter.out.str, outputIter)) { + throw new IllegalArgumentException("For input '" + input + "' the state output is '" + pttIter.out.str + + "' but training sample has remaining suffix '" + outputIter + '\''); } - return root; + pttIter.out = new Out(outputIter); } - static void addBlueStates(State parent, java.util.Queue blue) { + private static void addBlueStates(State parent, java.util.Queue blue) { for (int i = 0; i < parent.transitions.length; i++) { - if (parent.transitions[i] != null) { blue.add(new Blue(parent, i)); } + if (parent.transitions[i] != null) { + blue.add(new Blue(parent, i)); + } } } @@ -170,81 +133,9 @@ static Edge[] copyTransitions(Edge[] transitions) { return copy; } - public static Word dequeueLongestCommonPrefix(CompactSST automaton,int stateIdx) { - Word lcp = automaton.getStateProperty(stateIdx); - for (int symbol=0;symbol> outgoing = automaton.getTransition(stateIdx,symbol); - if(outgoing==null)continue; - if (lcp == null){ - lcp = outgoing.getOutput(); - }else{ - lcp = outgoing.getOutput().longestCommonPrefix(lcp); - } - } - if(lcp==null||lcp.length()==0)return null; - final Word stateOut = automaton.getStateProperty(stateIdx); - if(stateOut!=null){ - automaton.setStateProperty(stateIdx,stateOut.subWord(lcp.length())); - } - for (int symbol=0;symbol> outgoing = automaton.getTransition(stateIdx, symbol); - if (outgoing != null) { - automaton.setStateProperty(stateIdx,outgoing.getOutput().subWord(lcp.length())); - } - } - return lcp; - } - -/* - public static void onwardForm(CompactSST automaton) { - final HashMap>> allStates = new HashMap<>(); - for(int source:automaton.getStates()){ - allStates.put(source,new ArrayList<>()); - } - for(int source:automaton.getStates()){ - for(int symbol=0;symbol> outgoing = automaton.getTransition(source,symbol); - if(outgoing!=null){ - allStates.get(outgoing.getSuccId()).add(Pair.of(symbol,source)); - } - } - } - final Queue modified = new LinkedList<>(); - final HashSet modifiedLookup = new HashSet<>(); - for(Map.Entry>> stateAndReversedTransitions:allStates.entrySet()){ - final int target = stateAndReversedTransitions.getKey(); - final Word dequeued = dequeueLongestCommonPrefix(automaton,target); - if(dequeued!=null){ - for(Pair symbolAndSource: stateAndReversedTransitions.getValue()){ - final int symbol = symbolAndSource.getFirst(); - final int source = symbolAndSource.getSecond(); - final @Nullable CompactMealyTransition> incoming = automaton.getTransition(source,symbol); - assert incoming!=null; - automaton.setTransitionProperty(incoming,incoming.getOutput().concat(dequeued)); - if(modifiedLookup.add(source))modified.add(source); - } - } - } - while(!modified.isEmpty()){ - final int target = modified.poll(); - modifiedLookup.remove(target); - final Word dequeued = dequeueLongestCommonPrefix(automaton,target); - if(dequeued!=null){ - for(Pair symbolAndSource: allStates.get(target)){ - final int symbol = symbolAndSource.getFirst(); - final int source = symbolAndSource.getSecond(); - final @Nullable CompactMealyTransition> incoming = automaton.getTransition(source,symbol); - assert incoming!=null; - automaton.setTransitionProperty(incoming,incoming.getOutput().concat(dequeued)); - if(modifiedLookup.add(source))modified.add(source); - } - } - } - } -*/ public static void ostia(State transducer) { - final java.util.Queue blue = new LinkedList<>(); - final ArrayList red = new ArrayList<>(); + final Queue blue = new LinkedList<>(); + final List red = new ArrayList<>(); red.add(transducer); addBlueStates(transducer, blue); blue: @@ -262,8 +153,8 @@ public static void ostia(State transducer) { } private static boolean ostiaMerge(Blue blue, State redState, java.util.Queue blueToVisit) { - final HashMap merged = new HashMap<>(); - final ArrayList reachedBlueStates = new ArrayList<>(); + 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()) { mergedRedState.getKey().assign(mergedRedState.getValue()); @@ -278,8 +169,8 @@ private static boolean ostiaFold(State red, IntQueue pushedBack, State blueParent, int symbolIncomingToBlue, - HashMap mergedStates, - ArrayList reachedBlueStates) { + Map mergedStates, + List reachedBlueStates) { final State mergedRedState = mergedStates.computeIfAbsent(red, State::new); final State blueState = blueParent.transitions[symbolIncomingToBlue].target; final State mergedBlueState = new State(blueState); @@ -291,7 +182,7 @@ private static boolean ostiaFold(State red, if (mergedBlueState.out != null) { if (mergedRedState.out == null) { mergedRedState.out = mergedBlueState.out; - } else if (!eq(mergedRedState.out.str, mergedBlueState.out.str)) { + } else if (!IntQueue.eq(mergedRedState.out.str, mergedBlueState.out.str)) { return false; } } @@ -312,9 +203,7 @@ private static boolean ostiaFold(State red, commonPrefixBlue = commonPrefixBlue.next; commonPrefixRed = commonPrefixRed.next; } - assert commonPrefixBluePrev == null ? - commonPrefixBlue == transitionBlue.out : - commonPrefixBluePrev.next == commonPrefixBlue; + assert commonPrefixBluePrev == null || commonPrefixBluePrev.next == commonPrefixBlue; if (commonPrefixRed == null) { if (commonPrefixBluePrev == null) { transitionBlue.out = null; @@ -329,7 +218,6 @@ private static boolean ostiaFold(State red, reachedBlueStates)) { return false; } - } else { return false; } @@ -339,24 +227,25 @@ private static boolean ostiaFold(State red, return true; } - static ArrayList run(State init, Iterator input) { - ArrayList output = new ArrayList<>(); + public static @Nullable List run(State init, Iterator input) { + final List output = new ArrayList<>(); + State iter = init; while (input.hasNext()) { - final Edge edge = init.transitions[input.next()]; + final Edge edge = iter.transitions[input.next()]; if (edge == null) { return null; } - init = edge.target; + iter = edge.target; IntQueue q = edge.out; while (q != null) { output.add(q.value); q = q.next; } } - if (init.out == null) { + if (iter.out == null) { return null; } - IntQueue q = init.out.str; + IntQueue q = iter.out.str; while (q != null) { output.add(q.value); q = q.next; 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 index 58c7d311fb..539dc2c1ef 100644 --- 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 @@ -1,15 +1,30 @@ +/* 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; class Out { IntQueue str; - public Out(IntQueue str) { + Out(IntQueue str) { this.str = str; } @Override public String toString() { - return str.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 index d3a339ac66..ae9f1c830c 100644 --- 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 @@ -1,16 +1,24 @@ +/* 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 net.automatalib.commons.util.Pair; - class State { - public void assign(State other) { - out = other.out; - transitions = other.transitions; - } - - public Out out; - public Edge[] transitions; + Out out; + Edge[] transitions; State(int alphabetSize) { transitions = new Edge[alphabetSize]; @@ -21,8 +29,13 @@ public void assign(State other) { out = copy.out == null ? null : new Out(IntQueue.copyAndConcat(copy.out.str, null)); } + public void assign(State other) { + out = other.out; + transitions = other.transitions; + } + /** - * The IntQueue is consumed and should not be reused after calling this method + * The IntQueue is consumed and should not be reused after calling this method. */ void prepend(IntQueue prefix) { for (Edge edge : transitions) { @@ -38,7 +51,7 @@ void prepend(IntQueue prefix) { } /** - * The IntQueue is consumed and should not be reused after calling this method + * The IntQueue is consumed and should not be reused after calling this method. */ void prependButIgnoreMissingStateOutput(IntQueue prefix) { for (Edge edge : transitions) { @@ -51,32 +64,4 @@ void prependButIgnoreMissingStateOutput(IntQueue prefix) { } } - - IntQueue dequeueLongestCommonPrefix() { - Out lcp = out; - int len = out==null?-1:IntQueue.len(out.str); - for (Edge outgoing : transitions) { - if(outgoing==null)continue; - if (lcp == null){ - lcp = new Out(outgoing.out); - len = IntQueue.len(outgoing.out); - }else{ - len = Math.min(len,IntQueue.lcpLen(lcp.str,outgoing.out)); - } - } - if(lcp==null||len==0)return null; - assert len>0; - IntQueue dequeuedLcp = lcp.str; - if(out!=null){ - out.str = IntQueue.offset(out.str,len); - } - for (Edge outgoing : transitions) { - if (outgoing != null){ - outgoing.out = IntQueue.offset(outgoing.out,len); - } - } - IntQueue.offset(dequeuedLcp,len-1).next = null; - return dequeuedLcp; - } - } diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/MutableSubsequentialTransducer.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/MutableSubsequentialTransducer.java new file mode 100644 index 0000000000..6dbe220c25 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/MutableSubsequentialTransducer.java @@ -0,0 +1,36 @@ +/* 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 net.automatalib.automata.transducers; + +import net.automatalib.automata.MutableDeterministic; +import net.automatalib.words.Word; + +/** + * A {@link MutableDeterministic mutable} extension of a {@link SubsequentialTransducer}. + * + * @param + * state type + * @param + * input symbol type + * @param + * transition type + * @param + * output symbol type + * + * @author frohme + */ +public interface MutableSubsequentialTransducer + extends SubsequentialTransducer, MutableDeterministic, Word> {} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SSTVisualizationHelper.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SSTVisualizationHelper.java similarity index 59% rename from algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SSTVisualizationHelper.java rename to algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SSTVisualizationHelper.java index 7c2c4dc15d..deaad0b51c 100644 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SSTVisualizationHelper.java +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SSTVisualizationHelper.java @@ -1,4 +1,19 @@ -package net.automatalib.automata.transducers.impl.compact; +/* 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 net.automatalib.automata.transducers; import java.util.Map; @@ -35,7 +50,8 @@ public boolean getNodeProperties(S node, Map properties) { return false; } - properties.put(NodeAttrs.LABEL, automaton.getStateProperty(node).toString()); + final String oldLabel = properties.get(NodeAttrs.LABEL); + properties.put(NodeAttrs.LABEL, oldLabel + " / " + automaton.getStateProperty(node)); return true; } } diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducer.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducer.java new file mode 100644 index 0000000000..f3c7b87de7 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducer.java @@ -0,0 +1,109 @@ +/* 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 net.automatalib.automata.transducers; + +import java.util.Collection; +import java.util.List; + +import net.automatalib.automata.UniversalDeterministicAutomaton; +import net.automatalib.automata.concepts.DetSuffixOutputAutomaton; +import net.automatalib.automata.fsa.DFA; +import net.automatalib.automata.graphs.TransitionEdge; +import net.automatalib.automata.graphs.TransitionEdge.Property; +import net.automatalib.automata.graphs.UniversalAutomatonGraphView; +import net.automatalib.graphs.UniversalGraph; +import net.automatalib.ts.output.DeterministicOutputTS; +import net.automatalib.visualization.VisualizationHelper; +import net.automatalib.words.Word; +import net.automatalib.words.WordBuilder; + +/** + * A subsequential transducer (or SST) is an {@link DeterministicOutputTS} whose state and transition properties are + * output-{@link Word words}. Upon parsing a sequence of input symbols, each transition emits a {@link Word sequence} of + * output symbols. + *

+ * Implementation detail: + * There exist definitions of SSTs that associate each state with an additional notion of 'acceptance' in order to + * reject certain transductions. This implementation/interface denotes prefix-closed transductions, i.e. all states are + * accepting. If you would like to filter out certain transduction you may use a supplementary {@link DFA} for this + * decision problem. + * + * @param + * state type + * @param + * input symbol type + * @param + * transition type + * @param + * output symbol type + * + * @author frohme + */ +public interface SubsequentialTransducer extends DeterministicOutputTS, + DetSuffixOutputAutomaton>, + UniversalDeterministicAutomaton, Word> { + + @Override + default Word computeStateOutput(S state, Iterable input) { + // since the outputs are words of unknown length, we can't really pre-compute a sensible builder size + final WordBuilder result = new WordBuilder<>(); + + trace(state, input, result); + + return result.toWord(); + } + + @Override + default boolean trace(S state, Iterable input, List output) { + S iter = state; + + for (I sym : input) { + T trans = getTransition(iter, sym); + if (trans == null) { + return false; + } + Word out = getTransitionProperty(trans); + output.addAll(out.asList()); + iter = getSuccessor(trans); + } + + if (iter == null) { + return false; + } else { + output.addAll(getStateProperty(iter).asList()); + } + + return true; + } + + @Override + default UniversalGraph, Word, Property>> transitionGraphView(Collection inputs) { + return new SSTGraphView<>(this, inputs); + } + + class SSTGraphView> + extends UniversalAutomatonGraphView, Word, A> { + + public SSTGraphView(A automaton, Collection inputs) { + super(automaton, inputs); + } + + @Override + public VisualizationHelper> getVisualizationHelper() { + return new SSTVisualizationHelper<>(automaton); + } + } +} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducers.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java similarity index 73% rename from algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducers.java rename to algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java index ff0d09935c..55d5c08968 100644 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducers.java +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java @@ -1,4 +1,19 @@ -package net.automatalib.automata.transducers.impl.compact; +/* 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 net.automatalib.automata.transducers; import java.util.ArrayDeque; import java.util.Collection; @@ -19,7 +34,7 @@ private SubsequentialTransducers() { // prevent initialization } - public static > A toOSST( + public static > A toOnwardSST( SubsequentialTransducer sst, Collection inputs, A out) { @@ -33,8 +48,6 @@ public static lcp = computeLCP(out, inputs, s); - System.err.println("lcp: " + lcp); - if (!lcp.isEmpty()) { final Word oldStateProperty = out.getStateProperty(s); final Word newStateProperty = oldStateProperty.subWord(lcp.length()); @@ -58,8 +71,9 @@ public static newTransitionProperty = oldTransitionProperty.concat(lcp); out.setTransitionProperty(t, newTransitionProperty); - if(!queue.contains(incoming.getFirst()))//this if can improve performance a little + if (!queue.contains(incoming.getFirst())) { //this if can improve performance a little queue.add(incoming.getFirst()); + } } } } @@ -90,14 +104,17 @@ private static Mapping>> getIncomingTransitions(Subs return result; } - private static Word computeLCP(SubsequentialTransducer sst, + private static Word computeLCP(SubsequentialTransducer sst, Collection inputs, S s) { Word lcp = sst.getStateProperty(s); for (I i : inputs) { - lcp = lcp.longestCommonPrefix(sst.getTransitionProperty(s, i)); + T t = sst.getTransition(s, i); + if (t != null) { + lcp = lcp.longestCommonPrefix(sst.getTransitionProperty(s, i)); + } } return lcp; diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOSST.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOSST.java deleted file mode 100644 index 7dc5879351..0000000000 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactOSST.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.automatalib.automata.transducers.impl.compact; - -import net.automatalib.words.Alphabet; -import net.automatalib.words.Word; - -public class CompactOSST extends UniversalCompactDet, Word> - implements MutableOnwardSubsequentialTransducer>, O> { - - public CompactOSST(Alphabet alphabet) { - super(alphabet); - } - -// @Override -// public void setTransitionProperty(CompactMealyTransition> transition, Word property) { -// super.setTransitionProperty(transition, property); -// } -// -// @Override -// public void setTransition(int state, int input, int successor, Word property) { -// super.setTransition(state, input, successor, property); -// } -} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java index 548b49cf3c..b98e60f4fb 100644 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java @@ -1,5 +1,21 @@ +/* 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 net.automatalib.automata.transducers.impl.compact; +import net.automatalib.automata.transducers.MutableSubsequentialTransducer; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableOnwardSubsequentialTransducer.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableOnwardSubsequentialTransducer.java deleted file mode 100644 index f1ae11a72a..0000000000 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableOnwardSubsequentialTransducer.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.automatalib.automata.transducers.impl.compact; - -import net.automatalib.automata.MutableDeterministic; -import net.automatalib.words.Word; - -public interface MutableOnwardSubsequentialTransducer extends OnwardSubsequentialTransducer, - MutableDeterministic, Word> {} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableSubsequentialTransducer.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableSubsequentialTransducer.java deleted file mode 100644 index dd06159125..0000000000 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/MutableSubsequentialTransducer.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.automatalib.automata.transducers.impl.compact; - -import net.automatalib.automata.MutableDeterministic; -import net.automatalib.words.Word; - -public interface MutableSubsequentialTransducer - extends SubsequentialTransducer, MutableDeterministic, Word> {} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/OnwardSubsequentialTransducer.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/OnwardSubsequentialTransducer.java deleted file mode 100644 index c00675e2b9..0000000000 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/OnwardSubsequentialTransducer.java +++ /dev/null @@ -1,3 +0,0 @@ -package net.automatalib.automata.transducers.impl.compact; - -public interface OnwardSubsequentialTransducer extends SubsequentialTransducer {} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducer.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducer.java deleted file mode 100644 index d40d5f454c..0000000000 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/SubsequentialTransducer.java +++ /dev/null @@ -1,65 +0,0 @@ -package net.automatalib.automata.transducers.impl.compact; - -import java.util.Collection; -import java.util.Iterator; - -import net.automatalib.automata.UniversalDeterministicAutomaton; -import net.automatalib.automata.concepts.DetSuffixOutputAutomaton; -import net.automatalib.automata.graphs.TransitionEdge; -import net.automatalib.automata.graphs.TransitionEdge.Property; -import net.automatalib.automata.graphs.UniversalAutomatonGraphView; -import net.automatalib.graphs.UniversalGraph; -import net.automatalib.visualization.VisualizationHelper; -import net.automatalib.words.Word; -import net.automatalib.words.WordBuilder; - -public interface SubsequentialTransducer - extends DetSuffixOutputAutomaton>, UniversalDeterministicAutomaton, Word> { - - @Override - default Word computeStateOutput(S state, Iterable input) { - final WordBuilder result; - if (input instanceof Word) { - result = new WordBuilder<>(((Word) input).length()); - } else if (input instanceof Collection) { - result = new WordBuilder<>(((Collection) input).size()); - } else { - result = new WordBuilder<>(); - } - - final Iterator inputIter = input.iterator(); - S stateIter = state; - - while (inputIter.hasNext()) { - final I i = inputIter.next(); - final T t = getTransition(stateIter, i); - - if (t != null) { - result.append(getTransitionProperty(t)); - stateIter = getSuccessor(t); - } - } - - result.append(getStateProperty(stateIter)); - - return result.toWord(); - } - - @Override - default UniversalGraph, Word, Property>> transitionGraphView(Collection inputs) { - return new SSTGraphView<>(this, inputs); - } - - class SSTGraphView> - extends UniversalAutomatonGraphView, Word, A> { - - public SSTGraphView(A automaton, Collection inputs) { - super(automaton, inputs); - } - - @Override - public VisualizationHelper> getVisualizationHelper() { - return new SSTVisualizationHelper<>(automaton); - } - } -} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java index f82ee606e6..67cc8dc7ab 100644 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java @@ -1,3 +1,18 @@ +/* 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 net.automatalib.automata.transducers.impl.compact; import java.util.Arrays; 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 index 980bbf1fda..be3c1f46b3 100644 --- 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 @@ -22,19 +22,18 @@ import java.util.Iterator; import java.util.List; import java.util.Random; +import java.util.stream.IntStream; import com.google.common.collect.Iterators; import net.automatalib.automata.transducers.MealyMachine; -import net.automatalib.automata.transducers.impl.compact.CompactOSST; +import net.automatalib.automata.transducers.SubsequentialTransducer; +import net.automatalib.automata.transducers.SubsequentialTransducers; import net.automatalib.automata.transducers.impl.compact.CompactSST; -import net.automatalib.automata.transducers.impl.compact.OnwardSubsequentialTransducer; -import net.automatalib.automata.transducers.impl.compact.SubsequentialTransducer; -import net.automatalib.automata.transducers.impl.compact.SubsequentialTransducers; +import net.automatalib.commons.util.Pair; import net.automatalib.commons.util.collections.CollectionsUtil; import net.automatalib.util.automata.Automata; import net.automatalib.util.automata.conformance.WMethodTestsIterator; import net.automatalib.util.automata.random.RandomAutomata; -import net.automatalib.visualization.Visualization; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; import net.automatalib.words.impl.Alphabets; @@ -48,6 +47,34 @@ public class OSTIATest { private static final int SIZE = 10; private static final long SEED = 1337L; + /** + * Tests the example from Section 18.3.4 of Colin de la Higuera's book "Grammatical Inference". + */ + @Test + public void testStaticInvocation() { + // a = 0, b = 1 + List> samples = Arrays.asList(Pair.of(IntSeq.seq(0), IntSeq.seq(1)), + Pair.of(IntSeq.seq(1), IntSeq.seq(1)), + Pair.of(IntSeq.seq(0, 0), IntSeq.seq(0, 1)), + Pair.of(IntSeq.seq(0, 0, 0), IntSeq.seq(0, 0, 1)), + Pair.of(IntSeq.seq(0, 1, 0, 1), IntSeq.seq(0, 1, 0, 1))); + + State root = OSTIA.buildPtt(2, samples.iterator()); + OSTIA.ostia(root); + + Assert.assertEquals(OSTIA.run(root, IntStream.of(1).iterator()), Collections.singletonList(1)); + Assert.assertEquals(OSTIA.run(root, IntStream.of(1, 0).iterator()), Arrays.asList(1, 1)); + Assert.assertEquals(OSTIA.run(root, IntStream.of(1, 0, 0).iterator()), Arrays.asList(1, 0, 1)); + Assert.assertEquals(OSTIA.run(root, IntStream.of(1, 0, 0, 1).iterator()), Arrays.asList(1, 0, 0, 1, 0, 1)); + Assert.assertEquals(OSTIA.run(root, IntStream.of(1, 0, 0, 1, 0).iterator()), Arrays.asList(1, 0, 0, 1, 0, 1)); + Assert.assertEquals(OSTIA.run(root, IntStream.of(1, 0, 0, 1, 0, 1).iterator()), Arrays.asList(1, 0, 0, 1, 0, 1)); + + Assert.assertNull(OSTIA.run(root, IntStream.of(0, 1, 1).iterator())); + Assert.assertNull(OSTIA.run(root, IntStream.of(0, 1, 0, 0).iterator())); + Assert.assertNull(OSTIA.run(root, IntStream.of(0, 1, 0, 1, 0).iterator())); + Assert.assertNull(OSTIA.run(root, IntStream.of(0, 1, 0, 1, 1).iterator())); + } + @Test public void testMealySamples() { @@ -74,7 +101,7 @@ public void testMealySamples() { } } - @Test() + @Test public void testEquivalence() { final Random random = new Random(SEED); @@ -104,8 +131,8 @@ public void testEquivalence() { } final SubsequentialTransducer model = learner.computeModel(); - final OnwardSubsequentialTransducer osst = - SubsequentialTransducers.toOSST(sst, INPUTS, new CompactOSST<>(INPUTS)); + final SubsequentialTransducer osst = + SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS)); System.err.println(osst.size()); printStateProperties(osst); @@ -121,9 +148,9 @@ public void testEquivalence() { System.err.println("osst: " + autOut); System.err.println("model: " + modelOut); -// Visualization.visualize(sst); -// Visualization.visualize(osst.transitionGraphView(INPUTS)); -// Visualization.visualize(model.transitionGraphView(INPUTS)); + // Visualization.visualize(sst); + // Visualization.visualize(osst.transitionGraphView(INPUTS)); + // Visualization.visualize(model.transitionGraphView(INPUTS)); Assert.assertTrue(Automata.testEquivalence(osst, model, INPUTS)); } diff --git a/algorithms/passive/ostia/src/test/java/net/automatalib/automata/transducers/SubsequentialTransducersTest.java b/algorithms/passive/ostia/src/test/java/net/automatalib/automata/transducers/SubsequentialTransducersTest.java new file mode 100644 index 0000000000..1d5d091c4f --- /dev/null +++ b/algorithms/passive/ostia/src/test/java/net/automatalib/automata/transducers/SubsequentialTransducersTest.java @@ -0,0 +1,138 @@ +/* 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 net.automatalib.automata.transducers; + +import net.automatalib.automata.transducers.impl.compact.CompactSST; +import net.automatalib.util.automata.Automata; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; +import net.automatalib.words.impl.Alphabets; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class SubsequentialTransducersTest { + + private static final Alphabet INPUTS = Alphabets.characters('a', 'c'); + + @Test + public void testAlreadyOnwardModel() { + + final CompactSST sst = new CompactSST<>(INPUTS); + + final int s1 = sst.addInitialState(Word.fromCharSequence("x")); + final int s2 = sst.addState(Word.fromCharSequence("x")); + final int s3 = sst.addState(Word.fromCharSequence("x")); + + sst.setTransition(s1, (Character) 'a', s2, Word.fromCharSequence("x")); + sst.setTransition(s1, (Character) 'b', s2, Word.fromCharSequence("y")); + sst.setTransition(s1, (Character) 'c', s2, Word.fromCharSequence("z")); + + sst.setTransition(s2, (Character) 'a', s3, Word.fromCharSequence("x")); + sst.setTransition(s2, (Character) 'b', s3, Word.fromCharSequence("y")); + sst.setTransition(s2, (Character) 'c', s3, Word.fromCharSequence("z")); + + sst.setTransition(s3, (Character) 'a', s3, Word.fromCharSequence("x")); + sst.setTransition(s3, (Character) 'b', s3, Word.fromCharSequence("y")); + sst.setTransition(s3, (Character) 'c', s3, Word.fromCharSequence("z")); + + final CompactSST osst = + SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS)); + + Assert.assertTrue(Automata.testEquivalence(sst, osst, INPUTS)); + } + + @Test + public void testPartialModel() { + + final CompactSST sst = new CompactSST<>(INPUTS); + + final int s1 = sst.addInitialState(Word.fromCharSequence("x")); + final int s2 = sst.addState(Word.fromCharSequence("x")); + final int s3 = sst.addState(Word.fromCharSequence("x")); + + sst.setTransition(s1, (Character) 'a', s2, Word.fromCharSequence("x")); + sst.setTransition(s1, (Character) 'b', s2, Word.fromCharSequence("y")); + sst.setTransition(s1, (Character) 'c', s2, Word.fromCharSequence("z")); + + sst.setTransition(s2, (Character) 'a', s3, Word.fromCharSequence("x")); + sst.setTransition(s2, (Character) 'b', s3, Word.fromCharSequence("y")); + sst.setTransition(s2, (Character) 'c', s3, Word.fromCharSequence("z")); + + final CompactSST osst = + SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS)); + + final CompactSST expected = new CompactSST<>(INPUTS); + + final int e1 = expected.addInitialState(Word.fromCharSequence("x")); + final int e2 = expected.addState(Word.fromCharSequence("x")); + final int e3 = expected.addState(Word.epsilon()); + + expected.setTransition(e1, (Character) 'a', e2, Word.fromCharSequence("x")); + expected.setTransition(e1, (Character) 'b', e2, Word.fromCharSequence("y")); + expected.setTransition(e1, (Character) 'c', e2, Word.fromCharSequence("z")); + + expected.setTransition(e2, (Character) 'a', e3, Word.fromCharSequence("xx")); + expected.setTransition(e2, (Character) 'b', e3, Word.fromCharSequence("yx")); + expected.setTransition(e2, (Character) 'c', e3, Word.fromCharSequence("zx")); + + Assert.assertTrue(Automata.testEquivalence(expected, osst, INPUTS)); + } + + @Test + public void testMultiplePropagations() { + + final CompactSST sst = new CompactSST<>(INPUTS); + + final int s1 = sst.addInitialState(Word.fromCharSequence("x")); + final int s2 = sst.addState(Word.fromCharSequence("xx")); + final int s3 = sst.addState(Word.fromCharSequence("x")); + + sst.setTransition(s1, (Character) 'a', s2, Word.fromCharSequence("x")); + sst.setTransition(s1, (Character) 'b', s2, Word.fromCharSequence("y")); + sst.setTransition(s1, (Character) 'c', s2, Word.fromCharSequence("z")); + + sst.setTransition(s2, (Character) 'a', s3, Word.fromCharSequence("x")); + sst.setTransition(s2, (Character) 'b', s3, Word.fromCharSequence("x")); + sst.setTransition(s2, (Character) 'c', s3, Word.fromCharSequence("x")); + + sst.setTransition(s3, (Character) 'a', s3, Word.fromCharSequence("xx")); + sst.setTransition(s3, (Character) 'b', s3, Word.fromCharSequence("xy")); + sst.setTransition(s3, (Character) 'c', s3, Word.fromCharSequence("xz")); + + final CompactSST osst = + SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS)); + + final CompactSST expected = new CompactSST<>(INPUTS); + + final int e1 = expected.addInitialState(Word.fromCharSequence("x")); + final int e2 = expected.addState(Word.epsilon()); + final int e3 = expected.addState(Word.epsilon()); + + expected.setTransition(e1, (Character) 'a', e2, Word.fromCharSequence("xxx")); + expected.setTransition(e1, (Character) 'b', e2, Word.fromCharSequence("yxx")); + expected.setTransition(e1, (Character) 'c', e2, Word.fromCharSequence("zxx")); + + expected.setTransition(e2, (Character) 'a', e3, Word.epsilon()); + expected.setTransition(e2, (Character) 'b', e3, Word.epsilon()); + expected.setTransition(e2, (Character) 'c', e3, Word.epsilon()); + + expected.setTransition(e3, (Character) 'a', e3, Word.fromCharSequence("xx")); + expected.setTransition(e3, (Character) 'b', e3, Word.fromCharSequence("yx")); + expected.setTransition(e3, (Character) 'c', e3, Word.fromCharSequence("zx")); + + Assert.assertTrue(Automata.testEquivalence(expected, osst, INPUTS)); + } +} diff --git a/pom.xml b/pom.xml index 845cbab949..d7c4222f40 100644 --- a/pom.xml +++ b/pom.xml @@ -99,6 +99,12 @@ limitations under the License. Developer + + Aleksander Mendoza-Drosik + + Developer + + From b3e736602763ee1e590a6f99a52e364c37d17734 Mon Sep 17 00:00:00 2001 From: Alagris Date: Sun, 20 Dec 2020 12:46:47 +0100 Subject: [PATCH 10/21] OSTIA characteristic sample --- .../transducers/SubsequentialTransducers.java | 1 + .../learnlib/algorithms/ostia/OSTIATest.java | 116 +++++++++++------- 2 files changed, 70 insertions(+), 47 deletions(-) diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java index 55d5c08968..656ab6988b 100644 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java @@ -46,6 +46,7 @@ public static lcp = computeLCP(out, inputs, s); if (!lcp.isEmpty()) { 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 index be3c1f46b3..1aabd7750a 100644 --- 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 @@ -44,7 +44,6 @@ 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 int SIZE = 10; private static final long SEED = 1337L; /** @@ -78,6 +77,7 @@ public void testStaticInvocation() { @Test public void testMealySamples() { + final int SIZE = 10; final MealyMachine automaton = RandomAutomata.randomMealy(new Random(SEED), SIZE, INPUTS, OUTPUTS); @@ -105,53 +105,75 @@ public void testMealySamples() { public void testEquivalence() { final Random random = new Random(SEED); - final CompactSST sst = new CompactSST<>(INPUTS); - - final List> words = new ArrayList<>(); - for (List t : CollectionsUtil.allTuples(OUTPUTS, 1, 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 OSTIA learner = new OSTIA<>(INPUTS, Alphabets.fromCollection(OUTPUTS)); - - final Iterator> testIterator = new WMethodTestsIterator<>(sst, INPUTS, 0); - - while (testIterator.hasNext()) { - final Word input = testIterator.next(); - final Word out = sst.computeOutput(input); - System.out.println(input + "|" + out); - learner.addSample(input, out); + for (int size = 10; size < 20; size++) { + System.out.println(size); + final CompactSST sst = new CompactSST<>(INPUTS); + + final List> words = new ArrayList<>(); + for (List t : CollectionsUtil.allTuples(OUTPUTS, 1, 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 OSTIA learner = new OSTIA<>(INPUTS, Alphabets.fromCollection(OUTPUTS)); + + final Iterator> testIterator = new WMethodTestsIterator<>(sst, INPUTS, 0); + + learner.addSample(Word.epsilon(), sst.computeOutput(Word.epsilon())); + while (testIterator.hasNext()) { + final Word input = testIterator.next(); + final Word out = sst.computeOutput(input); + System.out.println(input + "|" + out); + learner.addSample(input, out); + for (char lookahead1 : INPUTS) { + final Word inputLookahead1 = input.append(lookahead1); + final Word outLookahead1 = sst.computeOutput(inputLookahead1); + learner.addSample(inputLookahead1, outLookahead1); + for (char lookahead2 : INPUTS) { + final Word inputLookahead2 = inputLookahead1.append(lookahead2); + final Word outLookahead2 = sst.computeOutput(inputLookahead2); + learner.addSample(inputLookahead2, outLookahead2); + } + } + } + + final SubsequentialTransducer model = learner.computeModel(); + final SubsequentialTransducer osst = + SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS)); + +// System.err.println(osst.size()); +// printStateProperties(osst); +// System.err.println("---"); +// System.err.println(model.size()); +// printStateProperties(model); + + final boolean areEquivalent = Automata.testEquivalence(osst, model, INPUTS); + if (!areEquivalent) { + System.err.println(osst.size()); + printStateProperties(osst); + System.err.println("---"); + System.err.println(model.size()); + printStateProperties(model); + + Word sepWord = Automata.findSeparatingWord(osst, model, INPUTS); + Word autOut = osst.computeOutput(sepWord); + Word modelOut = model.computeOutput(sepWord); + + System.err.println("sepWord: " + sepWord); + System.err.println("osst: " + autOut); + System.err.println("model: " + modelOut); + // Visualization.visualize(sst); + // Visualization.visualize(osst.transitionGraphView(INPUTS)); + // Visualization.visualize(model.transitionGraphView(INPUTS)); + } + Assert.assertTrue(areEquivalent); } - - final SubsequentialTransducer model = learner.computeModel(); - final SubsequentialTransducer osst = - SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS)); - - System.err.println(osst.size()); - printStateProperties(osst); - System.err.println("---"); - System.err.println(model.size()); - printStateProperties(model); - - Word sepWord = Automata.findSeparatingWord(osst, model, INPUTS); - Word autOut = osst.computeOutput(sepWord); - Word modelOut = model.computeOutput(sepWord); - - System.err.println("sepWord: " + sepWord); - System.err.println("osst: " + autOut); - System.err.println("model: " + modelOut); - - // Visualization.visualize(sst); - // Visualization.visualize(osst.transitionGraphView(INPUTS)); - // Visualization.visualize(model.transitionGraphView(INPUTS)); - Assert.assertTrue(Automata.testEquivalence(osst, model, INPUTS)); } private static void printStateProperties(SubsequentialTransducer sst) { From 061aaa60e245a8edbdcee8d02b17149856c0cb09 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Sun, 20 Dec 2020 13:05:14 +0100 Subject: [PATCH 11/21] some more refactorings * documentation * move some utility to AutomataLib * streamlining interfaces --- .../de/learnlib/algorithms/ostia/Blue.java | 3 + .../de/learnlib/algorithms/ostia/Edge.java | 3 + .../learnlib/algorithms/ostia/IntQueue.java | 4 ++ .../algorithms/ostia/OSSTWrapper.java | 17 +----- .../de/learnlib/algorithms/ostia/OSTIA.java | 13 ++-- .../de/learnlib/algorithms/ostia/Out.java | 3 + .../de/learnlib/algorithms/ostia/State.java | 3 + .../transducers/SubsequentialTransducer.java | 2 +- .../transducers/SubsequentialTransducers.java | 24 +++++++- .../impl/compact/UniversalCompactDet.java | 13 ++++ .../commons/smartcollections}/IntSeq.java | 60 +++++++++++++++++-- .../learnlib/algorithms/ostia/OSTIATest.java | 34 +++++------ 12 files changed, 137 insertions(+), 42 deletions(-) rename algorithms/passive/ostia/src/main/java/{de/learnlib/algorithms/ostia => net/automatalib/commons/smartcollections}/IntSeq.java (52%) 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 index c34693c877..24299ad9c7 100644 --- 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 @@ -15,6 +15,9 @@ */ package de.learnlib.algorithms.ostia; +/** + * @author Aleksander Mendoza-Drosik + */ class Blue { final State parent; 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 index 2c7b4e5e28..d054ffc5b8 100644 --- 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 @@ -15,6 +15,9 @@ */ package de.learnlib.algorithms.ostia; +/** + * @author Aleksander Mendoza-Drosik + */ class Edge { IntQueue out; 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 index a0f5fe3b0c..3a45d75211 100644 --- 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 @@ -19,8 +19,12 @@ import java.util.Set; import java.util.StringJoiner; +import net.automatalib.commons.smartcollections.IntSeq; import org.checkerframework.checker.nullness.qual.Nullable; +/** + * @author Aleksander Mendoza-Drosik + */ class IntQueue { int value; 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 index ac9f3425cd..8507f2a651 100644 --- 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 @@ -18,11 +18,8 @@ import java.util.ArrayDeque; import java.util.Collection; import java.util.HashSet; -import java.util.Iterator; -import java.util.List; import java.util.Queue; import java.util.Set; -import java.util.stream.StreamSupport; import net.automatalib.automata.transducers.SubsequentialTransducer; import net.automatalib.words.Alphabet; @@ -30,6 +27,9 @@ import net.automatalib.words.WordBuilder; import org.checkerframework.checker.nullness.qual.Nullable; +/** + * @author frohme + */ class OSSTWrapper implements SubsequentialTransducer { private final State root; @@ -42,17 +42,6 @@ class OSSTWrapper implements SubsequentialTransducer { this.outputAlphabet = outputAlphabet; } - @Override - public Word computeOutput(Iterable input) { - final Iterator transformedInput = - StreamSupport.stream(input.spliterator(), false).mapToInt(inputAlphabet).iterator(); - final List output = OSTIA.run(root, transformedInput); - - return output != null ? - output.stream().map(outputAlphabet::getSymbol).collect(Word.collector()) : - Word.epsilon(); - } - @Override public Collection getStates() { final Set cache = new HashSet<>(); 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 index 615d7f08cc..3c21593460 100644 --- 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 @@ -27,11 +27,16 @@ 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.Word; import org.checkerframework.checker.nullness.qual.Nullable; +/** + * @author Aleksander Mendoza-Drosik + * @author frohme + */ public class OSTIA implements PassiveLearningAlgorithm, I, Word> { private final Alphabet inputAlphabet; @@ -227,11 +232,11 @@ private static boolean ostiaFold(State red, return true; } - public static @Nullable List run(State init, Iterator input) { + public static @Nullable IntSeq run(State init, IntSeq input) { final List output = new ArrayList<>(); State iter = init; - while (input.hasNext()) { - final Edge edge = iter.transitions[input.next()]; + for (int i = 0; i < input.size(); i++) { + final Edge edge = iter.transitions[input.get(i)]; if (edge == null) { return null; } @@ -250,7 +255,7 @@ private static boolean ostiaFold(State red, output.add(q.value); q = q.next; } - return output; + return IntSeq.of(output); } } 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 index 539dc2c1ef..d0ccfdd210 100644 --- 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 @@ -15,6 +15,9 @@ */ package de.learnlib.algorithms.ostia; +/** + * @author Aleksander Mendoza-Drosik + */ class Out { IntQueue 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 index ae9f1c830c..cd4dcdff85 100644 --- 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 @@ -15,6 +15,9 @@ */ package de.learnlib.algorithms.ostia; +/** + * @author Aleksander Mendoza-Drosik + */ class State { Out out; diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducer.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducer.java index f3c7b87de7..92b91f8f61 100644 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducer.java +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducer.java @@ -33,7 +33,7 @@ /** * A subsequential transducer (or SST) is an {@link DeterministicOutputTS} whose state and transition properties are * output-{@link Word words}. Upon parsing a sequence of input symbols, each transition emits a {@link Word sequence} of - * output symbols. + * output symbols. After all inputs have been parsed, the output of the reached state will be emitted as well. *

* Implementation detail: * There exist definitions of SSTs that associate each state with an additional notion of 'acceptance' in order to diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java index 656ab6988b..caed2e8555 100644 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java @@ -28,17 +28,37 @@ import net.automatalib.util.automata.copy.AutomatonLowLevelCopy; import net.automatalib.words.Word; +/** + * Utility methods of {@link SubsequentialTransducer}s. + * + * @author frohme + */ public final class SubsequentialTransducers { private SubsequentialTransducers() { // prevent initialization } + /** + * Constructs a new onward subsequential transducer for a given {@link SubsequentialTransducer SST}. In an + * onward SST, for each state, the longest common prefix over the state output and the outputs of all outgoing + * transitions of a state has been pushed forward to the transition outputs of the incoming transitions. + * + * @param sst + * the original SST + * @param inputs + * the alphabet symbols to consider for this transformation + * @param out + * the target automaton to write the onward form to + * + * @return {@code out}, for convenience + */ public static > A toOnwardSST( SubsequentialTransducer sst, Collection inputs, A out) { + assert out.size() == 0; AutomatonLowLevelCopy.copy(AutomatonCopyMethod.STATE_BY_STATE, sst, inputs, out); final Mapping>> incomingTransitions = getIncomingTransitions(out, inputs); @@ -106,8 +126,8 @@ private static Mapping>> getIncomingTransitions(Subs } private static Word computeLCP(SubsequentialTransducer sst, - Collection inputs, - S s) { + Collection inputs, + S s) { Word lcp = sst.getStateProperty(s); diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java index 67cc8dc7ab..a88fc1cba0 100644 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java @@ -22,6 +22,19 @@ import net.automatalib.words.Alphabet; import org.checkerframework.checker.nullness.qual.Nullable; +/** + * A default implementation for {@link AbstractCompactDeterministic} that uses {@link CompactMealyTransition} as + * transition type and supports various types of state and transition properites. + * + * @param + * input symbol type + * @param + * state property type + * @param + * transition property type + * + * @author frohme + */ public class UniversalCompactDet extends AbstractCompactDeterministic, SP, TP> { diff --git a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java b/algorithms/passive/ostia/src/main/java/net/automatalib/commons/smartcollections/IntSeq.java similarity index 52% rename from algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java rename to algorithms/passive/ostia/src/main/java/net/automatalib/commons/smartcollections/IntSeq.java index d13524acac..c913fc5861 100644 --- a/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/IntSeq.java +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/commons/smartcollections/IntSeq.java @@ -13,19 +13,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.learnlib.algorithms.ostia; +package net.automatalib.commons.smartcollections; import java.util.Arrays; +import java.util.List; +import java.util.PrimitiveIterator.OfInt; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; -interface IntSeq { +/** + * An {@link IntSeq} is an abstract view on a finite, random-access data-structure for primitive integer values. It + * allows for a unified view on integer arrays, {@link List lists} of integers, {@link Word words} with an accompanying + * {@link Alphabet#getSymbolIndex(Object) alphabet index function}, etc. + * + * @author Aleksander Mendoza-Drosik + * @author frohme + */ +public interface IntSeq extends Iterable { int size(); int get(int index); + @Override + default OfInt iterator() { + return new OfInt() { + + int curr; + + { + this.curr = 0; + } + + @Override + public int nextInt() { + return get(curr++); + } + + @Override + public boolean hasNext() { + return curr < size(); + } + }; + } + static IntSeq of(Word word, Alphabet alphabet) { return new IntSeq() { @@ -36,7 +68,7 @@ public int size() { @Override public int get(int index) { - return alphabet.applyAsInt(word.getSymbol(index)); + return alphabet.getSymbolIndex(word.getSymbol(index)); } @Override @@ -46,7 +78,7 @@ public String toString() { }; } - static IntSeq seq(int... ints) { + static IntSeq of(int... ints) { return new IntSeq() { @Override @@ -65,4 +97,24 @@ public String toString() { } }; } + + static IntSeq of(List ints) { + return new IntSeq() { + + @Override + public int size() { + return ints.size(); + } + + @Override + public int get(int index) { + return ints.get(index); + } + + @Override + public String toString() { + return ints.toString(); + } + }; + } } 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 index 1aabd7750a..884adda712 100644 --- 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 @@ -22,13 +22,13 @@ import java.util.Iterator; import java.util.List; import java.util.Random; -import java.util.stream.IntStream; import com.google.common.collect.Iterators; import net.automatalib.automata.transducers.MealyMachine; import net.automatalib.automata.transducers.SubsequentialTransducer; import net.automatalib.automata.transducers.SubsequentialTransducers; import net.automatalib.automata.transducers.impl.compact.CompactSST; +import net.automatalib.commons.smartcollections.IntSeq; import net.automatalib.commons.util.Pair; import net.automatalib.commons.util.collections.CollectionsUtil; import net.automatalib.util.automata.Automata; @@ -52,26 +52,26 @@ public class OSTIATest { @Test public void testStaticInvocation() { // a = 0, b = 1 - List> samples = Arrays.asList(Pair.of(IntSeq.seq(0), IntSeq.seq(1)), - Pair.of(IntSeq.seq(1), IntSeq.seq(1)), - Pair.of(IntSeq.seq(0, 0), IntSeq.seq(0, 1)), - Pair.of(IntSeq.seq(0, 0, 0), IntSeq.seq(0, 0, 1)), - Pair.of(IntSeq.seq(0, 1, 0, 1), IntSeq.seq(0, 1, 0, 1))); + List> samples = 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))); State root = OSTIA.buildPtt(2, samples.iterator()); OSTIA.ostia(root); - Assert.assertEquals(OSTIA.run(root, IntStream.of(1).iterator()), Collections.singletonList(1)); - Assert.assertEquals(OSTIA.run(root, IntStream.of(1, 0).iterator()), Arrays.asList(1, 1)); - Assert.assertEquals(OSTIA.run(root, IntStream.of(1, 0, 0).iterator()), Arrays.asList(1, 0, 1)); - Assert.assertEquals(OSTIA.run(root, IntStream.of(1, 0, 0, 1).iterator()), Arrays.asList(1, 0, 0, 1, 0, 1)); - Assert.assertEquals(OSTIA.run(root, IntStream.of(1, 0, 0, 1, 0).iterator()), Arrays.asList(1, 0, 0, 1, 0, 1)); - Assert.assertEquals(OSTIA.run(root, IntStream.of(1, 0, 0, 1, 0, 1).iterator()), Arrays.asList(1, 0, 0, 1, 0, 1)); - - Assert.assertNull(OSTIA.run(root, IntStream.of(0, 1, 1).iterator())); - Assert.assertNull(OSTIA.run(root, IntStream.of(0, 1, 0, 0).iterator())); - Assert.assertNull(OSTIA.run(root, IntStream.of(0, 1, 0, 1, 0).iterator())); - Assert.assertNull(OSTIA.run(root, IntStream.of(0, 1, 0, 1, 1).iterator())); + 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.assertNull(OSTIA.run(root, IntSeq.of(0, 1, 1))); + Assert.assertNull(OSTIA.run(root, IntSeq.of(0, 1, 0, 0))); + Assert.assertNull(OSTIA.run(root, IntSeq.of(0, 1, 0, 1, 0))); + Assert.assertNull(OSTIA.run(root, IntSeq.of(0, 1, 0, 1, 1))); } @Test From 1e5addfdb4b153f30bc46b2d95638d337c316f0b Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Sun, 20 Dec 2020 13:43:32 +0100 Subject: [PATCH 12/21] attempt at handling initial states in onward transformation --- .../transducers/SubsequentialTransducers.java | 31 ++++++++++++++----- .../learnlib/algorithms/ostia/OSTIATest.java | 5 +-- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java index caed2e8555..af461cb1a9 100644 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.Deque; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import net.automatalib.commons.util.Pair; @@ -66,7 +67,6 @@ public static lcp = computeLCP(out, inputs, s); if (!lcp.isEmpty()) { @@ -85,15 +85,30 @@ public static incoming : incomingTransitions.get(s)) { - final T2 t = out.getTransition(incoming.getFirst(), incoming.getSecond()); + final Set> incoming = incomingTransitions.get(s); - final Word oldTransitionProperty = out.getTransitionProperty(t); - final Word newTransitionProperty = oldTransitionProperty.concat(lcp); + if (incoming.isEmpty()) { + // we want to push-back an lcp but have no incoming transitions -> create new state. + final S2 newState = out.addState(Word.epsilon()); + for (I i : inputs) { + out.setTransition(newState, i, s, lcp); + } + + if (Objects.equals(s, out.getInitialState())) { + out.setInitial(s, false); + out.setInitial(newState, true); + } + } else { + for (Pair trans : incoming) { + final T2 t = out.getTransition(trans.getFirst(), trans.getSecond()); - out.setTransitionProperty(t, newTransitionProperty); - if (!queue.contains(incoming.getFirst())) { //this if can improve performance a little - queue.add(incoming.getFirst()); + final Word oldTransitionProperty = out.getTransitionProperty(t); + final Word newTransitionProperty = oldTransitionProperty.concat(lcp); + + out.setTransitionProperty(t, newTransitionProperty); + if (!queue.contains(trans.getFirst())) { //this if can improve performance a little + queue.add(trans.getFirst()); + } } } } 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 index 884adda712..a1c4934362 100644 --- 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 @@ -106,7 +106,7 @@ public void testEquivalence() { final Random random = new Random(SEED); for (int size = 10; size < 20; size++) { - System.out.println(size); + System.err.println(size); final CompactSST sst = new CompactSST<>(INPUTS); final List> words = new ArrayList<>(); @@ -129,7 +129,7 @@ public void testEquivalence() { while (testIterator.hasNext()) { final Word input = testIterator.next(); final Word out = sst.computeOutput(input); - System.out.println(input + "|" + out); + System.err.println(input + "|" + out); learner.addSample(input, out); for (char lookahead1 : INPUTS) { final Word inputLookahead1 = input.append(lookahead1); @@ -155,6 +155,7 @@ public void testEquivalence() { final boolean areEquivalent = Automata.testEquivalence(osst, model, INPUTS); if (!areEquivalent) { + System.err.println("####"); System.err.println(osst.size()); printStateProperties(osst); System.err.println("---"); From 0999e92c4ff610ac5df523860f417dff1af66ee6 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Sun, 20 Dec 2020 21:14:00 +0100 Subject: [PATCH 13/21] fix onward computation --- algorithms/passive/ostia/pom.xml | 6 - .../de/learnlib/algorithms/ostia/State.java | 4 + .../transducers/SubsequentialTransducers.java | 109 +++++++++++---- .../learnlib/algorithms/ostia/OSTIATest.java | 132 ++++++------------ .../SubsequentialTransducersTest.java | 63 ++++++++- 5 files changed, 193 insertions(+), 121 deletions(-) diff --git a/algorithms/passive/ostia/pom.xml b/algorithms/passive/ostia/pom.xml index 354cd36cb8..edb183c8eb 100644 --- a/algorithms/passive/ostia/pom.xml +++ b/algorithms/passive/ostia/pom.xml @@ -69,12 +69,6 @@ limitations under the License. learnlib-learner-it-support - - net.automatalib - automata-dot-visualizer - test - - org.testng testng 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 index cd4dcdff85..123dee9407 100644 --- 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 @@ -67,4 +67,8 @@ void prependButIgnoreMissingStateOutput(IntQueue prefix) { } } + @Override + public String toString() { + return String.valueOf(out); + } } diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java index af461cb1a9..3005c9f0a8 100644 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java +++ b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java @@ -25,6 +25,7 @@ import net.automatalib.commons.util.Pair; import net.automatalib.commons.util.mappings.Mapping; import net.automatalib.commons.util.mappings.MutableMapping; +import net.automatalib.util.automata.Automata; import net.automatalib.util.automata.copy.AutomatonCopyMethod; import net.automatalib.util.automata.copy.AutomatonLowLevelCopy; import net.automatalib.words.Word; @@ -42,8 +43,9 @@ private SubsequentialTransducers() { /** * Constructs a new onward subsequential transducer for a given {@link SubsequentialTransducer SST}. In an - * onward SST, for each state, the longest common prefix over the state output and the outputs of all outgoing - * transitions of a state has been pushed forward to the transition outputs of the incoming transitions. + * onward SST, for each state except the initial state, the longest common prefix over the state output and the + * outputs of all outgoing transitions of a state is {@link Word#epsilon() epsilon}. This can be achieved by pushing + * back the longest common prefix to the transition outputs of the incoming transitions of each state. * * @param sst * the original SST @@ -58,6 +60,31 @@ public static sst, Collection inputs, A out) { + return toOnwardSST(sst, inputs, out, true); + } + + /** + * Constructs a new onward subsequential transducer for a given {@link SubsequentialTransducer SST}. In an + * onward SST, for each state except the initial state, the longest common prefix over the state output and the + * outputs of all outgoing transitions of a state is {@link Word#epsilon() epsilon}. This can be achieved by pushing + * back the longest common prefix to the transition outputs of the incoming transitions of each state. + * + * @param sst + * the original SST + * @param inputs + * the alphabet symbols to consider for this transformation + * @param out + * the target automaton to write the onward form to + * @param minimize + * a flag indicating whether the final result should be minimized + * + * @return {@code out}, for convenience + */ + public static > A toOnwardSST( + SubsequentialTransducer sst, + Collection inputs, + A out, + boolean minimize) { assert out.size() == 0; AutomatonLowLevelCopy.copy(AutomatonCopyMethod.STATE_BY_STATE, sst, inputs, out); @@ -65,8 +92,28 @@ public static >> incomingTransitions = getIncomingTransitions(out, inputs); final Deque queue = new ArrayDeque<>(out.getStates()); + final S2 oldInit = out.getInitialState(); + + if (!incomingTransitions.get(oldInit).isEmpty()) { + // copy initial state to prevent push-back of prefixes for the initial state. + out.setInitial(oldInit, false); + final S2 newInit = out.addInitialState(out.getStateProperty(oldInit)); + + for (I i : inputs) { + T2 oldT = out.getTransition(oldInit, i); + if (oldT != null) { + final S2 succ = out.getSuccessor(oldT); + out.addTransition(newInit, i, succ, out.getTransitionProperty(oldT)); + incomingTransitions.get(succ).add(Pair.of(newInit, i)); + } + } + } + while (!queue.isEmpty()) { final S2 s = queue.pop(); + if (Objects.equals(s, out.getInitialState())) { + continue; + } final Word lcp = computeLCP(out, inputs, s); if (!lcp.isEmpty()) { @@ -85,36 +132,50 @@ public static > incoming = incomingTransitions.get(s); + for (Pair trans : incomingTransitions.get(s)) { + final T2 t = out.getTransition(trans.getFirst(), trans.getSecond()); - if (incoming.isEmpty()) { - // we want to push-back an lcp but have no incoming transitions -> create new state. - final S2 newState = out.addState(Word.epsilon()); - for (I i : inputs) { - out.setTransition(newState, i, s, lcp); - } + final Word oldTransitionProperty = out.getTransitionProperty(t); + final Word newTransitionProperty = oldTransitionProperty.concat(lcp); - if (Objects.equals(s, out.getInitialState())) { - out.setInitial(s, false); - out.setInitial(newState, true); + out.setTransitionProperty(t, newTransitionProperty); + if (!queue.contains(trans.getFirst())) { + queue.add(trans.getFirst()); } - } else { - for (Pair trans : incoming) { - final T2 t = out.getTransition(trans.getFirst(), trans.getSecond()); + } + } + } - final Word oldTransitionProperty = out.getTransitionProperty(t); - final Word newTransitionProperty = oldTransitionProperty.concat(lcp); + return minimize ? Automata.invasiveMinimize(out, inputs) : out; + } - out.setTransitionProperty(t, newTransitionProperty); - if (!queue.contains(trans.getFirst())) { //this if can improve performance a little - queue.add(trans.getFirst()); - } - } - } + /** + * Checks whether a given {@link SubsequentialTransducer} is onward, i.e. if for each state (sans initial + * state) the longest common prefix over its state property and the transition properties of its outgoing edges + * equals {@link Word#epsilon() epsilon}. + * + * @param sst + * the SST to check + * @param inputs + * the input symbols to consider for this check + * + * @return {@code true} if {@code sst} is onward, {@code false} otherwise + */ + public static boolean isOnwardSST(SubsequentialTransducer sst, + Collection inputs) { + + for (S s : sst) { + if (Objects.equals(s, sst.getInitialState())) { + continue; + } + + final Word lcp = computeLCP(sst, inputs, s); + if (!lcp.isEmpty()) { + return false; } } - return out; + return true; } private static Mapping>> getIncomingTransitions(SubsequentialTransducer sst, 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 index a1c4934362..df5410619f 100644 --- 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 @@ -38,6 +38,7 @@ import net.automatalib.words.Word; import net.automatalib.words.impl.Alphabets; import org.testng.Assert; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class OSTIATest { @@ -46,19 +47,24 @@ public class OSTIATest { 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}}; + } + /** * Tests the example from Section 18.3.4 of Colin de la Higuera's book "Grammatical Inference". */ @Test public void testStaticInvocation() { // a = 0, b = 1 - List> samples = 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))); + final List> samples = 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))); - State root = OSTIA.buildPtt(2, samples.iterator()); + final State root = OSTIA.buildPtt(2, samples.iterator()); OSTIA.ostia(root); Assert.assertEquals(OSTIA.run(root, IntSeq.of(1)), IntSeq.of(1)); @@ -68,18 +74,15 @@ public void testStaticInvocation() { 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.assertNull(OSTIA.run(root, IntSeq.of(0, 1, 1))); - Assert.assertNull(OSTIA.run(root, IntSeq.of(0, 1, 0, 0))); Assert.assertNull(OSTIA.run(root, IntSeq.of(0, 1, 0, 1, 0))); Assert.assertNull(OSTIA.run(root, IntSeq.of(0, 1, 0, 1, 1))); } - @Test - public void testMealySamples() { + @Test(dataProvider = "sizes") + public void testMealySamples(int size) { - final int SIZE = 10; final MealyMachine automaton = - RandomAutomata.randomMealy(new Random(SEED), SIZE, INPUTS, OUTPUTS); + RandomAutomata.randomMealy(new Random(SEED), size, INPUTS, OUTPUTS); final OSTIA learner = new OSTIA<>(INPUTS, Alphabets.fromCollection(OUTPUTS)); @@ -101,86 +104,39 @@ public void testMealySamples() { } } - @Test - public void testEquivalence() { + @Test(dataProvider = "sizes") + public void testEquivalence(int size) { final Random random = new Random(SEED); - for (int size = 10; size < 20; size++) { - System.err.println(size); - final CompactSST sst = new CompactSST<>(INPUTS); - - final List> words = new ArrayList<>(); - for (List t : CollectionsUtil.allTuples(OUTPUTS, 1, 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 OSTIA learner = new OSTIA<>(INPUTS, Alphabets.fromCollection(OUTPUTS)); - - final Iterator> testIterator = new WMethodTestsIterator<>(sst, INPUTS, 0); - - learner.addSample(Word.epsilon(), sst.computeOutput(Word.epsilon())); - while (testIterator.hasNext()) { - final Word input = testIterator.next(); - final Word out = sst.computeOutput(input); - System.err.println(input + "|" + out); - learner.addSample(input, out); - for (char lookahead1 : INPUTS) { - final Word inputLookahead1 = input.append(lookahead1); - final Word outLookahead1 = sst.computeOutput(inputLookahead1); - learner.addSample(inputLookahead1, outLookahead1); - for (char lookahead2 : INPUTS) { - final Word inputLookahead2 = inputLookahead1.append(lookahead2); - final Word outLookahead2 = sst.computeOutput(inputLookahead2); - learner.addSample(inputLookahead2, outLookahead2); - } - } - } - - final SubsequentialTransducer model = learner.computeModel(); - final SubsequentialTransducer osst = - SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS)); - -// System.err.println(osst.size()); -// printStateProperties(osst); -// System.err.println("---"); -// System.err.println(model.size()); -// printStateProperties(model); - - final boolean areEquivalent = Automata.testEquivalence(osst, model, INPUTS); - if (!areEquivalent) { - System.err.println("####"); - System.err.println(osst.size()); - printStateProperties(osst); - System.err.println("---"); - System.err.println(model.size()); - printStateProperties(model); - - Word sepWord = Automata.findSeparatingWord(osst, model, INPUTS); - Word autOut = osst.computeOutput(sepWord); - Word modelOut = model.computeOutput(sepWord); - - System.err.println("sepWord: " + sepWord); - System.err.println("osst: " + autOut); - System.err.println("model: " + modelOut); - // Visualization.visualize(sst); - // Visualization.visualize(osst.transitionGraphView(INPUTS)); - // Visualization.visualize(model.transitionGraphView(INPUTS)); - } - Assert.assertTrue(areEquivalent); + final CompactSST sst = new CompactSST<>(INPUTS); + + final List> words = new ArrayList<>(); + for (List t : CollectionsUtil.allTuples(OUTPUTS, 0, 3)) { + words.add(Word.fromList(t)); } - } - private static void printStateProperties(SubsequentialTransducer sst) { - for (S s : sst) { - System.err.println(sst.getStateProperty(s)); + 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, Alphabets.fromCollection(OUTPUTS)); + + 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/java/net/automatalib/automata/transducers/SubsequentialTransducersTest.java b/algorithms/passive/ostia/src/test/java/net/automatalib/automata/transducers/SubsequentialTransducersTest.java index 1d5d091c4f..9c3860342b 100644 --- a/algorithms/passive/ostia/src/test/java/net/automatalib/automata/transducers/SubsequentialTransducersTest.java +++ b/algorithms/passive/ostia/src/test/java/net/automatalib/automata/transducers/SubsequentialTransducersTest.java @@ -49,8 +49,9 @@ public void testAlreadyOnwardModel() { sst.setTransition(s3, (Character) 'c', s3, Word.fromCharSequence("z")); final CompactSST osst = - SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS)); + SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS), false); + Assert.assertTrue(SubsequentialTransducers.isOnwardSST(osst, INPUTS)); Assert.assertTrue(Automata.testEquivalence(sst, osst, INPUTS)); } @@ -72,7 +73,9 @@ public void testPartialModel() { sst.setTransition(s2, (Character) 'c', s3, Word.fromCharSequence("z")); final CompactSST osst = - SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS)); + SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS), false); + + Assert.assertTrue(SubsequentialTransducers.isOnwardSST(osst, INPUTS)); final CompactSST expected = new CompactSST<>(INPUTS); @@ -113,7 +116,9 @@ public void testMultiplePropagations() { sst.setTransition(s3, (Character) 'c', s3, Word.fromCharSequence("xz")); final CompactSST osst = - SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS)); + SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS), false); + + Assert.assertTrue(SubsequentialTransducers.isOnwardSST(osst, INPUTS)); final CompactSST expected = new CompactSST<>(INPUTS); @@ -135,4 +140,56 @@ public void testMultiplePropagations() { Assert.assertTrue(Automata.testEquivalence(expected, osst, INPUTS)); } + + @Test + public void testLoopOnInitial() { + + final CompactSST sst = new CompactSST<>(INPUTS); + + final int s1 = sst.addInitialState(Word.fromCharSequence("x")); + final int s2 = sst.addState(Word.fromCharSequence("x")); + final int s3 = sst.addState(Word.fromCharSequence("x")); + + sst.setTransition(s1, (Character) 'a', s2, Word.epsilon()); + sst.setTransition(s1, (Character) 'b', s2, Word.epsilon()); + sst.setTransition(s1, (Character) 'c', s2, Word.epsilon()); + + sst.setTransition(s2, (Character) 'a', s3, Word.fromCharSequence("xx")); + sst.setTransition(s2, (Character) 'b', s3, Word.fromCharSequence("xy")); + sst.setTransition(s2, (Character) 'c', s3, Word.fromCharSequence("xz")); + + sst.setTransition(s3, (Character) 'a', s1, Word.fromCharSequence("xx")); + sst.setTransition(s3, (Character) 'b', s1, Word.fromCharSequence("xy")); + sst.setTransition(s3, (Character) 'c', s1, Word.fromCharSequence("xz")); + + final CompactSST osst = + SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS), false); + + Assert.assertTrue(SubsequentialTransducers.isOnwardSST(osst, INPUTS)); + + final CompactSST expected = new CompactSST<>(INPUTS); + + final int e1 = expected.addInitialState(Word.fromCharSequence("x")); + final int e2 = expected.addState(Word.epsilon()); + final int e3 = expected.addState(Word.epsilon()); + final int e4 = expected.addState(Word.epsilon()); + + expected.setTransition(e1, (Character) 'a', e2, Word.fromCharSequence("x")); + expected.setTransition(e1, (Character) 'b', e2, Word.fromCharSequence("x")); + expected.setTransition(e1, (Character) 'c', e2, Word.fromCharSequence("x")); + + expected.setTransition(e2, (Character) 'a', e3, Word.fromCharSequence("xx")); + expected.setTransition(e2, (Character) 'b', e3, Word.fromCharSequence("yx")); + expected.setTransition(e2, (Character) 'c', e3, Word.fromCharSequence("zx")); + + expected.setTransition(e3, (Character) 'a', e4, Word.fromCharSequence("xx")); + expected.setTransition(e3, (Character) 'b', e4, Word.fromCharSequence("yx")); + expected.setTransition(e3, (Character) 'c', e4, Word.fromCharSequence("zx")); + + expected.setTransition(e4, (Character) 'a', e2, Word.epsilon()); + expected.setTransition(e4, (Character) 'b', e2, Word.epsilon()); + expected.setTransition(e4, (Character) 'c', e2, Word.epsilon()); + + Assert.assertTrue(Automata.testEquivalence(expected, osst, INPUTS)); + } } From 9ec8ff00a0f7f85347ffe91941245d3c8b4cfc32 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Sun, 20 Dec 2020 21:56:12 +0100 Subject: [PATCH 14/21] some more cleanups --- .../java/de/learnlib/algorithms/ostia/OSTIA.java | 10 +--------- .../java/de/learnlib/algorithms/ostia/State.java | 12 ++++++++++-- algorithms/passive/pom.xml | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) 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 index 3c21593460..a14051caf1 100644 --- 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 @@ -122,7 +122,7 @@ private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { pttIter.out = new Out(outputIter); } - private static void addBlueStates(State parent, java.util.Queue blue) { + private static void addBlueStates(State parent, Queue blue) { for (int i = 0; i < parent.transitions.length; i++) { if (parent.transitions[i] != null) { blue.add(new Blue(parent, i)); @@ -130,14 +130,6 @@ private static void addBlueStates(State parent, java.util.Queue blue) { } } - static Edge[] copyTransitions(Edge[] transitions) { - final Edge[] copy = new Edge[transitions.length]; - for (int i = 0; i < copy.length; i++) { - copy[i] = transitions[i] == null ? null : new Edge(transitions[i]); - } - return copy; - } - public static void ostia(State transducer) { final Queue blue = new LinkedList<>(); final List red = new ArrayList<>(); 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 index 123dee9407..b0ae93f3f1 100644 --- 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 @@ -28,11 +28,19 @@ class State { } State(State copy) { - transitions = OSTIA.copyTransitions(copy.transitions); + transitions = copyTransitions(copy.transitions); out = copy.out == null ? null : new Out(IntQueue.copyAndConcat(copy.out.str, null)); } - public void assign(State other) { + private Edge[] copyTransitions(Edge[] transitions) { + final Edge[] copy = new Edge[transitions.length]; + for (int i = 0; i < copy.length; i++) { + copy[i] = transitions[i] == null ? null : new Edge(transitions[i]); + } + return copy; + } + + void assign(State other) { out = other.out; transitions = other.transitions; } diff --git a/algorithms/passive/pom.xml b/algorithms/passive/pom.xml index 790b9b059f..cf73d412f8 100644 --- a/algorithms/passive/pom.xml +++ b/algorithms/passive/pom.xml @@ -37,4 +37,4 @@ limitations under the License. rpni-edsm rpni-mdl - \ No newline at end of file + From dac0bba0685bb3d18bbf12b76f990f91d193f1fb Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Mon, 21 Dec 2020 02:44:39 +0100 Subject: [PATCH 15/21] switch to the development version of AutomataLib --- .../kv/mealy/KearnsVaziraniMealy.java | 8 +- .../lstar/mealy/ClassicLStarMealy.java | 4 +- .../lstar/mealy/ExtensibleLStarMealy.java | 4 +- algorithms/passive/ostia/pom.xml | 10 +- .../de/learnlib/algorithms/ostia/OSTIA.java | 14 +- .../MutableSubsequentialTransducer.java | 36 --- .../transducers/SSTVisualizationHelper.java | 57 ----- .../transducers/SubsequentialTransducer.java | 109 --------- .../transducers/SubsequentialTransducers.java | 220 ------------------ .../impl/compact/UniversalCompactDet.java | 157 ------------- .../commons/smartcollections/IntSeq.java | 120 ---------- .../learnlib/algorithms/ostia/OSTIAIT.java} | 15 +- .../learnlib/algorithms/ostia/OSTIATest.java | 8 +- .../SubsequentialTransducersTest.java | 195 ---------------- pom.xml | 2 +- .../learner/AbstractSSTPassiveLearnerIT.java | 90 +++++++ .../examples/DefaultLearningExample.java | 14 ++ .../de/learnlib/examples/LearningExample.java | 3 + .../learnlib/examples/LearningExamples.java | 14 ++ .../examples/sst/ExampleRandomSST.java | 57 +++++ 20 files changed, 215 insertions(+), 922 deletions(-) delete mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/MutableSubsequentialTransducer.java delete mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SSTVisualizationHelper.java delete mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducer.java delete mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java delete mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java delete mode 100644 algorithms/passive/ostia/src/main/java/net/automatalib/commons/smartcollections/IntSeq.java rename algorithms/passive/ostia/src/{main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java => test/java/de/learnlib/algorithms/ostia/OSTIAIT.java} (55%) delete mode 100644 algorithms/passive/ostia/src/test/java/net/automatalib/automata/transducers/SubsequentialTransducersTest.java create mode 100644 test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractSSTPassiveLearnerIT.java create mode 100644 test-support/learning-examples/src/main/java/de/learnlib/examples/sst/ExampleRandomSST.java diff --git a/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithms/kv/mealy/KearnsVaziraniMealy.java b/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithms/kv/mealy/KearnsVaziraniMealy.java index ac3f239b12..3d8044af44 100644 --- a/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithms/kv/mealy/KearnsVaziraniMealy.java +++ b/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithms/kv/mealy/KearnsVaziraniMealy.java @@ -37,9 +37,9 @@ import de.learnlib.datastructure.discriminationtree.model.LCAInfo; import de.learnlib.util.mealy.MealyUtil; import net.automatalib.SupportsGrowingAlphabet; +import net.automatalib.automata.base.compact.CompactTransition; import net.automatalib.automata.transducers.MealyMachine; import net.automatalib.automata.transducers.impl.compact.CompactMealy; -import net.automatalib.automata.transducers.impl.compact.CompactMealyTransition; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; import net.automatalib.words.impl.Alphabets; @@ -165,7 +165,7 @@ private void splitState(StateInfo> stateInfo, newOut = separatorInfo.subtree2Label; } else { newDiscriminator = newDiscriminator(sym, separator.getDiscriminator()); - CompactMealyTransition transition = hypothesis.getTransition(state, sym); + CompactTransition transition = hypothesis.getTransition(state, sym); assert transition != null; O transOut = hypothesis.getTransitionOutput(transition); oldOut = newOutcome(transOut, separatorInfo.subtree1Label); @@ -214,9 +214,9 @@ private void updateTransitions(List transList, int sourceState = (int) (encodedTrans >> Integer.SIZE); int transIdx = (int) (encodedTrans); - CompactMealyTransition trans = hypothesis.getTransition(sourceState, transIdx); + CompactTransition trans = hypothesis.getTransition(sourceState, transIdx); assert trans != null; - setTransition(sourceState, transIdx, succs.get(i), trans.getOutput()); + setTransition(sourceState, transIdx, succs.get(i), trans.getProperty()); } } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ClassicLStarMealy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ClassicLStarMealy.java index 449a774f7a..c8a86a77a2 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ClassicLStarMealy.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ClassicLStarMealy.java @@ -26,10 +26,10 @@ import de.learnlib.datastructure.observationtable.ObservationTable; import de.learnlib.datastructure.observationtable.Row; import de.learnlib.util.mealy.MealyUtil; +import net.automatalib.automata.base.compact.CompactTransition; import net.automatalib.automata.concepts.SuffixOutput; import net.automatalib.automata.transducers.MealyMachine; import net.automatalib.automata.transducers.impl.compact.CompactMealy; -import net.automatalib.automata.transducers.impl.compact.CompactMealyTransition; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; import org.checkerframework.checker.nullness.qual.Nullable; @@ -46,7 +46,7 @@ * @author Malte Isberner */ public class ClassicLStarMealy - extends AbstractExtensibleAutomatonLStar, I, @Nullable O, Integer, CompactMealyTransition, Void, O, CompactMealy> { + extends AbstractExtensibleAutomatonLStar, I, @Nullable O, Integer, CompactTransition, Void, O, CompactMealy> { /** * Constructor. diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ExtensibleLStarMealy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ExtensibleLStarMealy.java index 121b6627b8..14fd468a1d 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ExtensibleLStarMealy.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ExtensibleLStarMealy.java @@ -28,15 +28,15 @@ import de.learnlib.datastructure.observationtable.OTLearner.OTLearnerMealy; import de.learnlib.datastructure.observationtable.ObservationTable; import de.learnlib.datastructure.observationtable.Row; +import net.automatalib.automata.base.compact.CompactTransition; import net.automatalib.automata.concepts.SuffixOutput; import net.automatalib.automata.transducers.MealyMachine; import net.automatalib.automata.transducers.impl.compact.CompactMealy; -import net.automatalib.automata.transducers.impl.compact.CompactMealyTransition; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; public class ExtensibleLStarMealy - extends AbstractExtensibleAutomatonLStar, I, Word, Integer, CompactMealyTransition, Void, O, CompactMealy> + extends AbstractExtensibleAutomatonLStar, I, Word, Integer, CompactTransition, Void, O, CompactMealy> implements OTLearnerMealy { private final List outputTable = new ArrayList<>(); diff --git a/algorithms/passive/ostia/pom.xml b/algorithms/passive/ostia/pom.xml index edb183c8eb..a85be1639f 100644 --- a/algorithms/passive/ostia/pom.xml +++ b/algorithms/passive/ostia/pom.xml @@ -47,10 +47,6 @@ limitations under the License. net.automatalib automata-core - - net.automatalib - automata-util - org.checkerframework @@ -69,6 +65,12 @@ limitations under the License. learnlib-learner-it-support + + net.automatalib + automata-util + test + + org.testng testng 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 index a14051caf1..99f1966bcc 100644 --- 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 @@ -30,7 +30,9 @@ 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.Nullable; /** @@ -40,21 +42,23 @@ public class OSTIA implements PassiveLearningAlgorithm, I, Word> { private final Alphabet inputAlphabet; - private final Alphabet outputAlphabet; + private final GrowingAlphabet outputAlphabet; private final State root; - public OSTIA(Alphabet inputAlphabet, Alphabet outputAlphabet) { + public OSTIA(Alphabet inputAlphabet) { this.inputAlphabet = inputAlphabet; - this.outputAlphabet = outputAlphabet; + this.outputAlphabet = new GrowingMapAlphabet<>(); this.root = new State(inputAlphabet.size()); } @Override public void addSamples(Collection>> samples) { for (DefaultQuery> sample : samples) { + final Word output = sample.getOutput(); + this.outputAlphabet.addAll(output.asList()); buildPttOnward(root, - IntSeq.of(sample.getInput(), inputAlphabet), - IntQueue.asQueue(IntSeq.of(sample.getOutput(), outputAlphabet))); + sample.getInput().asIntSeq(inputAlphabet), + IntQueue.asQueue(output.asIntSeq(outputAlphabet))); } } diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/MutableSubsequentialTransducer.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/MutableSubsequentialTransducer.java deleted file mode 100644 index 6dbe220c25..0000000000 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/MutableSubsequentialTransducer.java +++ /dev/null @@ -1,36 +0,0 @@ -/* 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 net.automatalib.automata.transducers; - -import net.automatalib.automata.MutableDeterministic; -import net.automatalib.words.Word; - -/** - * A {@link MutableDeterministic mutable} extension of a {@link SubsequentialTransducer}. - * - * @param - * state type - * @param - * input symbol type - * @param - * transition type - * @param - * output symbol type - * - * @author frohme - */ -public interface MutableSubsequentialTransducer - extends SubsequentialTransducer, MutableDeterministic, Word> {} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SSTVisualizationHelper.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SSTVisualizationHelper.java deleted file mode 100644 index deaad0b51c..0000000000 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SSTVisualizationHelper.java +++ /dev/null @@ -1,57 +0,0 @@ -/* 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 net.automatalib.automata.transducers; - -import java.util.Map; - -import net.automatalib.automata.graphs.TransitionEdge; -import net.automatalib.automata.visualization.AutomatonVisualizationHelper; -import net.automatalib.words.Word; - -public class SSTVisualizationHelper - extends AutomatonVisualizationHelper> { - - public SSTVisualizationHelper(SubsequentialTransducer automaton) { - super(automaton); - } - - @Override - public boolean getEdgeProperties(S src, TransitionEdge edge, S tgt, Map properties) { - if (!super.getEdgeProperties(src, edge, tgt, properties)) { - return false; - } - - final StringBuilder labelBuilder = new StringBuilder(); - labelBuilder.append(edge.getInput()).append(" / "); - Word output = automaton.getTransitionProperty(edge.getTransition()); - if (output != null) { - labelBuilder.append(output); - } - properties.put(EdgeAttrs.LABEL, labelBuilder.toString()); - return true; - } - - @Override - public boolean getNodeProperties(S node, Map properties) { - if (!super.getNodeProperties(node, properties)) { - return false; - } - - final String oldLabel = properties.get(NodeAttrs.LABEL); - properties.put(NodeAttrs.LABEL, oldLabel + " / " + automaton.getStateProperty(node)); - return true; - } -} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducer.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducer.java deleted file mode 100644 index 92b91f8f61..0000000000 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducer.java +++ /dev/null @@ -1,109 +0,0 @@ -/* 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 net.automatalib.automata.transducers; - -import java.util.Collection; -import java.util.List; - -import net.automatalib.automata.UniversalDeterministicAutomaton; -import net.automatalib.automata.concepts.DetSuffixOutputAutomaton; -import net.automatalib.automata.fsa.DFA; -import net.automatalib.automata.graphs.TransitionEdge; -import net.automatalib.automata.graphs.TransitionEdge.Property; -import net.automatalib.automata.graphs.UniversalAutomatonGraphView; -import net.automatalib.graphs.UniversalGraph; -import net.automatalib.ts.output.DeterministicOutputTS; -import net.automatalib.visualization.VisualizationHelper; -import net.automatalib.words.Word; -import net.automatalib.words.WordBuilder; - -/** - * A subsequential transducer (or SST) is an {@link DeterministicOutputTS} whose state and transition properties are - * output-{@link Word words}. Upon parsing a sequence of input symbols, each transition emits a {@link Word sequence} of - * output symbols. After all inputs have been parsed, the output of the reached state will be emitted as well. - *

- * Implementation detail: - * There exist definitions of SSTs that associate each state with an additional notion of 'acceptance' in order to - * reject certain transductions. This implementation/interface denotes prefix-closed transductions, i.e. all states are - * accepting. If you would like to filter out certain transduction you may use a supplementary {@link DFA} for this - * decision problem. - * - * @param - * state type - * @param - * input symbol type - * @param - * transition type - * @param - * output symbol type - * - * @author frohme - */ -public interface SubsequentialTransducer extends DeterministicOutputTS, - DetSuffixOutputAutomaton>, - UniversalDeterministicAutomaton, Word> { - - @Override - default Word computeStateOutput(S state, Iterable input) { - // since the outputs are words of unknown length, we can't really pre-compute a sensible builder size - final WordBuilder result = new WordBuilder<>(); - - trace(state, input, result); - - return result.toWord(); - } - - @Override - default boolean trace(S state, Iterable input, List output) { - S iter = state; - - for (I sym : input) { - T trans = getTransition(iter, sym); - if (trans == null) { - return false; - } - Word out = getTransitionProperty(trans); - output.addAll(out.asList()); - iter = getSuccessor(trans); - } - - if (iter == null) { - return false; - } else { - output.addAll(getStateProperty(iter).asList()); - } - - return true; - } - - @Override - default UniversalGraph, Word, Property>> transitionGraphView(Collection inputs) { - return new SSTGraphView<>(this, inputs); - } - - class SSTGraphView> - extends UniversalAutomatonGraphView, Word, A> { - - public SSTGraphView(A automaton, Collection inputs) { - super(automaton, inputs); - } - - @Override - public VisualizationHelper> getVisualizationHelper() { - return new SSTVisualizationHelper<>(automaton); - } - } -} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java deleted file mode 100644 index 3005c9f0a8..0000000000 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/SubsequentialTransducers.java +++ /dev/null @@ -1,220 +0,0 @@ -/* 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 net.automatalib.automata.transducers; - -import java.util.ArrayDeque; -import java.util.Collection; -import java.util.Deque; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import net.automatalib.commons.util.Pair; -import net.automatalib.commons.util.mappings.Mapping; -import net.automatalib.commons.util.mappings.MutableMapping; -import net.automatalib.util.automata.Automata; -import net.automatalib.util.automata.copy.AutomatonCopyMethod; -import net.automatalib.util.automata.copy.AutomatonLowLevelCopy; -import net.automatalib.words.Word; - -/** - * Utility methods of {@link SubsequentialTransducer}s. - * - * @author frohme - */ -public final class SubsequentialTransducers { - - private SubsequentialTransducers() { - // prevent initialization - } - - /** - * Constructs a new onward subsequential transducer for a given {@link SubsequentialTransducer SST}. In an - * onward SST, for each state except the initial state, the longest common prefix over the state output and the - * outputs of all outgoing transitions of a state is {@link Word#epsilon() epsilon}. This can be achieved by pushing - * back the longest common prefix to the transition outputs of the incoming transitions of each state. - * - * @param sst - * the original SST - * @param inputs - * the alphabet symbols to consider for this transformation - * @param out - * the target automaton to write the onward form to - * - * @return {@code out}, for convenience - */ - public static > A toOnwardSST( - SubsequentialTransducer sst, - Collection inputs, - A out) { - return toOnwardSST(sst, inputs, out, true); - } - - /** - * Constructs a new onward subsequential transducer for a given {@link SubsequentialTransducer SST}. In an - * onward SST, for each state except the initial state, the longest common prefix over the state output and the - * outputs of all outgoing transitions of a state is {@link Word#epsilon() epsilon}. This can be achieved by pushing - * back the longest common prefix to the transition outputs of the incoming transitions of each state. - * - * @param sst - * the original SST - * @param inputs - * the alphabet symbols to consider for this transformation - * @param out - * the target automaton to write the onward form to - * @param minimize - * a flag indicating whether the final result should be minimized - * - * @return {@code out}, for convenience - */ - public static > A toOnwardSST( - SubsequentialTransducer sst, - Collection inputs, - A out, - boolean minimize) { - - assert out.size() == 0; - AutomatonLowLevelCopy.copy(AutomatonCopyMethod.STATE_BY_STATE, sst, inputs, out); - - final Mapping>> incomingTransitions = getIncomingTransitions(out, inputs); - final Deque queue = new ArrayDeque<>(out.getStates()); - - final S2 oldInit = out.getInitialState(); - - if (!incomingTransitions.get(oldInit).isEmpty()) { - // copy initial state to prevent push-back of prefixes for the initial state. - out.setInitial(oldInit, false); - final S2 newInit = out.addInitialState(out.getStateProperty(oldInit)); - - for (I i : inputs) { - T2 oldT = out.getTransition(oldInit, i); - if (oldT != null) { - final S2 succ = out.getSuccessor(oldT); - out.addTransition(newInit, i, succ, out.getTransitionProperty(oldT)); - incomingTransitions.get(succ).add(Pair.of(newInit, i)); - } - } - } - - while (!queue.isEmpty()) { - final S2 s = queue.pop(); - if (Objects.equals(s, out.getInitialState())) { - continue; - } - final Word lcp = computeLCP(out, inputs, s); - - if (!lcp.isEmpty()) { - final Word oldStateProperty = out.getStateProperty(s); - final Word newStateProperty = oldStateProperty.subWord(lcp.length()); - - out.setStateProperty(s, newStateProperty); - - for (I i : inputs) { - final T2 t = out.getTransition(s, i); - if (t != null) { - final Word oldTransitionProperty = out.getTransitionProperty(t); - final Word newTransitionProperty = oldTransitionProperty.subWord(lcp.length()); - - out.setTransitionProperty(t, newTransitionProperty); - } - } - - for (Pair trans : incomingTransitions.get(s)) { - final T2 t = out.getTransition(trans.getFirst(), trans.getSecond()); - - final Word oldTransitionProperty = out.getTransitionProperty(t); - final Word newTransitionProperty = oldTransitionProperty.concat(lcp); - - out.setTransitionProperty(t, newTransitionProperty); - if (!queue.contains(trans.getFirst())) { - queue.add(trans.getFirst()); - } - } - } - } - - return minimize ? Automata.invasiveMinimize(out, inputs) : out; - } - - /** - * Checks whether a given {@link SubsequentialTransducer} is onward, i.e. if for each state (sans initial - * state) the longest common prefix over its state property and the transition properties of its outgoing edges - * equals {@link Word#epsilon() epsilon}. - * - * @param sst - * the SST to check - * @param inputs - * the input symbols to consider for this check - * - * @return {@code true} if {@code sst} is onward, {@code false} otherwise - */ - public static boolean isOnwardSST(SubsequentialTransducer sst, - Collection inputs) { - - for (S s : sst) { - if (Objects.equals(s, sst.getInitialState())) { - continue; - } - - final Word lcp = computeLCP(sst, inputs, s); - if (!lcp.isEmpty()) { - return false; - } - } - - return true; - } - - private static Mapping>> getIncomingTransitions(SubsequentialTransducer sst, - Collection inputs) { - - final MutableMapping>> result = sst.createStaticStateMapping(); - - for (S s : sst) { - result.put(s, new HashSet<>()); - } - - for (S s : sst) { - for (I i : inputs) { - final T t = sst.getTransition(s, i); - - if (t != null) { - final S succ = sst.getSuccessor(t); - result.get(succ).add(Pair.of(s, i)); - } - } - } - - return result; - } - - private static Word computeLCP(SubsequentialTransducer sst, - Collection inputs, - S s) { - - Word lcp = sst.getStateProperty(s); - - for (I i : inputs) { - T t = sst.getTransition(s, i); - if (t != null) { - lcp = lcp.longestCommonPrefix(sst.getTransitionProperty(s, i)); - } - } - - return lcp; - } - -} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java b/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java deleted file mode 100644 index a88fc1cba0..0000000000 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/UniversalCompactDet.java +++ /dev/null @@ -1,157 +0,0 @@ -/* 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 net.automatalib.automata.transducers.impl.compact; - -import java.util.Arrays; - -import net.automatalib.automata.base.compact.AbstractCompact; -import net.automatalib.automata.base.compact.AbstractCompactDeterministic; -import net.automatalib.words.Alphabet; -import org.checkerframework.checker.nullness.qual.Nullable; - -/** - * A default implementation for {@link AbstractCompactDeterministic} that uses {@link CompactMealyTransition} as - * transition type and supports various types of state and transition properites. - * - * @param - * input symbol type - * @param - * state property type - * @param - * transition property type - * - * @author frohme - */ -public class UniversalCompactDet - extends AbstractCompactDeterministic, SP, TP> { - - private int[] transitions; - private @Nullable Object[] stateProperties; - private @Nullable Object[] transitionProperties; - - public UniversalCompactDet(Alphabet alphabet) { - this(alphabet, DEFAULT_INIT_CAPACITY, DEFAULT_RESIZE_FACTOR); - } - - public UniversalCompactDet(Alphabet alphabet, int stateCapacity, float resizeFactor) { - super(alphabet, stateCapacity, resizeFactor); - - final int numTrans = stateCapacity * numInputs(); - this.transitions = new int[numTrans]; - this.stateProperties = new Object[stateCapacity]; - this.transitionProperties = new Object[numTrans]; - - Arrays.fill(this.transitions, AbstractCompact.INVALID_STATE); - } - - @Override - public @Nullable CompactMealyTransition getTransition(int state, int input) { - final int idx = toMemoryIndex(state, input); - final int succ = transitions[idx]; - - if (succ == AbstractCompact.INVALID_STATE) { - return null; - } - - @SuppressWarnings("unchecked") - final TP output = (TP) transitionProperties[idx]; - - return new CompactMealyTransition<>(idx, succ, output); - } - - @Override - public int getIntSuccessor(CompactMealyTransition transition) { - return transition.getSuccId(); - } - - @Override - public void setStateProperty(int state, @Nullable SP property) { - this.stateProperties[state] = property; - } - - @Override - public void setTransitionProperty(CompactMealyTransition transition, TP property) { - transition.setOutput(property); - - if (transition.isAutomatonTransition()) { - transitionProperties[transition.getMemoryIdx()] = property; - } - } - - @Override - public CompactMealyTransition createTransition(int successor, TP property) { - return new CompactMealyTransition<>(successor, property); - } - - @Override - public void removeAllTransitions(Integer state) { - final int lower = state * numInputs(); - final int upper = lower + numInputs(); - Arrays.fill(transitions, lower, upper, AbstractCompact.INVALID_STATE); - Arrays.fill(transitionProperties, lower, upper, null); - - } - - @Override - public void setTransition(int state, int input, @Nullable CompactMealyTransition transition) { - if (transition == null) { - setTransition(state, input, AbstractCompact.INVALID_STATE, null); - } else { - setTransition(state, input, transition.getSuccId(), transition.getOutput()); - transition.setMemoryIdx(toMemoryIndex(state, input)); - } - } - - @Override - public void setTransition(int state, int input, int successor, TP property) { - final int idx = toMemoryIndex(state, input); - transitions[idx] = successor; - transitionProperties[idx] = property; - } - - @Override - @SuppressWarnings("unchecked") - public SP getStateProperty(int state) { - return (SP) stateProperties[state]; - } - - @Override - public TP getTransitionProperty(CompactMealyTransition transition) { - return transition.getOutput(); - } - - @Override - public void clear() { - int endIdx = size() * numInputs(); - Arrays.fill(stateProperties, 0, size(), null); - Arrays.fill(transitions, 0, endIdx, AbstractCompact.INVALID_STATE); - Arrays.fill(transitionProperties, 0, endIdx, null); - - super.clear(); - } - - @Override - protected void updateStateStorage(Payload payload) { - this.stateProperties = updateStateStorage(this.stateProperties, null, payload); - super.updateStateStorage(payload); - } - - @Override - protected void updateTransitionStorage(Payload payload) { - this.transitions = updateTransitionStorage(this.transitions, AbstractCompact.INVALID_STATE, payload); - this.transitionProperties = updateTransitionStorage(this.transitionProperties, null, payload); - } -} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/commons/smartcollections/IntSeq.java b/algorithms/passive/ostia/src/main/java/net/automatalib/commons/smartcollections/IntSeq.java deleted file mode 100644 index c913fc5861..0000000000 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/commons/smartcollections/IntSeq.java +++ /dev/null @@ -1,120 +0,0 @@ -/* 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 net.automatalib.commons.smartcollections; - -import java.util.Arrays; -import java.util.List; -import java.util.PrimitiveIterator.OfInt; - -import net.automatalib.words.Alphabet; -import net.automatalib.words.Word; - -/** - * An {@link IntSeq} is an abstract view on a finite, random-access data-structure for primitive integer values. It - * allows for a unified view on integer arrays, {@link List lists} of integers, {@link Word words} with an accompanying - * {@link Alphabet#getSymbolIndex(Object) alphabet index function}, etc. - * - * @author Aleksander Mendoza-Drosik - * @author frohme - */ -public interface IntSeq extends Iterable { - - int size(); - - int get(int index); - - @Override - default OfInt iterator() { - return new OfInt() { - - int curr; - - { - this.curr = 0; - } - - @Override - public int nextInt() { - return get(curr++); - } - - @Override - public boolean hasNext() { - return curr < size(); - } - }; - } - - static IntSeq of(Word word, Alphabet alphabet) { - return new IntSeq() { - - @Override - public int size() { - return word.size(); - } - - @Override - public int get(int index) { - return alphabet.getSymbolIndex(word.getSymbol(index)); - } - - @Override - public String toString() { - return word.toString(); - } - }; - } - - static IntSeq of(int... ints) { - return new IntSeq() { - - @Override - public int size() { - return ints.length; - } - - @Override - public int get(int index) { - return ints[index]; - } - - @Override - public String toString() { - return Arrays.toString(ints); - } - }; - } - - static IntSeq of(List ints) { - return new IntSeq() { - - @Override - public int size() { - return ints.size(); - } - - @Override - public int get(int index) { - return ints.get(index); - } - - @Override - public String toString() { - return ints.toString(); - } - }; - } -} diff --git a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java b/algorithms/passive/ostia/src/test/java/de/learnlib/algorithms/ostia/OSTIAIT.java similarity index 55% rename from algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java rename to algorithms/passive/ostia/src/test/java/de/learnlib/algorithms/ostia/OSTIAIT.java index b98e60f4fb..1384389687 100644 --- a/algorithms/passive/ostia/src/main/java/net/automatalib/automata/transducers/impl/compact/CompactSST.java +++ b/algorithms/passive/ostia/src/test/java/de/learnlib/algorithms/ostia/OSTIAIT.java @@ -13,16 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.automatalib.automata.transducers.impl.compact; +package de.learnlib.algorithms.ostia; -import net.automatalib.automata.transducers.MutableSubsequentialTransducer; +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 CompactSST extends UniversalCompactDet, Word> - implements MutableSubsequentialTransducer>, O> { +public class OSTIAIT extends AbstractSSTPassiveLearnerIT { - public CompactSST(Alphabet alphabet) { - super(alphabet); + @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 index df5410619f..8772bb8d40 100644 --- 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 @@ -26,7 +26,6 @@ import com.google.common.collect.Iterators; import net.automatalib.automata.transducers.MealyMachine; import net.automatalib.automata.transducers.SubsequentialTransducer; -import net.automatalib.automata.transducers.SubsequentialTransducers; import net.automatalib.automata.transducers.impl.compact.CompactSST; import net.automatalib.commons.smartcollections.IntSeq; import net.automatalib.commons.util.Pair; @@ -34,6 +33,7 @@ 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.impl.Alphabets; @@ -78,13 +78,13 @@ public void testStaticInvocation() { Assert.assertNull(OSTIA.run(root, IntSeq.of(0, 1, 0, 1, 1))); } - @Test(dataProvider = "sizes") + @Test(enabled = false, dataProvider = "sizes") public void testMealySamples(int size) { final MealyMachine automaton = RandomAutomata.randomMealy(new Random(SEED), size, INPUTS, OUTPUTS); - final OSTIA learner = new OSTIA<>(INPUTS, Alphabets.fromCollection(OUTPUTS)); + final OSTIA learner = new OSTIA<>(INPUTS); final List> trainingWords = new ArrayList<>(); final Iterator> testIterator = new WMethodTestsIterator<>(automaton, INPUTS, 0); @@ -125,7 +125,7 @@ public void testEquivalence(int size) { SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS)); Assert.assertTrue(SubsequentialTransducers.isOnwardSST(osst, INPUTS)); - final OSTIA learner = new OSTIA<>(INPUTS, Alphabets.fromCollection(OUTPUTS)); + final OSTIA learner = new OSTIA<>(INPUTS); final int lookAhead = 2; final Iterator> testIterator = new WMethodTestsIterator<>(sst, INPUTS, lookAhead); diff --git a/algorithms/passive/ostia/src/test/java/net/automatalib/automata/transducers/SubsequentialTransducersTest.java b/algorithms/passive/ostia/src/test/java/net/automatalib/automata/transducers/SubsequentialTransducersTest.java deleted file mode 100644 index 9c3860342b..0000000000 --- a/algorithms/passive/ostia/src/test/java/net/automatalib/automata/transducers/SubsequentialTransducersTest.java +++ /dev/null @@ -1,195 +0,0 @@ -/* 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 net.automatalib.automata.transducers; - -import net.automatalib.automata.transducers.impl.compact.CompactSST; -import net.automatalib.util.automata.Automata; -import net.automatalib.words.Alphabet; -import net.automatalib.words.Word; -import net.automatalib.words.impl.Alphabets; -import org.testng.Assert; -import org.testng.annotations.Test; - -public class SubsequentialTransducersTest { - - private static final Alphabet INPUTS = Alphabets.characters('a', 'c'); - - @Test - public void testAlreadyOnwardModel() { - - final CompactSST sst = new CompactSST<>(INPUTS); - - final int s1 = sst.addInitialState(Word.fromCharSequence("x")); - final int s2 = sst.addState(Word.fromCharSequence("x")); - final int s3 = sst.addState(Word.fromCharSequence("x")); - - sst.setTransition(s1, (Character) 'a', s2, Word.fromCharSequence("x")); - sst.setTransition(s1, (Character) 'b', s2, Word.fromCharSequence("y")); - sst.setTransition(s1, (Character) 'c', s2, Word.fromCharSequence("z")); - - sst.setTransition(s2, (Character) 'a', s3, Word.fromCharSequence("x")); - sst.setTransition(s2, (Character) 'b', s3, Word.fromCharSequence("y")); - sst.setTransition(s2, (Character) 'c', s3, Word.fromCharSequence("z")); - - sst.setTransition(s3, (Character) 'a', s3, Word.fromCharSequence("x")); - sst.setTransition(s3, (Character) 'b', s3, Word.fromCharSequence("y")); - sst.setTransition(s3, (Character) 'c', s3, Word.fromCharSequence("z")); - - final CompactSST osst = - SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS), false); - - Assert.assertTrue(SubsequentialTransducers.isOnwardSST(osst, INPUTS)); - Assert.assertTrue(Automata.testEquivalence(sst, osst, INPUTS)); - } - - @Test - public void testPartialModel() { - - final CompactSST sst = new CompactSST<>(INPUTS); - - final int s1 = sst.addInitialState(Word.fromCharSequence("x")); - final int s2 = sst.addState(Word.fromCharSequence("x")); - final int s3 = sst.addState(Word.fromCharSequence("x")); - - sst.setTransition(s1, (Character) 'a', s2, Word.fromCharSequence("x")); - sst.setTransition(s1, (Character) 'b', s2, Word.fromCharSequence("y")); - sst.setTransition(s1, (Character) 'c', s2, Word.fromCharSequence("z")); - - sst.setTransition(s2, (Character) 'a', s3, Word.fromCharSequence("x")); - sst.setTransition(s2, (Character) 'b', s3, Word.fromCharSequence("y")); - sst.setTransition(s2, (Character) 'c', s3, Word.fromCharSequence("z")); - - final CompactSST osst = - SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS), false); - - Assert.assertTrue(SubsequentialTransducers.isOnwardSST(osst, INPUTS)); - - final CompactSST expected = new CompactSST<>(INPUTS); - - final int e1 = expected.addInitialState(Word.fromCharSequence("x")); - final int e2 = expected.addState(Word.fromCharSequence("x")); - final int e3 = expected.addState(Word.epsilon()); - - expected.setTransition(e1, (Character) 'a', e2, Word.fromCharSequence("x")); - expected.setTransition(e1, (Character) 'b', e2, Word.fromCharSequence("y")); - expected.setTransition(e1, (Character) 'c', e2, Word.fromCharSequence("z")); - - expected.setTransition(e2, (Character) 'a', e3, Word.fromCharSequence("xx")); - expected.setTransition(e2, (Character) 'b', e3, Word.fromCharSequence("yx")); - expected.setTransition(e2, (Character) 'c', e3, Word.fromCharSequence("zx")); - - Assert.assertTrue(Automata.testEquivalence(expected, osst, INPUTS)); - } - - @Test - public void testMultiplePropagations() { - - final CompactSST sst = new CompactSST<>(INPUTS); - - final int s1 = sst.addInitialState(Word.fromCharSequence("x")); - final int s2 = sst.addState(Word.fromCharSequence("xx")); - final int s3 = sst.addState(Word.fromCharSequence("x")); - - sst.setTransition(s1, (Character) 'a', s2, Word.fromCharSequence("x")); - sst.setTransition(s1, (Character) 'b', s2, Word.fromCharSequence("y")); - sst.setTransition(s1, (Character) 'c', s2, Word.fromCharSequence("z")); - - sst.setTransition(s2, (Character) 'a', s3, Word.fromCharSequence("x")); - sst.setTransition(s2, (Character) 'b', s3, Word.fromCharSequence("x")); - sst.setTransition(s2, (Character) 'c', s3, Word.fromCharSequence("x")); - - sst.setTransition(s3, (Character) 'a', s3, Word.fromCharSequence("xx")); - sst.setTransition(s3, (Character) 'b', s3, Word.fromCharSequence("xy")); - sst.setTransition(s3, (Character) 'c', s3, Word.fromCharSequence("xz")); - - final CompactSST osst = - SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS), false); - - Assert.assertTrue(SubsequentialTransducers.isOnwardSST(osst, INPUTS)); - - final CompactSST expected = new CompactSST<>(INPUTS); - - final int e1 = expected.addInitialState(Word.fromCharSequence("x")); - final int e2 = expected.addState(Word.epsilon()); - final int e3 = expected.addState(Word.epsilon()); - - expected.setTransition(e1, (Character) 'a', e2, Word.fromCharSequence("xxx")); - expected.setTransition(e1, (Character) 'b', e2, Word.fromCharSequence("yxx")); - expected.setTransition(e1, (Character) 'c', e2, Word.fromCharSequence("zxx")); - - expected.setTransition(e2, (Character) 'a', e3, Word.epsilon()); - expected.setTransition(e2, (Character) 'b', e3, Word.epsilon()); - expected.setTransition(e2, (Character) 'c', e3, Word.epsilon()); - - expected.setTransition(e3, (Character) 'a', e3, Word.fromCharSequence("xx")); - expected.setTransition(e3, (Character) 'b', e3, Word.fromCharSequence("yx")); - expected.setTransition(e3, (Character) 'c', e3, Word.fromCharSequence("zx")); - - Assert.assertTrue(Automata.testEquivalence(expected, osst, INPUTS)); - } - - @Test - public void testLoopOnInitial() { - - final CompactSST sst = new CompactSST<>(INPUTS); - - final int s1 = sst.addInitialState(Word.fromCharSequence("x")); - final int s2 = sst.addState(Word.fromCharSequence("x")); - final int s3 = sst.addState(Word.fromCharSequence("x")); - - sst.setTransition(s1, (Character) 'a', s2, Word.epsilon()); - sst.setTransition(s1, (Character) 'b', s2, Word.epsilon()); - sst.setTransition(s1, (Character) 'c', s2, Word.epsilon()); - - sst.setTransition(s2, (Character) 'a', s3, Word.fromCharSequence("xx")); - sst.setTransition(s2, (Character) 'b', s3, Word.fromCharSequence("xy")); - sst.setTransition(s2, (Character) 'c', s3, Word.fromCharSequence("xz")); - - sst.setTransition(s3, (Character) 'a', s1, Word.fromCharSequence("xx")); - sst.setTransition(s3, (Character) 'b', s1, Word.fromCharSequence("xy")); - sst.setTransition(s3, (Character) 'c', s1, Word.fromCharSequence("xz")); - - final CompactSST osst = - SubsequentialTransducers.toOnwardSST(sst, INPUTS, new CompactSST<>(INPUTS), false); - - Assert.assertTrue(SubsequentialTransducers.isOnwardSST(osst, INPUTS)); - - final CompactSST expected = new CompactSST<>(INPUTS); - - final int e1 = expected.addInitialState(Word.fromCharSequence("x")); - final int e2 = expected.addState(Word.epsilon()); - final int e3 = expected.addState(Word.epsilon()); - final int e4 = expected.addState(Word.epsilon()); - - expected.setTransition(e1, (Character) 'a', e2, Word.fromCharSequence("x")); - expected.setTransition(e1, (Character) 'b', e2, Word.fromCharSequence("x")); - expected.setTransition(e1, (Character) 'c', e2, Word.fromCharSequence("x")); - - expected.setTransition(e2, (Character) 'a', e3, Word.fromCharSequence("xx")); - expected.setTransition(e2, (Character) 'b', e3, Word.fromCharSequence("yx")); - expected.setTransition(e2, (Character) 'c', e3, Word.fromCharSequence("zx")); - - expected.setTransition(e3, (Character) 'a', e4, Word.fromCharSequence("xx")); - expected.setTransition(e3, (Character) 'b', e4, Word.fromCharSequence("yx")); - expected.setTransition(e3, (Character) 'c', e4, Word.fromCharSequence("zx")); - - expected.setTransition(e4, (Character) 'a', e2, Word.epsilon()); - expected.setTransition(e4, (Character) 'b', e2, Word.epsilon()); - expected.setTransition(e4, (Character) 'c', e2, Word.epsilon()); - - Assert.assertTrue(Automata.testEquivalence(expected, osst, INPUTS)); - } -} diff --git a/pom.xml b/pom.xml index d7c4222f40..8bc743f08d 100644 --- a/pom.xml +++ b/pom.xml @@ -224,7 +224,7 @@ limitations under the License. 0.0.2 - 0.10.0 + 0.11.0-SNAPSHOT 0.1 1.9 3.7.0 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); + } +} From 5b58770c5e87cd1bbcc6e6d094386c704e49b05c Mon Sep 17 00:00:00 2001 From: Alagris Date: Fri, 1 Jan 2021 20:21:30 +0100 Subject: [PATCH 16/21] fixes bug in blue-fringe --- .../de/learnlib/algorithms/ostia/Blue.java | 6 +- .../de/learnlib/algorithms/ostia/Edge.java | 2 +- .../de/learnlib/algorithms/ostia/OSTIA.java | 151 +++++++++++++----- .../de/learnlib/algorithms/ostia/State.java | 44 +++-- .../learnlib/algorithms/ostia/OSTIATest.java | 6 +- pom.xml | 1 + 6 files changed, 151 insertions(+), 59 deletions(-) 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 index 24299ad9c7..e49755648e 100644 --- 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 @@ -20,15 +20,15 @@ */ class Blue { - final State parent; + final State.Original parent; final int symbol; - Blue(State parent, int symbol) { + Blue(State.Original parent, int symbol) { this.symbol = symbol; this.parent = parent; } - State state() { + State.Original state() { return parent.transitions[symbol].target; } 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 index d054ffc5b8..b86b45aaf1 100644 --- 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 @@ -21,7 +21,7 @@ class Edge { IntQueue out; - State target; + State.Original target; Edge() {} 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 index 99f1966bcc..c503a28624 100644 --- 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 @@ -15,14 +15,7 @@ */ package de.learnlib.algorithms.ostia; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Queue; +import java.util.*; import de.learnlib.api.algorithm.PassiveLearningAlgorithm; import de.learnlib.api.query.DefaultQuery; @@ -43,12 +36,12 @@ public class OSTIA implements PassiveLearningAlgorithm inputAlphabet; private final GrowingAlphabet outputAlphabet; - private final State root; + private final State.Original root; public OSTIA(Alphabet inputAlphabet) { this.inputAlphabet = inputAlphabet; this.outputAlphabet = new GrowingMapAlphabet<>(); - this.root = new State(inputAlphabet.size()); + this.root = new State.Original(inputAlphabet.size()); } @Override @@ -62,14 +55,18 @@ public void addSamples(Collection>> samples) { } } + private boolean hasBeenComputed = false; @Override public SubsequentialTransducer computeModel() { - ostia(root); + 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); + public static State.Original buildPtt(int alphabetSize, Iterator> informant) { + final State.Original root = new State.Original(alphabetSize); while (informant.hasNext()) { Pair inout = informant.next(); buildPttOnward(root, inout.getFirst(), IntQueue.asQueue(inout.getSecond())); @@ -77,8 +74,8 @@ public static State buildPtt(int alphabetSize, Iterator> in return root; } - private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { - State pttIter = ptt; + private static void buildPttOnward(State.Original ptt, IntSeq input, IntQueue output) { + State.Original pttIter = ptt; IntQueue outputIter = output; for (int i = 0; i < input.size(); i++) {//input index @@ -87,7 +84,7 @@ private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { if (pttIter.transitions[symbol] == null) { edge = new Edge(); edge.out = outputIter; - edge.target = new State(pttIter.transitions.length); + edge.target = new State.Original(pttIter.transitions.length); pttIter.transitions[symbol] = edge; outputIter = null; } else { @@ -126,57 +123,137 @@ private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { pttIter.out = new Out(outputIter); } - private static void addBlueStates(State parent, Queue blue) { + private static void addBlueStates(State.Original parent, Queue blue) { for (int i = 0; i < parent.transitions.length; i++) { if (parent.transitions[i] != null) { + assert !contains(blue,parent.transitions[i].target); + assert parent.transitions[i].target!=parent; blue.add(new Blue(parent, i)); } } } - public static void ostia(State transducer) { + private static boolean disjoint(Queue blue,LinkedHashSet red){ + for(Blue b:blue){ + if(red.contains(b.state()))return false; + } + return true; + } + private static boolean contains(Queue blue,State.Original state){ + for(Blue b:blue){ + if(state.equals(b.state()))return true; + } + return false; + } + private static boolean hasDuplicates(Queue blue){ + final HashSet unique = new HashSet<>(); + for(Blue b:blue){ + if(!unique.add(b.state()))return true; + } + return false; + } + private static boolean validateBlueAndRed(State.Original root, LinkedHashSet red,Queue blue){ + final HashSet reachable = new HashSet<>(); + isTree(root,reachable); + for(State.Original 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.Original root, HashSet nodes){ + final Queue toVisit = new LinkedList<>(); + toVisit.add(root); + boolean isTree = true; + while(!toVisit.isEmpty()){ + final State.Original s = toVisit.poll(); + if(nodes.add(s)) { + for (Edge edge : s.transitions) { + if (edge != null) { + toVisit.add(edge.target); + } + } + }else{ + isTree = false; + } + + } + return isTree; + } + public static void ostia(State.Original transducer) { final Queue blue = new LinkedList<>(); - final List red = new ArrayList<>(); + final LinkedHashSet red = new LinkedHashSet<>(); + assert isTree(transducer,new HashSet<>()); red.add(transducer); addBlueStates(transducer, blue); + assert !hasDuplicates(blue); + assert disjoint(blue,red); + assert validateBlueAndRed(transducer,red,blue); blue: while (!blue.isEmpty()) { final Blue next = blue.poll(); - final State blueState = next.state(); - for (State redState : red) { - if (ostiaMerge(next, redState, blue)) { + final State.Original blueState = next.state(); + assert isTree(blueState, new HashSet<>()); + assert !hasDuplicates(blue); + assert !contains(blue,blueState); + assert disjoint(blue,red); + for (State.Original redState : red) { + if (ostiaMerge(next, redState, blue, red)) { + assert disjoint(blue,red); + assert !hasDuplicates(blue); continue blue; } } + assert isTree(blueState, new HashSet<>()); + assert !hasDuplicates(blue); addBlueStates(blueState, blue); + assert !hasDuplicates(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, java.util.Queue blueToVisit) { - final Map merged = new HashMap<>(); + private static boolean ostiaMerge(Blue blue, State.Original redState, + Queue blueToVisit, LinkedHashSet 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()) { - mergedRedState.getKey().assign(mergedRedState.getValue()); + 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); + } } - blueToVisit.addAll(reachedBlueStates); return true; } return false; } - private static boolean ostiaFold(State red, + private static boolean ostiaFold(State.Original red, IntQueue pushedBack, - State blueParent, + State.Original blueParent, int symbolIncomingToBlue, - Map mergedStates, + Map mergedStates, List reachedBlueStates) { - final State mergedRedState = mergedStates.computeIfAbsent(red, State::new); - final State blueState = blueParent.transitions[symbolIncomingToBlue].target; - final State mergedBlueState = new State(blueState); + final State.Original blueState = blueParent.transitions[symbolIncomingToBlue].target; + assert red!=blueState; assert !mergedStates.containsKey(blueState); - mergedStates.computeIfAbsent(blueParent, State::new).transitions[symbolIncomingToBlue].target = red; + final State.Copy mergedRedState = mergedStates.computeIfAbsent(red, State.Copy::new); + final State.Copy mergedBlueState = new State.Copy(blueState); + mergedStates.computeIfAbsent(blueParent, State.Copy::new).transitions[symbolIncomingToBlue].target = red; final State prevBlue = mergedStates.put(blueState, mergedBlueState); assert prevBlue == null; mergedBlueState.prepend(pushedBack); @@ -193,7 +270,7 @@ private static boolean ostiaFold(State red, final Edge transitionRed = mergedRedState.transitions[i]; if (transitionRed == null) { mergedRedState.transitions[i] = new Edge(transitionBlue); - reachedBlueStates.add(new Blue(blueState, i)); + reachedBlueStates.add(new Blue(red, i)); } else { IntQueue commonPrefixRed = transitionRed.out; IntQueue commonPrefixBlue = transitionBlue.out; @@ -211,9 +288,11 @@ private static boolean ostiaFold(State red, } else { commonPrefixBluePrev.next = null; } + assert mergedBlueState.transitions[i]!=null; + assert mergedBlueState.transitions[i].target==blueState.transitions[i].target; if (!ostiaFold(transitionRed.target, commonPrefixBlue, - mergedBlueState, + blueState, i, mergedStates, reachedBlueStates)) { 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 index b0ae93f3f1..886170a669 100644 --- 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 @@ -20,31 +20,43 @@ */ class State { - Out out; - Edge[] transitions; + private State(){ - State(int alphabetSize) { - transitions = new Edge[alphabetSize]; } + static class Original extends State { + public Original(int alphabetSize) { + transitions = new Edge[alphabetSize]; + } + - State(State copy) { - transitions = copyTransitions(copy.transitions); - out = copy.out == null ? null : new Out(IntQueue.copyAndConcat(copy.out.str, null)); } + static class Copy extends State { + public final Original original; - private Edge[] copyTransitions(Edge[] transitions) { - final Edge[] copy = new Edge[transitions.length]; - for (int i = 0; i < copy.length; i++) { - copy[i] = transitions[i] == null ? null : new Edge(transitions[i]); + public Copy(Original original) { + this.original = original; + transitions = copyTransitions(original.transitions); + out = original.out == null ? null : new Out(IntQueue.copyAndConcat(original.out.str, null)); } - return copy; - } - void assign(State other) { - out = other.out; - transitions = other.transitions; + private Edge[] copyTransitions(Edge[] transitions) { + final Edge[] copy = new Edge[transitions.length]; + for (int i = 0; i < copy.length; i++) { + copy[i] = transitions[i] == null ? null : new Edge(transitions[i]); + } + return copy; + } + + public void assign() { + original.out = out; + original.transitions = transitions; + } } + Out out; + Edge[] transitions; + + /** * The IntQueue is consumed and should not be reused after calling this method. */ 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 index 8772bb8d40..ce475855b5 100644 --- 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 @@ -64,7 +64,7 @@ public void testStaticInvocation() { 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))); - final State root = OSTIA.buildPtt(2, samples.iterator()); + final State.Original root = OSTIA.buildPtt(2, samples.iterator()); OSTIA.ostia(root); Assert.assertEquals(OSTIA.run(root, IntSeq.of(1)), IntSeq.of(1)); @@ -74,8 +74,8 @@ public void testStaticInvocation() { 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.assertNull(OSTIA.run(root, IntSeq.of(0, 1, 0, 1, 0))); - Assert.assertNull(OSTIA.run(root, IntSeq.of(0, 1, 0, 1, 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(enabled = false, dataProvider = "sizes") diff --git a/pom.xml b/pom.xml index 8bc743f08d..13fd186744 100644 --- a/pom.xml +++ b/pom.xml @@ -101,6 +101,7 @@ limitations under the License. Aleksander Mendoza-Drosik + aleksander.mendoza.drosik@gmail.com Developer From aa11aa246570e6b7c6995f332f97ba4a5f244ae1 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Sat, 2 Jan 2021 18:50:28 +0100 Subject: [PATCH 17/21] some minor cleanups * refactoring the `State` class * fix issues reported by the code-anylsis tools --- algorithms/passive/ostia/pom.xml | 4 + .../de/learnlib/algorithms/ostia/Blue.java | 6 +- .../de/learnlib/algorithms/ostia/Edge.java | 2 +- .../learnlib/algorithms/ostia/IntQueue.java | 20 +- .../de/learnlib/algorithms/ostia/OSTIA.java | 227 ++++++++++-------- .../de/learnlib/algorithms/ostia/State.java | 64 +++-- .../learnlib/algorithms/ostia/OSTIATest.java | 6 +- 7 files changed, 176 insertions(+), 153 deletions(-) diff --git a/algorithms/passive/ostia/pom.xml b/algorithms/passive/ostia/pom.xml index a85be1639f..5fe54f6b44 100644 --- a/algorithms/passive/ostia/pom.xml +++ b/algorithms/passive/ostia/pom.xml @@ -39,6 +39,10 @@ limitations under the License. net.automatalib automata-api + + net.automatalib + automata-commons-smartcollections + net.automatalib automata-commons-util 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 index e49755648e..24299ad9c7 100644 --- 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 @@ -20,15 +20,15 @@ */ class Blue { - final State.Original parent; + final State parent; final int symbol; - Blue(State.Original parent, int symbol) { + Blue(State parent, int symbol) { this.symbol = symbol; this.parent = parent; } - State.Original state() { + State state() { return parent.transitions[symbol].target; } 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 index b86b45aaf1..d054ffc5b8 100644 --- 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 @@ -21,7 +21,7 @@ class Edge { IntQueue out; - State.Original target; + State target; Edge() {} 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 index 3a45d75211..5caf36a055 100644 --- 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 @@ -32,7 +32,14 @@ class IntQueue { @Override public String toString() { - return str(this); + 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) { @@ -47,17 +54,6 @@ public String toString() { return q; } - static String str(@Nullable IntQueue q) { - final StringJoiner sj = new StringJoiner(", ", "[", "]"); - - IntQueue iter = q; - while (iter != null) { - sj.add(Integer.toString(iter.value)); - iter = iter.next; - } - return sj.toString(); - } - static boolean eq(@Nullable IntQueue a, @Nullable IntQueue b) { IntQueue aIter = a; IntQueue bIter = b; 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 index c503a28624..6b6241605f 100644 --- 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 @@ -15,7 +15,17 @@ */ package de.learnlib.algorithms.ostia; -import java.util.*; +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.Queue; +import java.util.Set; import de.learnlib.api.algorithm.PassiveLearningAlgorithm; import de.learnlib.api.query.DefaultQuery; @@ -29,6 +39,10 @@ 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 */ @@ -36,12 +50,14 @@ public class OSTIA implements PassiveLearningAlgorithm inputAlphabet; private final GrowingAlphabet outputAlphabet; - private final State.Original root; + private final State root; + private boolean hasBeenComputed; public OSTIA(Alphabet inputAlphabet) { this.inputAlphabet = inputAlphabet; this.outputAlphabet = new GrowingMapAlphabet<>(); - this.root = new State.Original(inputAlphabet.size()); + this.root = new State(inputAlphabet.size()); + this.hasBeenComputed = false; } @Override @@ -55,18 +71,17 @@ public void addSamples(Collection>> samples) { } } - private boolean hasBeenComputed = false; @Override public SubsequentialTransducer computeModel() { - if(!hasBeenComputed) { + if (!hasBeenComputed) { hasBeenComputed = true; ostia(root); } return new OSSTWrapper<>(root, inputAlphabet, outputAlphabet); } - public static State.Original buildPtt(int alphabetSize, Iterator> informant) { - final State.Original root = new State.Original(alphabetSize); + 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())); @@ -74,8 +89,8 @@ public static State.Original buildPtt(int alphabetSize, Iterator blue) { + private static void addBlueStates(State parent, Queue blue) { for (int i = 0; i < parent.transitions.length; i++) { if (parent.transitions[i] != null) { - assert !contains(blue,parent.transitions[i].target); - assert parent.transitions[i].target!=parent; + assert !contains(blue, parent.transitions[i].target); + assert parent.transitions[i].target != parent; blue.add(new Blue(parent, i)); } } } - private static boolean disjoint(Queue blue,LinkedHashSet red){ - for(Blue b:blue){ - if(red.contains(b.state()))return false; - } - return true; - } - private static boolean contains(Queue blue,State.Original state){ - for(Blue b:blue){ - if(state.equals(b.state()))return true; - } - return false; - } - private static boolean hasDuplicates(Queue blue){ - final HashSet unique = new HashSet<>(); - for(Blue b:blue){ - if(!unique.add(b.state()))return true; - } - return false; - } - private static boolean validateBlueAndRed(State.Original root, LinkedHashSet red,Queue blue){ - final HashSet reachable = new HashSet<>(); - isTree(root,reachable); - for(State.Original 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.Original root, HashSet nodes){ - final Queue toVisit = new LinkedList<>(); - toVisit.add(root); - boolean isTree = true; - while(!toVisit.isEmpty()){ - final State.Original s = toVisit.poll(); - if(nodes.add(s)) { - for (Edge edge : s.transitions) { - if (edge != null) { - toVisit.add(edge.target); - } - } - }else{ - isTree = false; - } - - } - return isTree; - } - public static void ostia(State.Original transducer) { + public static void ostia(State transducer) { final Queue blue = new LinkedList<>(); - final LinkedHashSet red = new LinkedHashSet<>(); - assert isTree(transducer,new HashSet<>()); + final Set red = new LinkedHashSet<>(); + assert isTree(transducer, new HashSet<>()); red.add(transducer); addBlueStates(transducer, blue); - assert !hasDuplicates(blue); - assert disjoint(blue,red); - assert validateBlueAndRed(transducer,red,blue); + assert uniqueItems(blue); + assert disjoint(blue, red); + assert validateBlueAndRed(transducer, red, blue); blue: while (!blue.isEmpty()) { final Blue next = blue.poll(); - final State.Original blueState = next.state(); + final State blueState = next.state(); assert isTree(blueState, new HashSet<>()); - assert !hasDuplicates(blue); - assert !contains(blue,blueState); - assert disjoint(blue,red); - for (State.Original redState : red) { + 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 !hasDuplicates(blue); + assert disjoint(blue, red); + assert uniqueItems(blue); continue blue; } } assert isTree(blueState, new HashSet<>()); - assert !hasDuplicates(blue); + assert uniqueItems(blue); addBlueStates(blueState, blue); - assert !hasDuplicates(blue); - assert !contains(blue,blueState); - assert disjoint(blue,red); + assert uniqueItems(blue); + assert !contains(blue, blueState); + assert disjoint(blue, red); red.add(blueState); - assert disjoint(blue,red); - assert validateBlueAndRed(transducer,red,blue); + assert disjoint(blue, red); + assert validateBlueAndRed(transducer, red, blue); } } - private static boolean ostiaMerge(Blue blue, State.Original redState, - Queue blueToVisit, LinkedHashSet red) { - final Map merged = new HashMap<>(); + 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; + 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()); + for (Blue reachedBlueCandidate : reachedBlueStates) { + if (red.contains(reachedBlueCandidate.parent)) { + assert !contains(blueToVisit, reachedBlueCandidate.state()); blueToVisit.add(reachedBlueCandidate); } } @@ -242,14 +203,14 @@ private static boolean ostiaMerge(Blue blue, State.Original redState, return false; } - private static boolean ostiaFold(State.Original red, + private static boolean ostiaFold(State red, IntQueue pushedBack, - State.Original blueParent, + State blueParent, int symbolIncomingToBlue, - Map mergedStates, + Map mergedStates, List reachedBlueStates) { - final State.Original blueState = blueParent.transitions[symbolIncomingToBlue].target; - assert red!=blueState; + final State blueState = blueParent.transitions[symbolIncomingToBlue].target; + assert red != blueState; assert !mergedStates.containsKey(blueState); final State.Copy mergedRedState = mergedStates.computeIfAbsent(red, State.Copy::new); final State.Copy mergedBlueState = new State.Copy(blueState); @@ -288,8 +249,8 @@ private static boolean ostiaFold(State.Original red, } else { commonPrefixBluePrev.next = null; } - assert mergedBlueState.transitions[i]!=null; - assert mergedBlueState.transitions[i].target==blueState.transitions[i].target; + assert mergedBlueState.transitions[i] != null; + assert mergedBlueState.transitions[i].target == blueState.transitions[i].target; if (!ostiaFold(transitionRed.target, commonPrefixBlue, blueState, @@ -333,5 +294,71 @@ private static boolean ostiaFold(State.Original red, 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, State state) { + for (Blue b : blue) { + if (state.equals(b.state())) { + return true; + } + } + return false; + } + + private static boolean uniqueItems(Queue blue) { + final Set 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 LinkedList<>(); + toVisit.add(root); + boolean isTree = true; + while (!toVisit.isEmpty()) { + final 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/State.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/State.java index 886170a669..5b7aa765ca 100644 --- 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 @@ -18,44 +18,16 @@ /** * @author Aleksander Mendoza-Drosik */ -class State { - - private State(){ - - } - static class Original extends State { - public Original(int alphabetSize) { - transitions = new Edge[alphabetSize]; - } - - - } - static class Copy extends State { - public final Original original; - - public Copy(Original original) { - this.original = original; - transitions = copyTransitions(original.transitions); - out = original.out == null ? null : new Out(IntQueue.copyAndConcat(original.out.str, null)); - } - - private Edge[] copyTransitions(Edge[] transitions) { - final Edge[] copy = new Edge[transitions.length]; - for (int i = 0; i < copy.length; i++) { - copy[i] = transitions[i] == null ? null : new Edge(transitions[i]); - } - return copy; - } - - public void assign() { - original.out = out; - original.transitions = transitions; - } - } +public class State { Out out; Edge[] transitions; + private State() {} + + State(int alphabetSize) { + transitions = new Edge[alphabetSize]; + } /** * The IntQueue is consumed and should not be reused after calling this method. @@ -91,4 +63,28 @@ void prependButIgnoreMissingStateOutput(IntQueue prefix) { public String toString() { return String.valueOf(out); } + + static class Copy extends State { + + final State original; + + Copy(State original) { + this.original = original; + super.transitions = copyTransitions(original.transitions); + super.out = original.out == null ? null : new Out(IntQueue.copyAndConcat(original.out.str, null)); + } + + private Edge[] copyTransitions(Edge[] transitions) { + final Edge[] copy = new Edge[transitions.length]; + for (int i = 0; i < copy.length; i++) { + copy[i] = transitions[i] == null ? null : new Edge(transitions[i]); + } + return copy; + } + + void assign() { + original.out = out; + original.transitions = transitions; + } + } } 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 index ce475855b5..3198f447f0 100644 --- 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 @@ -64,7 +64,7 @@ public void testStaticInvocation() { 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))); - final State.Original root = OSTIA.buildPtt(2, samples.iterator()); + final State root = OSTIA.buildPtt(2, samples.iterator()); OSTIA.ostia(root); Assert.assertEquals(OSTIA.run(root, IntSeq.of(1)), IntSeq.of(1)); @@ -74,8 +74,8 @@ public void testStaticInvocation() { 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)); + 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(enabled = false, dataProvider = "sizes") From 56b23e73f9f91d9e042d717f4261aa8f0a1a3649 Mon Sep 17 00:00:00 2001 From: Alagris Date: Sat, 2 Jan 2021 20:53:43 +0100 Subject: [PATCH 18/21] brought back formal guarantees --- .../de/learnlib/algorithms/ostia/OSTIA.java | 2 +- .../de/learnlib/algorithms/ostia/State.java | 42 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) 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 index 6b6241605f..4ef126a51e 100644 --- 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 @@ -215,7 +215,7 @@ private static boolean ostiaFold(State red, final State.Copy mergedRedState = mergedStates.computeIfAbsent(red, State.Copy::new); final State.Copy mergedBlueState = new State.Copy(blueState); mergedStates.computeIfAbsent(blueParent, State.Copy::new).transitions[symbolIncomingToBlue].target = red; - final State prevBlue = mergedStates.put(blueState, mergedBlueState); + final State.Copy prevBlue = mergedStates.put(blueState, mergedBlueState); assert prevBlue == null; mergedBlueState.prepend(pushedBack); if (mergedBlueState.out != null) { 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 index 5b7aa765ca..cb23891ed8 100644 --- 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 @@ -23,28 +23,10 @@ public class State { Out out; Edge[] transitions; - private State() {} - State(int alphabetSize) { transitions = new Edge[alphabetSize]; } - /** - * The IntQueue is consumed and should not be reused after calling this method. - */ - void prepend(IntQueue prefix) { - for (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); - } - } - /** * The IntQueue is consumed and should not be reused after calling this method. */ @@ -64,14 +46,16 @@ public String toString() { return String.valueOf(out); } - static class Copy extends State { + static class Copy { + Out out; + Edge[] transitions; final State original; Copy(State original) { this.original = original; - super.transitions = copyTransitions(original.transitions); - super.out = original.out == null ? null : new Out(IntQueue.copyAndConcat(original.out.str, null)); + this.transitions = copyTransitions(original.transitions); + this.out = original.out == null ? null : new Out(IntQueue.copyAndConcat(original.out.str, null)); } private Edge[] copyTransitions(Edge[] transitions) { @@ -86,5 +70,21 @@ void assign() { original.out = out; original.transitions = transitions; } + + /** + * The IntQueue is consumed and should not be reused after calling this method. + */ + void prepend(IntQueue prefix) { + for (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); + } + } } } From 0c97f9900bd1097c57becfb53482a0814c4a826b Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Sat, 2 Jan 2021 22:26:24 +0100 Subject: [PATCH 19/21] aggregate shared data in parent class --- .../de/learnlib/algorithms/ostia/OSTIA.java | 14 ++--- .../de/learnlib/algorithms/ostia/State.java | 55 +---------------- .../learnlib/algorithms/ostia/StateCopy.java | 59 +++++++++++++++++++ .../algorithms/ostia/StateParent.java | 30 ++++++++++ 4 files changed, 99 insertions(+), 59 deletions(-) create mode 100644 algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/StateCopy.java create mode 100644 algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/StateParent.java 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 index 4ef126a51e..19e063149b 100644 --- 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 @@ -185,10 +185,10 @@ public static void ostia(State transducer) { } private static boolean ostiaMerge(Blue blue, State redState, Queue blueToVisit, Set red) { - final Map merged = new HashMap<>(); + 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()) { + for (Map.Entry mergedRedState : merged.entrySet()) { assert mergedRedState.getKey() == mergedRedState.getValue().original; mergedRedState.getValue().assign(); } @@ -207,15 +207,15 @@ private static boolean ostiaFold(State red, IntQueue pushedBack, State blueParent, int symbolIncomingToBlue, - Map mergedStates, + Map mergedStates, List reachedBlueStates) { final State blueState = blueParent.transitions[symbolIncomingToBlue].target; assert red != blueState; assert !mergedStates.containsKey(blueState); - final State.Copy mergedRedState = mergedStates.computeIfAbsent(red, State.Copy::new); - final State.Copy mergedBlueState = new State.Copy(blueState); - mergedStates.computeIfAbsent(blueParent, State.Copy::new).transitions[symbolIncomingToBlue].target = red; - final State.Copy prevBlue = mergedStates.put(blueState, mergedBlueState); + final StateCopy mergedRedState = mergedStates.computeIfAbsent(red, StateCopy::new); + final StateCopy mergedBlueState = new StateCopy(blueState); + mergedStates.computeIfAbsent(blueParent, StateCopy::new).transitions[symbolIncomingToBlue].target = red; + final StateCopy prevBlue = mergedStates.put(blueState, mergedBlueState); assert prevBlue == null; mergedBlueState.prepend(pushedBack); if (mergedBlueState.out != null) { 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 index cb23891ed8..615b9d1625 100644 --- 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 @@ -18,13 +18,11 @@ /** * @author Aleksander Mendoza-Drosik */ -public class State { - - Out out; - Edge[] transitions; +public class State extends StateParent { State(int alphabetSize) { - transitions = new Edge[alphabetSize]; + super.out = null; + super.transitions = new Edge[alphabetSize]; } /** @@ -40,51 +38,4 @@ void prependButIgnoreMissingStateOutput(IntQueue prefix) { out.str = IntQueue.copyAndConcat(prefix, out.str); } } - - @Override - public String toString() { - return String.valueOf(out); - } - - static class Copy { - - Out out; - Edge[] transitions; - final State original; - - Copy(State original) { - this.original = original; - this.transitions = copyTransitions(original.transitions); - this.out = original.out == null ? null : new Out(IntQueue.copyAndConcat(original.out.str, null)); - } - - private Edge[] copyTransitions(Edge[] transitions) { - final Edge[] copy = new Edge[transitions.length]; - for (int i = 0; i < copy.length; i++) { - copy[i] = transitions[i] == null ? null : new Edge(transitions[i]); - } - 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(IntQueue prefix) { - for (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/StateCopy.java b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/StateCopy.java new file mode 100644 index 0000000000..1bcddee10d --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/StateCopy.java @@ -0,0 +1,59 @@ +/* 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; + +/** + * @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 Edge[] copyTransitions(Edge[] transitions) { + final Edge[] copy = new Edge[transitions.length]; + for (int i = 0; i < copy.length; i++) { + copy[i] = transitions[i] == null ? null : new Edge(transitions[i]); + } + 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(IntQueue prefix) { + for (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..df6712a840 --- /dev/null +++ b/algorithms/passive/ostia/src/main/java/de/learnlib/algorithms/ostia/StateParent.java @@ -0,0 +1,30 @@ +/* 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; + +/** + * @author Aleksander Mendoza-Drosik + */ +class StateParent { + + Out out; + Edge[] transitions; + + @Override + public String toString() { + return String.valueOf(out); + } +} From 80f52108041aa912b39694ec83abf4a2d26f0365 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Sat, 2 Jan 2021 23:09:27 +0100 Subject: [PATCH 20/21] add some more test cases --- algorithms/passive/ostia/pom.xml | 5 ++ .../algorithms/ostia/OSSTWrapper.java | 4 +- .../learnlib/algorithms/ostia/OSTIATest.java | 75 ++++++++++++------- .../passive/ostia/src/test/resources/hyp.dot | 16 ++++ 4 files changed, 73 insertions(+), 27 deletions(-) create mode 100644 algorithms/passive/ostia/src/test/resources/hyp.dot diff --git a/algorithms/passive/ostia/pom.xml b/algorithms/passive/ostia/pom.xml index 5fe54f6b44..26a67075ce 100644 --- a/algorithms/passive/ostia/pom.xml +++ b/algorithms/passive/ostia/pom.xml @@ -69,6 +69,11 @@ limitations under the License. learnlib-learner-it-support + + net.automatalib + automata-serialization-dot + test + net.automatalib automata-util 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 index 8507f2a651..66f9499f73 100644 --- 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 @@ -17,7 +17,7 @@ import java.util.ArrayDeque; import java.util.Collection; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Queue; import java.util.Set; @@ -44,7 +44,7 @@ class OSSTWrapper implements SubsequentialTransducer { @Override public Collection getStates() { - final Set cache = new HashSet<>(); + final Set cache = new LinkedHashSet<>(); final Queue queue = new ArrayDeque<>(); queue.add(root); 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 index 3198f447f0..4d752b8f8c 100644 --- 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 @@ -15,6 +15,9 @@ */ 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; @@ -23,19 +26,22 @@ import java.util.List; import java.util.Random; -import com.google.common.collect.Iterators; -import net.automatalib.automata.transducers.MealyMachine; +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; @@ -53,16 +59,20 @@ public static Object[][] sizes() { } /** - * Tests the example from Section 18.3.4 of Colin de la Higuera's book "Grammatical Inference". + * 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() { - // a = 0, b = 1 - final List> samples = 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))); + final List> samples = getExampleSamples(); final State root = OSTIA.buildPtt(2, samples.iterator()); OSTIA.ostia(root); @@ -78,30 +88,45 @@ public void testStaticInvocation() { Assert.assertEquals(OSTIA.run(root, IntSeq.of(0, 1, 0, 1, 1)), IntSeq.of(0, 1, 0, 1, 1)); } - @Test(enabled = false, dataProvider = "sizes") - public void testMealySamples(int size) { + @Test(expectedExceptions = IllegalArgumentException.class) + public void testInconsistentSamples() { + final List> samples = new ArrayList<>(getExampleSamples()); - final MealyMachine automaton = - RandomAutomata.randomMealy(new Random(SEED), size, INPUTS, OUTPUTS); + samples.add(Pair.of(IntSeq.of(0, 1, 0, 1), IntSeq.of(0, 1, 0, 0))); - final OSTIA learner = new OSTIA<>(INPUTS); + OSTIA.buildPtt(2, samples.iterator()); + } - final List> trainingWords = new ArrayList<>(); - final Iterator> testIterator = new WMethodTestsIterator<>(automaton, INPUTS, 0); - Iterators.addAll(trainingWords, testIterator); + @Test + public void testVisualization() throws IOException { + final Alphabet alphabet = Alphabets.integers(0, 1); + final OSTIA learner = new OSTIA<>(alphabet); - for (Word input : trainingWords) { - learner.addSample(input, automaton.computeOutput(input)); - } + final WordBuilder wb = new WordBuilder<>(); + for (Pair p : getExampleSamples()) { + Iterables.addAll(wb, p.getFirst()); + final Word input = wb.toWord(); + wb.clear(); - final SubsequentialTransducer model = learner.computeModel(); + Iterables.addAll(wb, p.getSecond()); + final Word output = wb.toWord(); + wb.clear(); + + learner.addSample(input, output); + } - for (Word input : trainingWords) { - final Word output = model.computeOutput(input); - final Word expectedOutput = automaton.computeOutput(input); + final SubsequentialTransducer model = learner.computeModel(); + final String expectedHyp; - Assert.assertEquals(output, expectedOutput); + 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") 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; + +} From 5fe7822d5eeaa70dc5b764a033396f7c5cc17a76 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Sun, 3 Jan 2021 00:02:23 +0100 Subject: [PATCH 21/21] make null-analysis happy --- .../de/learnlib/algorithms/ostia/Blue.java | 8 ++- .../de/learnlib/algorithms/ostia/Edge.java | 4 +- .../learnlib/algorithms/ostia/IntQueue.java | 5 +- .../algorithms/ostia/OSSTWrapper.java | 4 +- .../de/learnlib/algorithms/ostia/OSTIA.java | 52 ++++++++++++------- .../de/learnlib/algorithms/ostia/Out.java | 6 ++- .../de/learnlib/algorithms/ostia/State.java | 6 ++- .../learnlib/algorithms/ostia/StateCopy.java | 13 +++-- .../algorithms/ostia/StateParent.java | 6 ++- 9 files changed, 69 insertions(+), 35 deletions(-) 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 index 24299ad9c7..5ff88883e5 100644 --- 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 @@ -15,6 +15,8 @@ */ package de.learnlib.algorithms.ostia; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * @author Aleksander Mendoza-Drosik */ @@ -28,8 +30,10 @@ class Blue { this.parent = parent; } - State state() { - return parent.transitions[symbol].target; + @Nullable State state() { + final @Nullable Edge edge = parent.transitions[symbol]; + assert edge != null; + return edge.target; } @Override 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 index d054ffc5b8..e2f9ce1d86 100644 --- 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 @@ -15,12 +15,14 @@ */ package de.learnlib.algorithms.ostia; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * @author Aleksander Mendoza-Drosik */ class Edge { - IntQueue out; + @Nullable IntQueue out; State target; Edge() {} 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 index 5caf36a055..fc91f11339 100644 --- 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 @@ -21,6 +21,7 @@ import net.automatalib.commons.smartcollections.IntSeq; import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.PolyNull; /** * @author Aleksander Mendoza-Drosik @@ -28,7 +29,7 @@ class IntQueue { int value; - IntQueue next; + @Nullable IntQueue next; @Override public String toString() { @@ -79,7 +80,7 @@ static boolean hasCycle(@Nullable IntQueue q) { return false; } - static IntQueue copyAndConcat(@Nullable IntQueue q, @Nullable IntQueue tail) { + static @PolyNull IntQueue copyAndConcat(@Nullable IntQueue q, @PolyNull IntQueue tail) { assert !hasCycle(q) && !hasCycle(tail); if (q == null) { return tail; 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 index 66f9499f73..faca344824 100644 --- 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 @@ -25,6 +25,7 @@ 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; /** @@ -50,7 +51,8 @@ public Collection getStates() { queue.add(root); while (!queue.isEmpty()) { - State s = queue.poll(); + @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) { 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 index 19e063149b..be3b161d4a 100644 --- 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 @@ -15,6 +15,7 @@ */ package de.learnlib.algorithms.ostia; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -24,6 +25,8 @@ 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; @@ -36,6 +39,7 @@ 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; /** @@ -89,9 +93,9 @@ public static State buildPtt(int alphabetSize, Iterator> in return root; } - private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { + private static void buildPttOnward(State ptt, IntSeq input, @Nullable IntQueue output) { State pttIter = ptt; - IntQueue outputIter = output; + @Nullable IntQueue outputIter = output; for (int i = 0; i < input.size(); i++) {//input index final int symbol = input.get(i); @@ -132,7 +136,7 @@ private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { 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.str + + throw new IllegalArgumentException("For input '" + input + "' the state output is '" + pttIter.out + "' but training sample has remaining suffix '" + outputIter + '\''); } pttIter.out = new Out(outputIter); @@ -140,9 +144,10 @@ private static void buildPttOnward(State ptt, IntSeq input, IntQueue output) { private static void addBlueStates(State parent, Queue blue) { for (int i = 0; i < parent.transitions.length; i++) { - if (parent.transitions[i] != null) { - assert !contains(blue, parent.transitions[i].target); - assert parent.transitions[i].target != parent; + final Edge transition = parent.transitions[i]; + if (transition != null) { + assert !contains(blue, transition.target); + assert transition.target != parent; blue.add(new Blue(parent, i)); } } @@ -159,8 +164,10 @@ public static void ostia(State transducer) { assert validateBlueAndRed(transducer, red, blue); blue: while (!blue.isEmpty()) { - final Blue next = blue.poll(); - final State blueState = next.state(); + @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); @@ -204,19 +211,27 @@ private static boolean ostiaMerge(Blue blue, State redState, Queue blueToV } private static boolean ostiaFold(State red, - IntQueue pushedBack, + @Nullable IntQueue pushedBack, State blueParent, int symbolIncomingToBlue, Map mergedStates, List reachedBlueStates) { - final State blueState = blueParent.transitions[symbolIncomingToBlue].target; + 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); - mergedStates.computeIfAbsent(blueParent, StateCopy::new).transitions[symbolIncomingToBlue].target = red; + 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) { @@ -249,8 +264,8 @@ private static boolean ostiaFold(State red, } else { commonPrefixBluePrev.next = null; } - assert mergedBlueState.transitions[i] != null; - assert mergedBlueState.transitions[i].target == blueState.transitions[i].target; + 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, @@ -305,9 +320,9 @@ private static boolean disjoint(Queue blue, Set red) { return true; } - private static boolean contains(Queue blue, State state) { + private static boolean contains(Queue blue, @Nullable State state) { for (Blue b : blue) { - if (state.equals(b.state())) { + if (Objects.equals(state, b.state())) { return true; } } @@ -315,7 +330,7 @@ private static boolean contains(Queue blue, State state) { } private static boolean uniqueItems(Queue blue) { - final Set unique = new HashSet<>(); + final Set<@Nullable State> unique = new HashSet<>(); for (Blue b : blue) { if (!unique.add(b.state())) { return false; @@ -341,11 +356,12 @@ private static boolean validateBlueAndRed(State root, Set red, Queue nodes) { - final Queue toVisit = new LinkedList<>(); + final Queue toVisit = new ArrayDeque<>(); toVisit.add(root); boolean isTree = true; while (!toVisit.isEmpty()) { - final State s = toVisit.poll(); + @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) { 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 index d0ccfdd210..d3fcb26642 100644 --- 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 @@ -15,14 +15,16 @@ */ package de.learnlib.algorithms.ostia; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * @author Aleksander Mendoza-Drosik */ class Out { - IntQueue str; + @Nullable IntQueue str; - Out(IntQueue str) { + Out(@Nullable IntQueue str) { this.str = 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 index 615b9d1625..28e19de774 100644 --- 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 @@ -15,6 +15,8 @@ */ package de.learnlib.algorithms.ostia; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * @author Aleksander Mendoza-Drosik */ @@ -28,8 +30,8 @@ public class State extends StateParent { /** * The IntQueue is consumed and should not be reused after calling this method. */ - void prependButIgnoreMissingStateOutput(IntQueue prefix) { - for (Edge edge : transitions) { + void prependButIgnoreMissingStateOutput(@Nullable IntQueue prefix) { + for (@Nullable Edge edge : transitions) { if (edge != null) { edge.out = IntQueue.copyAndConcat(prefix, edge.out); } 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 index 1bcddee10d..c1daf1b9f0 100644 --- 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 @@ -15,6 +15,8 @@ */ package de.learnlib.algorithms.ostia; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * @author Aleksander Mendoza-Drosik */ @@ -28,10 +30,11 @@ class StateCopy extends StateParent { this.original = original; } - private Edge[] copyTransitions(Edge[] transitions) { - final Edge[] copy = new Edge[transitions.length]; + private static @Nullable Edge[] copyTransitions(@Nullable Edge[] transitions) { + final @Nullable Edge[] copy = new Edge[transitions.length]; for (int i = 0; i < copy.length; i++) { - copy[i] = transitions[i] == null ? null : new Edge(transitions[i]); + @Nullable Edge edge = transitions[i]; + copy[i] = edge == null ? null : new Edge(edge); } return copy; } @@ -44,8 +47,8 @@ void assign() { /** * The IntQueue is consumed and should not be reused after calling this method. */ - void prepend(IntQueue prefix) { - for (Edge edge : transitions) { + void prepend(@Nullable IntQueue prefix) { + for (@Nullable Edge edge : transitions) { if (edge != null) { edge.out = IntQueue.copyAndConcat(prefix, edge.out); } 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 index df6712a840..0ec6e4c218 100644 --- 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 @@ -15,13 +15,15 @@ */ package de.learnlib.algorithms.ostia; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * @author Aleksander Mendoza-Drosik */ class StateParent { - Out out; - Edge[] transitions; + @Nullable Out out; + @Nullable Edge[] transitions; @Override public String toString() {