diff --git a/pom.xml b/pom.xml index afac763a..55f1db95 100644 --- a/pom.xml +++ b/pom.xml @@ -218,11 +218,6 @@ de.learnlib learnlib-util - - org.apache.commons - commons-lang3 - ${commons-lang.version} - tools.aqua jconstraints-core diff --git a/src/main/java/de/learnlib/ralib/automata/Assignment.java b/src/main/java/de/learnlib/ralib/automata/Assignment.java index 55840509..1c777d16 100644 --- a/src/main/java/de/learnlib/ralib/automata/Assignment.java +++ b/src/main/java/de/learnlib/ralib/automata/Assignment.java @@ -17,6 +17,7 @@ package de.learnlib.ralib.automata; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import de.learnlib.ralib.data.Constants; @@ -42,6 +43,31 @@ public Assignment(VarMapping assignment) this.assignment = assignment; } + public RegisterValuation valuation(RegisterValuation registers, ParameterValuation parameters, Constants consts) { + RegisterValuation val = new RegisterValuation(); + for (Map.Entry e : assignment.entrySet()) { + Register x = e.getKey(); + SymbolicDataValue sdv = e.getValue(); + DataValue d = sdv.isParameter() ? parameters.get((Parameter) sdv) : + sdv.isRegister() ? registers.get((Register) sdv) : + sdv.isConstant() ? consts.get((Constant) sdv) : + null; + if (d == null) { + throw new IllegalStateException("Illegal assignment: " + x + " := " + sdv); + } + val.put(x, d); + } + return val; + } + + /* + * @deprecated method is unsafe, use {@link #valuation()} instead + * Method is unsafe because it keeps registers that are not given a new assignment, which can cause + * a discrepancy in the number of registers a location has, depending on the path to the location. + * Method is deprecated rather than removed because the functionality is used by XML automata models. + * Removal of method requires refactoring of XML models. + */ + @Deprecated public RegisterValuation compute(RegisterValuation registers, ParameterValuation parameters, Constants consts) { RegisterValuation val = new RegisterValuation(); List rNames = assignment.keySet().stream().map(k -> k.getName()).toList(); @@ -56,6 +82,12 @@ public RegisterValuation compute(RegisterValuation registers, ParameterValuation val.put(e.getKey(), registers.get((Register) valp)); } else if (valp.isParameter()) { + DataValue dv = parameters.get((Parameter) valp); + for (Map.Entry ep : parameters.entrySet()) { + if (ep.getKey().equals(valp)) { + dv = ep.getValue(); + } + } val.put(e.getKey(), parameters.get((Parameter) valp)); } //TODO: check if we want to copy constant values into vars diff --git a/src/main/java/de/learnlib/ralib/automata/MutableRegisterAutomaton.java b/src/main/java/de/learnlib/ralib/automata/MutableRegisterAutomaton.java index 9604c225..797f7fdd 100644 --- a/src/main/java/de/learnlib/ralib/automata/MutableRegisterAutomaton.java +++ b/src/main/java/de/learnlib/ralib/automata/MutableRegisterAutomaton.java @@ -28,7 +28,6 @@ import de.learnlib.ralib.words.PSymbolInstance; import de.learnlib.ralib.words.ParameterizedSymbol; import net.automatalib.automaton.MutableDeterministic; -import net.automatalib.common.util.Pair; import net.automatalib.word.Word; /** @@ -119,39 +118,6 @@ protected List getTransitions(Word dw) { return tseq; } - protected List> getTransitionsAndValuations(Word dw) { - RegisterValuation vars = RegisterValuation.copyOf(getInitialRegisters()); - RALocation current = initial; - List> tvseq = new ArrayList<>(); - for (PSymbolInstance psi : dw) { - - ParameterValuation pars = ParameterValuation.fromPSymbolInstance(psi); - - Collection candidates = - current.getOut(psi.getBaseSymbol()); - - if (candidates == null) { - return null; - } - - boolean found = false; - for (Transition t : candidates) { - if (t.isEnabled(vars, pars, constants)) { - vars = t.execute(vars, pars, constants); - current = t.getDestination(); - tvseq.add(Pair.of(t, RegisterValuation.copyOf(vars))); - found = true; - break; - } - } - - if (!found) { - return null; - } - } - return tvseq; - } - @Override public RALocation getLocation(Word dw) { List tseq = getTransitions(dw); @@ -300,4 +266,43 @@ public Transition copyTransition(Transition t, RALocation s) { throw new UnsupportedOperationException("Not supported yet."); } + public RARun getRun(Word word) { + int n = word.length(); + RALocation[] locs = new RALocation[n+1]; + RegisterValuation[] vals = new RegisterValuation[n+1]; + PSymbolInstance[] symbols = new PSymbolInstance[n]; + Transition[] transitions = new Transition[n]; + + locs[0] = getInitialState(); + vals[0] = new RegisterValuation(); + + for (int i = 0; i < n; i++) { + symbols[i] = word.getSymbol(i); + ParameterValuation pars = ParameterValuation.fromPSymbolInstance(symbols[i]); + + Collection candidates = locs[i].getOut(symbols[i].getBaseSymbol()); + if (candidates == null) { + return null; + } + + boolean found = false; + + for (Transition t : candidates) { + if (t.isEnabled(vals[i], pars, constants)) { + transitions[i] = t; + vals[i+1] = t.execute(vals[i], pars, constants); + locs[i+1] = t.getDestination(); + found = true; + break; + } + } + + if (!found) { + return null; + } + } + + return new RARun(locs, vals, symbols, transitions); + } + } diff --git a/src/main/java/de/learnlib/ralib/automata/RARun.java b/src/main/java/de/learnlib/ralib/automata/RARun.java new file mode 100644 index 00000000..8a0e509d --- /dev/null +++ b/src/main/java/de/learnlib/ralib/automata/RARun.java @@ -0,0 +1,158 @@ +package de.learnlib.ralib.automata; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import de.learnlib.ralib.automata.output.OutputMapping; +import de.learnlib.ralib.automata.output.OutputTransition; +import de.learnlib.ralib.data.Constants; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.Mapping; +import de.learnlib.ralib.data.RegisterValuation; +import de.learnlib.ralib.data.SymbolicDataValue; +import de.learnlib.ralib.data.SymbolicDataValue.Parameter; +import de.learnlib.ralib.smt.VarsValuationVisitor; +import de.learnlib.ralib.words.PSymbolInstance; +import gov.nasa.jpf.constraints.api.Expression; +import gov.nasa.jpf.constraints.expressions.NumericBooleanExpression; +import gov.nasa.jpf.constraints.expressions.NumericComparator; +import gov.nasa.jpf.constraints.util.ExpressionUtil; + +/** + * Data structure containing the locations, register valuations, symbol instances + * and transitions at each step of a run of a hypothesis over a data word. + * + * @author fredrik + */ +public class RARun { + + private final RALocation[] locations; + private final RegisterValuation[] valuations; + private final PSymbolInstance[] symbols; + private final Transition[] transitions; + + public RARun(RALocation[] locations, RegisterValuation[] valuations, PSymbolInstance[] symbols, Transition[] transitions) { + this.locations = locations; + this.valuations = valuations; + this.symbols = symbols; + this.transitions = transitions; + } + + public RALocation getLocation(int i) { + return locations[i]; + } + + public RegisterValuation getValuation(int i) { + return valuations[i]; + } + + public PSymbolInstance getTransitionSymbol(int i) { + return symbols[i-1]; + } + + public Transition getRATransition(int i) { + return transitions[i-1]; + } + + /** + * Get the guard of the {@code Transition} at index {@code i}. If the {@code Transition} + * is an {@code OutputTransition}, the guard is computed from the transition's + * {@code OutputMapping}. + * + * @param i + * @return + */ + public Expression getGuard(int i) { + Transition transition = getRATransition(i); + if (transition == null) { + return null; + } + return transition instanceof OutputTransition ? + outputGuard((OutputTransition) transition) : + transition.getGuard(); + } + + /** + * Get the guard of the {@code Transition} at index {@code i}. If the {@code Transition} + * is an {@code OutputTransition}, the guard is computed from the transition's + * {@code OutputMapping}. Registers of the guard will be evaluated according to the + * data values from the register valuation at index {@code i}, and constants will be + * evaluated according to {@code consts}. + * + * @param i + * @return + */ + public Expression getGuard(int i, Constants consts) { + Expression guard = getGuard(i); + VarsValuationVisitor vvv = new VarsValuationVisitor(); + Mapping vals = new Mapping<>(); + vals.putAll(getValuation(i)); + vals.putAll(consts); + return vvv.apply(guard, vals); + } + + private Expression outputGuard(OutputTransition t) { + OutputMapping out = t.getOutput(); + + Set params = new LinkedHashSet<>(); + params.addAll(out.getFreshParameters()); + params.addAll(out.getOutput().keySet()); + Set regs = new LinkedHashSet<>(); + regs.addAll(out.getOutput().values()); + + Expression[] expressions = new Expression[params.size()]; + int index = 0; + + // fresh parameters + List prior = new ArrayList<>(); + List fresh = new ArrayList<>(out.getFreshParameters()); + Collections.sort(fresh, (a,b) -> Integer.compare(a.getId(), b.getId())); + for (Parameter p : fresh) { + Expression[] diseq = new Expression[prior.size() + regs.size()]; + int i = 0; + for (Parameter prev : prior) { + diseq[i++] = new NumericBooleanExpression(p, NumericComparator.NE, prev); + } + for (SymbolicDataValue s : regs) { + diseq[i++] = new NumericBooleanExpression(p, NumericComparator.NE, s); + } + expressions[index++] = ExpressionUtil.and(diseq); + prior.add(p); + } + + // mapped parameters + for (Map.Entry e : out.getOutput().entrySet()) { + Parameter p = e.getKey(); + SymbolicDataValue s = e.getValue(); + expressions[index++] = new NumericBooleanExpression(p, NumericComparator.EQ, s); + } + + return ExpressionUtil.and(expressions); + } + + @Override + public String toString() { + if (locations.length == 0) { + return "ε"; + } + + String str = "<" + locations[0] + ", " + valuations[0] + ">"; + for (int i = 1; i < locations.length; i++) { + str = str + + " -- " + + symbols[i-1] + + " -- <" + + locations[i] + + ", " + + valuations[i] + + ">"; + } + + return str; + } +} diff --git a/src/main/java/de/learnlib/ralib/automata/Transition.java b/src/main/java/de/learnlib/ralib/automata/Transition.java index ef7d6f72..190a24b6 100644 --- a/src/main/java/de/learnlib/ralib/automata/Transition.java +++ b/src/main/java/de/learnlib/ralib/automata/Transition.java @@ -53,6 +53,18 @@ public boolean isEnabled(RegisterValuation registers, ParameterValuation paramet return guard.evaluateSMT(SMTUtil.compose(registers, parameters, consts)); } + public RegisterValuation valuation(RegisterValuation registers, ParameterValuation parameters, Constants consts) { + return this.getAssignment().valuation(registers, parameters, consts); + } + + /* + * @deprecated method is unsafe, use {@link #valuation()} instead + * Method is unsafe because it keeps registers that are not given a new assignment, which can cause + * a discrepancy in the number of registers a location has, depending on the path to the location. + * Method is deprecated rather than removed because the functionality is used by XML automata models. + * Removal of method requires refactoring of XML models. + */ + @Deprecated public RegisterValuation execute(RegisterValuation registers, ParameterValuation parameters, Constants consts) { return this.getAssignment().compute(registers, parameters, consts); } diff --git a/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinder.java b/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinder.java index 0f73dc13..0eacb885 100644 --- a/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinder.java +++ b/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinder.java @@ -1,290 +1,374 @@ package de.learnlib.ralib.ceanalysis; -import java.util.*; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import de.learnlib.logging.Category; -import de.learnlib.query.DefaultQuery; -import de.learnlib.ralib.data.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import de.learnlib.ralib.automata.RALocation; +import de.learnlib.ralib.automata.RARun; +import de.learnlib.ralib.ct.CTHypothesis; +import de.learnlib.ralib.ct.CTLeaf; +import de.learnlib.ralib.ct.ClassificationTree; +import de.learnlib.ralib.ct.Prefix; +import de.learnlib.ralib.ct.ShortPrefix; +import de.learnlib.ralib.data.Bijection; +import de.learnlib.ralib.data.Constants; +import de.learnlib.ralib.data.DataType; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.Mapping; +import de.learnlib.ralib.data.RegisterValuation; +import de.learnlib.ralib.data.SymbolicDataValue; import de.learnlib.ralib.data.SymbolicDataValue.Parameter; -import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; -import de.learnlib.ralib.data.util.RemappingIterator; +import de.learnlib.ralib.data.SymbolicDataValue.Register; +import de.learnlib.ralib.data.util.PermutationIterator; import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.ParameterGenerator; -import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.SuffixValueGenerator; -import de.learnlib.ralib.learning.*; -import de.learnlib.ralib.learning.rastar.CEAnalysisResult; -import de.learnlib.ralib.oracles.SDTLogicOracle; +import de.learnlib.ralib.learning.SymbolicSuffix; +import de.learnlib.ralib.oracles.Branching; import de.learnlib.ralib.oracles.TreeOracle; import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; -import de.learnlib.ralib.smt.SMTUtil; -import de.learnlib.ralib.theory.Memorables; +import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.smt.ReplacingValuesVisitor; import de.learnlib.ralib.theory.SDT; +import de.learnlib.ralib.theory.Theory; import de.learnlib.ralib.words.PSymbolInstance; import de.learnlib.ralib.words.ParameterizedSymbol; import gov.nasa.jpf.constraints.api.Expression; import gov.nasa.jpf.constraints.util.ExpressionUtil; import net.automatalib.word.Word; +/** + * Analyzes counterexamples according to the SLλ algorithm. + * + * @author fredrik + */ public class PrefixFinder { - private final TreeOracle sulOracle; + public enum ResultType { + TRANSITION, + LOCATION + } - private TreeOracle hypOracle; + /** + * Container for the result of a counterexample analysis + */ + public record Result(Word prefix, ResultType result) {}; - private Hypothesis hypothesis; + private final CTHypothesis hyp; + private final ClassificationTree ct; - private final SDTLogicOracle sdtOracle; + private final TreeOracle sulOracle; + private final Map teachers; - private final SymbolicSuffixRestrictionBuilder restrictionBuilder; + private final SymbolicSuffixRestrictionBuilder restrBuilder; - private final Constants consts; + private final ConstraintSolver solver; - private SymbolicWord[] candidates; + private final Constants consts; - private final Map candidateCEs = new LinkedHashMap(); - private final Map storedQueries = new LinkedHashMap(); + public PrefixFinder(TreeOracle sulOracle, CTHypothesis hyp, ClassificationTree ct, + Map teachers, SymbolicSuffixRestrictionBuilder restrBuilder, + ConstraintSolver solver, Constants consts) { + this.hyp = hyp; + this.ct = ct; + this.sulOracle = sulOracle; + this.teachers = teachers; + this.restrBuilder = restrBuilder; + this.solver = solver; + this.consts = consts; + } - private static final Logger LOGGER = LoggerFactory.getLogger(PrefixFinder.class); + /** + * Analyze counterexample {@code ce} from right to leaf to find a transition or + * location discrepancy. If a discrepancy is found, returns the prefix which reveals + * the discrepancy, along with a {@code ResultType} indicating the type of discrepancy. + * + * @param ce + * @return + */ + public Result analyzeCounterExample(Word ce) { + RARun run = hyp.getRun(ce); + for (int i = ce.length(); i >= 1; i--) { + RALocation loc = run.getLocation(i-1); + RALocation locNext = run.getLocation(i); + PSymbolInstance symbol = run.getTransitionSymbol(i); + RegisterValuation runValuation = run.getValuation(i-1); + ParameterizedSymbol action = symbol.getBaseSymbol(); + + SymbolicSuffix vNext = new SymbolicSuffix(ce.prefix(i), ce.suffix(ce.length() - i), restrBuilder); + SymbolicSuffix v = new SymbolicSuffix(ce.prefix(i-1), ce.suffix(ce.length() - i + 1), restrBuilder); + + Expression gHyp = run.getGuard(i, consts); + + for (ShortPrefix u : hyp.getLeaf(loc).getShortPrefixes()) { + SDT sdt = sulOracle.treeQuery(u, v); + + Set uVals = hyp.getLeaf(loc).getPrefix(u).getRegisters(); + Mapping uToRunRenaming = valuationRenaming(u, runValuation); + Set> uToRunExtendedRenamings = extendedValuationRenamings(sdt, uVals, run, i); + + Branching branching = sulOracle.getInitialBranching(u, action, sdt); + for (Expression gSul : branching.guardSet()) { + for (Mapping renaming : uToRunExtendedRenamings) { + renaming.putAll(uToRunRenaming); + if (isGuardSatisfied(gSul, renaming, symbol)) { + Optional res = checkTransition(locNext, u, action, vNext, gHyp, gSul); + if (res.isEmpty()) { + res = checkLocation(locNext, u, action, vNext); + } + if (res.isPresent()) { + return res.get(); + } + } + } + } + } + } - public PrefixFinder(TreeOracle sulOracle, TreeOracle hypOracle, - Hypothesis hypothesis, SDTLogicOracle sdtOracle, - Constants consts) { + throw new IllegalStateException("Found no counterexample in " + ce); + } - this.sulOracle = sulOracle; - this.hypOracle = hypOracle; - this.hypothesis = hypothesis; - this.sdtOracle = sdtOracle; - this.consts = consts; - this.restrictionBuilder = sulOracle.getRestrictionBuilder(); - } + /** + * @param u + * @param val + * @return a mapping from the register valuation of {@code u} on the hypothesis to the {@code val} + */ + private Mapping valuationRenaming(Word u, RegisterValuation val) { + // get register mapping for u when run over the hypothesis + RARun uRun = hyp.getRun(u); + RegisterValuation uVal = uRun.getValuation(u.size()); + // create mapping from u's valuation to val + Mapping ret = new Mapping<>(); + for (Map.Entry e : uVal.entrySet()) { + DataValue replace = e.getValue(); + DataValue by = val.get(e.getKey()); + if (by == null) { + by = replace; + } + ret.put(replace, by); + } + return ret; + } - public CEAnalysisResult analyzeCounterexample(Word ce) { - int idx = findIndex(ce); - SymbolicWord sw = new SymbolicWord(candidates[idx].getPrefix(), candidates[idx].getSuffix()); - SDT tqr = null; //storedQueries.get(sw); - if (tqr == null) { - // THIS CAN (possibly) BE DONE WITHOUT A NEW TREE QUERY - tqr = sulOracle.treeQuery(sw.getPrefix(), sw.getSuffix()); - } - CEAnalysisResult result = new CEAnalysisResult(candidates[idx].getPrefix(), - candidates[idx].getSuffix(), - tqr); - - candidateCEs.put(candidates[idx], tqr); - storeCandidateCEs(ce, idx); - - return result; - } - - private int findIndex(Word ce) { - candidates = new SymbolicWord[ce.length()]; - int max = ce.length() - 1; - for (int idx=max; idx>=0; idx = idx-1) { - - Word prefix = ce.prefix(idx); - Word nextPrefix = ce.prefix(idx+1); - - LOGGER.trace(Category.DATASTRUCTURE, "idx: {} ce: {}", idx, ce); - LOGGER.trace(Category.DATASTRUCTURE, "idx: {} prefix: {}", idx, prefix); - LOGGER.trace(Category.DATASTRUCTURE, "idx: {} next: {}", idx, nextPrefix); - - // check for location counterexample ... - // - Word suffix = ce.suffix(ce.length() - nextPrefix.length()); - SymbolicSuffix symSuffix = new SymbolicSuffix(nextPrefix, suffix, restrictionBuilder); - LOC_CHECK: for (Word u : hypothesis.possibleAccessSequences(prefix)) { - Word uAlpha = hypothesis.transformTransitionSequence(nextPrefix, u); - SDT uAlphaResult = sulOracle.treeQuery(uAlpha, symSuffix); - storedQueries.put(new SymbolicWord(uAlpha, symSuffix), uAlphaResult); - - // check if the word is inequivalent to all access sequences - // - for (Word uPrime : hypothesis.possibleAccessSequences(nextPrefix)) { - SDT uPrimeResult = sulOracle.treeQuery(uPrime, symSuffix); - storedQueries.put(new SymbolicWord(uPrime, symSuffix), uPrimeResult); - - LOGGER.trace(Category.DATASTRUCTURE, "idx: {} u: {}", idx, u); - LOGGER.trace(Category.DATASTRUCTURE, "idx: {} ua: {}", idx, uAlpha); - LOGGER.trace(Category.DATASTRUCTURE, "idx: {} u': {}", idx, uPrime); - LOGGER.trace(Category.DATASTRUCTURE, "idx: {} v: {}", idx, symSuffix); - - // different sizes - // - if (!Memorables.typedSize(uPrimeResult.getDataValues()).equals( - Memorables.typedSize(uAlphaResult.getDataValues()))) { - continue; - } + /** + * Create extensions of the valuation from the hypothesis at index {@code id}, and map + * data values from {@code uSDT} to these extended valuations. + * The returned remappings do not include parameters present in the valuation at {@code id} + * (which should be mapped to parameters in {@code uVals}, except for duplicate parameters. + * For example, if the parameters of {@code run} at index {@code id} contain the data values + * 5,5,7 and the valuation contains 5, then the 7 and a single 5 will be considered for the + * extension of the valuation. Similarly, if {@code uSDT} has data values 0,1,2 with 0 in + * {@code uValuation}, then 1,2 will be considered. In this example, this method would + * return the mappings {1->5, 2->7} and {1->7, 2->5}. + * + * @param uSDT sdt for prefix {@code u} + * @param uVals memorable data values of {@code u} + * @param run + * @param id + * @return + */ + private Set> extendedValuationRenamings(SDT uSDT, Set uVals, RARun run, int id) { + Set> empty = new LinkedHashSet<>(); + empty.add(new Mapping<>()); + if (id < 1) { + return empty; + } - // remapping - // - RemappingIterator iterator = new RemappingIterator<>( - uPrimeResult.getDataValues(), uAlphaResult.getDataValues()); + // gather data values from uSDT, and remove values from uValuation + Set sdtVals = new LinkedHashSet<>(uSDT.getDataValues()); + for (DataValue d : uVals) { + sdtVals.remove(d); + } + if (sdtVals.isEmpty()) { + return empty; + } + DataValue[] sdtValsArr = sdtVals.toArray(new DataValue[sdtVals.size()]); - for (Bijection m : iterator) { - if (uAlphaResult.isEquivalent(uPrimeResult, m)) { - continue LOC_CHECK; - } - } + // gather data values from prefix of run at index id + ArrayList runVals = new ArrayList<>(); + for (int i = 1; i <= id-1; i++) { + for (DataValue d : run.getTransitionSymbol(i).getParameterValues()) { + runVals.add(d); + } + } + /* remove data values from valuation. + * may have multiple copies of same data value, which may be mapped to different + * data values in uSDT, so only remove one instance of data values in valuation + */ + for (DataValue d : run.getValuation(id-1).values()) { + runVals = removeFirst(runVals, d); + } + + // compute all possible permutations of mappings between extended uSDT values and run values + Set> renamings = new LinkedHashSet<>(); + PermutationIterator permit = new PermutationIterator(runVals.size()); + for (int[] order : permit) { + Mapping remapping = new Mapping<>(); + boolean valid = true; + for (int i = 0; i < sdtValsArr.length; i++) { + DataValue sdtVal = sdtValsArr[i]; + DataValue runVal = runVals.get(order[i]); + if (!sdtVal.getDataType().equals(runVal.getDataType())) { + valid = false; + break; } - // found a counterexample! - candidates[idx] = new SymbolicWord(uAlpha, symSuffix); - LOGGER.trace(Category.COUNTEREXAMPLE, "Counterexample for location"); - return idx; + remapping.put(sdtVal, runVal); } - - // check for transition counterexample ... - // - if (transitionHasCE(ce, idx-1)) { - LOGGER.trace(Category.COUNTEREXAMPLE, "Counterexample for transition"); - return idx; + if (valid) { + renamings.add(remapping); } } - throw new RuntimeException("should not reach here"); + + return renamings; } -// private Pair checkForCE(Word prefix, SymbolicSuffix suffix, Word transition) { -// SymbolicWord symWord = new SymbolicWord(prefix, suffix); -// SDT resHyp = hypOracle.treeQuery(prefix, suffix); -// SDT resSul; -// if (storedQueries.containsKey(symWord)) -// resSul = storedQueries.get(symWord); -// else { -// resSul = sulOracle.treeQuery(prefix, suffix); -// storedQueries.put(symWord, resSul); -// } -// -// boolean hasCE = sdtOracle.hasCounterexample(prefix, -// resHyp.getSdt(), resHyp.getPiv(), -// resSul.getSdt(), resSul.getPiv(), -// new TransitionGuard(), transition); -// -// return hasCE ? new ImmutablePair(resHyp, resSul) : null; -// } - - private boolean transitionHasCE(Word ce, int idx) { - if (idx+1 >= ce.length()) - return false; - - Word prefix = ce.prefix(idx+1); - - Word suffix = ce.suffix(ce.length() - (idx+1)); - SymbolicSuffix symSuffix = new SymbolicSuffix(prefix, suffix, restrictionBuilder); - - Set> locations = hypothesis.possibleAccessSequences(prefix); - for (Word location : locations) { - Word transition = hypothesis.transformTransitionSequence( - ce.prefix(idx+2), location); - - SDT resHyp = hypOracle.treeQuery(location, symSuffix); - SDT resSul; - SymbolicWord symWord = new SymbolicWord(location, symSuffix); - if (storedQueries.containsKey(symWord)) - resSul = storedQueries.get(symWord); - else { - resSul = sulOracle.treeQuery(location, symSuffix); - storedQueries.put(symWord, resSul); - } - - boolean hasCE = sdtOracle.hasCounterexample(location, - resHyp, - resSul, - ExpressionUtil.TRUE, transition); - - if (hasCE) { - SymbolicWord sw = candidate(location, symSuffix, resSul, resHyp, ce); - // new by falk - candidates[idx+1] = sw; - return true; + /** + * @param list + * @param d + * @return array containing data values of {@code list}, with one occurrence of {@code d} removed + */ + private ArrayList removeFirst(ArrayList list, DataValue d) { + ArrayList ret = new ArrayList<>(); + ret.addAll(list); + for (int i = 0; i < list.size(); i++) { + if (list.get(i).equals(d)) { + ret.remove(i); + break; } - } - return false; - } - - private void storeCandidateCEs(Word ce, int idx) { - if (idx+1 >= ce.length()) - return; - Word prefix = ce.prefix(idx+1); - - Word suffix = ce.suffix(ce.length() - (idx+1)); - SymbolicSuffix symSuffix = new SymbolicSuffix(prefix, suffix, restrictionBuilder); - - Set> locations = hypothesis.possibleAccessSequences(prefix); - for (Word location : locations) { - SymbolicWord symWord = new SymbolicWord(location, symSuffix); - SDT tqr = storedQueries.get(symWord); - - assert tqr != null; - - candidateCEs.put(symWord, tqr); - } - } - - private SymbolicWord candidate(Word prefix, - SymbolicSuffix symSuffix, SDT sdtSul, - SDT sdtHyp, Word ce) { - Word candidate = null; - - Expression expr = sdtOracle.getCEGuard(prefix, sdtSul, sdtHyp); - - Map, Boolean> sulPaths = sulOracle.instantiate(prefix, symSuffix, sdtSul); - for (Word path : sulPaths.keySet()) { - ParameterGenerator parGen = new ParameterGenerator(); - for (PSymbolInstance psi : prefix) { - for (DataType dt : psi.getBaseSymbol().getPtypes()) - parGen.next(dt); - } + } + return ret; + } - VarMapping renaming = new VarMapping<>(); - SuffixValueGenerator svGen = new SuffixValueGenerator(); - for (ParameterizedSymbol ps : symSuffix.getActions()) { - for (DataType dt : ps.getPtypes()) { - Parameter p = parGen.next(dt); - SuffixValue sv = svGen.next(dt); - renaming.put(sv, p); - } + /** + * Check for a transition discrepancy. This is done by checking whether there exists no + * {@code action}-extension of {@code u} in the leaf of {@code loc} that is equivalent + * to the {@code (hypGuard && sulGuard)} extension of {@code u} after {@code v}. + * + * @param loc the source location + * @param u short prefix from leaf of {@code loc} + * @param action the symbol of the next transition + * @param v the suffix after {@code u} and {@code action} + * @param hypGuard guard of {@code action} after {@code u} on the hypothesis + * @param sulGuard guard of {@code action} after {@code u} on the SUL + * @return an {@code Optional} containing the result if there is a transition discrepancy, or an empty {@code Optional} otherwise + */ + private Optional checkTransition(RALocation loc, + ShortPrefix u, + ParameterizedSymbol action, + SymbolicSuffix v, + Expression hypGuard, + Expression sulGuard) { + // rename hyp guard to match RP + ReplacingValuesVisitor rvv = new ReplacingValuesVisitor(); + Expression hypGuardRenamed = rvv.apply(hypGuard, u.getRpBijection().inverse().toVarMapping()); + Expression conjunction = ExpressionUtil.and(hypGuardRenamed, sulGuard); + + // instantiate a representative data value for the conjunction + DataType[] types = action.getPtypes(); + DataValue[] reprDataVals = new DataValue[types.length]; + for (int i = 0; i < types.length; i++) { + Optional reprDataVal = teachers.get(types[i]).instantiate(u, action, conjunction, i+1, consts, solver); + if (reprDataVal.isEmpty()) { + // guard unsat + return Optional.empty(); } - Expression exprR = SMTUtil.renameVars(expr, renaming); + reprDataVals[i] = reprDataVal.get(); + } + PSymbolInstance psi = new PSymbolInstance(action, reprDataVals); + Word uExtSul = u.append(psi); + + // check whether leaf of loc contains an extension of u that is equivalent to uExtSul after v + CTLeaf leaf = hyp.getLeaf(loc); + Iterator> extensions = ct.getExtensions(u, action) + .stream() + .filter(w -> leaf.getPrefixes().contains(w)) + .iterator(); + while (extensions.hasNext()) { + Word uExtHyp = extensions.next(); + SDT uExtHypSDT = sulOracle.treeQuery(uExtHyp, v).toRegisterSDT(uExtHyp, consts); + SDT uExtSulSDT = sulOracle.treeQuery(uExtSul, v).toRegisterSDT(uExtSul, consts); + + if (SDT.equivalentUnderId(uExtHypSDT, uExtSulSDT)) { + return Optional.empty(); // there is an equivalent extension, so no discrepancy + } + } - ParameterValuation pars = ParameterValuation.fromPSymbolWord(path); - Mapping vals = new Mapping<>(); - vals.putAll(pars); - vals.putAll(consts); + // no equivalent extension exists + Result res = new Result(uExtSul, ResultType.TRANSITION); + return Optional.of(res); + } - if (exprR.evaluateSMT(SMTUtil.compose(vals))) { - candidate = path.prefix(prefix.length() + 1); - SymbolicSuffix suffix = new SymbolicSuffix(candidate, ce.suffix(symSuffix.length() - 1), restrictionBuilder); - return new SymbolicWord(candidate, suffix); - } - } - throw new IllegalStateException("No CE transition found"); - } - - public Set> getCounterExamples() { - Set> ces = new LinkedHashSet>(); - for (Map.Entry e : candidateCEs.entrySet()) { - SymbolicWord sw = e.getKey(); - SDT tqr = e.getValue(); - Map, Boolean> cemaps = sulOracle.instantiate(sw.getPrefix(), sw.getSuffix(), tqr); - for (Map.Entry, Boolean> c : cemaps.entrySet()) { - ces.add(new DefaultQuery(c.getKey(), c.getValue())); - } - } - - return ces; - } - - public void setHypothesisTreeOracle(TreeOracle hypOracle) { - this.hypOracle = hypOracle; - } - - public void setHypothesis(Hypothesis hyp) { - this.hypothesis = hyp; - } - - //public void setComponents(Map, LocationComponent> components) { - // this.components = components; - //} + /** + * Check for a location discrepancy. This is done by checking whether there is some + * {@code action}-extension of {@code u} in the leaf of {@code locNext} such that there + * does not exist some short prefix in the leaf of {@code locNext} that is equivalent + * to the {@code action}-extension of {@code u} after {@code v}. + * + * @param locNext the destination location + * @param u short prefix in leaf prior to {@code locNext} in the run + * @param action the symbol of the next transition + * @param v the suffix after {@code u} and {@code action} + * @return an {@code Optional} containing the result if there is a location discrepancy, or an empty {@code Optional} otherwise + */ + private Optional checkLocation(RALocation locNext, + Word u, + ParameterizedSymbol action, + SymbolicSuffix v) { + CTLeaf leafNext = hyp.getLeaf(locNext); + Iterator extensions = ct.getExtensions(u, action) + .stream() + .filter(w -> leafNext.getPrefixes().contains(w)) + .map(w -> leafNext.getPrefix(w)) + .iterator(); + while (extensions.hasNext()) { + Prefix uExtended = extensions.next(); + Bijection uExtBijection = uExtended.getRpBijection(); + boolean noEquivU = true; + for (Prefix uNext : leafNext.getShortPrefixes()) { + Bijection uNextBijection = uNext.getRpBijection(); + Bijection gamma = uNextBijection.compose(uExtBijection.inverse()); + SDT uExtSDT = sulOracle.treeQuery(uExtended, v); + SDT uNextSDT = sulOracle.treeQuery(uNext, v); + if (SDT.equivalentUnderBijection(uNextSDT, uExtSDT, gamma) != null) { + noEquivU = false; + break; + } + } + if (noEquivU) { + Result res = new Result(uExtended, ResultType.LOCATION); + return Optional.of(res); + } + } + + return Optional.empty(); + } + + /** + * Check whether {@code guard} is satisfied by the parameters of {@code symbol}, after renaming + * the parameters of {@code guard} according to {@code renaming}. + * + * @param guard + * @param renaming + * @param symbol + * @return {@code true} if {@code symbol} satisfies {@code guard}, renamed according to {@code renaming} + */ + private boolean isGuardSatisfied(Expression guard, Mapping renaming, PSymbolInstance symbol) { + Mapping mapping = new Mapping<>(); + DataValue[] vals = symbol.getParameterValues(); + ParameterGenerator pgen = new ParameterGenerator(); + + ReplacingValuesVisitor rvv = new ReplacingValuesVisitor(); + Expression guardRenamed = rvv.apply(guard, renaming); + + for (int i = 0; i < vals.length; i++) { + Parameter p = pgen.next(vals[i].getDataType()); + mapping.put(p, vals[i]); + } + mapping.putAll(consts); + + return solver.isSatisfiable(guardRenamed, mapping); + } } diff --git a/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinderResult.java b/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinderResult.java new file mode 100644 index 00000000..ac7a5180 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinderResult.java @@ -0,0 +1,6 @@ +package de.learnlib.ralib.ceanalysis; + +public enum PrefixFinderResult { + TRANSITION, + LOCATION +} diff --git a/src/main/java/de/learnlib/ralib/ct/CTAutomatonBuilder.java b/src/main/java/de/learnlib/ralib/ct/CTAutomatonBuilder.java new file mode 100644 index 00000000..ab6d2ade --- /dev/null +++ b/src/main/java/de/learnlib/ralib/ct/CTAutomatonBuilder.java @@ -0,0 +1,278 @@ +package de.learnlib.ralib.ct; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import de.learnlib.ralib.automata.Assignment; +import de.learnlib.ralib.automata.RALocation; +import de.learnlib.ralib.automata.Transition; +import de.learnlib.ralib.automata.output.OutputMapping; +import de.learnlib.ralib.automata.output.OutputTransition; +import de.learnlib.ralib.data.Bijection; +import de.learnlib.ralib.data.Constants; +import de.learnlib.ralib.data.DataType; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.Mapping; +import de.learnlib.ralib.data.RegisterAssignment; +import de.learnlib.ralib.data.SymbolicDataValue; +import de.learnlib.ralib.data.SymbolicDataValue.Parameter; +import de.learnlib.ralib.data.SymbolicDataValue.Register; +import de.learnlib.ralib.data.VarMapping; +import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.ParameterGenerator; +import de.learnlib.ralib.learning.AutomatonBuilder; +import de.learnlib.ralib.learning.rastar.RaStar; +import de.learnlib.ralib.oracles.Branching; +import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.smt.ReplacingValuesVisitor; +import de.learnlib.ralib.words.OutputSymbol; +import de.learnlib.ralib.words.PSymbolInstance; +import de.learnlib.ralib.words.ParameterizedSymbol; +import gov.nasa.jpf.constraints.api.Expression; +import gov.nasa.jpf.constraints.expressions.NumericBooleanExpression; +import gov.nasa.jpf.constraints.expressions.NumericComparator; +import gov.nasa.jpf.constraints.expressions.PropositionalCompound; +import gov.nasa.jpf.constraints.util.ExpressionUtil; +import net.automatalib.word.Word; + +/** + * Builder class for building a {@link CTHypothesis} from a {@link ClassificationTree}. + * This class implements similar functionality as {@link AutomatonBuilder} and {@link IOAutomatonBuilder}, + * but tailored for the {@link SLLambda} and {@link SLCT} learning algorithms. + * + * {@code CTAutomatonBuilder} supports construction of automata from an incomplete classification tree, + * so long as the classification tree is closed and consistent. + * Multiple short prefixes in the same leaf, as well as one-symbol extensions without matching guards, + * will be ignored during construction. + * The access sequence for each location will be set to the representative prefix of the corresponding leaf. + * + * @author fredrik + */ +public class CTAutomatonBuilder { + + private final ClassificationTree ct; + + private final Map, RALocation> locations; // to keep track of which prefix maps to which location + private final Map leaves; + + private final CTHypothesis hyp; + + private final ConstraintSolver solver; + + private final Constants consts; + + private boolean ioMode; + + public CTAutomatonBuilder(ClassificationTree ct, Constants consts, boolean ioMode, ConstraintSolver solver) { + this.ct = ct; + this.consts = consts; + this.ioMode = ioMode; + this.solver = solver; + + locations = new LinkedHashMap<>(); + leaves = new LinkedHashMap<>(); + hyp = new CTHypothesis(consts, ct.getLeaves().size(), ioMode); + } + + public CTHypothesis buildHypothesis() { + computeLocations(); + computeTransitions(); + hyp.putLeaves(leaves); + Optional sink = ct.getSink(); + if (sink.isPresent()) { + hyp.setSink(hyp.getLocation(sink.get())); + } + return hyp; + } + + private void computeLocations() { + // compute initial location + CTLeaf initial = ct.getLeaf(RaStar.EMPTY_PREFIX); + RALocation l0 = hyp.addInitialState(initial.isAccepting()); + locations.put(RaStar.EMPTY_PREFIX, l0); + for (Word sp : initial.getShortPrefixes()) { + locations.put(sp, l0); + } + hyp.setAccessSequence(l0, RaStar.EMPTY_PREFIX); + leaves.put(initial, l0); + + // compute non-initial locations + for (CTLeaf leaf : ct.getLeaves()) { + if (leaf != initial) { + RALocation l = hyp.addState(leaf.isAccepting()); + hyp.setAccessSequence(l, leaf.getRepresentativePrefix()); + for (Word sp : leaf.getShortPrefixes()) { + locations.put(sp, l); + } + locations.put(leaf.getRepresentativePrefix(), l); + leaves.put(leaf, l); + } + } + } + + private void computeTransitions() { + for (CTLeaf leaf : ct.getLeaves()) { + for (Prefix prefix : leaf.getPrefixes()) { + computeTransition(leaf, prefix); + } + } + } + + private void computeTransition(CTLeaf dest_l, Prefix prefix) { + if (prefix.length() < 1) { + // empty prefix has no transition + return; + } + + Prefix dest_rp = dest_l.getRepresentativePrefix(); + Word src_id = prefix.prefix(prefix.length() - 1); + CTLeaf src_l = ct.getLeaf(src_id); + + assert src_l != null : "Source prefix not present in classification tree: " + src_id; + assert src_l.getPrefix(src_id) instanceof ShortPrefix : "Source prefix is not short: " + src_id; + + RALocation src_loc = locations.get(src_id); + RALocation dest_loc = locations.get(dest_rp); + + assert src_loc != null; + assert dest_loc != null; + + ParameterizedSymbol action = prefix.lastSymbol().getBaseSymbol(); + + Prefix src_prefix = src_l.getPrefix(src_id); + ShortPrefix src_u = (ShortPrefix)(src_prefix instanceof ShortPrefix ? + src_prefix : + src_l.getRepresentativePrefix()); + + // guard + Branching b = src_u.getBranching(action); + Expression guard = b.getBranches().get(prefix); + + // prefix may not yet have a guard if ct is incomplete, so find a corresponding guard + if (guard == null) { + for (Expression g : b.getBranches().values()) { + DataValue[] vals = prefix.lastSymbol().getParameterValues(); + Mapping pars = new Mapping<>(); + for (int i = 0; i < vals.length; i++) { + Parameter p = new Parameter(vals[i].getDataType(), i+1); + pars.put(p, vals[i]); + } + pars.putAll(consts); + if (solver.isSatisfiable(g, pars)) { + guard = g; + break; + } + } + } + + assert guard != null : "No guard for prefix " + prefix; + + for (Transition tr : hyp.getTransitions(src_loc, action)) { + if (tr.getGuard().equals(guard)) { + return; + } + } + + // rename guard to use register mapping of predecessor prefix + ReplacingValuesVisitor rvv = new ReplacingValuesVisitor(); + RegisterAssignment srcAssign = src_u.getAssignment(); + RegisterAssignment rpAssign = src_l.getRepresentativePrefix().getAssignment(); + RegisterAssignment srcAssignRemapped = srcAssign.relabel(registerRemapping(srcAssign, rpAssign, src_u.getRpBijection())); + guard = rvv.apply(guard, srcAssignRemapped); + + // compute register assignment + RegisterAssignment destAssign = dest_rp.getAssignment(); + Bijection remapping = prefix.getRpBijection(); + Assignment assign = AutomatonBuilder.computeAssignment(prefix, srcAssignRemapped, destAssign, remapping); + + Transition t = createTransition(action, guard, src_loc, dest_loc, assign); + + if (t != null) { + hyp.addTransition(src_loc, action, t); + hyp.setTransitionSequence(t, prefix); + } + } + + private Transition createTransition(ParameterizedSymbol action, Expression guard, + RALocation src_loc, RALocation dest_loc, Assignment assignment) { + if (ioMode && !dest_loc.isAccepting()) { + return null; + } + + if (!ioMode || !(action instanceof OutputSymbol)) { + // create input transition + return new Transition(action, guard, src_loc, dest_loc, assignment); + } + + // this is an output transition + + Expression expr = guard; + + VarMapping outmap = new VarMapping<>(); + analyzeExpression(expr, outmap); + + Set fresh = new LinkedHashSet<>(); + ParameterGenerator pgen = new ParameterGenerator(); + for (DataType t : action.getPtypes()) { + Parameter p = pgen.next(t); + if (!outmap.containsKey(p)) { + fresh.add(p); + } + } + + OutputMapping outMap = new OutputMapping(fresh, outmap); + + return new OutputTransition(ExpressionUtil.TRUE, + outMap, (OutputSymbol) action, src_loc, dest_loc, assignment); + } + + + private void analyzeExpression(Expression expr, + VarMapping outmap) { + + if (expr instanceof PropositionalCompound pc) { + analyzeExpression(pc.getLeft(), outmap); + analyzeExpression(pc.getRight(), outmap); + } + else if (expr instanceof NumericBooleanExpression nbe) { + if (nbe.getComparator() == NumericComparator.EQ) { + // FIXME: this is unchecked! + SymbolicDataValue left = (SymbolicDataValue) nbe.getLeft(); + SymbolicDataValue right = (SymbolicDataValue) nbe.getRight(); + + Parameter p = null; + SymbolicDataValue sv = null; + + if (left instanceof Parameter) { + if (right instanceof Parameter) { + throw new UnsupportedOperationException("not implemented yet."); + } + else { + p = (Parameter) left; + sv = right; + } + } + else { + p = (Parameter) right; + sv = left; + } + + outmap.put(p, sv); + } + } + } + + private VarMapping registerRemapping(RegisterAssignment raa, RegisterAssignment rab, Bijection bijection) { + VarMapping ret = new VarMapping<>(); + + for (Map.Entry be : bijection.entrySet()) { + Register replace = raa.get(be.getKey()); + Register by = rab.get(be.getValue()); + ret.put(replace, by); + } + + return ret; + } +} diff --git a/src/main/java/de/learnlib/ralib/ct/CTBranch.java b/src/main/java/de/learnlib/ralib/ct/CTBranch.java new file mode 100644 index 00000000..0d087902 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/ct/CTBranch.java @@ -0,0 +1,71 @@ +package de.learnlib.ralib.ct; + +import java.util.Set; + +import de.learnlib.ralib.data.Bijection; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.util.RemappingIterator; +import de.learnlib.ralib.smt.ConstraintSolver; + +/** + * Branch forming an edge between two nodes in a {@link ClassificationTree}. + * The branch is labeled by the representative path of the first prefix which + * was sifted through the branch. + * + * @author fredrik + * + * @see CTNode + * @see CTPath + */ +public class CTBranch { + private final CTNode child; + + private final CTPath reprPath; + + public CTBranch(CTPath path, CTNode child) { + this.reprPath = path; + this.child = child; + } + + public CTPath getRepresentativePath() { + return reprPath; + } + + public CTNode getChild() { + return child; + } + + /** + * Check whether the SDTs of {@code other} are equivalent to the SDTs of {@code this}. + * under the same {@link Bijection}. If so, returns the {@code Bijection} under which the {@code SDT}s + * are equivalent. + * + * @param other the path to compare for equivalence + * @param solver constraint solver to use when comparing for equivalence + * @return {@code Bijection} under which the {@code SDT}s of {@code other} are equivalent to those of {@code this}, or {@code null} if the {@code SDT}s are not equivalent. + * @see SDT + */ + public Bijection matches(CTPath other, ConstraintSolver solver) { + if (!reprPath.typeSizesMatch(other)) { + return null; + } + + Set regs = reprPath.getMemorable(); + Set otherRegs = other.getMemorable(); + + RemappingIterator it = new RemappingIterator<>(otherRegs, regs); + + for (Bijection vars : it) { + if (reprPath.isEquivalent(other, vars, solver)) { + return vars; + } + } + + return null; + } + + @Override + public String toString() { + return child.toString(); + } +} diff --git a/src/main/java/de/learnlib/ralib/ct/CTHypothesis.java b/src/main/java/de/learnlib/ralib/ct/CTHypothesis.java new file mode 100644 index 00000000..e2938965 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/ct/CTHypothesis.java @@ -0,0 +1,181 @@ +package de.learnlib.ralib.ct; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import de.learnlib.ralib.automata.Assignment; +import de.learnlib.ralib.automata.InputTransition; +import de.learnlib.ralib.automata.RALocation; +import de.learnlib.ralib.automata.RARun; +import de.learnlib.ralib.automata.Transition; +import de.learnlib.ralib.automata.output.OutputMapping; +import de.learnlib.ralib.automata.output.OutputTransition; +import de.learnlib.ralib.data.Constants; +import de.learnlib.ralib.data.ParameterValuation; +import de.learnlib.ralib.data.RegisterValuation; +import de.learnlib.ralib.data.VarMapping; +import de.learnlib.ralib.learning.Hypothesis; +import de.learnlib.ralib.words.InputSymbol; +import de.learnlib.ralib.words.OutputSymbol; +import de.learnlib.ralib.words.PSymbolInstance; +import de.learnlib.ralib.words.ParameterizedSymbol; +import gov.nasa.jpf.constraints.util.ExpressionUtil; +import net.automatalib.word.Word; + +/** + * Hypothesis constructed from a {@link ClassificationTree}. Maintains a mapping between + * locations in the hypothesis and leaf nodes of the {@code ClassificationTree} from which the + * hypothesis was constructed. + * + * @author fredrik + * @see Hypothesis + */ +public class CTHypothesis extends Hypothesis { + + private final BiMap leaves; + private RALocation sink = null; + private final boolean ioMode; + + public CTHypothesis(Constants consts, int leaves, boolean ioMode) { + super(consts); + this.ioMode = ioMode; + this.leaves = HashBiMap.create(leaves); + } + + public CTHypothesis(Constants consts, Map leaves, boolean ioMode) { + super(consts); + this.ioMode = ioMode; + this.leaves = HashBiMap.create(leaves.size()); + this.leaves.putAll(leaves); + } + + public void putLeaves(Map leaves) { + this.leaves.putAll(leaves); + } + + public void setSink(RALocation sink) { + this.sink = sink; + } + + public RALocation getSink() { + return sink; + } + + @Override + public @Nullable RALocation getSuccessor(RALocation state, ParameterizedSymbol input) { + return super.getSuccessor(state, input); + } + + /** + * Get location corresponding to {@code leaf}. + * + * @param leaf + * @return {@link RALocation} corresponding to {@code leaf} + */ + public RALocation getLocation(CTLeaf leaf) { + return leaves.get(leaf); + } + + /** + * Get leaf node of {@link ClassificationTree} from which this hypothesis was constructed. + * which corresponds to {@code location}. + * + * @param location + * @return leaf node corresponding to {@code location} + * @see CTLeaf + * @see RALocation + */ + public CTLeaf getLeaf(RALocation location) { + return leaves.inverse().get(location); + } + + @Override + protected List getTransitions(Word dw) { + List tseq = new LinkedList<>(); + RARun run = getRun(dw); + for (int i = 1; i <= dw.size(); i++) { + tseq.add(run.getRATransition(i)); + } + return tseq; + } + + @Override + public RALocation getLocation(Word dw) { + return getRun(dw).getLocation(dw.size()); + } + + @Override + public RARun getRun(Word word) { + int n = word.length(); + RALocation[] locs = new RALocation[n+1]; + RegisterValuation[] vals = new RegisterValuation[n+1]; + PSymbolInstance[] symbols = new PSymbolInstance[n]; + Transition[] transitions = new Transition[n]; + + locs[0] = getInitialState(); + vals[0] = new RegisterValuation(); + + for (int i = 0; i < n; i++) { + symbols[i] = word.getSymbol(i); + ParameterValuation pars = ParameterValuation.fromPSymbolInstance(symbols[i]); + + Collection candidates = locs[i].getOut(symbols[i].getBaseSymbol()); + if (candidates == null) { + return null; + } + + boolean found = false; + boolean output = candidates.isEmpty() ? false : + candidates.iterator().next() instanceof OutputTransition; + + for (Transition t : candidates) { + if (t.isEnabled(vals[i], pars, constants)) { + transitions[i] = t; + vals[i+1] = t.valuation(vals[i], pars, constants); + locs[i+1] = t.getDestination(); + found = true; + break; + } + } + + if (!found) { + // in IO automata, invalid sequences go to sink + if (ioMode) { + if ((symbols[i].getBaseSymbol() instanceof OutputSymbol && (output || candidates.isEmpty())) + || locs[i].equals(sink)) { + vals[i+1] = new RegisterValuation(); + locs[i+1] = getSink(); + transitions[i] = createSinkTransition(locs[i], locs[i+1], symbols[i].getBaseSymbol()); + } + } else { + return null; + } + } + } + + return new RARun(locs, vals, symbols, transitions); + } + + private Transition createSinkTransition(RALocation src, RALocation dest, ParameterizedSymbol ps) { + if (ps instanceof OutputSymbol) { + return new OutputTransition(new OutputMapping(), + (OutputSymbol) ps, + src, dest, + new Assignment(new VarMapping<>())); + } + if (ps instanceof InputSymbol) { + return new InputTransition(ExpressionUtil.TRUE, + (InputSymbol) ps, + src, dest, + new Assignment(new VarMapping<>())); + } + throw new IllegalArgumentException("Not input or output symbol: " + ps); + } +} diff --git a/src/main/java/de/learnlib/ralib/ct/CTInnerNode.java b/src/main/java/de/learnlib/ralib/ct/CTInnerNode.java new file mode 100644 index 00000000..0ff533a8 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/ct/CTInnerNode.java @@ -0,0 +1,151 @@ +package de.learnlib.ralib.ct; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import de.learnlib.ralib.data.Bijection; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.learning.SymbolicSuffix; +import de.learnlib.ralib.oracles.TreeOracle; +import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.words.PSymbolInstance; +import de.learnlib.ralib.words.ParameterizedSymbol; +import net.automatalib.word.Word; + +/** + * Inner node of a {@link ClassificationTree}, containing a {@link SymbolicSuffix}. + * Maintains a set of branches to child nodes. + * + * @author fredrik + * @see CTBranch + * @see CTNode + */ +public class CTInnerNode extends CTNode { + private final SymbolicSuffix suffix; + private final List branches; + + public CTInnerNode(CTNode parent, SymbolicSuffix suffix) { + super(parent); + this.suffix = suffix; + branches = new ArrayList<>(); + } + + public SymbolicSuffix getSuffix() { + return suffix; + } + + public List getBranches() { + return branches; + } + + protected CTBranch getBranch(CTNode child) { + for (CTBranch b : getBranches()) { + if (b.getChild() == child) { + return b; + } + } + return null; + } + + @Override + protected CTLeaf sift(Prefix prefix, TreeOracle oracle, ConstraintSolver solver, boolean ioMode) { + CTPath path = CTPath.computePath(oracle, prefix, getSuffixes(), ioMode); + + // find a matching branch and sift to child + for (CTBranch b : branches) { + Bijection vars = b.matches(path, solver); + if (vars != null) { + prefix = new Prefix(prefix, vars, path); + return b.getChild().sift(prefix, oracle, solver, ioMode); + } + } + + // no child with equivalent SDTs, create a new leaf + prefix = new Prefix(prefix, path); + CTLeaf leaf = new CTLeaf(prefix, this); + CTBranch branch = new CTBranch(path, leaf); + branches.add(branch); + return leaf; + } + + /** + * Replace {@code leaf} with a new {@link CTInnerNode} containing {@code suffix}. + * The prefixes in {@code leaf} will be sifted into this new inner node. + * If this sifting creates a new {@link CTLeaf}, the first prefix to be sifted + * into that leaf node will be made the representative prefix. + * + * @param leaf + * @param suffix + * @param oracle + * @param solver + * @param ioMode + * @param inputs + * @return a mapping of prefixes in {@code leaf} to their new leaf nodes + */ + protected Map, CTLeaf> refine(CTLeaf leaf, SymbolicSuffix suffix, TreeOracle oracle, ConstraintSolver solver, boolean ioMode, ParameterizedSymbol[] inputs) { + CTBranch b = getBranch(leaf); + assert b != null : "Node is not the parent of leaf " + leaf; + assert !getSuffixes().contains(suffix) : "Duplicate suffix: " + suffix; + + Set shorts = leaf.getShortPrefixes(); + + // replace leaf with a new inner node, with same path as leaf + CTInnerNode newNode = new CTInnerNode(this, suffix); + CTBranch newBranch = new CTBranch(b.getRepresentativePath(), newNode); + branches.remove(b); + branches.add(newBranch); + + // sift leaf's RP into the new inner node + Map, CTLeaf> leaves = new LinkedHashMap<>(); + CTLeaf l = sift(leaf.getRepresentativePrefix(), oracle, solver, ioMode); + leaves.put(leaf.getRepresentativePrefix(), l); + + // resift leaf's prefixes into this node (except the RP, which was already sifted) + Set prefixes = new LinkedHashSet<>(leaf.getPrefixes()); + prefixes.remove(leaf.getRepresentativePrefix()); + + for (Prefix u : prefixes) { + l = sift(u, oracle, solver, ioMode); + leaves.put(u, l); + } + + // make sure all short prefixes of leaf are still short + for (ShortPrefix u : shorts) { + if (!(u instanceof ShortPrefix)) { + leaves.get(u).elevatePrefix(u, oracle, inputs); + } + } + + return leaves; + } + + @Override + public List getSuffixes() { + List suffixes = new ArrayList<>(); + suffixes.add(suffix); + if (getParent() == null) { + return suffixes; + } + + suffixes.addAll(getParent().getSuffixes()); + return suffixes; + } + + /** + * @return {@code false} + */ + @Override + public boolean isLeaf() { + return false; + } + + @Override + public String toString() { + return "(" + suffix + ")"; + } + +} diff --git a/src/main/java/de/learnlib/ralib/ct/CTLeaf.java b/src/main/java/de/learnlib/ralib/ct/CTLeaf.java new file mode 100644 index 00000000..10621ed6 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/ct/CTLeaf.java @@ -0,0 +1,213 @@ +package de.learnlib.ralib.ct; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import de.learnlib.ralib.data.Bijection; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.learning.LocationComponent; +import de.learnlib.ralib.learning.PrefixContainer; +import de.learnlib.ralib.learning.SymbolicSuffix; +import de.learnlib.ralib.oracles.Branching; +import de.learnlib.ralib.oracles.TreeOracle; +import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.words.PSymbolInstance; +import de.learnlib.ralib.words.ParameterizedSymbol; +import net.automatalib.word.Word; + +/** + * Leaf node of a {@link ClassificationTree}, containing a number of prefixes. + * A leaf node must contain at least one prefix, which is the representative + * prefix of the leaf. Any number of prefixes may also be short prefixes. + * + * @author fredrik + * @see Prefix + * @see ShortPrefix + */ +public class CTLeaf extends CTNode implements LocationComponent { + private Prefix rp; + private final Set shortPrefixes; + private final Set prefixes; + + public CTLeaf(Prefix rp, CTNode parent) { + super(parent); + if (parent == null) { + throw new IllegalArgumentException("A leaf must have a parent"); + } + this.rp = rp; + shortPrefixes = new LinkedHashSet<>(); + prefixes = new LinkedHashSet<>(); + prefixes.add(rp); + if (rp instanceof ShortPrefix) { + ((ShortPrefix) rp).updateBranching(); + } + } + + @Override + public List getSuffixes() { + return getParent().getSuffixes(); + } + + /** + * @return all prefixes contained within this leaf + */ + public Set getPrefixes() { + return prefixes; + } + + /** + * Helper method for retrieving the {@link Prefix} representation of a {@code Word}. + * + * @param u + * @return the {@code Prefix} form of {@code u} if {@code u} is in this leaf, or {@code null} otherwise + */ + public Prefix getPrefix(Word u) { + for (Prefix p : prefixes) { + if (p.equals(u)) { + return p; + } + } + return null; + } + + /** + * @return all short prefixes in this leaf + */ + public Set getShortPrefixes() { + return shortPrefixes; + } + + /** + * @return the representative prefix of this leaf + */ + public Prefix getRepresentativePrefix() { + return rp; + } + + /** + * @return {@code true} + */ + @Override + public boolean isLeaf() { + return true; + } + + /** + * Returns {@code true} if this leaf is accepting, i.e., if a tree query for this + * leaf's representative prefix with the empty suffix (ε) is accepting. + * + * @return {@code true} if this leaf corresponds to an accepting location + */ + public boolean isAccepting() { + return rp.getPath().isAccepting(); + } + + /** + * Adds {@code prefix} to this leaf. If {@code prefix} is a short prefix, this method updates + * the branching of {@code prefix} to include initial guards from the conjunction of all SDTs + * along its path. + * + * @param prefix that will be added to this leaf + * @param oracle unused + * @param solver unused + * @param ioMode unused + * @return {@code this} + */ + @Override + protected CTLeaf sift(Prefix prefix, TreeOracle oracle, ConstraintSolver solver, boolean ioMode) { + prefixes.add(prefix); + if (prefix instanceof ShortPrefix) { + ShortPrefix sp = (ShortPrefix) prefix; + shortPrefixes.add(sp); + sp.updateBranching(); + } + return this; + } + + /** + * Elevate {@code u} to a short prefix by converting it to a {@link ShortPrefix}. The branching + * of {@code u} will be initialized to reflect the initial guards of the conjunction of SDTs + * along its path. + * + * @param u the prefix to elevate + * @param oracle tree oracle to use for updating the branching + * @param inputs all input symbols + * @return {@code u} as a {@code ShortPrefix} + * @see Branching + * @see SDT + * @see CTPath + */ + protected ShortPrefix elevatePrefix(Word u, TreeOracle oracle, ParameterizedSymbol ... inputs) { + Prefix prefix = getPrefix(u); + assert !(prefix instanceof ShortPrefix) : "Prefix is already short: " + prefix; + prefixes.remove(prefix); + ShortPrefix sp = new ShortPrefix(prefix, oracle, inputs); + shortPrefixes.add(sp); + prefixes.add(sp); + + if (prefix == rp) { + rp = sp; + } + return sp; + } + + @Override + public String toString() { + String str = "{RP:[" + rp.toString() + "]"; + for (Prefix u : prefixes) { + if (u != rp) { + str = str + ", " + (u instanceof ShortPrefix ? "SP:[" : "[") + u.toString() + "]"; + } + } + return str + "}"; + } + + /** + * @return this leaf's representative prefix + */ + @Override + public Word getAccessSequence() { + return getRepresentativePrefix(); + } + + @Override + public Bijection getRemapping(PrefixContainer r) { + assert r instanceof Prefix; + return ((Prefix) r).getRpBijection(); + } + + /** + * Get the branching for symbol {@code action} of the representative prefix. + * Requires that the representative prefix is a {@link ShortPrefix}. + * + * @param action + * @return {@code Branching} of {@code action} for the representative prefix of this leaf + */ + @Override + public Branching getBranching(ParameterizedSymbol action) { + assert rp instanceof ShortPrefix : "Representative prefix is not a short prefix"; + return ((ShortPrefix) rp).getBranching(action); + } + + /** + * @return the representative prefix of this leaf + */ + @Override + public PrefixContainer getPrimePrefix() { + return getRepresentativePrefix(); + } + + /** + * @return {@code Collection} of all prefixes in this leaf other than the representative prefix + */ + @Override + public Collection getOtherPrefixes() { + Collection prefs = new LinkedList<>(); + prefs.addAll(prefixes); + prefs.remove(rp); + return prefs; + } +} diff --git a/src/main/java/de/learnlib/ralib/ct/CTNode.java b/src/main/java/de/learnlib/ralib/ct/CTNode.java new file mode 100644 index 00000000..ec9c9a39 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/ct/CTNode.java @@ -0,0 +1,56 @@ +package de.learnlib.ralib.ct; + +import java.util.List; + +import de.learnlib.ralib.learning.SymbolicSuffix; +import de.learnlib.ralib.oracles.TreeOracle; +import de.learnlib.ralib.smt.ConstraintSolver; + +/** + * Node of a {@link ClassificationTree}. + * + * @author fredrik + */ +public abstract class CTNode { + private final CTNode parent; + + public CTNode(CTNode parent) { + this.parent = parent; + } + + /** + * + * @return immediate ancestor of this node + */ + public CTNode getParent() { + return parent; + } + + /** + * @return all symbolic suffixes from all the ancestor nodes. + */ + public abstract List getSuffixes(); + + /** + * + * @return {@code true} if this node is a {@link CTLeaf} + */ + public abstract boolean isLeaf(); + + /** + * Sifts {@code prefix} into the node. If this is a {@link CTLeaf}, adds {@code prefix} to it. + * If this is a {@link CTInnerNode}, a tree query is made for {@code prefix} with the node's + * {@link SymbolicSuffix}, after which {@code prefix} is sifted to the child whose path + * matches the tree query, determined with a call to {@link CTBranch#matches(CTPath, ConstraintSolver)}. + * If no such child exists, this method creates a new {@code CTLeaf} and adds {@code prefix} + * to it as its representative prefix. + * + * @param prefix the prefix to sift through this node + * @param oracle the {@link TreeOracle} to use when making tree queries + * @param solver the {@link ConstraintSolver} to use for comparing paths for equivalence + * @param ioMode {@code true} if using IO mode + * @return the {@code CTLeaf} node to which {@code prefix} is sifted + * @see CTPath + */ + protected abstract CTLeaf sift(Prefix prefix, TreeOracle oracle, ConstraintSolver solver, boolean ioMode); +} diff --git a/src/main/java/de/learnlib/ralib/ct/CTPath.java b/src/main/java/de/learnlib/ralib/ct/CTPath.java new file mode 100644 index 00000000..3cdf406c --- /dev/null +++ b/src/main/java/de/learnlib/ralib/ct/CTPath.java @@ -0,0 +1,164 @@ +package de.learnlib.ralib.ct; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import de.learnlib.ralib.data.Bijection; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.util.DataUtils; +import de.learnlib.ralib.learning.SymbolicSuffix; +import de.learnlib.ralib.learning.rastar.RaStar; +import de.learnlib.ralib.oracles.TreeOracle; +import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.theory.SDT; + +/** + * This data structure stores the SDTs from tree queries for a prefix along a path + * in a {@link ClassificationTree}. It contains much of the same functionality as + * {@link Row}, but adapted for use with classification trees. + * + * @author fredrik + * @author falk + * @see Row + */ +public class CTPath { + private final Map sdts; + private final MemorableSet memorable; + + private boolean ioMode; + + public CTPath(boolean ioMode) { + this.sdts = new LinkedHashMap<>(); + this.memorable = new MemorableSet(); + this.ioMode = ioMode; + } + + public void putSDT(SymbolicSuffix suffix, SDT sdt) { + assert !sdts.containsKey(suffix); + sdts.put(suffix, sdt); + memorable.addAll(sdt.getDataValues()); + } + + public MemorableSet getMemorable() { + return memorable; + } + + public SDT getSDT(SymbolicSuffix suffix) { + return sdts.get(suffix); + } + + public Map getSDTs() { + return sdts; + } + + public boolean isAccepting() { + SDT s = sdts.get(RaStar.EMPTY_SUFFIX); + return s.isAccepting(); + } + + /** + * Checks whether two paths are equivalent under {@code renaming}. + * + * @param other + * @param renaming + * @param solver + * @return {@code true} if the SDTs of {@code this} are equivalent to those of {@code other} under {@code renaming} + */ + public boolean isEquivalent(CTPath other, Bijection renaming, ConstraintSolver solver) { + if (!typeSizesMatch(other)) { + return false; + } + + if (!memorable.equals(other.memorable.relabel(renaming))) { + return false; + } + + for (Map.Entry e : sdts.entrySet()) { + SDT sdt1 = e.getValue(); + SDT sdt2 = other.sdts.get(e.getKey()); + + if (!sdt1.isEquivalent(sdt2, renaming)) { + return false; + } + } + return true; + } + + /** + * Checks whether the SDTs of {@code this} and {@code other} have the same number of + * data value types. + * + * @param other + * @return {@code true} if the number of types match for the SDTs of {@code this} and {@code other} + */ + protected boolean typeSizesMatch(CTPath other) { + if (!DataUtils.typedSize(memorable).equals(DataUtils.typedSize(other.memorable))) { + return false; + } + + for (Map.Entry e : sdts.entrySet()) { + SDT sdt1 = e.getValue(); + SDT sdt2 = other.sdts.get(e.getKey()); + + if (ioMode) { + if (sdt1 == null && sdt2 == null) { + continue; + } + + if (sdt1 == null || sdt2 == null) { + return false; + } + } + + if (!equalTypeSizes(sdt1.getDataValues(), sdt2.getDataValues())) { + return false; + } + } + return true; + } + + private static boolean equalTypeSizes(Set s1, Set s2) { + return DataUtils.typedSize(s1).equals(DataUtils.typedSize(s2)); + } + + /** + * Computes the path for {@code prefix} by computing SDTs for each suffix in {@code suffixes}. + * The SDTs already contained within the path {@code prefix} will be copied to the new path. + * Remaining SDTs are computed via tree queries. + * + * @param oracle the oracle to use for tree queries + * @param prefix the prefix for which the new path is to be computed + * @param suffixes the suffixes along the path + * @param ioMode {@code true} if the language being learned is an IO language + * @return a {@code CTPath} containing SDTs for each suffix in {@code suffixes} + */ + public static CTPath computePath(TreeOracle oracle, Prefix prefix, List suffixes, boolean ioMode) { + CTPath r = new CTPath(ioMode); + SDT sdt = prefix.getSDT(RaStar.EMPTY_SUFFIX); + sdt = sdt == null ? oracle.treeQuery(prefix, RaStar.EMPTY_SUFFIX) : sdt; + r.putSDT(RaStar.EMPTY_SUFFIX, sdt); + for (SymbolicSuffix s : suffixes) { + sdt = prefix.getSDT(s); + if (sdt == null) { + sdt = oracle.treeQuery(prefix, s); + } + + if (r.getSDT(s) == null) { + r.putSDT(s, sdt); + } + } + + return r; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Map.Entry e : this.sdts.entrySet()) { + sb.append("[").append(e.getKey()).append("->").append(e.getValue().toString().replaceAll("\\s+", " ")).append("] "); + } + return sb.toString(); + } +} diff --git a/src/main/java/de/learnlib/ralib/ct/ClassificationTree.java b/src/main/java/de/learnlib/ralib/ct/ClassificationTree.java new file mode 100644 index 00000000..d72c7ab8 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/ct/ClassificationTree.java @@ -0,0 +1,811 @@ +package de.learnlib.ralib.ct; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import de.learnlib.ralib.data.Bijection; +import de.learnlib.ralib.data.Constants; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.Mapping; +import de.learnlib.ralib.data.ParameterValuation; +import de.learnlib.ralib.data.SymbolicDataValue; +import de.learnlib.ralib.data.SymbolicDataValue.Parameter; +import de.learnlib.ralib.data.SymbolicDataValue.Register; +import de.learnlib.ralib.data.util.RemappingIterator; +import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.ParameterGenerator; +import de.learnlib.ralib.learning.SymbolicSuffix; +import de.learnlib.ralib.learning.rastar.RaStar; +import de.learnlib.ralib.oracles.Branching; +import de.learnlib.ralib.oracles.TreeOracle; +import de.learnlib.ralib.oracles.mto.OptimizedSymbolicSuffixBuilder; +import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; +import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.smt.ReplacingValuesVisitor; +import de.learnlib.ralib.theory.SDT; +import de.learnlib.ralib.words.DataWords; +import de.learnlib.ralib.words.OutputSymbol; +import de.learnlib.ralib.words.PSymbolInstance; +import de.learnlib.ralib.words.ParameterizedSymbol; +import gov.nasa.jpf.constraints.api.Expression; +import net.automatalib.word.Word; + +/** + * Data structure for a classification tree. Implements methods for sifting new prefixes into the tree + * and refining the tree with additional symbolic suffixes, as well as closedness and consistency checks. + * + * @author fredrik + */ +public class ClassificationTree { + private final CTInnerNode root; + + private final Map, CTLeaf> prefixes; + private final Set> shortPrefixes; + + private final ConstraintSolver solver; + private final TreeOracle oracle; + private final SymbolicSuffixRestrictionBuilder restrBuilder; + private final OptimizedSymbolicSuffixBuilder suffixBuilder; + + private final ParameterizedSymbol[] inputs; + private final List outputs; + + private final Constants consts; + + private boolean ioMode; + + public ClassificationTree(TreeOracle oracle, + ConstraintSolver solver, + SymbolicSuffixRestrictionBuilder restrBuilder, + OptimizedSymbolicSuffixBuilder suffixBuilder, + Constants consts, + boolean ioMode, + ParameterizedSymbol ... inputs) { + this.oracle = oracle; + this.solver = solver; + this.ioMode = ioMode; + this.inputs = inputs; + this.restrBuilder = restrBuilder; + this.suffixBuilder = suffixBuilder; + this.consts = consts; + + prefixes = new LinkedHashMap<>(); + shortPrefixes = new LinkedHashSet<>(); + outputs = outputSuffixes(inputs); + + root = new CTInnerNode(null, RaStar.EMPTY_SUFFIX); + } + + public Set getLeaves() { + return new LinkedHashSet<>(prefixes.values()); + } + + public CTLeaf getLeaf(Word u) { + return prefixes.get(u); + } + + public Set> getPrefixes() { + return new LinkedHashSet<>(prefixes.keySet()); + } + + public Set getShortPrefixes() { + Set sp = new LinkedHashSet<>(); + for (CTLeaf leaf : getLeaves()) { + sp.addAll(leaf.getShortPrefixes()); + } + assert sp.size() == shortPrefixes.size(); + assert sp.containsAll(shortPrefixes); + return sp; + } + + /** + * Get all one-symbol extensions of prefix {@code u}. + * + * @param u + * @return set of prefixes which are one-symbol extensions of {@code u} + */ + public Set getExtensions(Word u) { + Set extensions = new LinkedHashSet<>(); + for (ParameterizedSymbol action : inputs) { + extensions.addAll(getExtensions(u, action) + .stream() + .map(w -> getLeaf(w).getPrefix(w)) + .collect(Collectors.toList())); + } + return extensions; + } + + /** + * Get the sink node of the classification tree (IO mode only). The sink node of an IO classification + * tree is the leaf node in the rejecting branch of the tree (i.e., the branch for the empty symbolic suffix + * which is rejecting. Note that in IO mode, there should only be one rejecting leaf. + * + * @return an {@code Optional} containing the sink node if one exists, or an empty {@code Optional} otherwise + */ + public Optional getSink() { + if (!ioMode) { + return Optional.empty(); + } + + for (CTBranch branch : root.getBranches()) { + if (!branch.getRepresentativePath().isAccepting()) { + CTNode node = branch.getChild(); + while (!node.isLeaf()) { + CTInnerNode inner = (CTInnerNode) node; + List children = inner.getBranches(); + if (children.isEmpty()) { + return Optional.empty(); + } + node = children.stream().findFirst().get().getChild(); + } + return Optional.of((CTLeaf) node); + } + } + return Optional.empty(); + } + + /** + * Get all one-symbol {@code action}-extensions of prefix {@code u}. + * + * @param u + * @param action + * @return set of prefixes which are one-symbol extensions of {@code u} with symbol {@code action} + */ + public Set> getExtensions(Word u, ParameterizedSymbol action) { + return prefixes.keySet() + .stream() + .filter(w -> w.length() == u.length() + 1) + .filter(w -> w.prefix(w.length() - 1).equals(u) && w.lastSymbol().getBaseSymbol().equals(action)) + .collect(Collectors.toSet()); + } + + /** + * Initialize the classification tree by sifting the empty prefix. + */ + public void initialize() { + sift(RaStar.EMPTY_PREFIX); + } + + /////////////////////////////////////////// + // OPERATIONS ON THE CLASSIFICATION TREE // + /////////////////////////////////////////// + + /** + * Sift prefix into the tree. If prefix sifts to a new leaf, it becomes the representative prefix + * for that leaf. + * + * @param u prefix to sift + * @return the leaf into which {@code u} has been sifted + */ + public CTLeaf sift(Word u) { + Prefix prefix = new Prefix(u, new CTPath(ioMode)); + CTLeaf leaf = root.sift(prefix, oracle, solver, ioMode); + prefixes.put(u, leaf); + return leaf; + } + + /** + * Expands a prefix by turning it into a short prefix. The new short prefix will have + * branching information initialized from the initial guards of the conjunction of all its SDTs. + * + * @param u the prefix to be expanded + */ + public void expand(Word u) { + CTLeaf leaf = prefixes.get(u); + // sift u into leaf, if not already present + if (leaf == null) { + leaf = sift(u); + } + ShortPrefix prefix = leaf.elevatePrefix(u, oracle, inputs); + shortPrefixes.add(u); + + // sift one-symbol extensions of u + for (ParameterizedSymbol ps : inputs) { + Branching b = prefix.getBranching(ps); + for (Word ua : b.getBranches().keySet()) { + CTLeaf l = sift(ua); + prefixes.put(ua, l); + } + } + } + + /** + * Refine a {@code leaf} by adding a new inner node with above it. After the new inner node is added, + * all prefixes of {@code leaf} will be sifted into the classification tree with a call to + * {@link ClassificationTree#sift(Word)}. Any short prefix in {@code leaf} will have its branching + * updated. + * + * @param leaf the leaf to refine + * @param suffix the symbolic suffix to be contained within the new inner node + */ + public void refine(CTLeaf leaf, SymbolicSuffix suffix) { + CTInnerNode parent = (CTInnerNode) leaf.getParent(); + assert parent != null; + Map, CTLeaf> leaves = parent.refine(leaf, suffix, oracle, solver, ioMode, inputs); + prefixes.putAll(leaves); + + // make sure all short prefixes are still short (this may not be the case if a new leaf was created) + for (Word sp : shortPrefixes) { + CTLeaf l = prefixes.get(sp); + Prefix p = l.getPrefix(sp); + if (!(p instanceof ShortPrefix)) { + l.elevatePrefix(sp, oracle, inputs); + } + } + } + + /////////////////////// + // CLOSEDNESS CHECKS // + /////////////////////// + + /** + * Checks for output closedness, i.e., whether a symbolic suffix for each output symbol is + * present for each leaf. If not output closed, add one missing output suffix as a new inner + * node with a call to {@link ClassificationTree#refine(CTLeaf, SymbolicSuffix)}. + * + * @return {@code true} if output closed + */ + public boolean checkOutputClosed() { + if (!ioMode) { + return true; + } + return checkOutputClosed(root); + } + + private boolean checkOutputClosed(CTNode node) { + if (node.isLeaf()) { + CTLeaf leaf = (CTLeaf) node; + for (SymbolicSuffix v : outputs) { + if (!leaf.getSuffixes().contains(v)) { + refine(leaf, v); + return false; + } + } + return true; + } else { + CTInnerNode n = (CTInnerNode) node; + for (CTBranch b : n.getBranches()) { + if (!checkOutputClosed(b.getChild())) { + return false; + } + } + } + return true; + } + + /** + * Checks for location closedness, i.e., whether each leaf has at least one short prefix. If + * not location closed, this method expands, with a call to {@link ClassificationTree#expand(Word)}, + * the representative prefix of one leaf which does not have a short prefix. + * + * @return {@code true} if location closed + */ + public boolean checkLocationClosedness() { + for (CTLeaf leaf : getLeaves()) { + if (leaf.getShortPrefixes().isEmpty()) { + expand(leaf.getRepresentativePrefix()); + return false; + } + } + return true; + } + + /** + * Checks for transition closedness, i.e., whether each short prefix has a one-symbol extension + * for each guard. If not transition closed, one new one-symbol prefix will be sifted for one + * short prefix missing an extension. + * + * @return {@code true} if transition closed + */ + public boolean checkTransitionClosedness() { + for (CTLeaf leaf : getLeaves()) { + for (ShortPrefix u : leaf.getShortPrefixes()) { + for (ParameterizedSymbol a : inputs) { + for (Word ua : u.getBranching(a).getBranches().keySet()) { + if (!prefixes.containsKey(ua)) { + sift(ua); + return false; + } + } + } + } + } + return true; + } + + /** + * Checks for register closedness, i.e., whether for each short prefix {@code u}, the memorable parameters + * of {@code u} contain all the memorable parameters of its one-symbol extensions {@code ua(d)}. If not register + * closed, a new symbolic suffix {@code av}, formed by prepending the symbol {@code a} of {@code ua(d)} with a + * symbolic suffix {@code v} which reveals a missing memorable parameter of {@code ua(d)}, will be added to + * the leaf of {@code u}. Note that only one new symbolic suffix will be added, even if there are multiple + * short prefixes for which a memorable parameter is missing. + * + * @return {@code true} if register closed + */ + public boolean checkRegisterClosedness() { + for (Map.Entry, CTLeaf> e : prefixes.entrySet()) { + Word ua = e.getKey(); + CTLeaf leaf = e.getValue(); + if (ua.length() < 1) { + continue; + } + Word u = ua.prefix(ua.size() - 1); + Prefix ua_pref = leaf.getPrefix(ua); + CTLeaf u_leaf = prefixes.get(u); + + Set ua_mem = leaf.getPrefix(ua).getRegisters(); + Set u_mem = prefixes.get(u).getPrefix(u).getRegisters(); + Set a_mem = actionRegisters(ua); + + if (!consistentMemorable(ua_mem, u_mem, a_mem)) { + // memorables are missing, find suffix which reveals missing memorables + for (SymbolicSuffix v : leaf.getSuffixes()) { + Set s_mem = ua_pref.getSDT(v).getDataValues(); + if (!consistentMemorable(s_mem, u_mem, a_mem)) { + DataValue[] missingRegs = missingRegisters(s_mem, u_mem, a_mem); // registers to not optimize away + SymbolicSuffix av = extendSuffix(ua, v, missingRegs); + refine(u_leaf, av); + break; + } + } + return false; + } + } + return true; + } + + //////////////////////// + // CONSISTENCY CHECKS // + //////////////////////// + + /** + * Checks for location consistency. This property states that, for each pair of short prefixes {@code u} and {@code v}, + * each one-symbol extension {@code ua(d)} and {@code va(d)} of {@code u} and {@code v}, corresponding to the same + * guard, is in the same leaf. If the classification tree is not location consistent, this method refines the tree with a + * new symbolic suffix formed by prepending the symbolic suffix of the lowest common ancestor of {@code u} and {@code v} + * by the symbol {@code a} of the one-symbol extensions revealing the inconsistency. Note that only one location inconsistency + * will be resolved by this method. To resolve multiple inconsistencies, call the method multiple times. + * + * @return {@code true} if location consistent + */ + public boolean checkLocationConsistency() { + for (CTLeaf l : getLeaves()) { + Iterator sp = l.getShortPrefixes().iterator(); + if (!sp.hasNext()) { + continue; + } + ShortPrefix u = sp.next(); + while (sp.hasNext()) { + ShortPrefix uOther = sp.next(); + for (ParameterizedSymbol action : inputs) { + // loop over one-symbol extensions + for (Map.Entry, Expression> uEntry : u.getBranching(action).getBranches().entrySet()) { + // rename guard to make parameters consistent with RP + Word uExtension = uEntry.getKey(); + Expression uGuard = uEntry.getValue(); + Bijection uRenaming = u.getRpBijection().compose(uOther.getRpBijection().inverse()); + + ReplacingValuesVisitor rvv = new ReplacingValuesVisitor(); + Expression uGuardRenamed = rvv.apply(uGuard, uRenaming.toVarMapping()); + + // find equivalent one-symbol extension for RP + Optional> uOtherExtension = uOther.getBranching(action).getPrefix(uGuardRenamed, solver); + assert uOtherExtension.isPresent(); + + CTLeaf uExtLeaf = getLeaf(uExtension); + CTLeaf uOtherExtLeaf = getLeaf(uOtherExtension.get()); + if (uExtLeaf != uOtherExtLeaf) { + // inconsistent, refine leaf with extended suffix + SymbolicSuffix v = lca(uExtLeaf, uOtherExtLeaf).getSuffix(); + SymbolicSuffix av = extendSuffix(uExtension, uOtherExtension.get(), v); + refine(l, av); + return false; + } + } + } + } + } + return true; + } + + /** + * Checks for transition consistency. There are two types of transition consistency, (a) and (b). + * This method checks for both. The transition consistency (a) property states that all one-symbol + * extensions {@code ua(d)} of a prefix {@code u} which satisfy the same guard are in the same leaf. + * Transition consistency (b) states that all one-symbol extensions {@code ua(d)} of a prefix {@code u} + * that satisfy the same guard and are in the same leaf should have equivalent SDTs under identity renaming. + * If either property is not satisfied, the classification tree will be refined by adding a new inner + * node to the leaf of {@code u} with a symbolic suffix formed by prepending the symbolic suffix + * revealing the inconsistency by the symbol {@code a} of the one-symbol extension. + * Note that only one inconsistency will be resolved. Multiple inconsistencies can be resolved through + * multiple calls to this method. + * + * @return {@code true} if transition consistent + */ + public boolean checkTransitionConsistency() { + for (ShortPrefix u : getShortPrefixes()) { + for (ParameterizedSymbol action : inputs) { + Set> extensions = getExtensions(u, action); + for (Map.Entry, Expression> e : u.getBranching(action).getBranches().entrySet()) { + Word uA = e.getKey(); + Expression g = e.getValue(); + for (Word uB : extensions) { + if (uB.equals(uA)) { + continue; + } + + // check if guard for uA is satisfiable under mapping of uB + Mapping mapping = new Mapping<>(); + mapping.putAll(actionValuation(uB)); + mapping.putAll(consts); + if (solver.isSatisfiable(g, mapping)) { + // check transition consistency A + Optional av = transitionConsistentA(uA, uB); + if (av.isEmpty()) { + // check transition consistency B + av = transitionConsistentB(uA, uB); + } + if (av.isPresent()) { + refine(getLeaf(u), av.get()); + return false; + } + } + } + } + } + } + return true; + } + + private Optional transitionConsistentA(Word uA, Word uB) { + Word u = uA.prefix(uA.length() - 1); + CTLeaf uALeaf = getLeaf(uA); + CTLeaf uBLeaf = getLeaf(uB); + if (uALeaf != uBLeaf) { + CTLeaf uLeaf = getLeaf(u); + assert uLeaf != null : "Prefix is not short: " + u; + SymbolicSuffix v = lca(uALeaf, uBLeaf).getSuffix(); + SymbolicSuffix av = extendSuffix(uA, uB, v); + return Optional.of(av); + } + return Optional.empty(); + } + + private Optional transitionConsistentB(Word uA, Word uB) { + Prefix pA = getLeaf(uA).getPrefix(uA); + Prefix pB = getLeaf(uB).getPrefix(uB); + for (SymbolicSuffix v : getLeaf(uB).getSuffixes()) { + SDT sdtA = pA.getSDT(v).toRegisterSDT(uA, consts); + SDT sdtB = pB.getSDT(v).toRegisterSDT(uB, consts); + if (!SDT.equivalentUnderId(sdtA, sdtB)) { + CTLeaf uLeaf = getLeaf(uA.prefix(uA.length() - 1)); + assert uLeaf != null; + + // find registers that should not be removed through optimization + Register[] regs = inequivalentMapping(rpRegBijection(pA.getRpBijection(), pA), rpRegBijection(pB.getRpBijection(), pB)); + DataValue[] regVals = regsToDvs(regs, uA); + + SymbolicSuffix av = extendSuffix(uA, v, regVals); + if (suffixRevealsNewGuard(av, getLeaf(uA.prefix(uA.length() - 1)))) { + return Optional.of(av); + } + } + } + return Optional.empty(); + } + + /** + * Checks for register consistency. This property states that if there are any symmetries between + * memorable parameters of any prefix, then these symmetries must be present also in its + * one-symbol extensions. If this property does not hold for some prefix {@code u}, a new inner + * node will be added to break the symmetry of {@code u}. This node will contain a symbolic suffix + * formed by prepending the symbolic suffix breaking the symmetry in the one-symbol extension + * {@code ua(d)} by the symbol {@code a}. Not that only one inconsistency will be resolved in this + * manner. Multiple inconsistencies should be resolved with multiple calls to this method. + * + * @return {@code true} if register consistent + */ + public boolean checkRegisterConsistency() { + for (Prefix u : getShortPrefixes()) { + if (u.length() < 2) { + continue; + } + for (ParameterizedSymbol action : inputs) { + Iterator extensions = getExtensions(u, action) + .stream() + .map(w -> getLeaf(w).getPrefix(w)) + .iterator(); + while (extensions.hasNext()) { + Prefix uExtended = extensions.next(); + + // loop over all bijections exhibiting symmetry + RemappingIterator rit = new RemappingIterator<>(u.getRegisters(), u.getRegisters()); + for (Bijection gamma : rit) { + CTPath uPath = u.getPath(); + if (uPath.isEquivalent(uPath, gamma, solver)) { + // u exhibits symmetry under gamma + for (Map.Entry e : uExtended.getPath().getSDTs().entrySet()) { + SymbolicSuffix v = e.getKey(); + SDT uaSDT = e.getValue(); + if (SDT.equivalentUnderBijection(uaSDT, uaSDT, gamma) == null) { + // one-symbol extension uExtended does not exhibit symmetry under gamma + DataValue[] regs = gamma.keySet().toArray(new DataValue[gamma.size()]); + SymbolicSuffix av = extendSuffix(uExtended, v, regs); + refine(getLeaf(u), av); + return false; + } + } + } + } + } + } + } + return true; + } + + //////////////////// + // HELPER METHODS // + //////////////////// + + /** + * + * @param n1 + * @param n2 + * @return lowest common ancestor of {@code n1} and {@code n2} + */ + private CTInnerNode lca(CTNode n1, CTNode n2) { + if (n1 == n2) { + if (n1.isLeaf()) { + return (CTInnerNode) n1.getParent(); + } + return (CTInnerNode) n1; + } + int h1 = height(n1); + int h2 = height(n2); + + return lca(n1, h1, n2, h2); + } + + private CTInnerNode lca(CTNode n1, int h1, CTNode n2, int h2) { + if (n1 == n2) { + assert n1 instanceof CTInnerNode; + return (CTInnerNode) n1; + } + if (h1 < h2) { + return lca(n1, h1, n2.getParent(), h2 - 1); + } + if (h1 > h2) { + return lca(n1.getParent(), h1 - 1, n2, h2); + } + return lca(n1.getParent(), h1 - 1, n2.getParent(), h2 - 1); + } + + private int height(CTNode n) { + int h = 0; + while (n.getParent() != null) { + h++; + n = n.getParent(); + } + return h; + } + + /** + * + * @param ua_mem + * @param u_mem + * @param a_mem + * @return {@code true} if {@code ua_mem} contains all of {@code u_mem} and {@code a_mem} + */ + private boolean consistentMemorable(Set ua_mem, Set u_mem, Set a_mem) { + Set union = new LinkedHashSet<>(); + union.addAll(u_mem); + union.addAll(a_mem); + return union.containsAll(ua_mem); + } + + /** + * @param ua + * @return the set of data values in the last symbol instance of {@code ua} + */ + private Set actionRegisters(Word ua) { + int ua_arity = DataWords.paramLength(DataWords.actsOf(ua)); + int u_arity = ua_arity - ua.lastSymbol().getBaseSymbol().getArity(); + DataValue[] vals = DataWords.valsOf(ua); + + Set regs = new LinkedHashSet<>(); + for (int i = u_arity; i < ua_arity; i++) { + regs.add(vals[i]); + } + return regs; + } + + /** + * + * @param s_mem + * @param u_mem + * @param a_mem + * @return an array containing the data values of {@code s_mem} not contained in either {@code u_mem} or {@code a_mem} + */ + private DataValue[] missingRegisters(Set s_mem, Set u_mem, Set a_mem) { + Set union = new LinkedHashSet<>(u_mem); + union.addAll(a_mem); + Set difference = new LinkedHashSet<>(s_mem); + difference.removeAll(union); + return difference.toArray(new DataValue[difference.size()]); + } + + /** + * Form a new symbolic suffix by prepending {@code v} by the last symbol of {@code ua}, + * using suffix optimization. + * + * @param ua + * @param v + * @param missingRegs the register which should not be removed through suffix optimizations + * @return the last symbol of {@code ua} concatenated with {@code v} + */ + private SymbolicSuffix extendSuffix(Word ua, SymbolicSuffix v, DataValue[] missingRegs) { + if (suffixBuilder == null) { + PSymbolInstance a = ua.lastSymbol(); + Word u = ua.prefix(ua.length() - 1); + SymbolicSuffix alpha = new SymbolicSuffix(u, Word.fromSymbols(a), restrBuilder); + return alpha.concat(v); + } + + SDT u_sdt = prefixes.get(ua).getPrefix(ua).getSDT(v); + assert u_sdt != null : "SDT for symbolic suffix " + v + " does not exist for prefix " + ua; + + return suffixBuilder.extendSuffix(ua, u_sdt, v, missingRegs); + } + + /** + * Perform a tree query for the representative prefix of {@code leaf} with {@code av} to check + * whether {@code av} reveals additional guards. + * + * @param av + * @param leaf + * @return {@code true} if {@code av} reveals additional guards + */ + private boolean suffixRevealsNewGuard(SymbolicSuffix av, CTLeaf leaf) { + assert !leaf.getShortPrefixes().isEmpty() : "No short prefix in leaf " + leaf; + Word u = leaf.getShortPrefixes().iterator().next(); + SDT sdt = oracle.treeQuery(u, av); + ParameterizedSymbol a = av.getActions().firstSymbol(); + Branching branching = leaf.getBranching(a); + Branching newBranching = oracle.updateBranching(u, a, branching, sdt); + for (Expression guard : newBranching.getBranches().values()) { + if (!branching.getBranches().values().contains(guard)) { + return true; + } + } + return false; + } + + /** + * Convert {@code Bijection} to {@code Bijection} using the + * data values of {@code prefix} to determine register ids. + * + * @param bijection + * @param prefx + * @return + */ + private Bijection rpRegBijection(Bijection bijection, Word prefx) { + return Bijection.dvToRegBijection(bijection, prefx, getLeaf(prefx).getRepresentativePrefix()); + } + + /** + * Convert array of {@code Register} to array of {@code DataValue} by matching {@link Register#getId()} + * values to data value positions in {@code prefix}. + * + * @param regs + * @param prefix + * @return + */ + private DataValue[] regsToDvs(Register[] regs, Word prefix) { + DataValue[] vals = DataWords.valsOf(prefix); + DataValue[] ret = new DataValue[regs.length]; + for (int i = 0; i < ret.length; i++) { + ret[i] = vals[regs[i].getId()-1]; + } + return ret; + } + + /** + * Form a {@code SymbolicSuffix} by prepending {@code v} by the last symbol of {@code u1} and {@code u2}. + * The new suffix will be optimized for separating {@code u1} and {@code u2}. + * Note that {@code u1} and {@code u2} must have the same last symbol. + * + * @param u1 + * @param u2 + * @param v + * @return + */ + private SymbolicSuffix extendSuffix(Word u1, Word u2, SymbolicSuffix v) { + SDT sdt1 = getLeaf(u1).getPrefix(u1).getSDT(v); + SDT sdt2 = getLeaf(u2).getPrefix(u2).getSDT(v); + return suffixBuilder.extendDistinguishingSuffix(u1, sdt1, u2, sdt2, v); + } + + /** + * {@code ParameterValuation} of the last symbol instance of {@code ua}. + * + * @param ua + * @return + */ + private ParameterValuation actionValuation(Word ua) { + ParameterGenerator pgen = new ParameterGenerator(); + DataValue[] vals = ua.lastSymbol().getParameterValues(); + ParameterValuation valuation = new ParameterValuation(); + for (int i = 0; i < vals.length; i++) { + Parameter p = pgen.next(vals[i].getDataType()); + valuation.put(p, vals[i]); + } + return valuation; + } + + /** + * + * @param a + * @param b + * @return array of registers in {@code a} and {@code b} which are not mapped the same + */ + private Register[] inequivalentMapping(Bijection a, Bijection b) { + Set ret = new LinkedHashSet<>(); + for (Map.Entry ea : a.entrySet()) { + Register key = ea.getKey(); + Register val = b.get(key); + if (val == null) { + ret.add(key); + ret.add(ea.getValue()); + } else if (!val.equals(ea.getValue())) { + ret.add(key); + ret.add(val); + } + } + return ret.toArray(new Register[ret.size()]); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("CT: {"); + buildTreeString(builder, root, "", " ", " -- "); + builder.append("}"); + return builder.toString(); + } + + private void buildTreeString(StringBuilder builder, CTNode node, String currentIndentation, String indentation, String sep) { + if (node.isLeaf()) { + builder.append("\n").append(currentIndentation).append("Leaf: ").append(node); + } else { + CTInnerNode inner = (CTInnerNode) node; + builder.append("\n").append(currentIndentation).append("Inner: ").append(inner.getSuffix()); + if (!inner.getBranches().isEmpty()) { + Iterator iter = inner.getBranches().iterator(); + while (iter.hasNext()) { + builder.append("\n").append(currentIndentation); + CTBranch branch = iter.next(); + builder.append("Branch: ").append(branch.getRepresentativePath()); + buildTreeString(builder, branch.getChild(), indentation + currentIndentation, indentation, sep); + } + } + } + } + + private static List outputSuffixes(ParameterizedSymbol[] inputs) { + List ret = new ArrayList<>(); + for (ParameterizedSymbol ps : inputs) { + if (ps instanceof OutputSymbol) { + ret.add(new SymbolicSuffix(ps)); + } + } + return ret; + } + +} diff --git a/src/main/java/de/learnlib/ralib/ct/MemorableSet.java b/src/main/java/de/learnlib/ralib/ct/MemorableSet.java new file mode 100644 index 00000000..f04dd616 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/ct/MemorableSet.java @@ -0,0 +1,22 @@ +package de.learnlib.ralib.ct; + +import java.util.LinkedHashSet; + +import de.learnlib.ralib.data.Bijection; +import de.learnlib.ralib.data.DataValue; + +/** + * A set of memorable data values + * + * @author fredrik + */ +public class MemorableSet extends LinkedHashSet { + public MemorableSet relabel(Bijection renaming) { + MemorableSet renamed = new MemorableSet(); + for (DataValue dv : this) { + DataValue ndv = renaming.get(dv); + renamed.add((ndv == null) ? dv : ndv); + } + return renamed; + } +} diff --git a/src/main/java/de/learnlib/ralib/ct/Prefix.java b/src/main/java/de/learnlib/ralib/ct/Prefix.java new file mode 100644 index 00000000..a1cafbf5 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/ct/Prefix.java @@ -0,0 +1,205 @@ +package de.learnlib.ralib.ct; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Spliterator; +import java.util.function.Function; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import de.learnlib.ralib.data.Bijection; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.RegisterAssignment; +import de.learnlib.ralib.data.util.SymbolicDataValueGenerator; +import de.learnlib.ralib.learning.PrefixContainer; +import de.learnlib.ralib.learning.SymbolicSuffix; +import de.learnlib.ralib.theory.SDT; +import de.learnlib.ralib.words.PSymbolInstance; +import de.learnlib.ralib.words.ParameterizedSymbol; +import net.automatalib.word.Word; + +/** + * Data structure for a prefix stored within a leaf of a {@link ClassificationTree}. + * Along with the prefix itself, also stores the SDTs for each suffix along the path + * through which the prefix was sifted, as well as the {@code Bijection} under which + * the SDTs along the path are equivalent to those of the leaf's representative prefix + * (the RP bijection). If this prefix is the representative prefix, the RP bijection + * should be the identity mapping. + * + * @author fredrik + * @see CTLeaf + * @see CTPath + * @see Bijection + */ +public class Prefix extends Word implements PrefixContainer { + private final Word prefix; + private Bijection rpBijection; + private final CTPath path; + + public Prefix(Word u, Bijection rpRenaming, CTPath path) { + this.prefix = u instanceof Prefix ? ((Prefix) u).getPrefix() : u; + this.rpBijection = rpRenaming; + this.path = path; + } + + public Prefix(Word prefix, CTPath path) { + this(prefix, Bijection.identity(path.getMemorable()), path); + } + + public Prefix(Prefix prefix, Bijection rpRenaming) { + this(prefix.prefix, rpRenaming, prefix.path); + } + + public Prefix(Prefix other) { + this(other.prefix, other.rpBijection, other.path); + } + + public void setRpBijection(Bijection rpBijection) { + this.rpBijection = rpBijection; + } + + public SDT[] getSDTs(ParameterizedSymbol ps) { + List list = new ArrayList<>(); + for (Map.Entry e : path.getSDTs().entrySet()) { + Word acts = e.getKey().getActions(); + if (acts.length() > 0 && acts.firstSymbol().equals(ps)) { + list.add(e.getValue()); + } + } + return list.toArray(new SDT[list.size()]); + } + + public SDT getSDT(SymbolicSuffix s) { + return path.getSDT(s); + } + + public Bijection getRpBijection() { + return rpBijection; + } + + public MemorableSet getRegisters() { + return path.getMemorable(); + } + + public CTPath getPath() { + return path; + } + + @Override + public boolean equals(Object other) { + if (other instanceof Prefix) { + return ((Prefix) other).prefix.equals(prefix); + } + return prefix.equals(other); + } + + @Override + public Word getPrefix() { + return prefix; + } + + @Override + public RegisterAssignment getAssignment() { + RegisterAssignment ra = new RegisterAssignment(); + SymbolicDataValueGenerator.RegisterGenerator regGen = + new SymbolicDataValueGenerator.RegisterGenerator(); + + this.getRegisters().forEach( + dv -> ra.put(dv, regGen.next(dv.getDataType())) + ); + + return ra; + } + + @Override + public int hashCode() { + return prefix.hashCode(); + } + + @Override + public String toString() { + return prefix.toString(); + } + + @Override + public int length() { + return prefix.length(); + } + + @Override + public PSymbolInstance getSymbol(int index) { + return prefix.getSymbol(index); + } + + @Override + public Iterator iterator() { + return prefix.iterator(); + } + + @Override + public Spliterator spliterator() { + return prefix.spliterator(); + } + + @Override + public void writeToArray(int offset, @Nullable Object[] array, int tgtOffset, int length) { + prefix.writeToArray(offset, array, tgtOffset, length); + } + + @Override + public List asList() { + return prefix.asList(); + } + + @Override + public PSymbolInstance lastSymbol() { + return prefix.lastSymbol(); + } + + @Override + public Word append(PSymbolInstance symbol) { + return prefix.append(symbol); + } + + @Override + public Word prepend(PSymbolInstance symbol) { + return prefix.prepend(symbol); + } + + @Override + public boolean isPrefixOf(Word other) { + return prefix.isPrefixOf(other); + } + + @Override + public Word longestCommonPrefix(Word other) { + return prefix.longestCommonPrefix(other); + } + + @Override + public boolean isSuffixOf(Word other) { + return prefix.isSuffixOf(other); + } + + @Override + public Word longestCommonSuffix(Word other) { + return prefix.longestCommonSuffix(other); + } + + @Override + public Word flatten() { + return prefix.flatten(); + } + + @Override + public Word trimmed() { + return prefix.trimmed(); + } + + @Override + public Word transform(Function transformer) { + return prefix.transform(transformer); + } +} diff --git a/src/main/java/de/learnlib/ralib/ct/ShortPrefix.java b/src/main/java/de/learnlib/ralib/ct/ShortPrefix.java new file mode 100644 index 00000000..5af23ae6 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/ct/ShortPrefix.java @@ -0,0 +1,60 @@ +package de.learnlib.ralib.ct; + +import java.util.LinkedHashMap; +import java.util.Map; + +import de.learnlib.ralib.data.Bijection; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.oracles.Branching; +import de.learnlib.ralib.oracles.TreeOracle; +import de.learnlib.ralib.theory.SDT; +import de.learnlib.ralib.words.ParameterizedSymbol; + +/** + * Data structure for storing branching information of a short prefix, + * in addition to the SDTs from the suffixes along its path stored + * in {@link Prefix}. + * + * @author fredrik + */ +public class ShortPrefix extends Prefix { + + private final Map branching; + + private final TreeOracle oracle; + + private final ParameterizedSymbol[] inputs; + + public ShortPrefix(Prefix u, Bijection rpBijection, TreeOracle oracle, ParameterizedSymbol ... inputs) { + super(u, rpBijection); + this.oracle = oracle; + this.inputs = inputs; + branching = new LinkedHashMap<>(); + + for (ParameterizedSymbol ps : inputs) { + SDT[] sdts = getSDTs(ps); + Branching b = oracle.getInitialBranching(this, ps, sdts); + branching.put(ps, b); + } + } + + public ShortPrefix(Prefix u, TreeOracle oracle, ParameterizedSymbol ... inputs) { + this(u, u.getRpBijection(), oracle, inputs); + } + + public Branching getBranching(ParameterizedSymbol ps) { + return branching.get(ps); + } + + /** + * Update the branching according to the SDTs computed for the suffixes along the path to + * the leaf in which this short prefix is stored. + */ + public void updateBranching() { + for (ParameterizedSymbol ps : inputs) { + SDT[] sdts = getSDTs(ps); + Branching b = oracle.updateBranching(this, ps, branching.get(ps), sdts); + branching.put(ps, b); + } + } +} diff --git a/src/main/java/de/learnlib/ralib/data/Bijection.java b/src/main/java/de/learnlib/ralib/data/Bijection.java index 3c4c5d39..c7b4ee90 100644 --- a/src/main/java/de/learnlib/ralib/data/Bijection.java +++ b/src/main/java/de/learnlib/ralib/data/Bijection.java @@ -2,6 +2,11 @@ import java.util.*; +import de.learnlib.ralib.data.SymbolicDataValue.Register; +import de.learnlib.ralib.words.DataWords; +import de.learnlib.ralib.words.PSymbolInstance; +import net.automatalib.word.Word; + public class Bijection implements Map { @@ -148,4 +153,29 @@ private static void putAll(Map in, sur.put(val, key); } } + + public static Bijection dvToRegBijection(Bijection bijection, Word keyWord, Word valueWord) { + DataValue[] keyVals = DataWords.valsOf(keyWord); + DataValue[] valueVals = DataWords.valsOf(valueWord); + Bijection ret = new Bijection<>(); + for (Map.Entry e : bijection.entrySet()) { + DataValue key = e.getKey(); + DataValue val = e.getValue(); + int keyId = findFirstDv(key, keyVals); + int valueId = findFirstDv(val, valueVals); + Register regKey = new Register(keyVals[keyId].getDataType(), keyId+1); + Register regValue = new Register(valueVals[valueId].getDataType(), valueId+1); + ret.put(regKey, regValue); + } + return ret; + } + + private static int findFirstDv(DataValue dv, DataValue[] vals) { + for (int i = 0; i < vals.length; i++) { + if (vals[i].equals(dv)) { + return i; + } + } + throw new IllegalArgumentException("No matching data value for " + dv + ": " + vals); + } } diff --git a/src/main/java/de/learnlib/ralib/data/DataValue.java b/src/main/java/de/learnlib/ralib/data/DataValue.java index 8ec2ac93..f7449e3f 100644 --- a/src/main/java/de/learnlib/ralib/data/DataValue.java +++ b/src/main/java/de/learnlib/ralib/data/DataValue.java @@ -63,7 +63,7 @@ public boolean equals(Object obj) { if (!Objects.equals(this.type, other.type)) { return false; } - return this.getValue().equals(other.getValue()); + return this.getValue().compareTo(other.getValue()) == 0; } @Override diff --git a/src/main/java/de/learnlib/ralib/data/RegisterAssignment.java b/src/main/java/de/learnlib/ralib/data/RegisterAssignment.java index 17587cd0..18e3352c 100644 --- a/src/main/java/de/learnlib/ralib/data/RegisterAssignment.java +++ b/src/main/java/de/learnlib/ralib/data/RegisterAssignment.java @@ -2,17 +2,27 @@ import java.util.Map; +import de.learnlib.ralib.data.SymbolicDataValue.Register; + /** * A register assignment models which data values * of a prefix are stored in which registers. */ -public class RegisterAssignment extends Mapping { +public class RegisterAssignment extends Mapping { public RegisterValuation registerValuation() { RegisterValuation vars = new RegisterValuation(); - for (Map.Entry e : this.entrySet()) { + for (Map.Entry e : this.entrySet()) { vars.put(e.getValue(), e.getKey()); } return vars; } + + public RegisterAssignment relabel(VarMapping remapping) { + RegisterAssignment ret = new RegisterAssignment(); + for (Map.Entry e : entrySet()) { + ret.put(e.getKey(), remapping.get(e.getValue())); + } + return ret; + } } diff --git a/src/main/java/de/learnlib/ralib/data/util/DataUtils.java b/src/main/java/de/learnlib/ralib/data/util/DataUtils.java new file mode 100644 index 00000000..648edda4 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/data/util/DataUtils.java @@ -0,0 +1,37 @@ +package de.learnlib.ralib.data.util; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import de.learnlib.ralib.data.DataType; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.SuffixValuation; +import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; +import de.learnlib.ralib.data.TypedValue; +import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.SuffixValueGenerator; +import de.learnlib.ralib.words.PSymbolInstance; + +public class DataUtils { + + public static Map typedSize(Set set) { + Map ts = new LinkedHashMap<>(); + for (TypedValue s : set) { + Integer i = ts.get(s.getDataType()); + i = (i == null) ? 1 : i + 1; + ts.put(s.getDataType(), i); + } + return ts; + } + + public static SuffixValuation actionValuation(PSymbolInstance action) { + SuffixValuation valuation = new SuffixValuation(); + DataValue[] vals = action.getParameterValues(); + SuffixValueGenerator svgen = new SuffixValueGenerator(); + for (int i = 0; i < vals.length; i++) { + SuffixValue sv = svgen.next(vals[i].getDataType()); + valuation.put(sv, vals[i]); + } + return valuation; + } +} diff --git a/src/main/java/de/learnlib/ralib/data/util/RemappingIterator.java b/src/main/java/de/learnlib/ralib/data/util/RemappingIterator.java index 2fb90f3d..d13e4525 100644 --- a/src/main/java/de/learnlib/ralib/data/util/RemappingIterator.java +++ b/src/main/java/de/learnlib/ralib/data/util/RemappingIterator.java @@ -30,9 +30,12 @@ public boolean hasNext() { } @Override -// public VarMapping next() { public Bijection next() { assert next != null : "No more permutations"; + if (replace.length == 0) { + next = null; + return new Bijection<>(); + } Mapping vars = new Mapping<>(); for (int i = 0; i < replace.length; i++) { int index = next[i]; diff --git a/src/main/java/de/learnlib/ralib/dt/DT.java b/src/main/java/de/learnlib/ralib/dt/DT.java deleted file mode 100644 index 315b2485..00000000 --- a/src/main/java/de/learnlib/ralib/dt/DT.java +++ /dev/null @@ -1,571 +0,0 @@ -package de.learnlib.ralib.dt; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import org.apache.commons.lang3.tuple.Pair; - -import de.learnlib.ralib.data.Bijection; -import de.learnlib.ralib.data.Constants; -import de.learnlib.ralib.data.DataType; -import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; -import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.SuffixValueGenerator; -import de.learnlib.ralib.learning.LocationComponent; -import de.learnlib.ralib.learning.SymbolicSuffix; -import de.learnlib.ralib.learning.ralambda.DiscriminationTree; -import de.learnlib.ralib.learning.ralambda.RaLambda; -import de.learnlib.ralib.oracles.TreeOracle; -import de.learnlib.ralib.oracles.mto.OptimizedSymbolicSuffixBuilder; -import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; -import de.learnlib.ralib.theory.SDT; -import de.learnlib.ralib.theory.SDTGuard; -import de.learnlib.ralib.theory.SDTLeaf; -import de.learnlib.ralib.words.OutputSymbol; -import de.learnlib.ralib.words.PSymbolInstance; -import de.learnlib.ralib.words.ParameterizedSymbol; -import net.automatalib.word.Word; - -/** - * Implementation of discrimination tree. - * - * @author fredrik - */ -public class DT implements DiscriminationTree { - private final DTInnerNode root; - - private final ParameterizedSymbol[] inputs; - private final TreeOracle oracle; - private final boolean ioMode; - private final Constants consts; - private DTLeaf sink = null; - private final SymbolicSuffixRestrictionBuilder restrictionBuilder; - - public DT(TreeOracle oracle, boolean ioMode, Constants consts, ParameterizedSymbol... inputs) { - this.oracle = oracle; - this.ioMode = ioMode; - this.inputs = inputs; - this.consts = consts; - this.restrictionBuilder = oracle.getRestrictionBuilder(); - - Word epsilon = Word.epsilon(); - SymbolicSuffix suffEps = new SymbolicSuffix(epsilon, epsilon); - - root = new DTInnerNode(suffEps); - } - - public DT(DTInnerNode root, TreeOracle oracle, boolean ioMode, Constants consts, ParameterizedSymbol... inputs) { - this.root = root; - this.oracle = oracle; - this.ioMode = ioMode; - this.inputs = inputs; - this.consts = consts; - this.restrictionBuilder = oracle.getRestrictionBuilder(); - } - - public DT(DT dt) { - this.inputs = dt.inputs; - this.oracle = dt.oracle; - this.ioMode = dt.ioMode; - this.consts = dt.consts; - this.restrictionBuilder = dt.restrictionBuilder; - - root = new DTInnerNode(dt.root); - } - - @Override - public DTLeaf sift(Word prefix, boolean add) { - //System.out.println("SIFT: " + prefix + ", add: " + add); - DTLeaf leaf = getLeaf(prefix); - if (leaf != null) { - return leaf; - } - MappedPrefix mp = new MappedPrefix(prefix, new Bijection<>()); - DTLeaf result = sift(mp, root, add); - return result; - } - - public void initialize() { - if (ioMode) { - DTInnerNode parent = root; - MappedPrefix epsilon = new MappedPrefix(RaLambda.EMPTY_PREFIX, new Bijection<>()); - for (ParameterizedSymbol symbol : inputs) { - if (symbol instanceof OutputSymbol) { - DTInnerNode outputNode = new DTInnerNode(new SymbolicSuffix(symbol)); - PathResult r = PathResult.computePathResult(oracle, epsilon, parent.getSuffixes(), ioMode); - DTBranch branch = new DTBranch(outputNode, r); - outputNode.setParent(parent); - parent.addBranch(branch); - parent = outputNode; - } - } - sift(epsilon, root, true); - - for (DTBranch branch : root.getBranches()) { - if (!branch.getUrap().isAccepting()) - sink = (DTLeaf) branch.getChild(); - } - } else { - sift(RaLambda.EMPTY_PREFIX, true); - } - } - - private SDT makeRejectingSDT(OutputSymbol symbol, SuffixValueGenerator sgen, int paramIndex) { - if (paramIndex == symbol.getArity()) { - return SDTLeaf.REJECTING; - } else { - DataType param = symbol.getPtypes()[paramIndex]; - SuffixValue s = sgen.next(param); - LinkedHashMap map = new LinkedHashMap(); - map.put(new SDTGuard.SDTTrueGuard(s), makeRejectingSDT(symbol, sgen, paramIndex + 1)); - return new SDT(map); - } - } - - private DTLeaf sift(MappedPrefix mp, DTInnerNode from, boolean add) { - DTLeaf leaf = null; - DTInnerNode inner = from; - - // traverse tree from root to leaf - do { - SymbolicSuffix suffix = inner.getSuffix(); - Pair siftRes = inner.sift(mp, oracle, ioMode); - - if (siftRes == null) { - // discovered new location - leaf = new DTLeaf(oracle); - //tqr = mp.computeTQR(suffix, oracle); - PathResult r = PathResult.computePathResult(oracle, mp, inner.getSuffixes(), ioMode); - assert !mp.getTQRs().containsKey(suffix); - mp.addTQR(suffix, r.getSDTforSuffix(suffix)); - mp.updateRemapping(Bijection.identity(mp.memorableValues())); - leaf.setAccessSequence(mp); - DTBranch branch = new DTBranch(leaf, r); - inner.addBranch(branch); - leaf.setParent(inner); - leaf.start(this, ioMode, inputs); - leaf.updateBranching(this); - return leaf; - } - SDT tqr = siftRes.getValue().getSDTforSuffix(suffix); - mp.addTQR(suffix, tqr); - mp.updateRemapping(siftRes.getValue().getRemapping()); - if (!siftRes.getKey().isLeaf()) { - inner = (DTInnerNode) siftRes.getKey(); - } else { - leaf = (DTLeaf) siftRes.getKey(); - } - } while (leaf == null); - - if (add && !leaf.getAccessSequence().equals(mp.getPrefix())) { - if (mp instanceof ShortPrefix) { - leaf.addShortPrefix((ShortPrefix) mp); - } else { - leaf.addPrefix(mp); - } - } - return leaf; - } - - @Override - public void split(Word prefix, SymbolicSuffix suffix, DTLeaf leaf) { - //System.out.println("SPLIT: " + prefix + ", " + suffix + ", " + leaf); - // add new inner node - DTBranch branch = leaf.getParentBranch(); - DTInnerNode node = new DTInnerNode(suffix); - node.setParent(leaf.getParent()); - branch.setChild(node); // point branch to the new inner node - - // add the new leaf - MappedPrefix mp = leaf.getMappedPrefix(prefix); - //SDT tqr = mp.computeTQR(suffix, oracle); - DTLeaf newLeaf = new DTLeaf(mp, oracle); - newLeaf.setParent(node); - PathResult r = PathResult.computePathResult(oracle, mp, node.getSuffixes(), ioMode); - SDT tqr = r.getSDTforSuffix(suffix); - assert !mp.getTQRs().containsKey(suffix); - mp.addTQR(suffix, tqr); - mp.updateRemapping(Bijection.identity(mp.memorableValues())); - - DTBranch newBranch = new DTBranch(newLeaf, r); - node.addBranch(newBranch); - ShortPrefix sp = (ShortPrefix) leaf.getShortPrefixes().get(prefix); - - // update old leaf - boolean removed = leaf.removeShortPrefix(prefix); - assert (removed); // must not split a prefix that isn't there - - //SDT tqr2 = leaf.getPrimePrefix().computeTQR(suffix, oracle); - PathResult r2 = PathResult.computePathResult(oracle, leaf.getPrimePrefix(), node.getSuffixes(), ioMode); - SDT tqr2 = r2.getSDTforSuffix(suffix); - leaf.getPrimePrefix().addTQR(suffix, tqr2); - leaf.getPrimePrefix().updateRemapping(Bijection.identity(leaf.getPrimePrefix().memorableValues())); - // assert !tqr.getSdt().isEquivalent(tqr2.getSdt(), new VarMapping<>()); - DTBranch b = new DTBranch(leaf, r2); - leaf.setParent(node); - node.addBranch(b); - - // resift all transitions targeting this location - resift(leaf); - - newLeaf.start(this, sp.getBranching()); - newLeaf.updateBranching(this); - - if (removed) { - leaf.updateBranching(this); - } - } - - public void addSuffix(SymbolicSuffix suffix, DTLeaf leaf) { - - DTBranch branch = leaf.getParentBranch(); - DTInnerNode node = new DTInnerNode(suffix); - node.setParent(leaf.getParent()); - branch.setChild(node); - leaf.setParent(node); - - //SDT tqr = leaf.getPrimePrefix().computeTQR(suffix, oracle); - PathResult r = PathResult.computePathResult(oracle, leaf.getPrimePrefix(), node.getSuffixes(), ioMode); - SDT tqr = r.getSDTforSuffix(suffix); - assert !leaf.getPrimePrefix().getTQRs().containsKey(suffix); - leaf.getPrimePrefix().addTQR(suffix, tqr); - leaf.getPrimePrefix().updateRemapping(Bijection.identity(leaf.getPrimePrefix().memorableValues())); - - DTBranch newBranch = new DTBranch(leaf, r); - node.addBranch(newBranch); - - Set prefixes = new LinkedHashSet(); - leaf.getMappedExtendedPrefixes(prefixes); - for (MappedPrefix prefix : prefixes) { - leaf.removePrefix(prefix.getPrefix()); - DTLeaf l = sift(prefix, node, true); - //System.out.println("SIFTED: " + prefix + " to " + l); - } - - leaf.updateBranching(this); - } - - public boolean addLocation(Word target, DTLeaf src_c, DTLeaf dest_c, DTLeaf target_c) { - - Word prefix = target.prefix(target.length() - 1); - SymbolicSuffix suff1 = new SymbolicSuffix(prefix, target.suffix(1), restrictionBuilder); - SymbolicSuffix suff2 = findLCA(dest_c, target_c).getSuffix(); - SymbolicSuffix suffix = suff1.concat(suff2); - - DTInnerNode parent = src_c.getParent(); - while (parent != null) { - if (parent.getSuffix().equals(suffix)) - return false; - parent = parent.getParent(); - } - - split(prefix, suffix, src_c); - return true; - } - - /** - * resift all prefixes of a leaf, in order to add them to the correct leaf - * - * @param leaf - */ - private void resift(DTLeaf leaf) { - // Potential optimization: - // can keep TQRs up to the parent, as they should still be the same - - Set prefixes = new LinkedHashSet(); - leaf.getMappedExtendedPrefixes(prefixes); - DTInnerNode parent = leaf.getParent(); - for (MappedPrefix prefix : prefixes) { - leaf.removePrefix(prefix.getPrefix()); - sift(prefix, parent, true); - } - } - - public boolean checkIOSuffixes() { - if (ioMode) { - for (DTBranch b : root.getBranches()) { - if (b.getUrap().getSDTforSuffix(root.getSuffix()).isAccepting()) - return checkIOSuffixes(b.getChild()); - } - throw new java.lang.RuntimeException("No accepting child of root"); - } - else - return true; - } - - private boolean checkIOSuffixes(DTNode node) { - if (node.isLeaf()) { - boolean missingSuffix = false; - DTLeaf leaf = (DTLeaf) node; - MappedPrefix accessMP = leaf.getPrimePrefix(); - if (accessMP.getPrefix().length() == 0 - || accessMP.getPrefix().lastSymbol().getBaseSymbol() instanceof OutputSymbol) { - return true; - } - Set ioSuffixes = accessMP.getTQRs() - .keySet() - .stream() - .filter(s -> s.length() == 1) - .map(s -> s.getActions().firstSymbol()) - .filter(ps -> ps instanceof OutputSymbol) - .collect(Collectors.toSet()); - for (ParameterizedSymbol ps : inputs) { - if (ps instanceof OutputSymbol - && !ioSuffixes.contains(ps)) { - SymbolicSuffix suffix = new SymbolicSuffix(ps); - addSuffix(suffix, leaf); - missingSuffix = true; - } - } - return !missingSuffix; - } - boolean ret = true; - DTInnerNode inner = (DTInnerNode) node; - for (DTBranch b : Collections.unmodifiableCollection(new LinkedHashSet(inner.getBranches()))) { - ret = ret && checkIOSuffixes(b.getChild()); - } - return ret; - } - - public boolean checkVariableConsistency(OptimizedSymbolicSuffixBuilder suffixBuilder) { - return checkConsistency(this.root, suffixBuilder); - } - - private boolean checkConsistency(DTNode node, OptimizedSymbolicSuffixBuilder suffixBuilder) { - if (node.isLeaf()) { - DTLeaf leaf = (DTLeaf) node; - return leaf.checkVariableConsistency(this, this.consts, suffixBuilder); - } - boolean ret = true; - DTInnerNode inner = (DTInnerNode) node; - for (DTBranch b : Collections.unmodifiableCollection(new LinkedHashSet(inner.getBranches()))) { - ret = ret && checkConsistency(b.getChild(), suffixBuilder); - } - return ret; - } - - public boolean checkRegisterConsistency(OptimizedSymbolicSuffixBuilder suffixBuilder) { - return checkRegisterConsistency(root, suffixBuilder); - } - - private boolean checkRegisterConsistency(DTNode node, OptimizedSymbolicSuffixBuilder suffixBuilder) { - if (node.isLeaf()) { - DTLeaf leaf = (DTLeaf) node; - return leaf.checkRegisterConsistency(this, this.consts, suffixBuilder); - } - boolean ret = true; - DTInnerNode inner = (DTInnerNode) node; - for (DTBranch b : Collections.unmodifiableCollection(new LinkedHashSet(inner.getBranches()))) { - ret = ret && checkRegisterConsistency(b.getChild(), suffixBuilder); - } - return ret; - } - - /** - * check whether sifting a word into the dt leads to a refinement of the dt, i.e - * whether the location corresponding to word is already in the branching of the - * source location of word - * - * @param word - * @return true if sifting word into dt leads to refinement - */ - public boolean isRefinement(Word word) { - Word prefix = word.prefix(word.length() - 1); - DTLeaf prefixLeaf = getLeaf(prefix); - assert prefixLeaf != null; - - return prefixLeaf.isRefinemement(this, word); - } - - /** - * get leaf containing prefix as - * - * @param as - * @return leaf containing as, or null - */ - public DTLeaf getLeaf(Word as) { - return getLeaf(as, root); - } - - DTLeaf getLeaf(Word as, DTNode node) { - if (node.isLeaf()) { - DTLeaf leaf = (DTLeaf) node; - if (leaf.getPrimePrefix().getPrefix().equals(as) || leaf.getShortPrefixes().contains(as) - || leaf.getPrefixes().contains(as)) - return leaf; - } else { - DTInnerNode in = (DTInnerNode) node; - for (DTBranch b : in.getBranches()) { - DTLeaf l = getLeaf(as, b.getChild()); - if (l != null) - return l; - } - } - return null; - } - - public ParameterizedSymbol[] getInputs() { - return inputs; - } - - boolean getIoMode() { - return ioMode; - } - - TreeOracle getOracle() { - return oracle; - } - - public Collection getLeaves() { - Collection leaves = new ArrayList(); - getLeaves(root, leaves); - return leaves; - } - - private void getLeaves(DTNode node, Collection leaves) { - if (node.isLeaf()) - leaves.add((DTLeaf) node); - else { - DTInnerNode inner = (DTInnerNode) node; - for (DTBranch b : inner.getBranches()) - getLeaves(b.getChild(), leaves); - } - } - - private void getSuffixes(DTNode node, Collection suffixes) { - if (!node.isLeaf()) { - DTInnerNode inner = (DTInnerNode) node; - suffixes.add(inner.getSuffix()); - for (DTBranch b : inner.getBranches()) - getSuffixes(b.getChild(), suffixes); - } - } - - public Collection getSuffixes() { - Collection suffixes = new LinkedHashSet<>(); - getSuffixes(root, suffixes); - return suffixes; - } - - private void getAllPrefixes(Collection> prefs, DTNode node) { - if (node.isLeaf()) { - DTLeaf leaf = (DTLeaf) node; - prefs.addAll(leaf.getAllPrefixes()); - } else { - DTInnerNode inner = (DTInnerNode) node; - for (DTBranch b : inner.getBranches()) - getAllPrefixes(prefs, b.getChild()); - } - } - - public boolean isMissingParameter() { - return isMissingParameter(root); - } - - private boolean isMissingParameter(DTNode node) { - if (node.isLeaf()) - return ((DTLeaf)node).isMissingVariable(); - else { - for (DTBranch b : ((DTInnerNode)node).getBranches()) { - if (isMissingParameter(b.getChild())) - return true; - } - } - return false; - } - - @Override - public Map, LocationComponent> getComponents() { - Map, LocationComponent> components = new LinkedHashMap, LocationComponent>(); - collectComponents(components, root); - return components; - } - - private void collectComponents(Map, LocationComponent> comp, DTNode node) { - if (node.isLeaf()) { - DTLeaf leaf = (DTLeaf) node; - comp.put(leaf.getAccessSequence(), leaf); - } else { - DTInnerNode inner = (DTInnerNode) node; - for (DTBranch b : inner.getBranches()) { - collectComponents(comp, b.getChild()); - } - } - } - - /** - * find the lowest common ancestor of two leaves - * - * @param l1 - * @param l2 - * @return the lowest common ancestor of l1 and l2 - */ - public DTInnerNode findLCA(DTLeaf l1, DTLeaf l2) { - Deque path1 = new ArrayDeque(); - Deque path2 = new ArrayDeque(); - - if (l1.getParent() == l2.getParent()) - return l1.getParent(); - - DTInnerNode parent = l1.getParent(); - while (parent != null) { - path1.add(parent); - parent = parent.getParent(); - } - parent = l2.getParent(); - while (parent != null) { - path2.add(parent); - parent = parent.getParent(); - } - - while (!path1.isEmpty()) { - DTInnerNode node = path1.poll(); - if (path2.contains(node)) - return node; - } - return null; - } - - public DTLeaf getSink() { - return sink; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("DT: {"); - buildTreeString(builder, root, "", " ", " -- "); - builder.append("}"); - return builder.toString(); - } - - private void buildTreeString(StringBuilder builder, DTNode node, String currentIndentation, String indentation, - String sep) { - if (node.isLeaf()) { - builder.append("\n").append(currentIndentation).append("Leaf: ").append(node); - } else { - DTInnerNode inner = (DTInnerNode) node; - builder.append("\n").append(currentIndentation).append("Inner: ").append(inner.getSuffix()); - if (!inner.getBranches().isEmpty()) { - Iterator iter = inner.getBranches().iterator(); - while (iter.hasNext()) { - builder.append("\n").append(currentIndentation); - DTBranch branch = iter.next(); - builder.append("Branch: ").append(branch.getUrap()); - buildTreeString(builder, branch.getChild(), indentation + currentIndentation, indentation, sep); - } - } - //else { - // builder.append("(").append(inner.getSuffix()).append(",").append("∅").append(")"); - //} - } - } -} diff --git a/src/main/java/de/learnlib/ralib/dt/DTBranch.java b/src/main/java/de/learnlib/ralib/dt/DTBranch.java deleted file mode 100644 index ce5af3d7..00000000 --- a/src/main/java/de/learnlib/ralib/dt/DTBranch.java +++ /dev/null @@ -1,61 +0,0 @@ -package de.learnlib.ralib.dt; - -import de.learnlib.ralib.data.Bijection; -import de.learnlib.ralib.data.DataValue; -import de.learnlib.ralib.data.util.RemappingIterator; - -public class DTBranch { - - private final PathResult urap; - - private DTNode child; - - public DTBranch(DTNode child, PathResult row) { - //this.sdt = sdt; - this.child = child; - this.urap = row; - child.setParentBranch(this); - } - - public DTBranch(DTBranch b) { - child = b.child.copy(); - urap = b.urap.copy(); - child.setParentBranch(this); - } - - public void setChild(DTNode child) { - this.child = child; - child.setParentBranch(this); - } - - public DTNode getChild() { - return child; - } - - /** - * null if there is no match - * - * @param r - * @return - */ - public Bijection matches(PathResult r) { - if (!urap.couldBeEquivalentTo(r)) { - return null; - } - - RemappingIterator iterator = new RemappingIterator<>( - r.memorableValues(), urap.memorableValues()); - - for (Bijection m : iterator) { - //System.out.println("m: " + m); - if (r.isEquivalentTo(urap, m)) { - return m; - } - } - return null; - } - - PathResult getUrap() { - return urap; - } -} diff --git a/src/main/java/de/learnlib/ralib/dt/DTHyp.java b/src/main/java/de/learnlib/ralib/dt/DTHyp.java deleted file mode 100644 index 733c9503..00000000 --- a/src/main/java/de/learnlib/ralib/dt/DTHyp.java +++ /dev/null @@ -1,155 +0,0 @@ -package de.learnlib.ralib.dt; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import de.learnlib.ralib.data.Constants; -import de.learnlib.ralib.data.ParameterValuation; -import de.learnlib.ralib.data.RegisterAssignment; -import de.learnlib.ralib.data.RegisterValuation; -import de.learnlib.ralib.learning.Hypothesis; -import de.learnlib.ralib.oracles.Branching; -import de.learnlib.ralib.smt.SMTUtil; -import de.learnlib.ralib.words.PSymbolInstance; -import de.learnlib.ralib.words.ParameterizedSymbol; -import gov.nasa.jpf.constraints.api.Expression; -import net.automatalib.word.Word; - -public class DTHyp extends Hypothesis { - - private final DT dt; - - public DTHyp(Constants consts, DT dt) { - super(consts); - this.dt = dt; - } - - @Override - public boolean accepts(Word word) { - Word as = transformAccessSequence(word); - DTLeaf l = dt.getLeaf(as); - assert l != null; - return l.isAccepting(); - } - - @Override - public boolean isAccessSequence(Word word) { - if (super.isAccessSequence(word)) - return true; - DTLeaf leaf = dt.getLeaf(word); - if (leaf == null) - return false; - return leaf.getAccessSequence().equals(word) || - leaf.getShortPrefixes().contains(word); - } - - @Override - public Word transformAccessSequence(Word word) { - List> tseq = getDTTransitions(word); - if (tseq == null) { - return null; - } - if (tseq.isEmpty()) { - return Word.epsilon(); - } else { - return dt.getLeaf(tseq.get(tseq.size() - 1)).getAccessSequence(); - } - } - - @Override - public Set> possibleAccessSequences(Word word) { - Set> ret = new LinkedHashSet>(); - Word as = transformAccessSequence(word); - ret.add(as); - - DTLeaf leaf = dt.getLeaf(as); - assert leaf != null; - for (MappedPrefix mp : leaf.getShortPrefixes().get()) - ret.add(mp.getPrefix()); - return ret; - } - - protected List> getDTTransitions(Word dw) { - RegisterValuation vars = RegisterValuation.copyOf(getInitialRegisters()); - DTLeaf current = dt.getLeaf(Word.epsilon()); - List> tseq = new ArrayList<>(); - for (PSymbolInstance psi : dw) { - ParameterValuation pars = ParameterValuation.fromPSymbolInstance(psi); - - Map, Expression> candidates = - current.getBranching(psi.getBaseSymbol()).getBranches(); - - if (candidates == null) { - return null; - } - - RegisterAssignment ra = current.getPrimePrefix().getAssignment(); - - boolean found = false; - for (Map.Entry, Expression> e : candidates.entrySet()) { - Expression g = e.getValue(); - g = SMTUtil.valsToRegisters(g, ra); - if (g.evaluateSMT(SMTUtil.compose(vars, pars, this.constants))) { - Word w = e.getKey(); - vars = current.getAssignment(w, dt.getLeaf(w)).compute(vars, pars, this.constants); - current = dt.getLeaf(w); - tseq.add(w); - found = true; - break; - } - } - - if (!found) { - return null; - } - } - return tseq; - } - - @Override - public Word transformTransitionSequence(Word word) { - List> tseq = getDTTransitions(word); - if (tseq == null) - return dt.getLeaf(word).getAccessSequence(); - assert tseq.size() == word.size(); - return tseq.get(tseq.size() - 1); - } - - @Override - public Word transformTransitionSequence(Word word, - Word location) { - Word suffix = word.suffix(1); - - DTLeaf leaf = dt.getLeaf(location); - assert leaf != null; - assert leaf.getAccessSequence().equals(location) || leaf.getShortPrefixes().contains(location); - - if (leaf.getAccessSequence().equals(location)) { - Word tseq = transformTransitionSequence(word); - //System.out.println("TSEQ: " + tseq); - if (tseq == null) { - ParameterizedSymbol ps = suffix.firstSymbol().getBaseSymbol(); - for (Word p : leaf.getBranching(ps).getBranches().keySet()) { - DTLeaf l = dt.getLeaf(p); - if (l != null && l == dt.getSink()) - return p; - } - } - return tseq; - } - - ParameterizedSymbol ps = suffix.firstSymbol().getBaseSymbol(); - - ShortPrefix sp = (ShortPrefix)leaf.getShortPrefixes().get(location); - Word ret = branchWithSameGuard(word, sp.getBranching(ps)); - assert ret != null; - return ret; - } - - public Word branchWithSameGuard(Word word, MappedPrefix src_id, Branching branching) { - return branching.transformPrefix(word); - } -} diff --git a/src/main/java/de/learnlib/ralib/dt/DTInnerNode.java b/src/main/java/de/learnlib/ralib/dt/DTInnerNode.java deleted file mode 100644 index 038c5b07..00000000 --- a/src/main/java/de/learnlib/ralib/dt/DTInnerNode.java +++ /dev/null @@ -1,93 +0,0 @@ -package de.learnlib.ralib.dt; - -import java.util.*; - -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; - -import de.learnlib.ralib.data.Bijection; -import de.learnlib.ralib.data.DataValue; -import de.learnlib.ralib.learning.SymbolicSuffix; -import de.learnlib.ralib.oracles.TreeOracle; - -public class DTInnerNode extends DTNode { - - private final SymbolicSuffix suffix; - - private final Set branches; - - public DTInnerNode(SymbolicSuffix suffix) { - super(); - this.suffix = suffix; - branches = new LinkedHashSet(); - } - - public DTInnerNode(SymbolicSuffix suffix, Set branches) { - super(); - this.suffix = suffix; - this.branches = branches; - } - - public DTInnerNode(DTInnerNode n) { - suffix = n.suffix; - branches = new LinkedHashSet(); - for (DTBranch b : n.branches) { - DTBranch nb = new DTBranch(b); - b.getChild().setParent(this); - branches.add(nb); - } - } - - protected Pair sift(MappedPrefix prefix, TreeOracle oracle, boolean ioMode) { - PathResult r = PathResult.computePathResult(oracle, prefix, getSuffixes(), ioMode); - for (DTBranch b : branches) { - Bijection remapping = b.matches(r); - if (remapping != null) { - r.setRemapping(remapping); - return new ImmutablePair(b.getChild(), r); - } - } - r.setRemapping(Bijection.identity(r.memorableValues())); - return null; - } - - public void addBranch(DTBranch b) { - branches.add(b); - } - - public Set getBranches() { - return branches; - } - - public SymbolicSuffix getSuffix() { - return suffix; - } - - List getSuffixes() { - LinkedList suffixes = new LinkedList<>(); - getSuffixes(suffixes); - return suffixes; - } - - void getSuffixes(LinkedList suffixes) { - suffixes.addFirst(suffix); - if (parent != null) { - parent.getSuffixes(suffixes); - } - } - - @Override - public boolean isLeaf() { - return false; - } - - @Override - public DTInnerNode copy() { - return new DTInnerNode(this); - } - - @Override - public String toString() { - return "(" + suffix.toString() + ")"; - } -} diff --git a/src/main/java/de/learnlib/ralib/dt/DTLeaf.java b/src/main/java/de/learnlib/ralib/dt/DTLeaf.java deleted file mode 100644 index 54892cda..00000000 --- a/src/main/java/de/learnlib/ralib/dt/DTLeaf.java +++ /dev/null @@ -1,605 +0,0 @@ -package de.learnlib.ralib.dt; - -import java.util.*; -import java.util.Map.Entry; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import com.google.common.collect.Iterators; -import com.google.common.collect.Sets; - -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; - -import de.learnlib.ralib.automata.Assignment; -import de.learnlib.ralib.data.*; -import de.learnlib.ralib.learning.AutomatonBuilder; -import de.learnlib.ralib.learning.LocationComponent; -import de.learnlib.ralib.learning.PrefixContainer; -import de.learnlib.ralib.learning.SymbolicSuffix; -import de.learnlib.ralib.learning.ralambda.DiscriminationTree; -import de.learnlib.ralib.learning.rastar.RaStar; -import de.learnlib.ralib.oracles.Branching; -import de.learnlib.ralib.oracles.SDTLogicOracle; -import de.learnlib.ralib.oracles.TreeOracle; -import de.learnlib.ralib.oracles.mto.OptimizedSymbolicSuffixBuilder; -import de.learnlib.ralib.theory.SDT; -import de.learnlib.ralib.words.DataWords; -import de.learnlib.ralib.words.InputSymbol; -import de.learnlib.ralib.words.PSymbolInstance; -import de.learnlib.ralib.words.ParameterizedSymbol; -import gov.nasa.jpf.constraints.api.Expression; -import net.automatalib.word.Word; - -public class DTLeaf extends DTNode implements LocationComponent { - - private MappedPrefix access; - private PrefixSet shortPrefixes; - private PrefixSet otherPrefixes; - - private final Map branching = new LinkedHashMap(); - private final TreeOracle oracle; - - public DTLeaf(TreeOracle oracle) { - super(); - access = null; - shortPrefixes = new PrefixSet(); - otherPrefixes = new PrefixSet(); - this.oracle = oracle; - } - - public DTLeaf(MappedPrefix as, TreeOracle oracle) { - super(); - access = as; - shortPrefixes = new PrefixSet(); - otherPrefixes = new PrefixSet(); - this.oracle = oracle; - } - - public DTLeaf(DTLeaf l) { - access = new MappedPrefix(l.access, l.access.getRemapping()); - shortPrefixes = new PrefixSet(l.shortPrefixes); - otherPrefixes = new PrefixSet(l.otherPrefixes); - branching.putAll(l.branching); - oracle = l.oracle; - } - - public void addPrefix(Word p) { - otherPrefixes.add(new MappedPrefix(p, new Bijection<>())); - } - - public void addPrefix(MappedPrefix p) { - assert p.memorableValues().size() == access.memorableValues().size(); - otherPrefixes.add(p); - } - - void setAccessSequence(MappedPrefix mp) { - access = mp; - } - - public void addShortPrefix(ShortPrefix prefix) { - if (otherPrefixes.contains(prefix.getPrefix())) - otherPrefixes.remove(prefix.getPrefix()); - assert access != null; - shortPrefixes.add(prefix); - } - - public boolean removeShortPrefix(Word p) { - return shortPrefixes.remove(p); - } - - public boolean removePrefix(Word p) { - return shortPrefixes.removeIf((e) -> e.getPrefix().equals(p)) || otherPrefixes.removeIf((e) -> e.getPrefix().equals(p)); - } - - /** - * Clears the short and other prefix sets. - * The only prefix remaining will be the leaf's access sequence. - */ - public void clear() { - shortPrefixes = new PrefixSet(); - otherPrefixes = new PrefixSet(); - } - - public PrefixSet getShortPrefixes() { - return shortPrefixes; - } - - public PrefixSet getPrefixes() { - return otherPrefixes; - } - - @Override - public boolean isLeaf() { - return true; - } - - @Override - public boolean isAccepting() { - SDT tqr = access.getTQRs().get(RaStar.EMPTY_SUFFIX); - return tqr.isAccepting(); - } - - MappedPrefix getMappedPrefix(Word p) { - if (access.getPrefix().equals(p)) - return access; - MappedPrefix ret = shortPrefixes.get(p); - if (ret != null) - return ret; - ret = otherPrefixes.get(p); - return ret; - } - - @Override - public Word getAccessSequence() { - return access.getPrefix(); - } - - @Override - public Bijection getRemapping(PrefixContainer r) { - if (r.getPrefix().equals(this.getAccessSequence())) - return Bijection.identity(this.access.memorableValues()); - MappedPrefix mp = otherPrefixes.get(r.getPrefix()); - if (mp == null) - mp = shortPrefixes.get(r.getPrefix()); - - assert mp != null; - return mp.getRemapping(); - } - - @Override - public Branching getBranching(ParameterizedSymbol action) { - return branching.get(action); - } - - public Branching getBranching(ParameterizedSymbol action, Word src) { - Branching b = null; - ShortPrefix sp = (ShortPrefix)shortPrefixes.get(src); - if (sp != null) - b = sp.getBranching(action); - if (b == null) - return getBranching(action); - return b; - } - - @Override - public MappedPrefix getPrimePrefix() { - return access; - } - - Collection> getAllPrefixes() { - Collection> prefs = new ArrayList>(); - prefs.add(this.getAccessSequence()); - Iterator it = shortPrefixes.iterator(); - while (it.hasNext()) - prefs.add(it.next().getPrefix()); - it = otherPrefixes.iterator(); - while (it.hasNext()) - prefs.add(it.next().getPrefix()); - return prefs; - } - - void getMappedExtendedPrefixes(Collection mps) { - mps.addAll(shortPrefixes.get()); - mps.addAll(otherPrefixes.get()); - } - - @Override - public DTInnerNode getParent() { - return parent; - } - - public MappedPrefix getPrefix(Word prefix) { - if (getAccessSequence().equals(prefix)) - return getPrimePrefix(); - MappedPrefix mp = shortPrefixes.get(prefix); - if (mp == null) - mp = otherPrefixes.get(prefix); - return mp; - } - - public SDT getTQR(Word prefix, SymbolicSuffix suffix) { - MappedPrefix mp = getMappedPrefix(prefix); - return mp.getTQRs().get(suffix); - } - - void addTQRs(SymbolicSuffix suffix) { - Iterator it = Iterators.concat(shortPrefixes.iterator(), otherPrefixes.iterator()); - while (it.hasNext()) { - MappedPrefix mp = it.next(); - mp.computeTQR(suffix, oracle); - } - } - - void addTQRs(SymbolicSuffix s, boolean addToAccess) { - if (addToAccess) { - access.computeTQR(s, oracle); - } - addTQRs(s); - } - - @Override - public Collection getOtherPrefixes() { - return Stream.concat(otherPrefixes.get().stream(), shortPrefixes.get().stream()).collect(Collectors.toList()); - } - - /** - * Elevate a prefix from the set of other prefixes to the set of short prefix, - * and checks whether this leads to a refinement. The branches of prefix are - * sifted into the tree, and added to their respective leaves. If a branch of - * prefix leads to another location than the same branch of the access sequence, - * returns the diverging words as a Pair, otherwise returns null. - * - * @param dt - * @param prefix - * @param hyp - * @param logicOracle - * @return Pair of diverging words, if such a pair of words exists. Otherwise - * null. - */ - public Pair, Word> elevatePrefix(DT dt, Word prefix, - DTHyp hyp, SDTLogicOracle logicOracle) { - assert !shortPrefixes.contains(prefix) : prefix + " is already a short prefix"; - MappedPrefix mp = otherPrefixes.get(prefix); - assert mp != null : "Cannot elevate prefix that is not contained in leaf " + this + " === " + prefix; - ShortPrefix sp = new ShortPrefix(mp); - addShortPrefix(sp); - - return startPrefix(dt, sp, hyp, logicOracle); - } - - private Pair, Word> startPrefix(DT dt, ShortPrefix mp, DTHyp hyp, SDTLogicOracle logicOracle) { - - Pair, Word> divergance = null; - boolean input = DTLeaf.isInput(mp.getPrefix().lastSymbol().getBaseSymbol()); - for (ParameterizedSymbol ps : dt.getInputs()) { - - SDT[] sdtsP = this.getSDTsForInitialSymbol(mp, ps); - Branching prefixBranching = oracle.getInitialBranching(mp.getPrefix(), ps, sdtsP); - mp.putBranching(ps, prefixBranching); - - SDT[] sdtsAS = this.getSDTsForInitialSymbol(this.getPrimePrefix(), ps); - Branching accessBranching = oracle.getInitialBranching(getAccessSequence(), ps, sdtsAS); - - assert prefixBranching.getBranches().size() == accessBranching.getBranches().size(); - - for (Word p : prefixBranching.getBranches().keySet()) { - if (dt.getIoMode() && ((input ^ !isInput(ps)) || hyp.getLocation(p) == null)) { - dt.getSink().addPrefix(p); - continue; - } - - Word a = null; - if (hyp == null) { - // no hypothesis yet, assume all true guards - for (Word w : accessBranching.getBranches().keySet()) { - if (w.lastSymbol().getBaseSymbol().equals(p.lastSymbol().getBaseSymbol())) { - a = w; - break; - } - } - } else { - a = branchWithSameGuard(p, prefixBranching, this.getRemapping(mp), accessBranching, logicOracle); - } - assert a != null; - DTLeaf leaf = dt.sift(p, true); - - if (!dt.getLeaf(a).equals(leaf)) { - divergance = new ImmutablePair, Word>(p, a); - } - } - } - return divergance; - } - - static public Word branchWithSameGuard(Word word, Branching wordBranching, Bijection remapping, Branching accBranching, SDTLogicOracle oracle) { - Word a = null; - Expression g = null; - for (Entry, Expression> e : wordBranching.getBranches().entrySet()) { - if (e.getKey().equals(word)) { - g = e.getValue(); - break; - } - } - assert g != null; - //System.out.println("w:" + word); - //System.out.println("g: " + g); - //System.out.println("pi: " + remapping); - for (Entry, Expression> e : accBranching.getBranches().entrySet()) { - Expression ag = e.getValue(); - //System.out.println("ag: " + ag); - boolean eq = oracle.areEquivalent(ag, remapping, g, new Mapping()); - if (eq) { - a = e.getKey(); - break; - } - } - assert a != null; - return a; - } - - public boolean isRefinemement(DT dt, Word word) { - ParameterizedSymbol ps = word.lastSymbol().getBaseSymbol(); - DTLeaf target = dt.getLeaf(word); - - Branching b = getBranching(ps); - boolean refinement = true; - for (Word w : b.getBranches().keySet()) { - if (dt.getLeaf(w) == target) - refinement = false; - } - - return refinement; - } - - void start(DT dt, boolean ioMode, ParameterizedSymbol... inputs) { - boolean input = isInputComponent(); - for (ParameterizedSymbol ps : inputs) { - - SDT[] sdts = this.getSDTsForInitialSymbol(ps); - Branching b = oracle.getInitialBranching(getAccessSequence(), ps, sdts); - branching.put(ps, b); - for (Word prefix : b.getBranches().keySet()) { - if (ioMode && (dt.getSink() != null) && (input ^ isInput(ps))) - dt.getSink().addPrefix(prefix); - else - dt.sift(prefix, true); - } - } - } - - void start(DT dt, Map branching) { - this.branching.putAll(branching); - } - - boolean updateBranching(DT dt) { - boolean ret = true; - for (ParameterizedSymbol p : branching.keySet()) { - boolean ub = updateBranching(p, dt); - ret = ret && ub; - } - return ret; - } - - private boolean updateBranching(ParameterizedSymbol ps, DiscriminationTree dt) { - Branching b = branching.get(ps); - SDT[] sdts = getSDTsForInitialSymbol(ps); - Branching newB = oracle.updateBranching(access.getPrefix(), ps, b, sdts); - boolean ret = true; - - for (Word prefix : newB.getBranches().keySet()) { - if (!b.getBranches().containsKey(prefix)) { - dt.sift(prefix, true); - ret = false; - } - } - - for (MappedPrefix sp : shortPrefixes.get()) { - ret &= updateBranching(ps, (ShortPrefix) sp, dt); - } - - branching.put(ps, newB); - return ret; - } - - private boolean updateBranching(ParameterizedSymbol ps, ShortPrefix sp, DiscriminationTree dt) { - Branching b = sp.getBranching(ps); - SDT[] sdts = getSDTsForInitialSymbol(sp, ps); - Branching newB = oracle.updateBranching(sp.getPrefix(), ps, b, sdts); - boolean ret = true; - - for (Word prefix : newB.getBranches().keySet()) { - if (!b.getBranches().containsKey(prefix)) { - dt.sift(prefix, true); - ret = false; - } - } - sp.putBranching(ps, newB); - return ret; - } - - private SDT[] getSDTsForInitialSymbol(ParameterizedSymbol p) { - return getSDTsForInitialSymbol(this.getPrimePrefix(), p); - } - - private SDT[] getSDTsForInitialSymbol(MappedPrefix mp, ParameterizedSymbol p) { - List sdts = new ArrayList<>(); - for (Entry e : mp.getTQRs().entrySet()) { - Word acts = e.getKey().getActions(); - if (acts.length() > 0 && acts.firstSymbol().equals(p)) { - sdts.add(e.getValue()); - } - } - return sdts.toArray(new SDT[] {}); - } - - public boolean checkVariableConsistency(DT dt, Constants consts, OptimizedSymbolicSuffixBuilder suffixBuilder) { - if (!checkVariableConsistency(access, dt, consts, suffixBuilder)) { - return false; - } - - Iterator it = otherPrefixes.iterator(); - while (it.hasNext()) { - if (!checkVariableConsistency(it.next(), dt, consts, suffixBuilder)) - return false; - } - it = shortPrefixes.iterator(); - while (it.hasNext()) { - if (!checkVariableConsistency(it.next(), dt, consts, suffixBuilder)) - return false; - } - return true; - } - - private boolean checkVariableConsistency(MappedPrefix mp, DT dt, Constants consts, OptimizedSymbolicSuffixBuilder suffixBuilder) { - if (mp.getPrefix().length() < 2) - return true; - - Word prefix = mp.getPrefix().prefix(mp.getPrefix().length() - 1); - DTLeaf prefixLeaf = dt.getLeaf(prefix); - MappedPrefix prefixMapped = prefixLeaf.getMappedPrefix(prefix); - - Set memPrefix = prefixMapped.memorableValues(); - Set memMP = mp.memorableValues(); - - int max = DataWords.paramLength(DataWords.actsOf(prefix)); - List mpVals = Arrays.stream(DataWords.valsOf(mp.getPrefix())).toList(); - - for (DataValue d : memMP) { - boolean prefixMissingParam = !memPrefix.contains(d) || prefixMapped.missingParameter.contains(d); - if (prefixMissingParam && mpVals.indexOf(d) < max) { - List prefixSuffixes = prefixMapped.getAllSuffixesForMemorable(d); - List suffixes = mp.getAllSuffixesForMemorable(d); - assert !suffixes.isEmpty(); - for (SymbolicSuffix suffix : suffixes) { - SDT sdt = mp.getTQRs().get(suffix); - // suffixBuilder == null ==> suffix.isOptimizedGeneric() - assert suffixBuilder != null || suffix.isOptimizationGeneric() : "Optimized with restriction builder, but no restriction builder provided"; - SymbolicSuffix newSuffix = suffixBuilder != null ? - suffixBuilder.extendSuffix(mp.getPrefix(), sdt, suffix, d) : - new SymbolicSuffix(mp.getPrefix(), suffix, consts); - if (prefixSuffixes.contains(newSuffix)) - continue; - SDT tqr = oracle.treeQuery(prefix, newSuffix); - - if (tqr.getDataValues().contains(d)) { - dt.addSuffix(newSuffix, prefixLeaf); - mp.missingParameter.remove(d); - return false; - } - } - if (!prefixMapped.missingParameter.contains(d)) { - mp.missingParameter.add(d); - } - } else { - mp.missingParameter.remove(d); - } - } - - return true; - } - - public boolean checkRegisterConsistency(DT dt, Constants consts, OptimizedSymbolicSuffixBuilder suffixBuilder) { - if (access.memorableValues().isEmpty()) - return true; - - if (!checkRegisterConsistency(access, dt, consts, suffixBuilder)) - return false; - - Iterator it = otherPrefixes.iterator(); - while (it.hasNext()) { - if (!checkRegisterConsistency(it.next(), dt, consts, suffixBuilder)) - return false; - } - it = shortPrefixes.iterator(); - while (it.hasNext()) { - if (!checkRegisterConsistency(it.next(), dt, consts, suffixBuilder)) - return false; - } - return true; - } - - public boolean checkRegisterConsistency(MappedPrefix mp, DT dt, Constants consts, OptimizedSymbolicSuffixBuilder suffixBuilder) { - if (mp.getPrefix().length() < 2) - return true; - - Set memMP = Set.of(mp.memorableValues().toArray(new DataValue[0])); - if (memMP.isEmpty()) - return true; - - Word prefix = mp.getPrefix().prefix(mp.getPrefix().length() - 1); - DTLeaf prefixLeaf = dt.getLeaf(prefix); - MappedPrefix prefixMapped = prefixLeaf.getMappedPrefix(prefix); - Set memPrefix = Set.of(prefixMapped.memorableValues().toArray(new DataValue[0])); - - Set paramsIntersect = Sets.intersection(memPrefix, memMP); - if (prefixMapped.equivalentRenamings(memPrefix).size() < 2) - return true; -// if (renamingsPrefix.size() < 2) -// return true; // there are no equivalent renamings - - if (!paramsIntersect.isEmpty() && paramsIntersect.size() < memPrefix.size()) { - // word shares some data values with prefix, but not all - for (Map.Entry e : mp.getTQRs().entrySet()) { - Set pmap = Set.of(e.getValue().getDataValues().toArray(new DataValue[0])); - if (!Sets.intersection(pmap, paramsIntersect).isEmpty()) { - SDT sdt = e.getValue(); - SymbolicSuffix newSuffix = suffixBuilder != null ? - suffixBuilder.extendSuffix(mp.getPrefix(), sdt, e.getKey()) : - new SymbolicSuffix(mp.getPrefix(), e.getKey(), consts); - if (!prefixMapped.getTQRs().containsKey(newSuffix)) { - dt.addSuffix(newSuffix, prefixLeaf); - return false; - } - } - } - } - - Set> renamingsPrefix = prefixMapped.equivalentRenamings(paramsIntersect); - if (renamingsPrefix.size() < 2) - return true; // there are no equivalent renamings - Set> renamingsMP = mp.equivalentRenamings(paramsIntersect); - Set> difference = Sets.difference(renamingsPrefix, renamingsMP); - if (!difference.isEmpty()) { - // there are symmetric parameter mappings in the prefix which are not symmetric in mp - for (Map.Entry e : mp.getTQRs().entrySet()) { - SDT sdt = e.getValue(); - for (Bijection vm : difference) { - //VarMapping renaming = new VarMapping<>(); - /*for (Map.Entry paramRenaming : vm.entrySet()) { - Register oldRegister = memPrefix.get(paramRenaming.getKey()); - Register newRegister = memPrefix.get(paramRenaming.getValue()); - renaming.put(oldRegister, newRegister); - }*/ - if (!sdt.isEquivalent(sdt, vm)) { - SymbolicSuffix newSuffix = suffixBuilder != null ? - suffixBuilder.extendSuffix(mp.getPrefix(), sdt, e.getKey()) : - new SymbolicSuffix(mp.getPrefix(), e.getKey(), consts); - dt.addSuffix(newSuffix, prefixLeaf); - return false; - } - } - } - } - return true; - } - public boolean isMissingVariable() { - Collection prefixes = new ArrayList<>(); - getMappedExtendedPrefixes(prefixes); - for (MappedPrefix mp : prefixes) { - if (!mp.missingParameter.isEmpty()) - return true; - } - return !access.missingParameter.isEmpty(); - } - - public boolean isInputComponent() { - if (this.getAccessSequence().length() == 0) - return true; - - ParameterizedSymbol ps = this.getAccessSequence().lastSymbol().getBaseSymbol(); - return !isInput(ps); - } - - public static boolean isInput(ParameterizedSymbol ps) { - return (ps instanceof InputSymbol); - } - - public Assignment getAssignment(Word dest_id, DTLeaf dest_c) { - MappedPrefix r = dest_c.getPrefix(dest_id); - RegisterAssignment srcAssign = getPrimePrefix().getAssignment(); - RegisterAssignment dstAssign = dest_c.access.getAssignment(); - Bijection remap = dest_c.getRemapping(r); - return AutomatonBuilder.computeAssignment(r.getPrefix(), srcAssign, dstAssign, remap); - } - - @Override - public DTLeaf copy() { - return new DTLeaf(this); - } - - @Override - public String toString() { - return getAllPrefixes().toString(); - } -} diff --git a/src/main/java/de/learnlib/ralib/dt/DTNode.java b/src/main/java/de/learnlib/ralib/dt/DTNode.java deleted file mode 100644 index 3bdc1311..00000000 --- a/src/main/java/de/learnlib/ralib/dt/DTNode.java +++ /dev/null @@ -1,31 +0,0 @@ -package de.learnlib.ralib.dt; - -public abstract class DTNode { - protected DTInnerNode parent; - protected DTBranch parentBranch; - - public DTNode() { - parent = null; - parentBranch = null; - } - - public void setParent(DTInnerNode parent) { - this.parent = parent; - } - - public void setParentBranch(DTBranch b) { - parentBranch = b; - } - - public DTInnerNode getParent() { - return parent; - } - - public DTBranch getParentBranch() { - return parentBranch; - } - - public abstract boolean isLeaf(); - - public abstract DTNode copy(); -} diff --git a/src/main/java/de/learnlib/ralib/dt/MappedPrefix.java b/src/main/java/de/learnlib/ralib/dt/MappedPrefix.java deleted file mode 100644 index 957b6595..00000000 --- a/src/main/java/de/learnlib/ralib/dt/MappedPrefix.java +++ /dev/null @@ -1,112 +0,0 @@ -package de.learnlib.ralib.dt; - -import java.util.*; -import java.util.Map.Entry; - -import de.learnlib.ralib.data.*; -import de.learnlib.ralib.data.util.RemappingIterator; -import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.RegisterGenerator; -import de.learnlib.ralib.learning.PrefixContainer; -import de.learnlib.ralib.learning.SymbolicSuffix; -import de.learnlib.ralib.oracles.TreeOracle; -import de.learnlib.ralib.theory.Memorables; -import de.learnlib.ralib.theory.SDT; -import de.learnlib.ralib.words.PSymbolInstance; -import net.automatalib.word.Word; - -public class MappedPrefix implements PrefixContainer { - - private final Word prefix; - private final RegisterGenerator regGen = new RegisterGenerator(); - - private Bijection remapping; - private final Map tqrs = new LinkedHashMap(); - public final Set missingParameter = new LinkedHashSet<>(); - - public MappedPrefix(Word prefix, Bijection remapping) { - this.prefix = prefix; - this.remapping = remapping; - } - - MappedPrefix(MappedPrefix mp, Bijection remapping) { - this.prefix = mp.getPrefix(); - tqrs.putAll(mp.getTQRs()); - this.remapping = remapping; - } - - public Set> equivalentRenamings(Set params) { - assert new HashSet<>(memorableValues()).containsAll(params); - - Set> renamings = new LinkedHashSet<>(); - RemappingIterator iter = new RemappingIterator<>(params, params); - LOC: for (Bijection b : iter) { - for (SDT tqr : tqrs.values()) { - if (!tqr.isEquivalent(tqr, b)) - continue LOC; - } - renamings.add(b); - } - return renamings; - } - - /* - * Performs a tree query for the (new) suffix and stores it in its internal map. - * Returns the result. - */ - SDT computeTQR(SymbolicSuffix suffix, TreeOracle oracle) { - SDT tqr = oracle.treeQuery(prefix, suffix); - addTQR(suffix, tqr); - return tqr; - } - - void addTQR(SymbolicSuffix s, SDT tqr) { - if (tqrs.containsKey(s) || tqr == null) return; - tqrs.put(s, tqr); - } - - public Map getTQRs() { - return tqrs; - } - - @Override - public Word getPrefix() { - return this.prefix; - } - - public Bijection getRemapping() { - return remapping; - } - - public void updateRemapping(Bijection remapping) { - this.remapping = remapping; - } - - public Set memorableValues() { - return Memorables.memorableValues(tqrs.values()); - } - - @Override - public RegisterAssignment getAssignment() { - return Memorables.getAssignment(tqrs.values()); - } - - @Override - public String toString() { - return "{" + prefix.toString() + ", " + Arrays.toString(memorableValues().toArray()) + "}"; - } - - SymbolicSuffix getSuffixForMemorable(DataValue d) { - return tqrs.entrySet().stream() - .filter(e -> e.getValue().getDataValues().contains(d)) - .findFirst() - .orElseThrow(() -> new IllegalStateException("This line is not supposed to be reached.")) - .getKey(); - } - - List getAllSuffixesForMemorable(DataValue d) { - return tqrs.entrySet().stream() - .filter(e -> e.getValue().getDataValues().contains(d)) - .map(Entry::getKey) - .toList(); - } -} diff --git a/src/main/java/de/learnlib/ralib/dt/PathResult.java b/src/main/java/de/learnlib/ralib/dt/PathResult.java deleted file mode 100644 index 0d6905b3..00000000 --- a/src/main/java/de/learnlib/ralib/dt/PathResult.java +++ /dev/null @@ -1,185 +0,0 @@ -package de.learnlib.ralib.dt; - - -import java.util.*; -import java.util.stream.Collectors; - -import de.learnlib.ralib.data.*; -import de.learnlib.ralib.data.util.SymbolicDataValueGenerator; -import de.learnlib.ralib.learning.SymbolicSuffix; -import de.learnlib.ralib.learning.rastar.RaStar; -import de.learnlib.ralib.oracles.TreeOracle; -import de.learnlib.ralib.theory.Memorables; -import de.learnlib.ralib.theory.SDT; -import de.learnlib.ralib.words.InputSymbol; -import de.learnlib.ralib.words.OutputSymbol; - -/** - * this is a copy of the functionality of row - * with additions / modifications for use in the DT - * - * @author falk - */ -public class PathResult { - - private final Map results; - - private Bijection remapping; - - private final SymbolicDataValueGenerator.RegisterGenerator regGen = new SymbolicDataValueGenerator.RegisterGenerator(); - - private final boolean ioMode; - - private PathResult(boolean ioMode) { - this.results = new LinkedHashMap<>(); - this.ioMode = ioMode; - } - - private void addResult(SymbolicSuffix s, SDT tqr) { - this.results.put(s, tqr); - } - - public boolean isEquivalentTo(PathResult other, Bijection renaming) { - return isEquivalentTo(other, SDTRelabeling.fromBijection(renaming)); - } - - public boolean isEquivalentTo(PathResult other, SDTRelabeling renaming) { - if (!couldBeEquivalentTo(other)) { - return false; - } - - if (!Memorables.relabel(this.memorableValues(), renaming).equals(other.memorableValues())) { - return false; - } - - for (Map.Entry e : this.results.entrySet()) { - SDT c1 = e.getValue(); - SDT c2 = other.results.get(e.getKey()); - - if (ioMode) { - if (c1 == null && c2 == null) { - continue; - } - - if (c1 == null || c2 == null) { - return false; - } - } - - if (!isEquivalentTo(c1, c2, renaming)) { - return false; - } - } - return true; - } - - boolean isEquivalentTo(SDT c1, SDT c2, SDTRelabeling renaming) { - if (!couldBeEquivalentTo(c1, c2)) return false; - return Memorables.relabel(c1.getDataValues(), renaming).equals(c2.getDataValues()) && c2.isEquivalent(c1, renaming); - } - - boolean couldBeEquivalentTo(PathResult other) { - if (!Memorables.typedSize(this.memorableValues()).equals(Memorables.typedSize(other.memorableValues()))) { - return false; - } - - for (Map.Entry e : this.results.entrySet()) { - SDT c1 = e.getValue(); - SDT c2 = other.results.get(e.getKey()); - - if (ioMode) { - if (c1 == null && c2 == null) { - continue; - } - - if (c1 == null || c2 == null) { - return false; - } - } - - if (!couldBeEquivalentTo(c1, c2)) { - return false; - } - } - return true; - } - - public Set memorableValues() { - return results.values().stream() - .flatMap(sdt -> sdt.getDataValues().stream()) - .collect(Collectors.toSet()); - } - - - private boolean couldBeEquivalentTo(SDT c1, SDT c2) { - return Memorables.typedSize(c1.getDataValues()).equals(Memorables.typedSize(c2.getDataValues())); - } - - /** - * computes a new row object from a prefix and a set of symbolic suffixes. - * - * @param oracle - * @param prefix - * @param suffixes - * @return - */ - public static PathResult computePathResult(TreeOracle oracle, - MappedPrefix prefix, List suffixes, boolean ioMode) { - - PathResult r = new PathResult(ioMode); - for (SymbolicSuffix s : suffixes) { - //todo: potential for optimization - if (ioMode && !s.getActions().isEmpty()) { - // error row - if (!prefix.getPrefix().isEmpty() && !r.isAccepting()) { - //log.log(Level.INFO, "Not adding suffix " + s + " to error row " + r.getPrefix()); - continue; - } - // unmatching suffix - if ((prefix.getPrefix().isEmpty() && (s.getActions().firstSymbol() instanceof OutputSymbol)) - || (!prefix.getPrefix().isEmpty() && prefix.getPrefix().lastSymbol().getBaseSymbol() instanceof InputSymbol == s.getActions().firstSymbol() instanceof InputSymbol)) { - //log.log(Level.INFO, "Not adding suffix " + s + " to unmatching row " + r.getPrefix()); - continue; - } - } - SDT tqr = prefix.getTQRs().get(s); - if (tqr == null) { - tqr = oracle.treeQuery(prefix.getPrefix(), s); - } - //System.out.println("TQ: " + prefix + " : " + s + " : " + tqr); - r.addResult(s, tqr); - } - return r; - } - - public boolean isAccepting() { - SDT c = this.results.get(RaStar.EMPTY_SUFFIX); - return c.isAccepting(); - } - - public Bijection getRemapping() { - return remapping; - } - - public void setRemapping(Bijection remapping) { - this.remapping = remapping; - } - - public SDT getSDTforSuffix(SymbolicSuffix suffix) { - return results.get(suffix); - } - - public PathResult copy() { - PathResult r = new PathResult(ioMode); - r.results.putAll(this.results); - return r; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - this.results.forEach((key, value) -> sb.append("[").append(key).append("->") - .append(value.toString().replaceAll("\\s+", " ")).append("] ")); - return sb.toString(); - } -} diff --git a/src/main/java/de/learnlib/ralib/dt/PrefixSet.java b/src/main/java/de/learnlib/ralib/dt/PrefixSet.java deleted file mode 100644 index 2bc0f682..00000000 --- a/src/main/java/de/learnlib/ralib/dt/PrefixSet.java +++ /dev/null @@ -1,88 +0,0 @@ -package de.learnlib.ralib.dt; - -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.function.Predicate; - -import de.learnlib.ralib.data.Bijection; -import de.learnlib.ralib.data.DataValue; -import de.learnlib.ralib.words.PSymbolInstance; -import net.automatalib.word.Word; - -public class PrefixSet { - private final Set prefixes; - - public PrefixSet() { - prefixes = new LinkedHashSet(); - } - - public PrefixSet(PrefixSet ps) { - prefixes = new LinkedHashSet(ps.get()); - } - - public void add(MappedPrefix p) { - prefixes.add(p); - } - - public void add(Word p, Bijection pmap) { - prefixes.add(new MappedPrefix(p, pmap)); - } - - public boolean remove(MappedPrefix p) { - return prefixes.remove(p); - } - - public boolean removeIf(Predicate filter) { - return prefixes.removeIf(filter); - } - - public boolean remove(Word p) { - return prefixes.removeIf(mp -> mp.getPrefix().equals(p)); - } - - public Set get() { - return prefixes; - } - - public MappedPrefix get(Word p) { - for (MappedPrefix mp : prefixes) { - if (mp.getPrefix().equals(p)) - return mp; - } - return null; - } - - public Iterator iterator() { - return prefixes.iterator(); - } - - public boolean contains(MappedPrefix p) { - return prefixes.contains(p); - } - - public boolean contains(Word word) { - return prefixes.stream().anyMatch(mp -> mp.getPrefix().equals(word)); - } - - public Collection> getWords() { - Collection> words = new LinkedHashSet>(); - for (MappedPrefix p : prefixes) - words.add(p.getPrefix()); - return words; - } - - public int length() { - return prefixes.size(); - } - - public boolean isEmpty() { - return prefixes.isEmpty(); - } - - @Override - public String toString() { - return prefixes.toString(); - } -} diff --git a/src/main/java/de/learnlib/ralib/dt/ShortPrefix.java b/src/main/java/de/learnlib/ralib/dt/ShortPrefix.java deleted file mode 100644 index b1f8b72d..00000000 --- a/src/main/java/de/learnlib/ralib/dt/ShortPrefix.java +++ /dev/null @@ -1,35 +0,0 @@ -package de.learnlib.ralib.dt; - -import java.util.LinkedHashMap; -import java.util.Map; - -import de.learnlib.ralib.data.Bijection; -import de.learnlib.ralib.oracles.Branching; -import de.learnlib.ralib.words.PSymbolInstance; -import de.learnlib.ralib.words.ParameterizedSymbol; -import net.automatalib.word.Word; - -public class ShortPrefix extends MappedPrefix { - - private final Map branching = new LinkedHashMap(); - - public ShortPrefix(Word prefix) { - super(prefix, new Bijection<>()); - } - - public ShortPrefix(MappedPrefix mp) { - super(mp, mp.getRemapping()); - } - - public Map getBranching() { - return branching; - } - - public Branching getBranching(ParameterizedSymbol ps) { - return branching.get(ps); - } - - void putBranching(ParameterizedSymbol ps, Branching b) { - branching.put(ps, b); - } -} diff --git a/src/main/java/de/learnlib/ralib/learning/AutomatonBuilder.java b/src/main/java/de/learnlib/ralib/learning/AutomatonBuilder.java index e6404e33..670fe1bc 100644 --- a/src/main/java/de/learnlib/ralib/learning/AutomatonBuilder.java +++ b/src/main/java/de/learnlib/ralib/learning/AutomatonBuilder.java @@ -30,8 +30,6 @@ import de.learnlib.ralib.data.*; import de.learnlib.ralib.data.SymbolicDataValue.Parameter; import de.learnlib.ralib.data.SymbolicDataValue.Register; -import de.learnlib.ralib.dt.DT; -import de.learnlib.ralib.dt.DTHyp; import de.learnlib.ralib.learning.rastar.RaStar; import de.learnlib.ralib.oracles.Branching; import de.learnlib.ralib.smt.ReplacingValuesVisitor; @@ -65,12 +63,6 @@ public AutomatonBuilder(Map, LocationComponent> components this.automaton = new Hypothesis(consts); } - public AutomatonBuilder(Map, LocationComponent> components, Constants consts, DT dt) { - this.consts = consts; - this.components = components; - this.automaton = new DTHyp(consts, dt); - } - public Hypothesis toRegisterAutomaton() { LOGGER.debug(Category.EVENT, "computing hypothesis"); computeLocations(); @@ -127,9 +119,6 @@ private void computeTransition(LocationComponent dest_c, PrefixContainer r) { //this.components.get(src_id); assert src_c != null; -// if (src_c == null && automaton instanceof DTHyp) -// return; - // locations RALocation src_loc = this.locations.get(src_id); RALocation dest_loc = this.locations.get(dest_id); @@ -150,11 +139,6 @@ private void computeTransition(LocationComponent dest_c, PrefixContainer r) { ReplacingValuesVisitor rvv = new ReplacingValuesVisitor(); guard = rvv.apply(guard, src_c.getPrimePrefix().getAssignment()); - // TODO: better solution - // guard is null because r is transition from a short prefix - if (automaton instanceof DTHyp && guard == null) - return; - assert true; assert guard != null; @@ -164,8 +148,6 @@ private void computeTransition(LocationComponent dest_c, PrefixContainer r) { Bijection remapping = dest_c.getRemapping(r); Assignment assign = computeAssignment(r.getPrefix(), srcAssign, destAssign, remapping); - //System.out.println(assign); - // create transition Transition t = createTransition(action, guard, src_loc, dest_loc, assign); if (t != null) { diff --git a/src/main/java/de/learnlib/ralib/learning/Hypothesis.java b/src/main/java/de/learnlib/ralib/learning/Hypothesis.java index d62c6aae..a6a1ca78 100644 --- a/src/main/java/de/learnlib/ralib/learning/Hypothesis.java +++ b/src/main/java/de/learnlib/ralib/learning/Hypothesis.java @@ -17,10 +17,8 @@ package de.learnlib.ralib.learning; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Set; import de.learnlib.AccessSequenceTransformer; import de.learnlib.ralib.automata.MutableRegisterAutomaton; @@ -28,18 +26,7 @@ import de.learnlib.ralib.automata.Transition; import de.learnlib.ralib.automata.TransitionSequenceTransformer; import de.learnlib.ralib.data.Constants; -import de.learnlib.ralib.data.ParameterValuation; -import de.learnlib.ralib.data.RegisterAssignment; -import de.learnlib.ralib.data.RegisterValuation; -import de.learnlib.ralib.data.SymbolicDataValue; -import de.learnlib.ralib.data.SymbolicDataValue.Register; -import de.learnlib.ralib.data.VarMapping; -import de.learnlib.ralib.oracles.Branching; -import de.learnlib.ralib.smt.SMTUtil; import de.learnlib.ralib.words.PSymbolInstance; -import de.learnlib.ralib.words.ParameterizedSymbol; -import gov.nasa.jpf.constraints.api.Expression; -import net.automatalib.common.util.Pair; import net.automatalib.word.Word; /** @@ -79,12 +66,6 @@ public Word transformAccessSequence(Word word) return accessSequences.get(loc); } - public Set> possibleAccessSequences(Word word) { - Set> ret = new LinkedHashSet>(); - ret.add(transformAccessSequence(word)); - return ret; - } - @Override public boolean isAccessSequence(Word word) { return accessSequences.containsValue(word); @@ -93,42 +74,9 @@ public boolean isAccessSequence(Word word) { @Override public Word transformTransitionSequence(Word word) { List tseq = getTransitions(word); - // System.out.println("TSEQ: " + tseq); if (tseq == null) return null; assert tseq.size() == word.length(); Transition last = tseq.get(tseq.size() - 1); return transitionSequences.get(last); } - - public Word transformTransitionSequence(Word word, Word loc) { - return transformTransitionSequence(word); - } - - public Word branchWithSameGuard(Word word, Branching branching) { - ParameterizedSymbol ps = word.lastSymbol().getBaseSymbol(); - - List> tvseq = getTransitionsAndValuations(word); - RegisterValuation vars = tvseq.get(tvseq.size()-1).getSecond(); - ParameterValuation pval = ParameterValuation.fromPSymbolInstance(word.lastSymbol()); - - for (Map.Entry, Expression> e : branching.getBranches().entrySet()) { - if (e.getKey().lastSymbol().getBaseSymbol().equals(ps)) { - Word prefix = e.getKey().prefix(e.getKey().size()-1); - RegisterValuation varsRef = getTransitionsAndValuations(prefix).get(getTransitionsAndValuations(prefix).size()-1).getSecond(); - // System.out.println(varsRef); - RegisterAssignment ra = new RegisterAssignment(); - varsRef.forEach((key, value) -> ra.put(value, key)); - Expression guard = SMTUtil.valsToRegisters(e.getValue(), ra); - if (guard.evaluateSMT(SMTUtil.compose(vars, pval, constants))) { - return e.getKey(); - } - } - } - return null; - } - - public VarMapping getLastTransitionAssignment(Word word) { - List tseq = getTransitions(word); - return tseq.get(tseq.size() - 1).getAssignment().getAssignment(); - } } diff --git a/src/main/java/de/learnlib/ralib/learning/IOAutomatonBuilder.java b/src/main/java/de/learnlib/ralib/learning/IOAutomatonBuilder.java index ab8cfd65..72a074b1 100644 --- a/src/main/java/de/learnlib/ralib/learning/IOAutomatonBuilder.java +++ b/src/main/java/de/learnlib/ralib/learning/IOAutomatonBuilder.java @@ -35,7 +35,6 @@ import de.learnlib.ralib.data.SymbolicDataValue.Parameter; import de.learnlib.ralib.data.VarMapping; import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.ParameterGenerator; -import de.learnlib.ralib.dt.DT; import de.learnlib.ralib.words.OutputSymbol; import de.learnlib.ralib.words.PSymbolInstance; import de.learnlib.ralib.words.ParameterizedSymbol; @@ -64,16 +63,6 @@ public IOAutomatonBuilder(Map, LocationComponent> componen } } - public IOAutomatonBuilder(Map, LocationComponent> components, - Constants consts, DT dt) { - super(components, consts, dt); - - this.reverseConsts = new LinkedHashMap<>(); - for (Entry c : consts) { - reverseConsts.put(c.getValue().getValue(), c.getKey()); - } - } - @Override protected Transition createTransition(ParameterizedSymbol action, Expression guard, RALocation src_loc, RALocation dest_loc, diff --git a/src/main/java/de/learnlib/ralib/learning/ralambda/DiscriminationTree.java b/src/main/java/de/learnlib/ralib/learning/ralambda/DiscriminationTree.java deleted file mode 100644 index 0ebd7c36..00000000 --- a/src/main/java/de/learnlib/ralib/learning/ralambda/DiscriminationTree.java +++ /dev/null @@ -1,37 +0,0 @@ -package de.learnlib.ralib.learning.ralambda; - -import java.util.Map; - -import de.learnlib.ralib.dt.DTLeaf; -import de.learnlib.ralib.learning.LocationComponent; -import de.learnlib.ralib.learning.SymbolicSuffix; -import de.learnlib.ralib.words.PSymbolInstance; -import net.automatalib.word.Word; - -/** - * This interface describes the methods needed in a discrimination tree during learning. - * - * @author fredrik - */ -public interface DiscriminationTree { - - /** - * Sift a prefix into the DT to find the corresponding leaf. If add is true, also adds the prefix to the set of non-short prefixes of the corresponding leaf. - * - * @param prefix - * @param add - * @return the leaf corresponding to prefix - */ - DTLeaf sift(Word prefix, boolean add); - - /** - * Split a prefix from a leaf node into a new leaf. Adds a new inner node using the suffix as a discriminator. - * - * @param prefix - * @param suffix - * @param leaf - */ - void split(Word prefix, SymbolicSuffix suffix, DTLeaf leaf); - - Map, LocationComponent> getComponents(); -} diff --git a/src/main/java/de/learnlib/ralib/learning/ralambda/RaLambda.java b/src/main/java/de/learnlib/ralib/learning/ralambda/RaLambda.java deleted file mode 100644 index cdde4ca9..00000000 --- a/src/main/java/de/learnlib/ralib/learning/ralambda/RaLambda.java +++ /dev/null @@ -1,500 +0,0 @@ -package de.learnlib.ralib.learning.ralambda; - -import java.util.ArrayDeque; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import de.learnlib.logging.Category; -import de.learnlib.query.DefaultQuery; -import de.learnlib.ralib.ceanalysis.PrefixFinder; -import de.learnlib.ralib.data.*; -import de.learnlib.ralib.dt.DT; -import de.learnlib.ralib.dt.DTHyp; -import de.learnlib.ralib.dt.DTLeaf; -import de.learnlib.ralib.dt.MappedPrefix; -import de.learnlib.ralib.dt.ShortPrefix; -import de.learnlib.ralib.learning.AutomatonBuilder; -import de.learnlib.ralib.learning.Hypothesis; -import de.learnlib.ralib.learning.IOAutomatonBuilder; -import de.learnlib.ralib.learning.LocationComponent; -import de.learnlib.ralib.learning.QueryStatistics; -import de.learnlib.ralib.learning.RaLearningAlgorithm; -import de.learnlib.ralib.learning.RaLearningAlgorithmName; -import de.learnlib.ralib.learning.SymbolicSuffix; -import de.learnlib.ralib.learning.rastar.CEAnalysisResult; -import de.learnlib.ralib.oracles.Branching; -import de.learnlib.ralib.oracles.SDTLogicOracle; -import de.learnlib.ralib.oracles.TreeOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; -import de.learnlib.ralib.oracles.mto.OptimizedSymbolicSuffixBuilder; -import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; -import de.learnlib.ralib.smt.ConstraintSolver; -import de.learnlib.ralib.theory.SDT; -import de.learnlib.ralib.words.PSymbolInstance; -import de.learnlib.ralib.words.ParameterizedSymbol; -import net.automatalib.word.Word; - -public class RaLambda implements RaLearningAlgorithm { - - public static final Word EMPTY_PREFIX = Word.epsilon(); - - public static final SymbolicSuffix EMPTY_SUFFIX = new SymbolicSuffix(Word.epsilon(), - Word.epsilon()); - - private final DT dt; - - private final Constants consts; - - private final Deque> counterexamples = new LinkedList<>(); - private final Deque> candidateCEs = new LinkedList<>(); - - private DTHyp hyp = null; - - private final TreeOracle sulOracle; - - private final SDTLogicOracle sdtLogicOracle; - - private final TreeOracleFactory hypOracleFactory; - - private final OptimizedSymbolicSuffixBuilder suffixBuilder; - private final SymbolicSuffixRestrictionBuilder restrictionBuilder; - private ConstraintSolver solver = null; - - private QueryStatistics queryStats = null; - - private final boolean ioMode; - - private static final Logger LOGGER = LoggerFactory.getLogger(RaLambda.class); - - private boolean useOldAnalyzer; - - private final Map, Boolean> guardPrefixes = new LinkedHashMap, Boolean>(); - - private PrefixFinder prefixFinder = null; - - public RaLambda(TreeOracle oracle, TreeOracleFactory hypOracleFactory, SDTLogicOracle sdtLogicOracle, Constants consts, - boolean ioMode, ParameterizedSymbol... inputs) { - - this(oracle, hypOracleFactory, sdtLogicOracle, consts, ioMode, false, inputs); - } - - public RaLambda(TreeOracle oracle, TreeOracleFactory hypOracleFactory, SDTLogicOracle sdtLogicOracle, Constants consts, - boolean ioMode, boolean useOldAnalyzer, ParameterizedSymbol... inputs) { - - this(oracle, hypOracleFactory, sdtLogicOracle, consts, ioMode, useOldAnalyzer, false, inputs); - } - - public RaLambda(TreeOracle oracle, TreeOracleFactory hypOracleFactory, SDTLogicOracle sdtLogicOracle, Constants consts, - boolean ioMode, boolean useOldAnalyzer, boolean thoroughSearch, ParameterizedSymbol... inputs) { - - this.ioMode = ioMode; - this.consts = consts; - this.sulOracle = oracle; - this.sdtLogicOracle = sdtLogicOracle; - this.hypOracleFactory = hypOracleFactory; - this.useOldAnalyzer = useOldAnalyzer; - this.restrictionBuilder = oracle.getRestrictionBuilder(); - this.suffixBuilder = new OptimizedSymbolicSuffixBuilder(consts, restrictionBuilder); - this.dt = new DT(oracle, ioMode, consts, inputs); - this.dt.initialize(); - } - - public RaLambda(TreeOracle oracle, TreeOracleFactory hypOracleFactory, SDTLogicOracle sdtLogicOracle, Constants consts, - ParameterizedSymbol... inputs) { - this(oracle, hypOracleFactory, sdtLogicOracle, consts, false, false, inputs); - } - - @Override - public void addCounterexample(DefaultQuery ce) { - LOGGER.info(Category.EVENT, "adding counterexample: {}", ce); - counterexamples.add(ce); - } - - @Override - public void learn() { - - if (hyp == null) { - buildNewHypothesis(); - } - - while (analyzeCounterExample()); - - if (queryStats != null) - queryStats.hypothesisConstructed(); - - } - - private void buildNewHypothesis() { - - Map, LocationComponent> components = new LinkedHashMap, LocationComponent>(); - components.putAll(dt.getComponents()); - AutomatonBuilder ab = new AutomatonBuilder(components, consts, dt); - - hyp = (DTHyp) ab.toRegisterAutomaton(); - if (prefixFinder != null) { - prefixFinder.setHypothesis(hyp); - //prefixFinder.setComponents(components); - prefixFinder.setHypothesisTreeOracle(hypOracleFactory.createTreeOracle(hyp)); - } - } - - private boolean analyzeCounterExample() { -// if (useOldAnalyzer) -// return analyzeCounterExampleOld(); - LOGGER.info(Category.PHASE, "Analyzing Counterexample"); - - if (candidateCEs.isEmpty()) { - prefixFinder = null; - if (counterexamples.isEmpty()) { - assert noShortPrefixes() || !dt.isMissingParameter(); - return false; - } - else { - DefaultQuery ce = counterexamples.poll(); - candidateCEs.push(ce); - } - } - - TreeOracle hypOracle = hypOracleFactory.createTreeOracle(hyp); - - if (prefixFinder == null) { - //Map, LocationComponent> components = new LinkedHashMap, LocationComponent>(); - //components.putAll(dt.getComponents()); - prefixFinder = new PrefixFinder(sulOracle, hypOracle, hyp, sdtLogicOracle, consts); - } - - boolean foundce = false; - DefaultQuery ce = null; - Deque> ces = new ArrayDeque>(); - ces.addAll(candidateCEs); - while(!foundce && !ces.isEmpty()) { - ce = ces.poll(); - boolean hypce = hyp.accepts(ce.getInput()); - boolean sulce = ce.getOutput(); - //System.out.println("ce: " + ce + " - " + sulce + " vs. " + hypce); - foundce = hypce != sulce; - } - - if (!foundce) { - candidateCEs.clear(); - return true; - } - - if (queryStats != null) { - queryStats.analyzingCounterExample(); - queryStats.analyzeCE(ce.getInput()); - } - Word ceWord = ce.getInput(); - CEAnalysisResult result = prefixFinder.analyzeCounterexample(ceWord); - Word transition = result.getPrefix(); // u alpha(d) - //System.out.println("new prefix: " + transition); - - for (DefaultQuery q : prefixFinder.getCounterExamples()) { - if (!candidateCEs.contains(q)) - candidateCEs.addLast(q); - } - - if (queryStats != null) - queryStats.processingCounterExample(); - - if (isGuardRefinement(transition)) { - addPrefix(transition); - } - else { - expand(transition); - } - - while (!dt.checkIOSuffixes()); - - boolean consistent = false; - while (!consistent) { - - consistent = checkLocationConsistency(); - - if (!checkRegisterClosedness()) { - consistent = false; - } - - if (!checkGuardConsistency()) { - consistent = false; - } - - if (!checkRegisterConsistency()) { - consistent = false; - } - } - - if (noShortPrefixes() && !dt.isMissingParameter()) { - buildNewHypothesis(); - } - //System.out.println(hyp); - return true; - } - - private boolean isGuardRefinement(Word word) { - return dt.getLeaf(word) == null; - } - - private void addPrefix(Word u) { - dt.sift(u, true); - } - - private void expand(Word u) { - DTLeaf l = dt.getLeaf(u); - assert l != null; - l.elevatePrefix(dt, u, hyp, sdtLogicOracle); - } - - private boolean checkLocationConsistency() { - - for (DTLeaf l : dt.getLeaves()) { - MappedPrefix mp = l.getPrimePrefix(); - Iterator it = l.getShortPrefixes().iterator(); - while (it.hasNext()) { - ShortPrefix sp = (ShortPrefix)it.next(); - SymbolicSuffix suffix = null; - for (ParameterizedSymbol psi : dt.getInputs()) { - Branching access_b = l.getBranching(psi); - Branching prefix_b = sp.getBranching(psi); - for (Word ws : prefix_b.getBranches().keySet()) { - Word wa = DTLeaf.branchWithSameGuard(ws, prefix_b, l.getRemapping(sp), access_b, sdtLogicOracle); - //System.out.println("wa: " + wa + ", ws: " + ws); - DTLeaf la = dt.getLeaf(wa); - DTLeaf ls = dt.getLeaf(ws); - if (la != ls) { - SymbolicSuffix v = distinguishingSuffix(wa, la, ws, ls); - if (suffix == null || suffix.length() > v.length()) { - suffix = v; - } - assert suffix != null; - } - } - } - if (suffix != null) { - dt.split(sp.getPrefix(), suffix, l); - return false; - } - } - } - return true; - } - - private boolean checkRegisterClosedness() { - return dt.checkVariableConsistency(suffixBuilder); - } - - private boolean checkRegisterConsistency() { - return dt.checkRegisterConsistency(suffixBuilder); - } - - private boolean checkGuardConsistency() { - for (DTLeaf dest_c : dt.getLeaves()) { - Collection> words = new LinkedHashSet<>(); - words.add(dest_c.getAccessSequence()); - words.addAll(dest_c.getPrefixes().getWords()); - words.addAll(dest_c.getShortPrefixes().getWords()); - for (Word dest_id : words) { - if (dest_id.length() == 0) { - continue; - } - Word src_id = dest_id.prefix(dest_id.length() - 1); - DTLeaf src_c = dt.getLeaf(src_id); - - Branching hypBranching = null; - if (src_c.getAccessSequence().equals(src_id)) { - hypBranching = src_c.getBranching(dest_id.lastSymbol().getBaseSymbol()); - } else { - ShortPrefix sp = (ShortPrefix) src_c.getShortPrefixes().get(src_id); - assert sp != null; - hypBranching = sp.getBranching(dest_id.lastSymbol().getBaseSymbol()); - } - if (hypBranching.getBranches().get(dest_id) != null) { - // word already in branching, no guard refinement needed - continue; - } - Word hyp_id = branchWithSameGuard(dest_c.getPrefix(dest_id), hypBranching); - - SymbolicSuffix suffix = null; - - DTLeaf hyp_c = dt.getLeaf(hyp_id); - if (hyp_c != dest_c) { - suffix = distinguishingSuffix(hyp_id, hyp_c, dest_id, dest_c); - } else { - List suffixes = new LinkedList<>(); - Map dest_sdts = new LinkedHashMap<>(); - Map hyp_sdts = new LinkedHashMap<>(); - for (Map.Entry e : dest_c.getPrefix(dest_id).getTQRs().entrySet()) { - SymbolicSuffix s = e.getKey(); - SDT dest_sdt = e.getValue(); - SDT hyp_sdt = hyp_c.getPrefix(hyp_id).getTQRs().get(s); - assert hyp_sdt != null; - - if (!SDT.equivalentUnderId(dest_sdt.toRegisterSDT(dest_id, consts), hyp_sdt.toRegisterSDT(hyp_id, consts))) { - suffixes.add(s); - dest_sdts.put(s, dest_sdt); - hyp_sdts.put(s, hyp_sdt); - } - } - - if (suffixes.isEmpty()) { - continue; - } - - Collections.sort(suffixes, (sa, sb) -> sa.length() > sb.length() ? 1 : - sa.length() < sb.length() ? -1 : 0); - - for (SymbolicSuffix s : suffixes) { - SymbolicSuffix testSuffix; - SDT hyp_sdt = hyp_sdts.get(s); - - if (suffixBuilder != null) { - SDT dest_sdt = dest_sdts.get(s); - DataValue[] regs = remappedRegisters(dest_sdt, hyp_sdt); - testSuffix = suffixBuilder.extendSuffix(dest_id, dest_sdt, s, regs); - } else { - testSuffix = new SymbolicSuffix(src_id, dest_id.suffix(1), restrictionBuilder); - testSuffix = testSuffix.concat(s); - } - - SDT testSDT = sulOracle.treeQuery(src_id, testSuffix); - Branching testBranching = sulOracle.updateBranching(src_id, dest_id.lastSymbol().getBaseSymbol(), hypBranching, testSDT); - if (testBranching.getBranches().get(dest_id) != null) { - suffix = testSuffix; - break; - } - } - } - - if (suffix != null) { - dt.addSuffix(suffix, src_c); - return false; - } - } - } - - return true; - } - - private SymbolicSuffix distinguishingSuffix(Word wa, DTLeaf ca, Word wb, DTLeaf cb) { - Word sa = wa.suffix(1); - Word sb = wb.suffix(1); - - assert sa.getSymbol(0).getBaseSymbol().equals(sb.getSymbol(0).getBaseSymbol()); - - SymbolicSuffix v = dt.findLCA(ca, cb).getSuffix(); - - Word prefixA = wa.prefix(wa.length() - 1); - Word prefixB = wb.prefix(wb.length() - 1); - - SDT tqrA = ca.getTQR(wa, v); - SDT tqrB = cb.getTQR(wb, v); - - assert tqrA != null && tqrB != null; - - SDT sdtA = tqrA; - SDT sdtB = tqrB; - - if (suffixBuilder != null && solver != null) { - //return suffixBuilder.extendDistinguishingSuffix(wa, sdtA, wb, sdtB, v); - SymbolicSuffix suffix = suffixBuilder.distinguishingSuffixFromSDTs(wa, sdtA, wb, sdtB, v.getActions(), solver); - return suffix; - } - - SymbolicSuffix alpha_a = new SymbolicSuffix(prefixA, sa, restrictionBuilder); - SymbolicSuffix alpha_b = new SymbolicSuffix(prefixB, sb, restrictionBuilder); - return alpha_a.getFreeValues().size() > alpha_b.getFreeValues().size() - ? alpha_a.concat(v) - : alpha_b.concat(v); - } - - private boolean noShortPrefixes() { - for (DTLeaf l : dt.getLeaves()) { - if (!l.getShortPrefixes().isEmpty()) { - return false; - } - } - return true; - } - - public void setUseOldAnalyzer(boolean useOldAnalyzer) { - this.useOldAnalyzer = useOldAnalyzer; - } - - private Word branchWithSameGuard(MappedPrefix mp, Branching branching) { - Word dw = mp.getPrefix(); - - return branching.transformPrefix(dw); - } - - private DataValue[] remappedRegisters(SDT sdt1, SDT sdt2) { - Bijection bijection = SDT.equivalentUnderBijection(sdt1, sdt2); - assert bijection != null; - List vals = new LinkedList<>(); - for (Map.Entry e : bijection.entrySet()) { - if (!e.getKey().equals(e.getValue())) { - vals.add(e.getKey()); - vals.add(e.getValue()); - } - } - return vals.toArray(new DataValue[vals.size()]); - } - - @Override - public Hypothesis getHypothesis() { - Map, LocationComponent> components = new LinkedHashMap, LocationComponent>(); - components.putAll(dt.getComponents()); - AutomatonBuilder ab; - if (ioMode) { - ab = new IOAutomatonBuilder(components, consts); - } else { - ab = new AutomatonBuilder(components, consts); - } - return ab.toRegisterAutomaton(); - } - - public DT getDT() { - return dt; - } - - public DTHyp getDTHyp() { - return hyp; - } - - public Map, LocationComponent> getComponents() { - return dt.getComponents(); - } - - @Override - public void setStatisticCounter(QueryStatistics queryStats) { - this.queryStats = queryStats; - } - - @Override - public QueryStatistics getQueryStatistics() { - return queryStats; - } - - public void setSolver(ConstraintSolver solver) { - this.solver = solver; - } - - @Override - public RaLearningAlgorithmName getName() { - return RaLearningAlgorithmName.RALAMBDA; - } - - @Override - public String toString() { - return dt.toString(); - } -} diff --git a/src/main/java/de/learnlib/ralib/learning/ralambda/RaDT.java b/src/main/java/de/learnlib/ralib/learning/ralambda/SLCT.java similarity index 58% rename from src/main/java/de/learnlib/ralib/learning/ralambda/RaDT.java rename to src/main/java/de/learnlib/ralib/learning/ralambda/SLCT.java index 7c430f0c..247f4f0d 100644 --- a/src/main/java/de/learnlib/ralib/learning/ralambda/RaDT.java +++ b/src/main/java/de/learnlib/ralib/learning/ralambda/SLCT.java @@ -10,37 +10,37 @@ import de.learnlib.logging.Category; import de.learnlib.query.DefaultQuery; +import de.learnlib.ralib.ct.CTAutomatonBuilder; +import de.learnlib.ralib.ct.CTLeaf; +import de.learnlib.ralib.ct.ClassificationTree; import de.learnlib.ralib.data.Constants; -import de.learnlib.ralib.dt.DT; -import de.learnlib.ralib.dt.DTHyp; -import de.learnlib.ralib.dt.DTLeaf; -import de.learnlib.ralib.learning.AutomatonBuilder; import de.learnlib.ralib.learning.CounterexampleAnalysis; import de.learnlib.ralib.learning.Hypothesis; -import de.learnlib.ralib.learning.IOAutomatonBuilder; import de.learnlib.ralib.learning.LocationComponent; import de.learnlib.ralib.learning.QueryStatistics; import de.learnlib.ralib.learning.RaLearningAlgorithm; import de.learnlib.ralib.learning.RaLearningAlgorithmName; import de.learnlib.ralib.learning.rastar.CEAnalysisResult; +import de.learnlib.ralib.learning.rastar.RaStar; import de.learnlib.ralib.oracles.SDTLogicOracle; import de.learnlib.ralib.oracles.TreeOracle; import de.learnlib.ralib.oracles.TreeOracleFactory; import de.learnlib.ralib.oracles.mto.OptimizedSymbolicSuffixBuilder; import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; +import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.words.PSymbolInstance; import de.learnlib.ralib.words.ParameterizedSymbol; import net.automatalib.word.Word; -public class RaDT implements RaLearningAlgorithm { +public class SLCT implements RaLearningAlgorithm { - private final DT dt; + private final ClassificationTree ct; - private final Constants consts; + private final Constants consts; - private final Deque> counterexamples = new LinkedList<>(); + private final Deque> counterexamples; - private DTHyp hyp = null; + private Hypothesis hyp; private final TreeOracle sulOracle; @@ -51,47 +51,66 @@ public class RaDT implements RaLearningAlgorithm { private final OptimizedSymbolicSuffixBuilder suffixBuilder; private final SymbolicSuffixRestrictionBuilder restrictionBuilder; - private QueryStatistics queryStats = null; + private QueryStatistics queryStats; private final boolean ioMode; - private static final Logger LOGGER = LoggerFactory.getLogger(RaDT.class); + private final ConstraintSolver solver; - public RaDT(TreeOracle oracle, TreeOracleFactory hypOracleFactory, SDTLogicOracle sdtLogicOracle, Constants consts, - boolean ioMode, ParameterizedSymbol... inputs) { - this.sulOracle = oracle; - this.hypOracleFactory = hypOracleFactory; - this.sdtLogicOracle = sdtLogicOracle; + private static final Logger LOGGER = LoggerFactory.getLogger(SLCT.class); + + public SLCT(TreeOracle sulOracle, TreeOracleFactory hypOracleFactory, SDTLogicOracle sdtLogicOracle, + Constants consts, boolean ioMode, ConstraintSolver solver, ParameterizedSymbol ... inputs) { this.consts = consts; + this.sulOracle = sulOracle; + this.sdtLogicOracle = sdtLogicOracle; + this.hypOracleFactory = hypOracleFactory; + this.solver = solver; this.ioMode = ioMode; - this.restrictionBuilder = oracle.getRestrictionBuilder(); - this.suffixBuilder = new OptimizedSymbolicSuffixBuilder(consts, restrictionBuilder); - this.dt = new DT(oracle, ioMode, consts, inputs); - this.dt.initialize(); - } - - private void buildHypothesis() { - Map, LocationComponent> components = new LinkedHashMap, LocationComponent>(); - components.putAll(dt.getComponents()); - - AutomatonBuilder ab = new AutomatonBuilder(components, consts, dt); - hyp = (DTHyp) ab.toRegisterAutomaton(); + restrictionBuilder = sulOracle.getRestrictionBuilder(); + suffixBuilder = new OptimizedSymbolicSuffixBuilder(consts, restrictionBuilder); + counterexamples = new LinkedList<>(); + hyp = null; + ct = new ClassificationTree(sulOracle, solver, restrictionBuilder, suffixBuilder, consts, ioMode, inputs); + ct.sift(RaStar.EMPTY_PREFIX); } @Override public void learn() { - if (hyp == null) { - buildHypothesis(); - } + if (hyp == null) { + while (!checkClosedness()); + buildHypothesis(); + } - while (analyzeCounterExample()); + while (analyzeCounterExample()); - if (queryStats != null) { - queryStats.hypothesisConstructed(); - } + if (queryStats != null) { + queryStats.hypothesisConstructed(); + } + } + + private boolean checkClosedness() { + if (!ct.checkOutputClosed()) { + return false; + } + if (!ct.checkLocationClosedness()) { + return false; + } + if (!ct.checkTransitionClosedness()) { + return false; + } + if (!ct.checkRegisterClosedness()) { + return false; + } + return true; } - private boolean analyzeCounterExample() { + private void buildHypothesis() { + CTAutomatonBuilder ab = new CTAutomatonBuilder(ct, consts, ioMode, solver); + hyp = ab.buildHypothesis(); + } + + private boolean analyzeCounterExample() { LOGGER.info(Category.PHASE, "Analyzing Counterexample"); if (counterexamples.isEmpty()) { return false; @@ -99,14 +118,16 @@ private boolean analyzeCounterExample() { TreeOracle hypOracle = hypOracleFactory.createTreeOracle(hyp); - Map, LocationComponent> components = new LinkedHashMap, LocationComponent>(); - components.putAll(dt.getComponents()); - CounterexampleAnalysis analysis = new CounterexampleAnalysis(sulOracle, hypOracle, hyp, sdtLogicOracle, - components, consts); + Map, LocationComponent> components = new LinkedHashMap<>(); + for (CTLeaf leaf : ct.getLeaves()) { + for (Word u : leaf.getPrefixes()) { + components.put(u, leaf); + } + } + CounterexampleAnalysis analysis = new CounterexampleAnalysis(sulOracle, hypOracle, hyp, sdtLogicOracle, components, consts); DefaultQuery ce = counterexamples.peek(); - // check if ce still is a counterexample ... boolean hypce = hyp.accepts(ce.getInput()); boolean sulce = ce.getOutput(); if (hypce == sulce) { @@ -127,37 +148,27 @@ private boolean analyzeCounterExample() { } Word accSeq = hyp.transformAccessSequence(res.getPrefix()); - DTLeaf leaf = dt.getLeaf(accSeq); - dt.addSuffix(res.getSuffix(), leaf); - while(!dt.checkIOSuffixes()); - while(!dt.checkVariableConsistency(suffixBuilder)); + CTLeaf leaf = ct.getLeaf(accSeq); + assert leaf != null : "Prefix not in classification tree: " + accSeq; + ct.refine(leaf, res.getSuffix()); + + while(!checkClosedness()); + buildHypothesis(); return true; - } + } @Override public void addCounterexample(DefaultQuery ce) { LOGGER.info(Category.EVENT, "adding counterexample: " + ce); counterexamples.add(ce); - } + } @Override public Hypothesis getHypothesis() { - Map, LocationComponent> components = new LinkedHashMap, LocationComponent>(); - components.putAll(dt.getComponents()); - AutomatonBuilder ab; - if (ioMode) { - ab = new IOAutomatonBuilder(components, consts); - } else { - ab = new AutomatonBuilder(components, consts); - } - return ab.toRegisterAutomaton(); + return hyp; } - public DT getDT() { - return dt; - } - @Override public void setStatisticCounter(QueryStatistics queryStats) { this.queryStats = queryStats; @@ -165,7 +176,7 @@ public void setStatisticCounter(QueryStatistics queryStats) { @Override public QueryStatistics getQueryStatistics() { - return queryStats; + return queryStats; } @Override @@ -173,4 +184,7 @@ public RaLearningAlgorithmName getName() { return RaLearningAlgorithmName.RADT; } + public ClassificationTree getCT() { + return ct; + } } diff --git a/src/main/java/de/learnlib/ralib/learning/ralambda/SLLambda.java b/src/main/java/de/learnlib/ralib/learning/ralambda/SLLambda.java new file mode 100644 index 00000000..bfbb618f --- /dev/null +++ b/src/main/java/de/learnlib/ralib/learning/ralambda/SLLambda.java @@ -0,0 +1,205 @@ +package de.learnlib.ralib.learning.ralambda; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.Map; + +import de.learnlib.query.DefaultQuery; +import de.learnlib.ralib.ceanalysis.PrefixFinder; +import de.learnlib.ralib.ceanalysis.PrefixFinder.Result; +import de.learnlib.ralib.ct.CTAutomatonBuilder; +import de.learnlib.ralib.ct.CTHypothesis; +import de.learnlib.ralib.ct.ClassificationTree; +import de.learnlib.ralib.data.Constants; +import de.learnlib.ralib.data.DataType; +import de.learnlib.ralib.learning.Hypothesis; +import de.learnlib.ralib.learning.QueryStatistics; +import de.learnlib.ralib.learning.RaLearningAlgorithm; +import de.learnlib.ralib.learning.RaLearningAlgorithmName; +import de.learnlib.ralib.oracles.TreeOracle; +import de.learnlib.ralib.oracles.mto.OptimizedSymbolicSuffixBuilder; +import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; +import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.theory.Theory; +import de.learnlib.ralib.words.PSymbolInstance; +import de.learnlib.ralib.words.ParameterizedSymbol; +import net.automatalib.word.Word; + +public class SLLambda implements RaLearningAlgorithm { + + private final ClassificationTree ct; + + private final Constants consts; + + private final Deque> counterexamples; + + private CTHypothesis hyp; + + private final TreeOracle sulOracle; + + private final OptimizedSymbolicSuffixBuilder suffixBuilder; + private final SymbolicSuffixRestrictionBuilder restrictionBuilder; + + private final Map teachers; + + private QueryStatistics queryStats; + + private final boolean ioMode; + + private final ConstraintSolver solver; + + public SLLambda(TreeOracle sulOracle, Map teachers, + Constants consts, boolean ioMode, ConstraintSolver solver, + ParameterizedSymbol ... inputs) { + this.sulOracle = sulOracle; + this.teachers = teachers; + this.consts = consts; + this.ioMode = ioMode; + this.solver = solver; + restrictionBuilder = sulOracle.getRestrictionBuilder(); + suffixBuilder = new OptimizedSymbolicSuffixBuilder(consts, restrictionBuilder); + counterexamples = new LinkedList<>(); + hyp = null; + ct = new ClassificationTree(sulOracle, solver, restrictionBuilder, suffixBuilder, consts, ioMode, inputs); + ct.initialize(); + } + + @Override + public void learn() { + if (hyp == null) { + while (!checkClosedness()); + buildHypothesis(); + } + + while (analyzeCounterExample()); + + if (queryStats != null) { + queryStats.hypothesisConstructed(); + } + } + + private boolean checkClosedness() { + if (!ct.checkOutputClosed()) { + return false; + } + if (!ct.checkLocationClosedness()) { + return false; + } + if (!ct.checkTransitionClosedness()) { + return false; + } + if (!ct.checkRegisterClosedness()) { + return false; + } + return true; + } + + private boolean checkConsistency() { + if (!ct.checkLocationConsistency()) { + return false; + } + if (!ct.checkTransitionConsistency()) { + return false; + } + if (!ct.checkRegisterConsistency()) { + return false; + } + return true; + } + + private void buildHypothesis() { + CTAutomatonBuilder ab = new CTAutomatonBuilder(ct, consts, ioMode, solver); + hyp = ab.buildHypothesis(); + } + + private boolean analyzeCounterExample() { + if (counterexamples.isEmpty()) { + return false; + } + + // check whether ce is still a ce + DefaultQuery ce = counterexamples.peek(); + Word ceWord = ce.getInput(); + boolean accHyp = hyp.accepts(ceWord); + boolean accSul = ce.getOutput(); + if (accHyp == accSul) { + // not a ce, dequeue it + counterexamples.poll(); + return true; + } + + // analyze counterexample + + if (queryStats != null) { + queryStats.analyzingCounterExample(); + queryStats.analyzeCE(ceWord); + } + + PrefixFinder prefixFinder = new PrefixFinder(sulOracle, + hyp, + ct, + teachers, + restrictionBuilder, + solver, + consts); + + Result res = prefixFinder.analyzeCounterExample(ceWord); + + // process counterexample + + if (queryStats != null) + queryStats.processingCounterExample(); + + switch (res.result()) { + case TRANSITION: + assert !ct.getPrefixes().contains(res.prefix()) : "Duplicate prefix: " + res.prefix(); + ct.sift(res.prefix()); + break; + case LOCATION: + assert !ct.getShortPrefixes().contains(res.prefix()) : "Prefix already short: " + res.prefix(); + ct.expand(res.prefix()); + break; + } + + boolean closedAndConsistent = false; + while (!closedAndConsistent) { + if (checkClosedness()) { + if (checkConsistency()) { + closedAndConsistent = true; + } + } + } + + buildHypothesis(); + return true; + } + + @Override + public void addCounterexample(DefaultQuery ce) { + counterexamples.add(ce); + } + + @Override + public Hypothesis getHypothesis() { + return hyp; + } + + @Override + public void setStatisticCounter(QueryStatistics queryStats) { + this.queryStats = queryStats; + } + + @Override + public QueryStatistics getQueryStatistics() { + return queryStats; + } + + @Override + public RaLearningAlgorithmName getName() { + return RaLearningAlgorithmName.RALAMBDA; + } + + public ClassificationTree getCT() { + return ct; + } +} diff --git a/src/main/java/de/learnlib/ralib/oracles/Branching.java b/src/main/java/de/learnlib/ralib/oracles/Branching.java index d8791606..a7024614 100644 --- a/src/main/java/de/learnlib/ralib/oracles/Branching.java +++ b/src/main/java/de/learnlib/ralib/oracles/Branching.java @@ -16,8 +16,16 @@ */ package de.learnlib.ralib.oracles; +import java.util.LinkedHashSet; import java.util.Map; +import java.util.Optional; +import java.util.Set; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.Mapping; +import de.learnlib.ralib.data.SymbolicDataValue; +import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; +import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.words.PSymbolInstance; import gov.nasa.jpf.constraints.api.Expression; import net.automatalib.word.Word; @@ -31,4 +39,50 @@ public interface Branching { Map, Expression> getBranches(); Word transformPrefix(Word prefix); + + /** + * Return a prefix {@code u} within this branching such that the parameters of the last + * symbol of {@code u} satisfy {@code guard}. + * + * @param guard + * @param solver + * @return an {@code Optional} encasing a prefix matching {@code guard} if one exists, + * or an empty {@code Optional} otherwise + */ + default Optional> getPrefix(Expression guard, ConstraintSolver solver) { + // if guard is a value, return the corresponding key + Optional> prefix = getBranches().entrySet() + .stream() + .filter(e -> e.getValue().equals(guard)) + .map(e -> e.getKey()) + .findFirst(); + if (prefix.isPresent()) { + return prefix; + } + + // find a guard where the last symbol's values satisfy guard + for (Map.Entry, Expression> e : getBranches().entrySet()) { + DataValue[] vals = e.getKey().lastSymbol().getParameterValues(); + Mapping valuation = new Mapping<>(); + for (int i = 0; i < vals.length; i++) { + SuffixValue sv = new SuffixValue(vals[i].getDataType(), i+1); + valuation.put(sv, vals[i]); + } + if (solver.isSatisfiable(guard, valuation)) { + return Optional.of(e.getKey()); + } + } + + return Optional.empty(); + } + + /** + * @return a set of all initial guards in this branching + */ + default Set> guardSet() { + Set> guards = new LinkedHashSet<>(); + guards.addAll(getBranches().values()); + return guards; + } + } diff --git a/src/main/java/de/learnlib/ralib/oracles/mto/MultiTheoryTreeOracle.java b/src/main/java/de/learnlib/ralib/oracles/mto/MultiTheoryTreeOracle.java index 200f1b23..97078c4b 100644 --- a/src/main/java/de/learnlib/ralib/oracles/mto/MultiTheoryTreeOracle.java +++ b/src/main/java/de/learnlib/ralib/oracles/mto/MultiTheoryTreeOracle.java @@ -21,8 +21,10 @@ import java.util.HashSet; import java.util.LinkedHashMap; 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 java.util.stream.Collectors; import java.util.stream.Stream; @@ -54,6 +56,8 @@ import de.learnlib.ralib.theory.SDTLeaf; import de.learnlib.ralib.theory.Theory; import de.learnlib.ralib.words.DataWords; +import de.learnlib.ralib.words.InputSymbol; +import de.learnlib.ralib.words.OutputSymbol; import de.learnlib.ralib.words.PSymbolInstance; import de.learnlib.ralib.words.ParameterizedSymbol; import net.automatalib.common.util.Pair; @@ -88,6 +92,9 @@ public MultiTheoryTreeOracle(DataWordOracle oracle, Map teache @Override public SDT treeQuery(Word prefix, SymbolicSuffix suffix) { + if (!isValid(prefix)) { + return makeRejectingSDT(suffix); + } SDT sdt = treeQuery(prefix, suffix, new WordValuation(), constants, new SuffixValuation()); //System.out.println(sdt); return sdt; @@ -472,4 +479,53 @@ public Map getTeachers() { public SymbolicSuffixRestrictionBuilder getRestrictionBuilder() { return restrictionBuilder; } + + private boolean isValid(Word word) { + if (word.length() < 1) { + return true; + } + + Word actions = DataWords.actsOf(word); + if (actions.stream() + .filter(a -> a instanceof OutputSymbol) + .findAny() + .isEmpty()) { + return true; + } + + boolean inExpected = true; + for (ParameterizedSymbol action : actions) { + if (inExpected ^ (action instanceof InputSymbol)) { + return false; + } + inExpected = !inExpected; + } + + return true; + } + + private SDT makeRejectingSDT(SymbolicSuffix suffix) { + Queue types = new LinkedList<>(); + for (ParameterizedSymbol ps : suffix.getActions()) { + for (DataType type : ps.getPtypes()) { + types.offer(type); + } + } + return makeRejectingSDT(1, types); + } + + private SDT makeRejectingSDT(int param, Queue types) { + if (types.isEmpty()) { + return SDTLeaf.REJECTING; + } + + DataType type = types.poll(); + SuffixValue sv = new SuffixValue(type, param); + SDTGuard g = new SDTGuard.SDTTrueGuard(sv); + + Map child = new LinkedHashMap<>(); + child.put(g, makeRejectingSDT(param+1, types)); + + return new SDT(child); + } } diff --git a/src/main/java/de/learnlib/ralib/smt/ConstraintSolver.java b/src/main/java/de/learnlib/ralib/smt/ConstraintSolver.java index b7701eea..bb1f5827 100644 --- a/src/main/java/de/learnlib/ralib/smt/ConstraintSolver.java +++ b/src/main/java/de/learnlib/ralib/smt/ConstraintSolver.java @@ -17,6 +17,7 @@ package de.learnlib.ralib.smt; import java.util.HashMap; +import java.util.Optional; import java.util.Properties; import de.learnlib.ralib.data.DataValue; @@ -24,6 +25,7 @@ import de.learnlib.ralib.data.SymbolicDataValue; import gov.nasa.jpf.constraints.api.Expression; import gov.nasa.jpf.constraints.api.SolverContext; +import gov.nasa.jpf.constraints.api.Valuation; import gov.nasa.jpf.constraints.solvers.nativez3.NativeZ3SolverProvider; /** @@ -37,7 +39,7 @@ public class ConstraintSolver { private final static gov.nasa.jpf.constraints.api.ConstraintSolver solver = new NativeZ3SolverProvider().createSolver(new Properties()); - public boolean isSatisfiable(Expression expr, Mapping val) { + public boolean isSatisfiable(Expression expr, Mapping val) { Expression test = SMTUtil.toExpression(expr, val); Boolean r = cache.get(test); if (r == null) { @@ -48,4 +50,14 @@ public boolean isSatisfiable(Expression expr, Mapping solve(Expression expr) { + Valuation res = new Valuation(); + gov.nasa.jpf.constraints.api.ConstraintSolver.Result r = solver.solve(expr, res); + if (r == gov.nasa.jpf.constraints.api.ConstraintSolver.Result.SAT) { + return Optional.of(res); + } + return Optional.empty(); + } + } diff --git a/src/main/java/de/learnlib/ralib/smt/SMTUtil.java b/src/main/java/de/learnlib/ralib/smt/SMTUtil.java index 3c376e2d..43d99bb5 100644 --- a/src/main/java/de/learnlib/ralib/smt/SMTUtil.java +++ b/src/main/java/de/learnlib/ralib/smt/SMTUtil.java @@ -65,16 +65,16 @@ public static Expression valsToRegisters(Expression expr, Regi return replacer.applyRegs(expr, map); } - public static Expression toExpression(Expression expr, Mapping val) { + public static Expression toExpression(Expression expr, Mapping val) { Map map = new HashMap<>(); Expression valExpr = toExpression(val, map); return ExpressionUtil.and(expr, valExpr); } - public static Expression toExpression(Mapping val, Map map) { + public static Expression toExpression(Mapping val, Map map) { Expression[] elems = new Expression[val.size()]; int i = 0; - for (Map.Entry entry : val.entrySet()) { + for (Map.Entry entry : val.entrySet()) { elems[i++] = new NumericBooleanExpression(getOrCreate(entry.getKey(), map), NumericComparator.EQ, toConstant(entry.getValue())); } return ExpressionUtil.and(elems); diff --git a/src/main/java/de/learnlib/ralib/smt/VarsValuationVisitor.java b/src/main/java/de/learnlib/ralib/smt/VarsValuationVisitor.java new file mode 100644 index 00000000..65747c11 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/smt/VarsValuationVisitor.java @@ -0,0 +1,26 @@ +package de.learnlib.ralib.smt; + +import java.util.LinkedHashMap; +import java.util.Map; + +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.Mapping; +import de.learnlib.ralib.data.SymbolicDataValue; +import gov.nasa.jpf.constraints.api.Expression; +import gov.nasa.jpf.constraints.api.Variable; +import gov.nasa.jpf.constraints.util.DuplicatingVisitor; + +public class VarsValuationVisitor extends DuplicatingVisitor, ? extends Expression>> { + + @Override + public Expression visit(Variable v, Map, ? extends Expression> data) { + Expression val = data.get(v); + return (val != null) ? val.requireAs(v.getType()) : v; + } + + public Expression apply(Expression expr, Mapping valuation) { + Map, Expression> map = new LinkedHashMap<>(); + valuation.forEach((k,v) -> map.put(k, v.asExpression())); + return visit(expr, map).requireAs(expr.getType()); + } +} diff --git a/src/main/java/de/learnlib/ralib/theory/SDT.java b/src/main/java/de/learnlib/ralib/theory/SDT.java index 371e75e0..4d9bdc37 100644 --- a/src/main/java/de/learnlib/ralib/theory/SDT.java +++ b/src/main/java/de/learnlib/ralib/theory/SDT.java @@ -29,6 +29,7 @@ import de.learnlib.ralib.words.DataWords; import de.learnlib.ralib.words.PSymbolInstance; import gov.nasa.jpf.constraints.api.Expression; +import gov.nasa.jpf.constraints.expressions.Negation; import gov.nasa.jpf.constraints.util.ExpressionUtil; import net.automatalib.word.Word; @@ -438,6 +439,13 @@ public boolean isEquivalentUnderCondition(SDT other, Expression conditi for (Map.Entry, Boolean> entry : expressions.entrySet()) { Expression x = entry.getKey(); Boolean outcome = entry.getValue(); + + Set> guardComplement = new LinkedHashSet<>(expressions.keySet()); + guardComplement.remove(x); + for (Expression g : guardComplement) { + x = ExpressionUtil.and(x, new Negation(g)); + } + for (Map.Entry, Boolean> otherEntry : otherExpressions.entrySet()) { if (outcome != otherEntry.getValue()) { Expression otherX = otherEntry.getKey(); @@ -445,7 +453,6 @@ public boolean isEquivalentUnderCondition(SDT other, Expression conditi Expression con = ExpressionUtil.and(x, renamed); ConstraintSolver solver = new ConstraintSolver(); if (solver.isSatisfiable(con, new Mapping<>())) { - return false; } } @@ -473,10 +480,6 @@ public static Bijection equivalentUnderBijection(SDT sdt1, SDT sdt2, return null; } - if (new HashSet<>(regs1).containsAll(regs2)) { - return sdt1.isEquivalentUnderCondition(sdt2, ExpressionUtil.TRUE) ? bi : null; - } - Set replace = new LinkedHashSet<>(regs1); replace.removeAll(bi.values()); Set by = new LinkedHashSet<>(regs2); diff --git a/src/main/java/de/learnlib/ralib/theory/Theory.java b/src/main/java/de/learnlib/ralib/theory/Theory.java index f668a152..bc702328 100644 --- a/src/main/java/de/learnlib/ralib/theory/Theory.java +++ b/src/main/java/de/learnlib/ralib/theory/Theory.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import de.learnlib.ralib.data.Constants; @@ -29,8 +30,10 @@ import de.learnlib.ralib.data.WordValuation; import de.learnlib.ralib.learning.SymbolicSuffix; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; +import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.words.PSymbolInstance; import de.learnlib.ralib.words.ParameterizedSymbol; +import gov.nasa.jpf.constraints.api.Expression; import net.automatalib.word.Word; /** @@ -98,6 +101,22 @@ DataValue instantiate(Word prefix, Constants constants, SDTGuard guard, SuffixValue param, Set oldDvs); + /** + * Instantiate a representative data value for the parameter {@code param} + * of {@code ps} that satisfies {@code guard}. + * + * @param prefix + * @param ps + * @param guard + * @param param + * @param constants + * @param solver + * @return an {@code Optional} containing a data value satisfying {@code guard}, or an empty {@code Optional} if {@code guard} is unsatisfiable + */ + public Optional instantiate(Word prefix, + ParameterizedSymbol ps, Expression guard, int param, + Constants constants, ConstraintSolver solver); + SuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, Word prefix, Word suffix, Constants consts); SuffixValueRestriction restrictSuffixValue(SDTGuard guard, Map prior); diff --git a/src/main/java/de/learnlib/ralib/theory/equality/EqualityTheory.java b/src/main/java/de/learnlib/ralib/theory/equality/EqualityTheory.java index 8c55ef5d..5f24241a 100644 --- a/src/main/java/de/learnlib/ralib/theory/equality/EqualityTheory.java +++ b/src/main/java/de/learnlib/ralib/theory/equality/EqualityTheory.java @@ -26,7 +26,9 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,6 +41,7 @@ import de.learnlib.ralib.learning.SymbolicSuffix; import de.learnlib.ralib.oracles.io.IOOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; +import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.theory.*; import de.learnlib.ralib.theory.SDT; import de.learnlib.ralib.theory.SDTLeaf; @@ -46,6 +49,7 @@ import de.learnlib.ralib.words.OutputSymbol; import de.learnlib.ralib.words.PSymbolInstance; import de.learnlib.ralib.words.ParameterizedSymbol; +import gov.nasa.jpf.constraints.api.Expression; import net.automatalib.word.Word; /** @@ -408,6 +412,38 @@ private SDT makeRejectingBranch(int nextSufIndex, int maxIndex, DataType type) { } } + @Override + public Optional instantiate(Word prefix, + ParameterizedSymbol ps, Expression guard, int param, + Constants constants, ConstraintSolver solver) { + Parameter p = new Parameter(ps.getPtypes()[param-1], param); + Set vals = DataWords.valSet(prefix, p.getDataType()); + vals.addAll(vals.stream() + .filter(v -> v.getDataType().equals(p.getDataType())) + .collect(Collectors.toSet())); + vals.addAll(constants.values()); + DataValue fresh = getFreshValue(new LinkedList<>(vals)); + + if (isSatisfiableWithEquality(guard, p, fresh, solver, constants)) { + return Optional.of(fresh); + } + + for (DataValue val : vals) { + if (isSatisfiableWithEquality(guard, p, val, solver, constants)) { + return Optional.of(val); + } + } + + return Optional.empty(); + } + + private boolean isSatisfiableWithEquality(Expression guard, Parameter p, DataValue val, ConstraintSolver solver, Constants consts) { + Mapping valuation = new Mapping<>(); + valuation.put(p, val); + valuation.putAll(consts); + return solver.isSatisfiable(guard, valuation); + } + @Override public SuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, Word prefix, Word suffix, Constants consts) { // for now, use generic restrictions with equality theory diff --git a/src/main/java/de/learnlib/ralib/theory/inequality/InequalityTheoryWithEq.java b/src/main/java/de/learnlib/ralib/theory/inequality/InequalityTheoryWithEq.java index 6997f19b..f87217cd 100644 --- a/src/main/java/de/learnlib/ralib/theory/inequality/InequalityTheoryWithEq.java +++ b/src/main/java/de/learnlib/ralib/theory/inequality/InequalityTheoryWithEq.java @@ -23,16 +23,20 @@ import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import de.learnlib.ralib.data.*; import de.learnlib.ralib.data.SymbolicDataValue.Constant; +import de.learnlib.ralib.data.SymbolicDataValue.Parameter; import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; import de.learnlib.ralib.learning.SymbolicSuffix; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; +import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.theory.EquivalenceClassFilter; import de.learnlib.ralib.theory.FreshSuffixValue; import de.learnlib.ralib.theory.SDT; @@ -46,7 +50,10 @@ import gov.nasa.jpf.constraints.api.Expression; import gov.nasa.jpf.constraints.api.Valuation; import gov.nasa.jpf.constraints.api.Variable; +import gov.nasa.jpf.constraints.expressions.NumericBooleanExpression; +import gov.nasa.jpf.constraints.expressions.NumericComparator; import gov.nasa.jpf.constraints.solvers.nativez3.NativeZ3Solver; +import gov.nasa.jpf.constraints.util.ExpressionUtil; import net.automatalib.word.Word; /** @@ -194,11 +201,13 @@ private List sort(Collection list) { * @param sdts - a mapping from SDT guards to their corresponding sub-SDTs * @param equivClasses - a mapping from data values to corresponding SDT guards * @param filteredOut - data values removed through suffix optimization + * @param prior - suffix valuation for prior suffix values * @return a mapping from merged SDT guards to their respective sub-trees */ protected Map mergeGuards(Map sdts, Map equivClasses, - Collection filteredOut) { + Collection filteredOut, + SuffixValuation prior) { Map merged = new LinkedHashMap<>(); List ecValuesSorted = sort(equivClasses.keySet()); @@ -237,10 +246,10 @@ protected Map mergeGuards(Map sdts, currSdt = nextSdt; continue; } else { - if (equivalentWithRenaming(currSdt, currGuard, nextSdt, nextGuard)) { + if (equivalentWithRenaming(currSdt, currGuard, nextSdt, nextGuard, prior)) { // if left guard is equality, check for equality with previous guard if (currGuard instanceof SDTGuard.EqualityGuard && prevGuard != null && - !equivalentWithRenaming(prevSdt, prevGuard, nextSdt, nextGuard)) { + !equivalentWithRenaming(prevSdt, prevGuard, nextSdt, nextGuard, prior)) { keepMerging = false; } } else { @@ -294,9 +303,10 @@ protected Map mergeGuards(Map sdts, * @param guard1 * @param sdt2 * @param guard2 + * @param prior * @return true if sdt1 is equivalent to sdt2, or can be under equality guard2, or vice versa */ - private boolean equivalentWithRenaming(SDT sdt1, SDTGuard guard1, SDT sdt2, SDTGuard guard2) { + private boolean equivalentWithRenaming(SDT sdt1, SDTGuard guard1, SDT sdt2, SDTGuard guard2, SuffixValuation prior) { if (guard1 != null && guard1 instanceof SDTGuard.EqualityGuard) { Expression renaming = SDTGuard.toExpr(guard1); return sdt1.isEquivalentUnderCondition(sdt2, renaming); @@ -304,7 +314,17 @@ private boolean equivalentWithRenaming(SDT sdt1, SDTGuard guard1, SDT sdt2, SDTG Expression renaming = SDTGuard.toExpr(guard2); return sdt2.isEquivalentUnderCondition(sdt1, renaming); } - return sdt1.isEquivalent(sdt2, new Bijection<>()); + + // constrain suffix values + Expression guard1Expr = SDTGuard.toExpr(guard1); + Expression guard2Expr = SDTGuard.toExpr(guard2); + Expression condition = ExpressionUtil.or(guard1Expr, guard2Expr); + for (Map.Entry e : prior.entrySet()) { + Expression expr = new NumericBooleanExpression(e.getKey(), NumericComparator.EQ, e.getValue()); + condition = ExpressionUtil.and(condition, expr); + } + + return sdt1.isEquivalentUnderCondition(sdt2, condition); } /** @@ -435,7 +455,7 @@ public SDT treeQuery(Word prefix, Collection filteredOut = new ArrayList<>(); filteredOut.addAll(equivClasses.keySet()); filteredOut.removeAll(filteredEquivClasses.keySet()); - Map merged = mergeGuards(children, equivClasses, filteredOut); + Map merged = mergeGuards(children, equivClasses, filteredOut, suffixValues); Map reversed = new LinkedHashMap<>(); List keys = new ArrayList<>(merged.keySet()); @@ -603,6 +623,47 @@ public DataValue instantiate( return returnThis; } + public Optional instantiate(Word prefix, + ParameterizedSymbol ps, Expression guard, int param, + Constants constants, ConstraintSolver solver) { + Parameter p = new Parameter(ps.getPtypes()[param-1], param); + Set vals = DataWords.valSet(prefix, p.getDataType()); + vals.addAll(vals.stream() + .filter(w -> w.getDataType().equals(p.getDataType())) + .collect(Collectors.toSet())); + DataValue fresh = getFreshValue(new LinkedList<>(vals)); + + if (isSatisfiableWithEquality(guard, p, fresh, solver)) { + return Optional.of(fresh); + } + + List> diseqList = new LinkedList<>(); + vals.stream().forEach(d -> diseqList.add(new NumericBooleanExpression(p, NumericComparator.NE, d))); + Expression diseqs = ExpressionUtil.and(diseqList.toArray(new Expression[diseqList.size()])); + + Optional valuation = solver.solve(ExpressionUtil.and(guard, diseqs)); + if (valuation.isPresent()) { + BigDecimal dv = valuation.get().getValue(p); + assert dv != null : "No valuation for " + p + " in " + valuation; + DataValue ret = new DataValue(p.getDataType(), dv); + return Optional.of(ret); + } + + for (DataValue val : vals) { + if (isSatisfiableWithEquality(guard, p, val, solver)) { + return Optional.of(val); + } + } + + return Optional.empty(); + } + + private boolean isSatisfiableWithEquality(Expression guard, Parameter p, DataValue val, ConstraintSolver solver) { + ParameterValuation valuation = new ParameterValuation(); + valuation.put(p, val); + return solver.isSatisfiable(guard, valuation); + } + public void useSuffixOptimization(boolean useSuffixOpt) { this.useSuffixOpt = useSuffixOpt; } diff --git a/src/main/java/de/learnlib/ralib/tools/ClassAnalyzer.java b/src/main/java/de/learnlib/ralib/tools/ClassAnalyzer.java index 080d7bdb..4af96822 100644 --- a/src/main/java/de/learnlib/ralib/tools/ClassAnalyzer.java +++ b/src/main/java/de/learnlib/ralib/tools/ClassAnalyzer.java @@ -37,8 +37,8 @@ import de.learnlib.ralib.equivalence.IORandomWalk; import de.learnlib.ralib.learning.Hypothesis; import de.learnlib.ralib.learning.RaLearningAlgorithm; -import de.learnlib.ralib.learning.ralambda.RaDT; -import de.learnlib.ralib.learning.ralambda.RaLambda; +import de.learnlib.ralib.learning.ralambda.SLCT; +import de.learnlib.ralib.learning.ralambda.SLLambda; import de.learnlib.ralib.learning.rastar.RaStar; import de.learnlib.ralib.oracles.DataWordOracle; import de.learnlib.ralib.oracles.SimulatorOracle; @@ -249,11 +249,10 @@ public TreeOracle createTreeOracle(RegisterAutomaton hyp) { this.rastar = new RaStar(mto, hypFactory, mlo, consts, true, actions); break; case AbstractToolWithRandomWalk.LEARNER_SLLAMBDA: - this.rastar = new RaLambda(mto, hypFactory, mlo, consts, true, actions); - ((RaLambda)this.rastar).setSolver(solver); + this.rastar = new SLLambda(mto, teachers, consts, true, solver, actions); break; case AbstractToolWithRandomWalk.LEARNER_RADT: - this.rastar = new RaDT(mto, hypFactory, mlo, consts, true, actions); + this.rastar = new SLCT(mto, hypFactory, mlo, consts, true, solver, actions); break; default: throw new ConfigurationException("Unknown Learning algorithm: " + this.learner); diff --git a/src/main/java/de/learnlib/ralib/tools/IOSimulator.java b/src/main/java/de/learnlib/ralib/tools/IOSimulator.java index 35ebd306..603fc45d 100644 --- a/src/main/java/de/learnlib/ralib/tools/IOSimulator.java +++ b/src/main/java/de/learnlib/ralib/tools/IOSimulator.java @@ -37,8 +37,8 @@ import de.learnlib.ralib.learning.MeasuringOracle; import de.learnlib.ralib.learning.QueryStatistics; import de.learnlib.ralib.learning.RaLearningAlgorithm; -import de.learnlib.ralib.learning.ralambda.RaDT; -import de.learnlib.ralib.learning.ralambda.RaLambda; +import de.learnlib.ralib.learning.ralambda.SLCT; +import de.learnlib.ralib.learning.ralambda.SLLambda; import de.learnlib.ralib.learning.rastar.RaStar; import de.learnlib.ralib.oracles.DataWordOracle; import de.learnlib.ralib.oracles.SimulatorOracle; @@ -218,11 +218,10 @@ public TreeOracle createTreeOracle(RegisterAutomaton hyp) { this.rastar = new RaStar(mto, hypFactory, mlo, consts, true, actions); break; case AbstractToolWithRandomWalk.LEARNER_SLLAMBDA: - this.rastar = new RaLambda(mto, hypFactory, mlo, consts, true, actions); - ((RaLambda)this.rastar).setSolver(solver); + this.rastar = new SLLambda(mto, teachers, consts, true, solver, actions); break; case AbstractToolWithRandomWalk.LEARNER_RADT: - this.rastar = new RaDT(mto, hypFactory, mlo, consts, true, actions); + this.rastar = new SLCT(mto, hypFactory, mlo, consts, true, solver, actions); break; default: throw new ConfigurationException("Unknown Learning algorithm: " + this.learner); diff --git a/src/main/java/de/learnlib/ralib/tools/theories/UniqueIntegerEqualityTheory.java b/src/main/java/de/learnlib/ralib/tools/theories/UniqueIntegerEqualityTheory.java index 033a7798..af7fd026 100644 --- a/src/main/java/de/learnlib/ralib/tools/theories/UniqueIntegerEqualityTheory.java +++ b/src/main/java/de/learnlib/ralib/tools/theories/UniqueIntegerEqualityTheory.java @@ -5,6 +5,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Optional; import de.learnlib.ralib.data.Constants; import de.learnlib.ralib.data.DataType; @@ -12,12 +13,15 @@ import de.learnlib.ralib.data.SymbolicDataValue; import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; import de.learnlib.ralib.oracles.io.IOOracle; +import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.theory.SDTGuard; import de.learnlib.ralib.theory.SuffixValueRestriction; import de.learnlib.ralib.theory.UnrestrictedSuffixValue; import de.learnlib.ralib.theory.equality.UniqueEqualityTheory; import de.learnlib.ralib.tools.classanalyzer.TypedTheory; import de.learnlib.ralib.words.PSymbolInstance; +import de.learnlib.ralib.words.ParameterizedSymbol; +import gov.nasa.jpf.constraints.api.Expression; import net.automatalib.word.Word; public class UniqueIntegerEqualityTheory extends UniqueEqualityTheory implements TypedTheory { @@ -63,6 +67,13 @@ public Collection getAllNextValues(List vals) { return ret; } + @Override + public Optional instantiate(Word prefix, + ParameterizedSymbol ps, Expression guard, int param, + Constants constants, ConstraintSolver solver) { + throw new RuntimeException("Not implemented"); + } + @Override public SuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, Word prefix, Word suffix, Constants consts) { diff --git a/src/test/java/de/learnlib/ralib/CacheDataWordOracle.java b/src/test/java/de/learnlib/ralib/CacheDataWordOracle.java index c03ac7f8..149bd73c 100644 --- a/src/test/java/de/learnlib/ralib/CacheDataWordOracle.java +++ b/src/test/java/de/learnlib/ralib/CacheDataWordOracle.java @@ -40,7 +40,6 @@ public void processQueries(Collection> boolean answer = traceBoolean(query.getInput()); query.answer(answer); } - countQueries(queries.size()); } private boolean traceBoolean(Word query) { @@ -49,6 +48,7 @@ private boolean traceBoolean(Word query) { return ret; } ret = oracle.answerQuery(query); + countQueries(1); addToCache(query, ret); return ret; } diff --git a/src/test/java/de/learnlib/ralib/RaLibLearningExperimentRunner.java b/src/test/java/de/learnlib/ralib/RaLibLearningExperimentRunner.java index be676121..3ba50792 100644 --- a/src/test/java/de/learnlib/ralib/RaLibLearningExperimentRunner.java +++ b/src/test/java/de/learnlib/ralib/RaLibLearningExperimentRunner.java @@ -18,8 +18,8 @@ import de.learnlib.ralib.learning.MeasuringOracle; import de.learnlib.ralib.learning.RaLearningAlgorithm; import de.learnlib.ralib.learning.RaLearningAlgorithmName; -import de.learnlib.ralib.learning.ralambda.RaDT; -import de.learnlib.ralib.learning.ralambda.RaLambda; +import de.learnlib.ralib.learning.ralambda.SLCT; +import de.learnlib.ralib.learning.ralambda.SLLambda; import de.learnlib.ralib.learning.rastar.RaStar; import de.learnlib.ralib.oracles.DataWordOracle; import de.learnlib.ralib.oracles.TreeOracleFactory; @@ -125,11 +125,10 @@ public Hypothesis run(RaLearningAlgorithmName algorithmName, DataWordOracle data learner = new RaStar(mto, hypFactory, mlo, consts, ioMode, actionSymbols); break; case RALAMBDA: - learner = new RaLambda(mto, hypFactory, mlo, consts, ioMode, useOldAnalyzer, actionSymbols); - ((RaLambda)learner).setSolver(solver); + learner = new SLLambda(mto, teachers, consts, ioMode, solver, actionSymbols); break; case RADT: - learner = new RaDT(mto, hypFactory, mlo, consts, ioMode, actionSymbols); + learner = new SLCT(mto, hypFactory, mlo, consts, ioMode, solver, actionSymbols); break; default: throw new UnsupportedOperationException(String.format("Algorithm %s not supported", algorithmName)); diff --git a/src/test/java/de/learnlib/ralib/automata/RaRunTest.java b/src/test/java/de/learnlib/ralib/automata/RaRunTest.java new file mode 100644 index 00000000..63668150 --- /dev/null +++ b/src/test/java/de/learnlib/ralib/automata/RaRunTest.java @@ -0,0 +1,134 @@ +package de.learnlib.ralib.automata; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import de.learnlib.ralib.RaLibTestSuite; +import de.learnlib.ralib.automata.output.OutputMapping; +import de.learnlib.ralib.automata.output.OutputTransition; +import de.learnlib.ralib.data.Constants; +import de.learnlib.ralib.data.DataType; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.SymbolicDataValue; +import de.learnlib.ralib.data.SymbolicDataValue.Constant; +import de.learnlib.ralib.data.SymbolicDataValue.Parameter; +import de.learnlib.ralib.data.SymbolicDataValue.Register; +import de.learnlib.ralib.data.VarMapping; +import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.ParameterGenerator; +import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.RegisterGenerator; +import de.learnlib.ralib.words.InputSymbol; +import de.learnlib.ralib.words.OutputSymbol; +import de.learnlib.ralib.words.PSymbolInstance; +import gov.nasa.jpf.constraints.api.Expression; +import gov.nasa.jpf.constraints.expressions.NumericBooleanExpression; +import gov.nasa.jpf.constraints.expressions.NumericComparator; +import gov.nasa.jpf.constraints.util.ExpressionUtil; +import net.automatalib.word.Word; + +public class RaRunTest extends RaLibTestSuite { + + final DataType DT = new DataType("int"); + final InputSymbol A = new InputSymbol("a", new DataType[] {DT}); + final OutputSymbol B = new OutputSymbol("b", new DataType[] {DT, DT, DT, DT}); + + @Test + public void testMutableRARun() { + MutableRegisterAutomaton ra = new MutableRegisterAutomaton(); + + RALocation l0 = ra.addInitialState(); + RALocation l1 = ra.addState(); + RALocation l2 = ra.addState(); + + ParameterGenerator pgen = new ParameterGenerator(); + RegisterGenerator rgen = new RegisterGenerator(); + Parameter p1 = pgen.next(DT); + Register r1 = rgen.next(DT); + + Expression gT = ExpressionUtil.TRUE; + Expression gEq = new NumericBooleanExpression(r1, NumericComparator.EQ, p1); + Expression gNe = new NumericBooleanExpression(r1, NumericComparator.NE, p1); + + VarMapping storeP1 = new VarMapping<>(); + storeP1.put(r1, p1); + + Assignment assP1 = new Assignment(storeP1); + Assignment assNo = new Assignment(new VarMapping<>()); + + ra.addTransition(l0, A, new InputTransition(gT, A, l0, l1, assP1)); + ra.addTransition(l1, A, new InputTransition(gEq, A, l1, l0, assNo)); + ra.addTransition(l1, A, new InputTransition(gNe, A, l1, l2, assP1)); + ra.addTransition(l2, A, new InputTransition(gEq, A, l2, l1, assP1)); + ra.addTransition(l2, A, new InputTransition(gNe, A, l2, l0, assNo)); + + DataValue dv1 = new DataValue(DT, BigDecimal.ONE); + DataValue dv2 = new DataValue(DT, BigDecimal.valueOf(2)); + PSymbolInstance psi1 = new PSymbolInstance(A, dv1); + PSymbolInstance psi2 = new PSymbolInstance(A, dv2); + Word word1 = Word.fromSymbols(psi1, psi1, psi2); + + RARun run = ra.getRun(word1); + + Assert.assertEquals(run.getLocation(0), l0); + Assert.assertEquals(run.getLocation(1), l1); + Assert.assertEquals(run.getLocation(2), l0); + Assert.assertEquals(run.getLocation(3), l1); + + Assert.assertTrue(run.getValuation(0).isEmpty()); + Assert.assertEquals(run.getValuation(1).get(r1), dv1); + Assert.assertEquals(run.getValuation(3).get(r1), dv2); + + Assert.assertEquals(run.getTransitionSymbol(1), psi1); + Assert.assertEquals(run.getTransitionSymbol(2), psi1); + Assert.assertEquals(run.getTransitionSymbol(3), psi2); + } + + @Test + public void testGetGuard() { + Parameter p1 = new Parameter(DT, 1); + Parameter p2 = new Parameter(DT, 2); + Parameter p3 = new Parameter(DT, 3); + Parameter p4 = new Parameter(DT, 4); + Register r1 = new Register(DT, 1); + Constant c1 = new Constant(DT, 1); + Constants consts = new Constants(); + consts.put(c1, new DataValue(DT, BigDecimal.valueOf(-1))); + + MutableRegisterAutomaton ra = new MutableRegisterAutomaton(consts); + RALocation l0 = ra.addInitialState(); + RALocation l1 = ra.addState(); + RALocation l2 = ra.addState(); + + VarMapping mapping = new VarMapping<>(); + mapping.put(r1, p1); + Assignment assign = new Assignment(mapping); + Collection fresh = new ArrayList<>(); + fresh.add(p2); + fresh.add(p1); + VarMapping outmap = new VarMapping<>(); + outmap.put(p3, r1); + outmap.put(p4, c1); + OutputMapping out = new OutputMapping(fresh, outmap); + + ra.addTransition(l0, A, new InputTransition(ExpressionUtil.TRUE, A, l0, l1, assign)); + ra.addTransition(l1, B, new OutputTransition(ExpressionUtil.TRUE, out, B, l1, l2, new Assignment(new VarMapping<>()))); + + Word word = Word.fromSymbols( + new PSymbolInstance(A, new DataValue(DT, BigDecimal.ZERO)), + new PSymbolInstance(B, new DataValue(DT, BigDecimal.ONE), + new DataValue(DT, BigDecimal.valueOf(2)), + new DataValue(DT, BigDecimal.ZERO), + new DataValue(DT, consts.get(c1).getValue()))); + + RARun run = ra.getRun(word); + Expression guard = run.getGuard(2, consts); + + Assert.assertEquals(guard.toString(), + "((((('p1' != 0) && ('p1' != -1)) && " + + "((('p2' != 'p1') && ('p2' != 0)) && ('p2' != -1))) && " + + "('p3' == 0)) && ('p4' == -1))"); + } +} diff --git a/src/test/java/de/learnlib/ralib/ceanalysis/PrefixFinderTest.java b/src/test/java/de/learnlib/ralib/ceanalysis/PrefixFinderTest.java index caf2b993..85f7a2ad 100644 --- a/src/test/java/de/learnlib/ralib/ceanalysis/PrefixFinderTest.java +++ b/src/test/java/de/learnlib/ralib/ceanalysis/PrefixFinderTest.java @@ -29,25 +29,37 @@ import org.testng.annotations.Test; import de.learnlib.ralib.RaLibTestSuite; +import de.learnlib.ralib.automata.Assignment; +import de.learnlib.ralib.automata.InputTransition; +import de.learnlib.ralib.automata.MutableRegisterAutomaton; +import de.learnlib.ralib.automata.RALocation; import de.learnlib.ralib.automata.RegisterAutomaton; +import de.learnlib.ralib.ceanalysis.PrefixFinder.Result; +import de.learnlib.ralib.ct.CTAutomatonBuilder; +import de.learnlib.ralib.ct.CTHypothesis; +import de.learnlib.ralib.ct.ClassificationTree; import de.learnlib.ralib.data.Constants; import de.learnlib.ralib.data.DataType; import de.learnlib.ralib.data.DataValue; -import de.learnlib.ralib.dt.DTHyp; -import de.learnlib.ralib.dt.DTLeaf; -import de.learnlib.ralib.learning.Hypothesis; -import de.learnlib.ralib.learning.ralambda.RaLambda; -import de.learnlib.ralib.learning.rastar.RaStar; +import de.learnlib.ralib.data.SymbolicDataValue; +import de.learnlib.ralib.data.SymbolicDataValue.Parameter; +import de.learnlib.ralib.data.SymbolicDataValue.Register; +import de.learnlib.ralib.data.VarMapping; import de.learnlib.ralib.oracles.DataWordOracle; -import de.learnlib.ralib.oracles.SDTLogicOracle; import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; +import de.learnlib.ralib.oracles.mto.OptimizedSymbolicSuffixBuilder; +import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.theory.Theory; +import de.learnlib.ralib.tools.theories.DoubleInequalityTheory; import de.learnlib.ralib.tools.theories.IntegerEqualityTheory; +import de.learnlib.ralib.words.InputSymbol; import de.learnlib.ralib.words.PSymbolInstance; +import gov.nasa.jpf.constraints.api.Expression; +import gov.nasa.jpf.constraints.expressions.NumericBooleanExpression; +import gov.nasa.jpf.constraints.expressions.NumericComparator; +import gov.nasa.jpf.constraints.util.ExpressionUtil; import net.automatalib.word.Word; /** @@ -70,18 +82,21 @@ public void testPrefixFinder() { MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( dwOracle, teachers, new Constants(), solver); - SDTLogicOracle slo = new MultiTheorySDTLogicOracle(consts, solver); - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, - new Constants(), solver); + SymbolicSuffixRestrictionBuilder rb = new SymbolicSuffixRestrictionBuilder(consts, teachers); + OptimizedSymbolicSuffixBuilder sb = new OptimizedSymbolicSuffixBuilder(consts, rb); + ClassificationTree ct = new ClassificationTree(mto, solver, rb, sb, consts, false, + I_LOGIN, I_LOGOUT, I_REGISTER); - RaStar rastar = new RaStar(mto, hypFactory, slo, - consts, I_LOGIN, I_LOGOUT, I_REGISTER); - - rastar.learn(); - final Hypothesis hyp = rastar.getHypothesis(); - // System.out.println(hyp); + ct.initialize(); + boolean closed = false; + while(!closed) { + closed = ct.checkLocationClosedness() && + ct.checkTransitionClosedness() && + ct.checkRegisterClosedness(); + } + CTAutomatonBuilder ab = new CTAutomatonBuilder(ct, consts, false, solver); + final CTHypothesis hyp = ab.buildHypothesis(); Word ce = Word.fromSymbols( new PSymbolInstance(I_REGISTER, @@ -89,63 +104,159 @@ public void testPrefixFinder() { new PSymbolInstance(I_LOGIN, new DataValue(T_UID, BigDecimal.ONE), new DataValue(T_PWD, BigDecimal.ONE))); - PrefixFinder pf = new PrefixFinder( - mto, - hypFactory.createTreeOracle(hyp), hyp, - slo, - // rastar.getComponents(), - consts - ); + PrefixFinder pf = new PrefixFinder(mto, hyp, ct, teachers, rb, solver, consts); - Word prefix = pf.analyzeCounterexample(ce).getPrefix(); - Assert.assertEquals(prefix.toString(), "register[0[T_uid], 0[T_pwd]]"); + Result res = pf.analyzeCounterExample(ce); + Word prefix = res.prefix(); + Assert.assertEquals(res.result(), PrefixFinder.ResultType.LOCATION); + Assert.assertEquals(prefix.toString(), "register[0[T_uid], 0[T_pwd]]"); } @Test public void testPrefixFinderMultipleAccessSequences() { - Constants consts = new Constants(); - RegisterAutomaton sul = de.learnlib.ralib.example.stack.StackAutomatonExample.AUTOMATON; - DataWordOracle dwOracle = new SimulatorOracle(sul); + Constants consts = new Constants(); + RegisterAutomaton sul = de.learnlib.ralib.example.stack.StackAutomatonExample.AUTOMATON; + DataWordOracle dwOracle = new SimulatorOracle(sul); - final Map teachers = new LinkedHashMap<>(); - teachers.put(T_INT, new IntegerEqualityTheory(T_INT)); + final Map teachers = new LinkedHashMap<>(); + teachers.put(T_INT, new IntegerEqualityTheory(T_INT)); ConstraintSolver solver = new ConstraintSolver(); MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( dwOracle, teachers, new Constants(), solver); - SDTLogicOracle slo = new MultiTheorySDTLogicOracle(consts, solver); - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, - new Constants(), solver); - RaLambda ralambda = new RaLambda(mto, hypFactory, slo, - consts, I_PUSH, I_POP); + SymbolicSuffixRestrictionBuilder rb = new SymbolicSuffixRestrictionBuilder(consts, teachers); + OptimizedSymbolicSuffixBuilder sb = new OptimizedSymbolicSuffixBuilder(consts, rb); + ClassificationTree ct = new ClassificationTree(mto, solver, rb, sb, consts, false, + I_PUSH, I_POP); - ralambda.learn(); - final DTHyp hyp = ralambda.getDTHyp(); - // System.out.println(hyp); + ct.initialize(); + boolean closed = false; + while(!closed) { + closed = ct.checkLocationClosedness() && + ct.checkTransitionClosedness() && + ct.checkRegisterClosedness(); + } + CTAutomatonBuilder ab = new CTAutomatonBuilder(ct, consts, false, solver); + final CTHypothesis hyp = ab.buildHypothesis(); Word shortPrefix = Word.fromSymbols( new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ZERO))); - DTLeaf leaf = ralambda.getDT().getLeaf(shortPrefix); - leaf.elevatePrefix(ralambda.getDT(), shortPrefix, hyp, slo); + ct.expand(shortPrefix); Word ce = Word.fromSymbols( new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ZERO)), new PSymbolInstance(I_POP, new DataValue(T_INT, BigDecimal.ZERO)), new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ONE))); - PrefixFinder pf = new PrefixFinder( - mto, - hypFactory.createTreeOracle(hyp), hyp, - slo, - // ralambda.getComponents(), - consts - ); + PrefixFinder pf = new PrefixFinder(mto, hyp, ct, teachers, rb, solver, consts); + + Result res = pf.analyzeCounterExample(ce); + Assert.assertEquals(res.result(), PrefixFinder.ResultType.TRANSITION); + Assert.assertEquals(res.prefix().toString(), "push[0[T_int]] pop[0[T_int]]"); + } + + private final DataType DT = new DataType("double"); + private final InputSymbol A = new InputSymbol("α", DT); + private final InputSymbol B = new InputSymbol("β"); + + private RegisterAutomaton buildTestAutomaton() { + MutableRegisterAutomaton ra = new MutableRegisterAutomaton(); + + Register x1 = new Register(DT, 1); + Parameter p1 = new Parameter(DT, 1); + + RALocation l0 = ra.addInitialState(true); + RALocation l1 = ra.addState(false); + RALocation l2 = ra.addState(false); + RALocation l3 = ra.addState(false); + + Expression gTrue = ExpressionUtil.TRUE; + Expression gGT = new NumericBooleanExpression(x1, NumericComparator.GE, p1); + Expression gLT = new NumericBooleanExpression(x1, NumericComparator.LT, p1); + + VarMapping mapX1P1 = new VarMapping<>(); + mapX1P1.put(x1, p1); + VarMapping mapNo = new VarMapping<>(); + + Assignment assX1P1 = new Assignment(mapX1P1); + Assignment assNo = new Assignment(mapNo); + + ra.addTransition(l0, A, new InputTransition(gTrue, A, l0, l1, assX1P1)); + ra.addTransition(l0, B, new InputTransition(gTrue, B, l0, l0, assNo)); + ra.addTransition(l1, A, new InputTransition(gGT, A, l1, l2, assNo)); + ra.addTransition(l1, A, new InputTransition(gLT, A, l1, l3, assNo)); + ra.addTransition(l1, B, new InputTransition(gTrue, B, l1, l0, assNo)); + ra.addTransition(l2, A, new InputTransition(gTrue, A, l2, l0, assNo)); + ra.addTransition(l2, B, new InputTransition(gTrue, B, l2, l2, assNo)); + ra.addTransition(l3, A, new InputTransition(gTrue, A, l3, l0, assNo)); + ra.addTransition(l3, B, new InputTransition(gTrue, B, l3, l0, assNo)); + + return ra; + } + + @Test + public void testAnalyzeCELocation() { + RegisterAutomaton ra = buildTestAutomaton(); + DataWordOracle dwOracle = new SimulatorOracle(ra); + + final Map teachers = new LinkedHashMap<>(); + DoubleInequalityTheory dit = new DoubleInequalityTheory(DT); + dit.useSuffixOptimization(false); + teachers.put(DT, dit); + + ConstraintSolver solver = new ConstraintSolver(); + Constants consts = new Constants(); + + MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(dwOracle, teachers, consts, solver); + + SymbolicSuffixRestrictionBuilder restrBuilder = new SymbolicSuffixRestrictionBuilder(consts, teachers); + OptimizedSymbolicSuffixBuilder suffixBuilder = new OptimizedSymbolicSuffixBuilder(consts, restrBuilder); + + DataValue dv0 = new DataValue(DT, BigDecimal.ZERO); + DataValue dv1 = new DataValue(DT, BigDecimal.ONE); + DataValue dv2 = new DataValue(DT, BigDecimal.valueOf(2)); + DataValue dv3 = new DataValue(DT, BigDecimal.valueOf(3)); + + ClassificationTree ct = new ClassificationTree(mto, solver, restrBuilder, suffixBuilder, consts, false, A, B); + + ct.initialize(); + ct.checkLocationClosedness(); + ct.checkLocationClosedness(); + + CTAutomatonBuilder ab = new CTAutomatonBuilder(ct, new Constants(), false, solver); + CTHypothesis hyp = ab.buildHypothesis(); + + Word ce1 = Word.fromSymbols( + new PSymbolInstance(A, dv1), + new PSymbolInstance(A, dv2), + new PSymbolInstance(A, dv3)); + + PrefixFinder pf = new PrefixFinder(mto, hyp, ct, teachers, restrBuilder, solver, consts); + + Result res = pf.analyzeCounterExample(ce1); + Assert.assertEquals(res.result(), PrefixFinder.ResultType.LOCATION); + Assert.assertEquals(res.prefix().toString(), "α[1[double]] α[2[double]]"); + + ct.expand(res.prefix()); + boolean consistent = ct.checkLocationConsistency(); + Assert.assertFalse(consistent); + + ab = new CTAutomatonBuilder(ct, consts, false, solver); + hyp = ab.buildHypothesis(); + pf = new PrefixFinder(mto, hyp, ct, teachers, restrBuilder, solver, consts); + + Word ce2 = Word.fromSymbols( + new PSymbolInstance(A, dv1), + new PSymbolInstance(A, dv0), + new PSymbolInstance(B)); + + Assert.assertFalse(hyp.accepts(ce2) == ra.accepts(ce2)); - Word prefix = pf.analyzeCounterexample(ce).getPrefix(); - Assert.assertEquals(prefix.toString(), "push[0[T_int]] pop[0[T_int]]"); + res = pf.analyzeCounterExample(ce2); + Assert.assertEquals(res.result(), PrefixFinder.ResultType.TRANSITION); + Assert.assertEquals(res.prefix().toString(), "α[1[double]] α[0[double]]"); } } diff --git a/src/test/java/de/learnlib/ralib/ct/CTConsistencyTest.java b/src/test/java/de/learnlib/ralib/ct/CTConsistencyTest.java new file mode 100644 index 00000000..e29ab939 --- /dev/null +++ b/src/test/java/de/learnlib/ralib/ct/CTConsistencyTest.java @@ -0,0 +1,365 @@ +package de.learnlib.ralib.ct; + +import static de.learnlib.ralib.example.stack.StackAutomatonExample.AUTOMATON; +import static de.learnlib.ralib.example.stack.StackAutomatonExample.I_POP; +import static de.learnlib.ralib.example.stack.StackAutomatonExample.I_PUSH; +import static de.learnlib.ralib.example.stack.StackAutomatonExample.T_INT; + +import java.math.BigDecimal; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import de.learnlib.ralib.RaLibTestSuite; +import de.learnlib.ralib.TestUtil; +import de.learnlib.ralib.automata.Assignment; +import de.learnlib.ralib.automata.InputTransition; +import de.learnlib.ralib.automata.MutableRegisterAutomaton; +import de.learnlib.ralib.automata.RALocation; +import de.learnlib.ralib.automata.RegisterAutomaton; +import de.learnlib.ralib.data.Constants; +import de.learnlib.ralib.data.DataType; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.SymbolicDataValue; +import de.learnlib.ralib.data.SymbolicDataValue.Parameter; +import de.learnlib.ralib.data.SymbolicDataValue.Register; +import de.learnlib.ralib.data.VarMapping; +import de.learnlib.ralib.learning.SymbolicSuffix; +import de.learnlib.ralib.learning.rastar.RaStar; +import de.learnlib.ralib.oracles.DataWordOracle; +import de.learnlib.ralib.oracles.SimulatorOracle; +import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; +import de.learnlib.ralib.oracles.mto.OptimizedSymbolicSuffixBuilder; +import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; +import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.theory.Theory; +import de.learnlib.ralib.tools.theories.DoubleInequalityTheory; +import de.learnlib.ralib.tools.theories.IntegerEqualityTheory; +import de.learnlib.ralib.words.InputSymbol; +import de.learnlib.ralib.words.PSymbolInstance; +import gov.nasa.jpf.constraints.api.Expression; +import gov.nasa.jpf.constraints.expressions.NumericBooleanExpression; +import gov.nasa.jpf.constraints.expressions.NumericComparator; +import gov.nasa.jpf.constraints.util.ExpressionUtil; +import net.automatalib.word.Word; + +public class CTConsistencyTest extends RaLibTestSuite { + + private static DataType DT = new DataType("double"); + private static InputSymbol A = new InputSymbol("a", new DataType[] {DT}); + private static InputSymbol B = new InputSymbol("b"); + + private static InputSymbol ALPHA = new InputSymbol("α", new DataType[] {T_INT}); + private static InputSymbol BETA = new InputSymbol("β", new DataType[] {T_INT}); + + @Test + public void testConsistencyStack() { + + Map teachers = new LinkedHashMap<>(); + teachers.put(T_INT, new IntegerEqualityTheory(T_INT)); + + Constants consts = new Constants(); + + SymbolicSuffixRestrictionBuilder restrBuilder = new SymbolicSuffixRestrictionBuilder(consts, teachers); + OptimizedSymbolicSuffixBuilder suffixBuilder = new OptimizedSymbolicSuffixBuilder(consts, restrBuilder); + + RegisterAutomaton sul = AUTOMATON; + DataWordOracle dwOracle = new SimulatorOracle(sul); + ConstraintSolver solver = new ConstraintSolver(); + + MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(dwOracle, teachers, consts, solver); + + DataValue dv0 = new DataValue(T_INT, BigDecimal.ZERO); + DataValue dv1 = new DataValue(T_INT, BigDecimal.ONE); + + PSymbolInstance push0 = new PSymbolInstance(I_PUSH, dv0); + PSymbolInstance push1 = new PSymbolInstance(I_PUSH, dv1); + PSymbolInstance pop0 = new PSymbolInstance(I_POP, dv0); + + Word pu0 = Word.fromSymbols(push0); + Word pu0pu1 = Word.fromSymbols(push0, push1); + Word pu0po0 = Word.fromSymbols(push0, pop0); + + SymbolicSuffix s1 = new SymbolicSuffix(pu0, Word.fromSymbols(push1)); + + ClassificationTree ct = new ClassificationTree(mto, solver, restrBuilder, suffixBuilder, consts, false, I_PUSH, I_POP); + + ct.initialize(); + boolean closed = ct.checkLocationClosedness(); + closed = ct.checkLocationClosedness(); + Assert.assertFalse(closed); + + ct.expand(pu0); + closed = ct.checkLocationClosedness(); + Assert.assertTrue(closed); + + ct.expand(pu0pu1); + closed = ct.checkLocationClosedness(); + Assert.assertTrue(closed); + + ct.refine(ct.getLeaf(pu0pu1), s1); + boolean consistent = ct.checkLocationConsistency(); + Assert.assertFalse(consistent); + + ct.sift(pu0po0); + consistent = ct.checkTransitionConsistency(); + Assert.assertFalse(consistent); + } + + @Test + public void testLocationConsistency() { + RegisterAutomaton sul = buildLocationConsistencyAutomaton(); + DataWordOracle dwOracle = new SimulatorOracle(sul); + + final Map teachers = new LinkedHashMap<>(); + teachers.put(DT, new DoubleInequalityTheory(DT)); + + Constants consts = new Constants(); + + SymbolicSuffixRestrictionBuilder restrBuilder = new SymbolicSuffixRestrictionBuilder(consts, teachers); + OptimizedSymbolicSuffixBuilder suffixBuilder = new OptimizedSymbolicSuffixBuilder(consts, restrBuilder); + + ConstraintSolver solver = TestUtil.getZ3Solver(); + MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( + dwOracle, teachers, consts, solver); + + DataValue dv0 = new DataValue(DT, BigDecimal.ZERO); + DataValue dv1 = new DataValue(DT, BigDecimal.ONE); + DataValue dv2 = new DataValue(DT, BigDecimal.valueOf(2)); + + Word a1 = Word.fromSymbols(new PSymbolInstance(A, dv1)); + Word a1a2 = Word.fromSymbols( + new PSymbolInstance(A, dv1), + new PSymbolInstance(A, dv2)); + Word a1a0 = Word.fromSymbols( + new PSymbolInstance(A, dv1), + new PSymbolInstance(A, dv0)); + Word a1a2a2 = Word.fromSymbols( + new PSymbolInstance(A, dv1), + new PSymbolInstance(A, dv2), + new PSymbolInstance(A, dv2)); + Word a1a0a0 = Word.fromSymbols( + new PSymbolInstance(A, dv1), + new PSymbolInstance(A, dv0), + new PSymbolInstance(A, dv0)); + + ClassificationTree ct = new ClassificationTree(mto, solver, restrBuilder, suffixBuilder, consts, true, A, B); + ct.initialize(); + + boolean closed = ct.checkLocationClosedness(); + Assert.assertFalse(closed); + closed = ct.checkLocationClosedness(); + Assert.assertTrue(closed); + + boolean consistent = ct.checkLocationConsistency(); + Assert.assertTrue(consistent); + ct.expand(a1); + consistent = ct.checkLocationConsistency(); + Assert.assertTrue(consistent); + + ct.expand(a1a2); + closed = ct.checkLocationClosedness(); + Assert.assertFalse(closed); + consistent = ct.checkLocationConsistency(); + Assert.assertFalse(consistent); + + ct.sift(a1a0); + ct.expand(a1a0); + consistent = ct.checkLocationConsistency(); + Assert.assertFalse(consistent); + consistent = ct.checkLocationConsistency(); + Assert.assertTrue(consistent); + consistent = ct.checkTransitionConsistency(); + Assert.assertTrue(consistent); + + ct.sift(a1a2a2); + ct.sift(a1a0a0); + consistent = ct.checkLocationConsistency(); + Assert.assertTrue(consistent); + consistent = ct.checkTransitionConsistency(); + Assert.assertFalse(consistent); + } + + private RegisterAutomaton buildLocationConsistencyAutomaton() { + MutableRegisterAutomaton ra = new MutableRegisterAutomaton(); + + RALocation l0 = ra.addInitialState(true); + RALocation l1 = ra.addState(true); + RALocation l2 = ra.addState(true); + RALocation l3 = ra.addState(true); + RALocation l4 = ra.addState(false); + + Register r1 = new Register(DT, 1); + Parameter p1 = new Parameter(DT, 1); + + Expression gT = ExpressionUtil.TRUE; + Expression gGE = NumericBooleanExpression.create(p1, NumericComparator.GE, r1); + Expression gLt = NumericBooleanExpression.create(p1, NumericComparator.LT, r1); + Expression gEq = NumericBooleanExpression.create(p1, NumericComparator.EQ, r1); + Expression gNE = NumericBooleanExpression.create(p1, NumericComparator.NE, r1); + + VarMapping storeP1 = new VarMapping<>(); + storeP1.put(r1, p1); + VarMapping storeR1 = new VarMapping<>(); + storeR1.put(r1, r1); + + Assignment assP1 = new Assignment(storeP1); + Assignment assR1 = new Assignment(storeR1); + Assignment assNo = new Assignment(new VarMapping<>()); + + ra.addTransition(l0, A, new InputTransition(gT, A, l0, l1, assP1)); + ra.addTransition(l0, B, new InputTransition(gT, B, l0, l0, assNo)); + + ra.addTransition(l1, A, new InputTransition(gGE, A, l1, l2, assP1)); + ra.addTransition(l1, A, new InputTransition(gLt, A, l1, l3, assP1)); + ra.addTransition(l1, B, new InputTransition(gT, B, l1, l0, assNo)); + + ra.addTransition(l2, A, new InputTransition(gEq, A, l2, l4, assNo)); + ra.addTransition(l2, A, new InputTransition(gNE, A, l2, l0, assNo)); + ra.addTransition(l2, B, new InputTransition(gT, B, l2, l4, assNo)); + + ra.addTransition(l3, A, new InputTransition(gEq, A, l3, l1, assR1)); + ra.addTransition(l3, A, new InputTransition(gNE, A, l3, l0, assNo)); + ra.addTransition(l3, B, new InputTransition(gT, B, l3, l4, assNo)); + + ra.addTransition(l4, A, new InputTransition(gT, A, l4, l4, assNo)); + ra.addTransition(l4, B, new InputTransition(gT, B, l4, l4, assNo)); + + return ra; + } + + @Test + public void testRegisterConsistency() { + RegisterAutomaton sul = buildRegisterConsistencyAutomaton(); + DataWordOracle dwOracle = new SimulatorOracle(sul); + + final Map teachers = new LinkedHashMap<>(); + IntegerEqualityTheory iet = new IntegerEqualityTheory(T_INT); + iet.setUseSuffixOpt(false); + teachers.put(T_INT, iet); + + Constants consts = new Constants(); + + SymbolicSuffixRestrictionBuilder restrBuilder = new SymbolicSuffixRestrictionBuilder(consts, teachers); + OptimizedSymbolicSuffixBuilder suffixBuilder = new OptimizedSymbolicSuffixBuilder(consts, restrBuilder); + + ConstraintSolver solver = TestUtil.getZ3Solver(); + MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( + dwOracle, teachers, consts, solver); + + DataValue dv0 = new DataValue(T_INT, BigDecimal.ZERO); + DataValue dv1 = new DataValue(T_INT, BigDecimal.ONE); + DataValue dv2 = new DataValue(T_INT, BigDecimal.valueOf(2)); + + Word b0 = Word.fromSymbols(new PSymbolInstance(BETA, dv0)); + Word a0 = Word.fromSymbols(new PSymbolInstance(ALPHA, dv0)); + Word a0a1 = Word.fromSymbols( + new PSymbolInstance(ALPHA, dv0), + new PSymbolInstance(ALPHA, dv1)); + Word a0b1 = Word.fromSymbols( + new PSymbolInstance(ALPHA, dv0), + new PSymbolInstance(BETA, dv1)); + Word a0a1a0 = Word.fromSymbols( + new PSymbolInstance(ALPHA, dv0), + new PSymbolInstance(ALPHA, dv1), + new PSymbolInstance(ALPHA, dv0)); + Word b2b1 = Word.fromSymbols( + new PSymbolInstance(BETA, dv2), + new PSymbolInstance(BETA, dv1)); + + SymbolicSuffix sa = new SymbolicSuffix(RaStar.EMPTY_PREFIX, a0); + SymbolicSuffix sb = new SymbolicSuffix(RaStar.EMPTY_PREFIX, b0); + SymbolicSuffix sab = new SymbolicSuffix(RaStar.EMPTY_PREFIX, a0b1); + SymbolicSuffix sbb = new SymbolicSuffix(a0a1, b2b1); + + ClassificationTree ct = new ClassificationTree(mto, solver, restrBuilder, suffixBuilder, consts, false, ALPHA, BETA); + ct.initialize(); + + ct.refine(ct.getLeaf(RaStar.EMPTY_PREFIX), sa); + ct.refine(ct.getLeaf(RaStar.EMPTY_PREFIX), sb); + ct.refine(ct.getLeaf(RaStar.EMPTY_PREFIX), sab); + + ct.sift(a0a1a0); + ct.sift(a0a1); + ct.sift(a0); + ct.sift(b0); + + Assert.assertEquals(ct.getLeaves().size(), 5); + for (int i = 0; i < ct.getLeaves().size(); i++) { + Assert.assertFalse(ct.checkLocationClosedness()); + } + Assert.assertTrue(ct.checkLocationClosedness()); + + boolean consistent = ct.checkRegisterConsistency(); + Assert.assertFalse(consistent); + consistent = ct.checkRegisterConsistency(); + Assert.assertTrue(consistent); + + Assert.assertTrue(ct.getLeaf(a0a1) + .getRepresentativePrefix() + .getPath() + .getSDTs() + .keySet() + .contains(sbb)); + } + + private RegisterAutomaton buildRegisterConsistencyAutomaton() { + MutableRegisterAutomaton ra = new MutableRegisterAutomaton(); + + RALocation l0 = ra.addInitialState(false); + RALocation l1 = ra.addState(false); + RALocation l2 = ra.addState(false); + RALocation l3 = ra.addState(true); + RALocation ls = ra.addState(false); + + Register r1 = new Register(T_INT, 1); + Register r2 = new Register(T_INT, 2); + Parameter p1 = new Parameter(T_INT, 1); + + Expression gT = ExpressionUtil.TRUE; + Expression gEqR1 = NumericBooleanExpression.create(p1, NumericComparator.EQ, r1); + Expression gEqR2 = NumericBooleanExpression.create(p1, NumericComparator.EQ, r2); + Expression gNER1 = NumericBooleanExpression.create(p1, NumericComparator.NE, r1); + Expression gNER1R2 = ExpressionUtil.and( + NumericBooleanExpression.create(p1, NumericComparator.NE, r1), + NumericBooleanExpression.create(p1, NumericComparator.NE, r2)); + + VarMapping storeP1inR1 = new VarMapping<>(); + storeP1inR1.put(r1, p1); + VarMapping storeP1inR2 = new VarMapping<>(); + storeP1inR2.put(r2, p1); + VarMapping storeP1inR2andR2inR1 = new VarMapping<>(); + storeP1inR2andR2inR1.put(r1, p1); + storeP1inR2andR2inR1.put(r2, r1); + VarMapping storeR2inR1 = new VarMapping<>(); + storeR2inR1.put(r1, r2); + + Assignment assP1inR1 = new Assignment(storeP1inR1); + Assignment assP1inR2 = new Assignment(storeP1inR2); + Assignment assP1inR1andR2inR1 = new Assignment(storeP1inR2andR2inR1); + Assignment assR2inR1 = new Assignment(storeR2inR1); + Assignment assNo = new Assignment(new VarMapping<>()); + + ra.addTransition(l0, ALPHA, new InputTransition(gT, ALPHA, l0, l1, assP1inR1)); + ra.addTransition(l0, BETA, new InputTransition(gT, BETA, l0, ls, assNo)); + + ra.addTransition(l1, ALPHA, new InputTransition(gT, ALPHA, l1, l2, assP1inR2)); + ra.addTransition(l1, BETA, new InputTransition(gNER1, BETA, l1, l2, assP1inR1andR2inR1)); + ra.addTransition(l1, BETA, new InputTransition(gEqR1, BETA, l1, l3, assNo)); + + ra.addTransition(l2, ALPHA, new InputTransition(gEqR1, ALPHA, l2, l3, assNo)); + ra.addTransition(l2, ALPHA, new InputTransition(gEqR2, ALPHA, l2, l3, assNo)); + ra.addTransition(l2, ALPHA, new InputTransition(gNER1R2, ALPHA, l2, ls, assNo)); + ra.addTransition(l2, BETA, new InputTransition(gT, BETA, l2, l1, assR2inR1)); + + ra.addTransition(l3, ALPHA, new InputTransition(gT, ALPHA, l3, ls, assNo)); + ra.addTransition(l3, BETA, new InputTransition(gT, BETA, l3, ls, assNo)); + + ra.addTransition(ls, ALPHA, new InputTransition(gT, ALPHA, ls, ls, assNo)); + ra.addTransition(ls, BETA, new InputTransition(gT, BETA, ls, ls, assNo)); + + return ra; + } +} diff --git a/src/test/java/de/learnlib/ralib/ct/CTTest.java b/src/test/java/de/learnlib/ralib/ct/CTTest.java new file mode 100644 index 00000000..be970022 --- /dev/null +++ b/src/test/java/de/learnlib/ralib/ct/CTTest.java @@ -0,0 +1,195 @@ +package de.learnlib.ralib.ct; + +import static de.learnlib.ralib.example.priority.PriorityQueueOracle.OFFER; +import static de.learnlib.ralib.example.priority.PriorityQueueOracle.POLL; +import static de.learnlib.ralib.example.priority.PriorityQueueOracle.doubleType; +import static de.learnlib.ralib.example.stack.StackAutomatonExample.AUTOMATON; +import static de.learnlib.ralib.example.stack.StackAutomatonExample.I_POP; +import static de.learnlib.ralib.example.stack.StackAutomatonExample.I_PUSH; +import static de.learnlib.ralib.example.stack.StackAutomatonExample.T_INT; + +import java.math.BigDecimal; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import de.learnlib.ralib.TestUtil; +import de.learnlib.ralib.automata.RegisterAutomaton; +import de.learnlib.ralib.data.Constants; +import de.learnlib.ralib.data.DataType; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.learning.Hypothesis; +import de.learnlib.ralib.learning.SymbolicSuffix; +import de.learnlib.ralib.oracles.DataWordOracle; +import de.learnlib.ralib.oracles.SimulatorOracle; +import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; +import de.learnlib.ralib.oracles.mto.OptimizedSymbolicSuffixBuilder; +import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; +import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.theory.Theory; +import de.learnlib.ralib.tools.theories.DoubleInequalityTheory; +import de.learnlib.ralib.tools.theories.IntegerEqualityTheory; +import de.learnlib.ralib.words.PSymbolInstance; +import net.automatalib.word.Word; + +public class CTTest { + @Test + public void testStackCT() { + Map teachers = new LinkedHashMap<>(); + teachers.put(T_INT, new IntegerEqualityTheory(T_INT)); + + Constants consts = new Constants(); + + SymbolicSuffixRestrictionBuilder restrBuilder = new SymbolicSuffixRestrictionBuilder(consts, teachers); + OptimizedSymbolicSuffixBuilder suffixBuilder = new OptimizedSymbolicSuffixBuilder(consts, restrBuilder); + + RegisterAutomaton sul = AUTOMATON; + DataWordOracle dwOracle = new SimulatorOracle(sul); + ConstraintSolver solver = new ConstraintSolver(); + + MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(dwOracle, teachers, consts, solver); + + DataValue dv0 = new DataValue(T_INT, BigDecimal.ZERO); + DataValue dv1 = new DataValue(T_INT, BigDecimal.ONE); + + PSymbolInstance push0 = new PSymbolInstance(I_PUSH, dv0); + PSymbolInstance push1 = new PSymbolInstance(I_PUSH, dv1); + PSymbolInstance pop0 = new PSymbolInstance(I_POP, dv0); + PSymbolInstance pop1 = new PSymbolInstance(I_POP, dv1); + + Word w1 = Word.fromSymbols(push0); + Word w3 = Word.fromSymbols(push0, push1); + Word w4 = Word.fromSymbols(push0, pop0); + Word w5 = Word.fromSymbols(push0, push1, pop1); + + SymbolicSuffix s1 = new SymbolicSuffix(w1, Word.fromSymbols(push1)); + SymbolicSuffix s2 = new SymbolicSuffix(Word.epsilon(), Word.fromSymbols(push0, push1)); + SymbolicSuffix s3 = new SymbolicSuffix(w1, Word.fromSymbols(pop0)); + + ClassificationTree ct = new ClassificationTree(mto, solver, restrBuilder, suffixBuilder, consts, false, I_PUSH, I_POP); + + ct.sift(Word.epsilon()); + boolean consistent = ct.checkLocationClosedness(); + Assert.assertFalse(consistent); + Assert.assertEquals(ct.getLeaves().size(), 2); + Assert.assertEquals(ct.getPrefixes().size(), 3); + + consistent = ct.checkLocationClosedness(); + Assert.assertFalse(consistent); + consistent = ct.checkLocationClosedness(); + Assert.assertTrue(consistent); + + ct.expand(w1); + Assert.assertEquals(ct.getLeaves().size(), 2); + Assert.assertEquals(ct.getPrefixes().size(), 7); + + ct.refine(ct.getLeaf(w3), s1); + Assert.assertEquals(ct.getLeaves().size(), 3); + Assert.assertEquals(ct.getPrefixes().size(), 7); + + consistent = ct.checkLocationClosedness(); + Assert.assertFalse(consistent); + + ct.refine(ct.getLeaf(w1), s2); + Assert.assertEquals(ct.getLeaves().size(), 4); + Assert.assertEquals(ct.getPrefixes().size(), 9); + + ct.refine(ct.getLeaf(w1), s3); + consistent = ct.checkTransitionClosedness(); + Assert.assertFalse(consistent); + Assert.assertEquals(ct.getLeaves().size(), 4); + Assert.assertEquals(ct.getPrefixes().size(), 10); + Assert.assertTrue(ct.getLeaf(Word.epsilon()).getPrefixes().contains(w4)); + + ct.refine(ct.getLeaf(w3), s3); + consistent = ct.checkTransitionClosedness(); + Assert.assertFalse(consistent); + Assert.assertEquals(ct.getLeaves().size(), 4); + Assert.assertEquals(ct.getPrefixes().size(), 11); + Assert.assertTrue(ct.getLeaf(w1).getPrefixes().contains(w5)); + + consistent = ct.checkRegisterClosedness(); + Assert.assertFalse(consistent); + Assert.assertTrue(ct.getLeaf(w3).getRepresentativePrefix().getRegisters().contains(dv0)); + Assert.assertTrue(ct.getLeaf(w3).getRepresentativePrefix().getRegisters().contains(dv1)); + + CTAutomatonBuilder ab = new CTAutomatonBuilder(ct, consts, false, solver); + Hypothesis hyp = ab.buildHypothesis(); + + Assert.assertEquals(hyp.getStates().size(), 4); + Assert.assertEquals(hyp.getTransitions().size(), 10); + Assert.assertEquals(hyp.getAccessSequences().size(), hyp.getStates().size()); + } + + @Test + public void testPQCT() { + DataWordOracle dwOracle = new de.learnlib.ralib.example.priority.PriorityQueueOracle(2); + + final Map teachers = new LinkedHashMap<>(); + teachers.put(doubleType, new DoubleInequalityTheory(doubleType)); + + Constants consts = new Constants(); + + SymbolicSuffixRestrictionBuilder restrBuilder = new SymbolicSuffixRestrictionBuilder(consts, teachers); + OptimizedSymbolicSuffixBuilder suffixBuilder = new OptimizedSymbolicSuffixBuilder(consts, restrBuilder); + + ConstraintSolver solver = TestUtil.getZ3Solver(); + MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( + dwOracle, teachers, new Constants(), solver); + + DataValue dv0 = new DataValue(doubleType, BigDecimal.ZERO); + DataValue dv1 = new DataValue(doubleType, BigDecimal.ONE); + DataValue dv2 = new DataValue(doubleType, BigDecimal.valueOf(2)); + + PSymbolInstance offer0 = new PSymbolInstance(OFFER, dv0); + PSymbolInstance offer1 = new PSymbolInstance(OFFER, dv1); + PSymbolInstance offer2 = new PSymbolInstance(OFFER, dv2); + PSymbolInstance poll0 = new PSymbolInstance(POLL, dv0); + PSymbolInstance poll1 = new PSymbolInstance(POLL, dv1); + + Word o1 = Word.fromSymbols(offer1); + Word o1o2 = Word.fromSymbols(offer1, offer2); + Word o1o0 = Word.fromSymbols(offer1, offer0); + Word o1p1 = Word.fromSymbols(offer1, poll1); + Word o1o2p1 = Word.fromSymbols(offer1, offer2, poll1); + Word o1o0p0 = Word.fromSymbols(offer1, offer0, poll0); + + ClassificationTree ct = new ClassificationTree(mto, solver, restrBuilder, suffixBuilder, consts, false, OFFER, POLL); + + ct.sift(Word.epsilon()); + boolean closed = ct.checkLocationClosedness(); + Assert.assertFalse(closed); + closed = ct.checkTransitionClosedness(); + Assert.assertTrue(closed); + closed = ct.checkLocationClosedness(); + Assert.assertFalse(closed); + + ct.expand(o1); + ct.sift(o1p1); + boolean consistent = ct.checkTransitionConsistency(); + Assert.assertFalse(consistent); + + ct.expand(o1o2); + consistent = ct.checkLocationConsistency(); + Assert.assertFalse(consistent); + closed = ct.checkRegisterClosedness(); + Assert.assertFalse(closed); + + ct.sift(o1o0); + consistent = ct.checkTransitionConsistency(); + Assert.assertFalse(consistent); + + closed = ct.checkRegisterClosedness(); + Assert.assertTrue(closed); + + CTAutomatonBuilder ab = new CTAutomatonBuilder(ct, new Constants(), false, solver); + Hypothesis hyp = ab.buildHypothesis(); + + Assert.assertEquals(hyp.getStates().size(), ct.getLeaves().size()); + Assert.assertEquals(hyp.getTransitions().size(), ct.getPrefixes().size() - 1); + Assert.assertTrue(hyp.accepts(o1o2p1)); + Assert.assertTrue(hyp.accepts(o1o0p0));; + } +} diff --git a/src/test/java/de/learnlib/ralib/dt/DTInnerNodeTest.java b/src/test/java/de/learnlib/ralib/dt/DTInnerNodeTest.java deleted file mode 100644 index 613d59cf..00000000 --- a/src/test/java/de/learnlib/ralib/dt/DTInnerNodeTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package de.learnlib.ralib.dt; - -import static de.learnlib.ralib.example.stack.StackAutomatonExample.AUTOMATON; -import static de.learnlib.ralib.example.stack.StackAutomatonExample.I_POP; -import static de.learnlib.ralib.example.stack.StackAutomatonExample.I_PUSH; -import static de.learnlib.ralib.example.stack.StackAutomatonExample.T_INT; - -import java.math.BigDecimal; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.testng.Assert; -import org.testng.annotations.Test; - -import de.learnlib.ralib.automata.RegisterAutomaton; -import de.learnlib.ralib.data.Bijection; -import de.learnlib.ralib.data.Constants; -import de.learnlib.ralib.data.DataType; -import de.learnlib.ralib.data.DataValue; -import de.learnlib.ralib.learning.SymbolicSuffix; -import de.learnlib.ralib.oracles.DataWordOracle; -import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; -import de.learnlib.ralib.smt.ConstraintSolver; -import de.learnlib.ralib.theory.Theory; -import de.learnlib.ralib.tools.theories.IntegerEqualityTheory; -import de.learnlib.ralib.words.PSymbolInstance; -import net.automatalib.word.Word; - -public class DTInnerNodeTest { - - @Test - public void testSiftDTInnerNode() { - RegisterAutomaton sul = AUTOMATON; - DataWordOracle dwOracle = new SimulatorOracle(sul); - - final Map teachers = new LinkedHashMap<>(); - teachers.put(T_INT, new IntegerEqualityTheory(T_INT)); - - ConstraintSolver solver = new ConstraintSolver(); - - MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( - dwOracle, teachers, new Constants(), solver); - - Word p1 = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(1))), - new PSymbolInstance(I_POP, new DataValue(T_INT, new BigDecimal(1)))); - - Word p2 = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(1))), - new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(2)))); - - Word epsilon = Word.epsilon(); - Word push = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(1)))); - - Word suffix = Word.fromSymbols( - new PSymbolInstance(I_POP, new DataValue(T_INT, new BigDecimal(1)))); - - SymbolicSuffix symbSuffix = new SymbolicSuffix(epsilon, suffix); - - DTInnerNode node = new DTInnerNode(symbSuffix); - DTLeaf child1 = new DTLeaf(new MappedPrefix(epsilon, new Bijection<>()), mto); - DTLeaf child2 = new DTLeaf(new MappedPrefix(push, new Bijection<>()), mto); - - PathResult r1 = PathResult.computePathResult(mto, new MappedPrefix(epsilon, new Bijection<>()), node.getSuffixes(), false); - PathResult r2 = PathResult.computePathResult(mto, new MappedPrefix(push, new Bijection<>()), node.getSuffixes(), false); - - node.addBranch(new DTBranch(child1, r1)); - node.addBranch(new DTBranch(child2, r2)); - - DTNode test1 = node.sift(new MappedPrefix(p1, new Bijection<>()), mto, false).getKey(); - DTNode test2 = node.sift(new MappedPrefix(p2, new Bijection<>()), mto, false).getKey(); - - Assert.assertEquals(test1, child1); - Assert.assertEquals(test2, child2); - } -} diff --git a/src/test/java/de/learnlib/ralib/dt/DTTest.java b/src/test/java/de/learnlib/ralib/dt/DTTest.java deleted file mode 100644 index 5e312a41..00000000 --- a/src/test/java/de/learnlib/ralib/dt/DTTest.java +++ /dev/null @@ -1,221 +0,0 @@ -package de.learnlib.ralib.dt; - -import static de.learnlib.ralib.example.stack.StackAutomatonExample.AUTOMATON; -import static de.learnlib.ralib.example.stack.StackAutomatonExample.I_POP; -import static de.learnlib.ralib.example.stack.StackAutomatonExample.I_PUSH; -import static de.learnlib.ralib.example.stack.StackAutomatonExample.T_INT; - -import java.math.BigDecimal; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.testng.Assert; -import org.testng.annotations.Test; - -import de.learnlib.ralib.automata.RegisterAutomaton; -import de.learnlib.ralib.data.*; -import de.learnlib.ralib.learning.SymbolicSuffix; -import de.learnlib.ralib.oracles.DataWordOracle; -import de.learnlib.ralib.oracles.SDTLogicOracle; -import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracle; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; -import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; -import de.learnlib.ralib.smt.ConstraintSolver; -import de.learnlib.ralib.theory.SDT; -import de.learnlib.ralib.theory.Theory; -import de.learnlib.ralib.tools.theories.IntegerEqualityTheory; -import de.learnlib.ralib.words.PSymbolInstance; -import net.automatalib.word.Word; - -public class DTTest { - - private DT buildFullTreePrimesOnly(TreeOracle oracle) { - Word prePop = Word.fromSymbols( - new PSymbolInstance(I_POP, new DataValue(T_INT, new BigDecimal(1)))); - Word prePush = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(1)))); - Word prePushPush = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(1))), - new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(2)))); - Word epsilon = Word.epsilon(); - - SymbolicSuffix suffEps = new SymbolicSuffix(epsilon, epsilon); - SymbolicSuffix suffPop = new SymbolicSuffix(epsilon, prePop); - SymbolicSuffix suffPush = new SymbolicSuffix(epsilon, prePush); - - SDT tqrPop = oracle.treeQuery(prePop, suffEps); - SDT tqrEps = oracle.treeQuery(epsilon, suffPop); - SDT tqrPush = oracle.treeQuery(prePush, suffPush); - SDT tqrPushPush = oracle.treeQuery(prePushPush, suffPush); - - DTInnerNode nodeEps = new DTInnerNode(suffEps); - DTInnerNode nodePop = new DTInnerNode(suffPop); - DTInnerNode nodePush = new DTInnerNode(suffPush); - - PathResult rPop = PathResult.computePathResult(oracle, new MappedPrefix(prePop, new Bijection<>()), nodeEps.getSuffixes(), false); - PathResult rEps = PathResult.computePathResult(oracle, new MappedPrefix(epsilon, new Bijection<>()), nodePop.getSuffixes(), false); - PathResult rPush = PathResult.computePathResult(oracle, new MappedPrefix(prePush, new Bijection<>()), nodePush.getSuffixes(), false); - PathResult rPushPush = PathResult.computePathResult(oracle, new MappedPrefix(prePushPush, new Bijection<>()), nodePush.getSuffixes(), false); - PathResult rInnerPop = PathResult.computePathResult(oracle, new MappedPrefix(epsilon, new Bijection<>()), nodeEps.getSuffixes(), false); - PathResult rInnerPush = PathResult.computePathResult(oracle, new MappedPrefix(prePush, new Bijection<>()), nodePop.getSuffixes(), false); - - DTLeaf leafPop = new DTLeaf(new MappedPrefix(prePop, new Bijection<>()), oracle); - DTLeaf leafEps = new DTLeaf(new MappedPrefix(epsilon, new Bijection<>()), oracle); - DTLeaf leafPush = new DTLeaf(new MappedPrefix(prePush, new Bijection<>()), oracle); - DTLeaf leafPushPush = new DTLeaf(new MappedPrefix(prePushPush, new Bijection<>()), oracle); - leafPop.setParent(nodeEps); - leafEps.setParent(nodePop); - leafPush.setParent(nodePush); - leafPushPush.setParent(nodePush); - - DTBranch brPop = new DTBranch(leafPop, rPop); - DTBranch brEps = new DTBranch(leafEps, rEps); - DTBranch brPush = new DTBranch(leafPush, rPush); - DTBranch brPushPush = new DTBranch(leafPushPush, rPushPush); - DTBranch brInnerPush = new DTBranch(nodePush, rInnerPush); - DTBranch brInnerPop = new DTBranch(nodePop, rInnerPop); - - leafPush.getPrimePrefix().addTQR(suffPop, oracle.treeQuery(prePush, suffPop)); - leafPushPush.getPrimePrefix().addTQR(suffPop, oracle.treeQuery(prePushPush, suffPop)); - - nodeEps.addBranch(brPop); - nodeEps.addBranch(brInnerPop); - nodePop.addBranch(brEps); - nodePop.addBranch(brInnerPush); - nodePush.addBranch(brPush); - nodePush.addBranch(brPushPush); - - return new DT(nodeEps, oracle, false, new Constants(), I_PUSH, I_POP); - } - - private DT buildSimpleTree(TreeOracle oracle) { - Word prePop = Word.fromSymbols( - new PSymbolInstance(I_POP, new DataValue(T_INT, new BigDecimal(1)))); - Word epsilon = Word.epsilon(); - - SymbolicSuffix suffEps = new SymbolicSuffix(epsilon, epsilon); - SymbolicSuffix suffPop = new SymbolicSuffix(epsilon, prePop); - - SDT tqrPop = oracle.treeQuery(prePop, suffEps); - SDT tqrEps = oracle.treeQuery(epsilon, suffPop); - - DTInnerNode nodeEps = new DTInnerNode(suffEps); - DTInnerNode nodePop = new DTInnerNode(suffPop); - - PathResult rPop = PathResult.computePathResult(oracle, new MappedPrefix(prePop, new Bijection<>()), nodeEps.getSuffixes(), false); - PathResult rEps = PathResult.computePathResult(oracle, new MappedPrefix(epsilon, new Bijection<>()), nodePop.getSuffixes(), false); - PathResult rInnerPop= PathResult.computePathResult(oracle, new MappedPrefix(epsilon, new Bijection<>()), nodeEps.getSuffixes(), false); - - DTLeaf leafPop = new DTLeaf(new MappedPrefix(prePop, new Bijection<>()), oracle); - DTLeaf leafEps = new DTLeaf(new MappedPrefix(epsilon, new Bijection<>()), oracle); - leafPop.setParent(nodeEps); - leafEps.setParent(nodePop); - - DTBranch brPop = new DTBranch(leafPop, rPop); - DTBranch brEps = new DTBranch(leafEps, rEps); - DTBranch brInnerPop = new DTBranch(nodePop, rInnerPop); - - nodeEps.addBranch(brPop); - nodeEps.addBranch(brInnerPop); - nodePop.addBranch(brEps); - - return new DT(nodeEps, oracle, false, new Constants(), I_PUSH, I_POP); - } - - @Test - public void testSiftDT() { - RegisterAutomaton sul = AUTOMATON; - DataWordOracle dwOracle = new SimulatorOracle(sul); - - final Map teachers = new LinkedHashMap<>(); - teachers.put(T_INT, new IntegerEqualityTheory(T_INT)); - - ConstraintSolver solver = new ConstraintSolver(); - - MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(dwOracle, teachers, new Constants(), solver); - DT dt = buildFullTreePrimesOnly(mto); - - Word prePush1Pop1 = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(1))), - new PSymbolInstance(I_POP, new DataValue(T_INT, new BigDecimal(1)))); - - Word prePush1Pop2 = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(1))), - new PSymbolInstance(I_POP, new DataValue(T_INT, new BigDecimal(2)))); - - Word prePushPushPop = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(1))), - new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(2))), - new PSymbolInstance(I_POP, new DataValue(T_INT, new BigDecimal(2)))); - - Word accessEps = Word.epsilon(); - Word accessPop = Word.fromSymbols( - new PSymbolInstance(I_POP, new DataValue(T_INT,new BigDecimal(1)))); - Word accessPush = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT,new BigDecimal(1)))); - - DTLeaf leafPush1Pop1 = dt.sift(prePush1Pop1, true); - DTLeaf leafPush1Pop2 = dt.sift(prePush1Pop2, true); - DTLeaf leafPushPushPop = dt.sift(prePushPushPop, true); - - Assert.assertEquals(accessEps, leafPush1Pop1.getAccessSequence()); - Assert.assertEquals(accessPop, leafPush1Pop2.getAccessSequence()); - Assert.assertEquals(accessPush, leafPushPushPop.getAccessSequence()); - - // test en passant discovery - dt = buildSimpleTree(mto); - int leavesBeforeDiscovery = dt.getLeaves().size(); - Word prePush = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(1)))); - Word prePushPush = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(1))), - new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(2)))); - DTLeaf newLeaf = dt.sift(prePush, true); - - Assert.assertEquals(dt.getLeaves().size(), leavesBeforeDiscovery + 1); - Assert.assertTrue(newLeaf.getAllPrefixes().contains(prePushPush)); - Assert.assertTrue(dt.getLeaf(accessEps).getAllPrefixes().contains(prePush1Pop1)); - Assert.assertTrue(dt.getLeaf(accessPop).getAllPrefixes().contains(prePush1Pop2)); - } - - @Test - public void testSplitDT() { - Constants consts = new Constants(); - RegisterAutomaton sul = AUTOMATON; - DataWordOracle dwOracle = new SimulatorOracle(sul); - - final Map teachers = new LinkedHashMap<>(); - teachers.put(T_INT, new IntegerEqualityTheory(T_INT)); - - ConstraintSolver solver = new ConstraintSolver(); - - MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(dwOracle, teachers, new Constants(), solver); - SDTLogicOracle slo = new MultiTheorySDTLogicOracle(consts, solver); - - DT dt = new DT(mto, false, consts, I_PUSH, I_POP); - dt.initialize(); - - DTHyp hyp = new DTHyp(consts, dt); - - Word prePush = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(0)))); - Word prePop = Word.fromSymbols( - new PSymbolInstance(I_POP, new DataValue(T_INT, new BigDecimal(0)))); - Word eps = Word.epsilon(); - SymbolicSuffix suffPop = new SymbolicSuffix(eps, prePop); - - DTLeaf leafEps = dt.getLeaf(eps); - leafEps.elevatePrefix(dt, prePush, hyp, slo); - - dt.split(prePush, suffPop, leafEps); - - // assert new leaf added for PUSH(0) - DTLeaf leafPush = dt.getLeaf(prePush); - Assert.assertEquals(prePush, leafPush.getAccessSequence()); - - // assert epsilon and push(0) are both children of inner node pop - Assert.assertEquals(suffPop, leafEps.getParent().getSuffix()); - Assert.assertEquals(suffPop, leafPush.getParent().getSuffix()); - } -} diff --git a/src/test/java/de/learnlib/ralib/dt/RegisterConsistencyTest.java b/src/test/java/de/learnlib/ralib/dt/RegisterConsistencyTest.java deleted file mode 100644 index 5df076f0..00000000 --- a/src/test/java/de/learnlib/ralib/dt/RegisterConsistencyTest.java +++ /dev/null @@ -1,158 +0,0 @@ -package de.learnlib.ralib.dt; - -import java.math.BigDecimal; -import java.util.List; -import java.util.Map; - -import org.testng.Assert; -import org.testng.annotations.Test; - -import de.learnlib.ralib.RaLibTestSuite; -import de.learnlib.ralib.data.*; -import de.learnlib.ralib.data.SymbolicDataValue.Parameter; -import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; -import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.ParameterGenerator; -import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.RegisterGenerator; -import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.SuffixValueGenerator; -import de.learnlib.ralib.learning.SymbolicSuffix; -import de.learnlib.ralib.oracles.Branching; -import de.learnlib.ralib.oracles.TreeOracle; -import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; -import de.learnlib.ralib.theory.SDT; -import de.learnlib.ralib.theory.SDTGuard; -import de.learnlib.ralib.theory.SDTLeaf; -import de.learnlib.ralib.words.InputSymbol; -import de.learnlib.ralib.words.PSymbolInstance; -import de.learnlib.ralib.words.ParameterizedSymbol; -import net.automatalib.word.Word; - -public class RegisterConsistencyTest extends RaLibTestSuite { - - private static final DataType T_INT = new DataType("int"); - - private static final InputSymbol A = new InputSymbol("a", T_INT); - - private static class DummyDT extends DT { - - private static class DummyOracle implements TreeOracle { - - @Override - public SDT treeQuery(Word prefix, SymbolicSuffix suffix) { - return null; - } - - @Override - public Branching getInitialBranching(Word prefix, ParameterizedSymbol ps, - SDT... sdts) { - return null; - } - - @Override - public Branching updateBranching(Word prefix, ParameterizedSymbol ps, Branching current, - SDT... sdts) { - return null; - } - - @Override - public Map, Boolean> instantiate(Word prefix, SymbolicSuffix suffix, - SDT sdt) { - return null; - } - - @Override - public SymbolicSuffixRestrictionBuilder getRestrictionBuilder() { - return null; - } - - } - - private final DTLeaf prefixLeaf; - private final DTLeaf leaf; - - public SymbolicSuffix addedSuffix = null; - - public DummyDT(MappedPrefix word, MappedPrefix prefix) { - super(new DummyOracle(), false, new Constants(), (ParameterizedSymbol[])null); - leaf = new DTLeaf(word, null); - prefixLeaf = new DTLeaf(prefix, null); - } - - @Override - public DTLeaf getLeaf(Word word) { - if (word.equals(leaf.getAccessSequence())) - return leaf; - if (word.equals(prefixLeaf.getAccessSequence())) - return prefixLeaf; - return null; - } - - @Override - public void addSuffix(SymbolicSuffix suffix, DTLeaf leaf) { - addedSuffix = suffix; - } - } - - @Test - public void testSymmetry() { - Word word = Word.fromSymbols( - new PSymbolInstance(A, new DataValue(T_INT, BigDecimal.ZERO)), - new PSymbolInstance(A, new DataValue(T_INT, BigDecimal.ONE)), - new PSymbolInstance(A, new DataValue(T_INT, BigDecimal.ONE))); - Word suffixWord = Word.fromSymbols( - new PSymbolInstance(A, new DataValue(T_INT, BigDecimal.ZERO)), - new PSymbolInstance(A, new DataValue(T_INT, BigDecimal.ONE))); - Word suffixExpected = Word.fromSymbols( - new PSymbolInstance(A, new DataValue(T_INT, BigDecimal.ONE)), - new PSymbolInstance(A, new DataValue(T_INT, BigDecimal.ZERO)), - new PSymbolInstance(A, new DataValue(T_INT, BigDecimal.ONE))); - Word prefix = word.prefix(2); - SymbolicSuffix symSuffixEps = new SymbolicSuffix(Word.epsilon(), Word.epsilon()); - SymbolicSuffix symSuffixWord = new SymbolicSuffix(word, suffixWord); - SymbolicSuffix symSuffixPrefix = new SymbolicSuffix(prefix, word.suffix(1)); - SymbolicSuffix symSuffixExpected = new SymbolicSuffix(prefix, suffixExpected); - - RegisterGenerator rgen = new RegisterGenerator(); - ParameterGenerator pgen = new ParameterGenerator(); - SuffixValueGenerator svgen = new SuffixValueGenerator(); - - Parameter p1 = pgen.next(T_INT); - Parameter p2 = pgen.next(T_INT); - DataValue r1 = new DataValue(T_INT, BigDecimal.ZERO); //rgen.next(T_INT); - DataValue r2 = new DataValue(T_INT, BigDecimal.ONE); // rgen.next(T_INT); - SuffixValue s1 = svgen.next(T_INT); - SuffixValue s2 = svgen.next(T_INT); - - Constants consts = new Constants(); - - SDT sdtEps = SDTLeaf.ACCEPTING; - SDT sdtPrefix = new SDT(Map.of( - new SDTGuard.SDTOrGuard(s1, List.of(new SDTGuard.EqualityGuard(s1, r1), new SDTGuard.EqualityGuard(s1, r2))), SDTLeaf.ACCEPTING, - new SDTGuard.SDTAndGuard(s1, List.of(new SDTGuard.DisequalityGuard(s1, r1), new SDTGuard.DisequalityGuard(s1, r2))), SDTLeaf.REJECTING)); - SDT sdtWord = new SDT(Map.of( - new SDTGuard.EqualityGuard(s1, r1), new SDT(Map.of( - new SDTGuard.EqualityGuard(s2, r2), SDTLeaf.ACCEPTING, - new SDTGuard.DisequalityGuard(s2, r2), SDTLeaf.REJECTING)), - new SDTGuard.DisequalityGuard(s1, r1), new SDT(Map.of( - new SDTGuard.SDTTrueGuard(s2), SDTLeaf.REJECTING)))); - - SDT tqrEps = sdtEps; - SDT tqrPrefix = sdtPrefix; - SDT tqrWord = sdtWord; - - MappedPrefix mpWord = new MappedPrefix(word, Bijection.identity(sdtWord.getDataValues())); - MappedPrefix mpPrefix = new MappedPrefix(prefix, Bijection.identity(sdtPrefix.getDataValues())); - - mpWord.addTQR(new SymbolicSuffix(Word.epsilon(), Word.epsilon()), tqrEps); - mpWord.addTQR(symSuffixWord, tqrWord); - mpPrefix.addTQR(symSuffixEps, tqrEps); - mpPrefix.addTQR(symSuffixPrefix, tqrPrefix); - - DummyDT dt = new DummyDT(mpWord, mpPrefix); - DTLeaf leafWord = dt.getLeaf(word); - - boolean consistent = leafWord.checkRegisterConsistency(dt, consts, null); - Assert.assertFalse(consistent); - Assert.assertEquals(dt.addedSuffix.getActions(), symSuffixExpected.getActions()); - } - -} diff --git a/src/test/java/de/learnlib/ralib/example/list/ArrayListDataWordOracle.java b/src/test/java/de/learnlib/ralib/example/list/ArrayListDataWordOracle.java new file mode 100644 index 00000000..2b7d0a0a --- /dev/null +++ b/src/test/java/de/learnlib/ralib/example/list/ArrayListDataWordOracle.java @@ -0,0 +1,59 @@ +package de.learnlib.ralib.example.list; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.function.Supplier; + +import de.learnlib.query.Query; +import de.learnlib.ralib.data.DataType; +import de.learnlib.ralib.oracles.DataWordOracle; +import de.learnlib.ralib.words.InputSymbol; +import de.learnlib.ralib.words.PSymbolInstance; +import de.learnlib.ralib.words.ParameterizedSymbol; +import net.automatalib.word.Word; + +public class ArrayListDataWordOracle implements DataWordOracle { + public static final DataType TYPE = new DataType("int"); + + public static final InputSymbol ADD = new InputSymbol("add", TYPE); + public static final InputSymbol REMOVE = new InputSymbol("remove", TYPE); + + private final Supplier factory; + + public ArrayListDataWordOracle(Supplier factory) { + this.factory = factory; + } + + @Override + public void processQueries(Collection> queries) { + for (Query query : queries) { + query.answer(answer(query.getInput())); + } + } + + private boolean answer(Word w) { + ArrayListWrapper list = factory.get(); + for (PSymbolInstance psi : w) { + try { + if (!accepts(psi, list)) { + return false; + } + } catch(Exception ignore) { + return false; + } + } + return true; + } + + private boolean accepts(PSymbolInstance psi, ArrayListWrapper list) { + ParameterizedSymbol ps = psi.getBaseSymbol(); + BigDecimal val = psi.getParameterValues()[0].getValue(); + if (ps.equals(ADD)) { + return list.add(val); + } + if (ps.equals(REMOVE)) { + return list.remove(val); + } + return false; + } +} diff --git a/src/test/java/de/learnlib/ralib/example/list/ArrayListIODataWordOracle.java b/src/test/java/de/learnlib/ralib/example/list/ArrayListIODataWordOracle.java new file mode 100644 index 00000000..3871b2ed --- /dev/null +++ b/src/test/java/de/learnlib/ralib/example/list/ArrayListIODataWordOracle.java @@ -0,0 +1,88 @@ +package de.learnlib.ralib.example.list; + +import static de.learnlib.ralib.example.list.ArrayListDataWordOracle.ADD; +import static de.learnlib.ralib.example.list.ArrayListDataWordOracle.REMOVE; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Optional; +import java.util.function.Supplier; + +import de.learnlib.query.Query; +import de.learnlib.ralib.oracles.DataWordOracle; +import de.learnlib.ralib.words.InputSymbol; +import de.learnlib.ralib.words.OutputSymbol; +import de.learnlib.ralib.words.PSymbolInstance; +import de.learnlib.ralib.words.ParameterizedSymbol; +import net.automatalib.word.Word; + +public class ArrayListIODataWordOracle implements DataWordOracle { + + public static final OutputSymbol VOID = new OutputSymbol("void"); + public static final OutputSymbol TRUE = new OutputSymbol("true"); + public static final OutputSymbol FALSE = new OutputSymbol("false"); + + private final Supplier factory; + + public ArrayListIODataWordOracle(Supplier factory) { + this.factory = factory; + } + + @Override + public void processQueries(Collection> queries) { + for (Query query : queries) { + query.answer(answer(query.getInput())); + } + } + + private boolean answer(Word query) { + if (query.size() < 1) { + return true; + } + if (!isValid(query)) { + return false; + } + + ArrayListWrapper list = factory.get(); + + Optional expectedOutput = Optional.empty(); + for (PSymbolInstance psi : query) { + if (expectedOutput.isEmpty()) { + expectedOutput = Optional.of(answer(psi, list)); + } else { + if (!psi.getBaseSymbol().equals(expectedOutput.get())) { + return false; + } + expectedOutput = Optional.empty(); + } + } + return true; + } + + private OutputSymbol answer(PSymbolInstance in, ArrayListWrapper list) { + ParameterizedSymbol symbol = in.getBaseSymbol(); + if (!(symbol instanceof InputSymbol || symbol.equals(ADD) || symbol.equals(REMOVE))) { + throw new IllegalArgumentException("Not a valid input symbol: " + symbol); + } + + BigDecimal val = in.getParameterValues()[0].getValue(); + + if (symbol.equals(ADD)) { + list.add(val); + return VOID; + } + return list.remove(val) ? TRUE : FALSE; + } + + private boolean isValid(Word query) { + boolean inExpected = true; + for (PSymbolInstance psi : query) { + ParameterizedSymbol symbol = psi.getBaseSymbol(); + if (inExpected ^ (symbol instanceof InputSymbol)) { + return false; + } + inExpected = !inExpected; + } + return true; + } +} diff --git a/src/test/java/de/learnlib/ralib/example/list/ArrayListWrapper.java b/src/test/java/de/learnlib/ralib/example/list/ArrayListWrapper.java new file mode 100644 index 00000000..1a96501e --- /dev/null +++ b/src/test/java/de/learnlib/ralib/example/list/ArrayListWrapper.java @@ -0,0 +1,36 @@ +package de.learnlib.ralib.example.list; + +import java.math.BigDecimal; +import java.util.ArrayList; + +public class ArrayListWrapper { + public static final int DEFAULT_MAX_CAPACITY = 3; + + private final ArrayList list; + private final int capacity; + + public ArrayListWrapper(int capacity) { + this.capacity = capacity; + this.list = new ArrayList<>(); + } + + public ArrayListWrapper() { + this(DEFAULT_MAX_CAPACITY); + } + + public boolean add(BigDecimal e) { + if (list.size() < capacity) { + list.add(e); + return true; + } + return false; + } + + public boolean remove(BigDecimal e) { + if (list.contains(e)) { + list.remove(e); + return true; + } + return false; + } +} diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/GeneratedHypothesesTest.java b/src/test/java/de/learnlib/ralib/learning/ralambda/GeneratedHypothesesTest.java deleted file mode 100644 index 678267cf..00000000 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/GeneratedHypothesesTest.java +++ /dev/null @@ -1,69 +0,0 @@ -package de.learnlib.ralib.learning.ralambda; - -import static de.learnlib.ralib.example.stack.StackAutomatonExample.AUTOMATON; -import static de.learnlib.ralib.example.stack.StackAutomatonExample.I_POP; -import static de.learnlib.ralib.example.stack.StackAutomatonExample.I_PUSH; -import static de.learnlib.ralib.example.stack.StackAutomatonExample.T_INT; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.testng.Assert; -import org.testng.annotations.Test; - -import de.learnlib.ralib.RaLibTestSuite; -import de.learnlib.ralib.automata.RegisterAutomaton; -import de.learnlib.ralib.data.Constants; -import de.learnlib.ralib.data.DataType; -import de.learnlib.ralib.dt.DTHyp; -import de.learnlib.ralib.learning.Hypothesis; -import de.learnlib.ralib.learning.Measurements; -import de.learnlib.ralib.learning.MeasuringOracle; -import de.learnlib.ralib.oracles.DataWordOracle; -import de.learnlib.ralib.oracles.SDTLogicOracle; -import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; -import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; -import de.learnlib.ralib.smt.ConstraintSolver; -import de.learnlib.ralib.theory.Theory; -import de.learnlib.ralib.tools.theories.IntegerEqualityTheory; - -public class GeneratedHypothesesTest extends RaLibTestSuite { - - /** - * Tests that {@link RaLambda#getHypothesis()} returns a generic {@link Hypothesis} - * (and not a specialized Hypothesis, such as a {@link DTHyp}, which is a lot slower) - */ - @Test - public void testGetHypothesis() { - Constants consts = new Constants(); - RegisterAutomaton sul = AUTOMATON; - DataWordOracle dwOracle = new SimulatorOracle(sul); - ConstraintSolver solver = new ConstraintSolver(); - - final Map teachers = new LinkedHashMap<>(); - IntegerEqualityTheory theory = new IntegerEqualityTheory(T_INT); - theory.setUseSuffixOpt(false); - teachers.put(T_INT, theory); - - Measurements mes = new Measurements(); - - MeasuringOracle mto = new MeasuringOracle(new MultiTheoryTreeOracle( - dwOracle, teachers, new Constants(), solver), mes); - - SDTLogicOracle slo = new MultiTheorySDTLogicOracle(consts, solver); - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, - new Constants(), solver); - - RaLambda ralambda = new RaLambda(mto, hypFactory, slo, consts, false, false, I_PUSH, I_POP); - ralambda.setSolver(solver); - - ralambda.learn(); - RegisterAutomaton hyp = ralambda.getHypothesis(); - - Assert.assertEquals(hyp.getClass(), Hypothesis.class); - } -} diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/IOHandlingTest.java b/src/test/java/de/learnlib/ralib/learning/ralambda/IOHandlingTest.java index c72f2008..a7728e41 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/IOHandlingTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/IOHandlingTest.java @@ -33,12 +33,9 @@ import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.ParameterGenerator; import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.RegisterGenerator; import de.learnlib.ralib.equivalence.IOEquivalenceTest; -import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; import de.learnlib.ralib.oracles.io.IOCache; import de.learnlib.ralib.oracles.io.IOFilter; import de.learnlib.ralib.oracles.io.IOOracle; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.sul.DataWordSUL; @@ -82,7 +79,6 @@ private static RegisterAutomaton buildAutomatonWithNoOutputParams() { RALocation l1 = ra.addState(true); RALocation l2 = ra.addState(true); RALocation l3 = ra.addState(true); -// RALocation ls = ra.addState(false); // registers and parameters RegisterGenerator rgen = new RegisterGenerator(); @@ -110,30 +106,17 @@ private static RegisterAutomaton buildAutomatonWithNoOutputParams() { // initial location ra.addTransition(l0, IN, new InputTransition(trueGuard, IN, l0, l1, storeAssign)); -// ra.addTransition(l0, OK, new OutputTransition(outMapping, OK, l0, ls, noAssign)); -// ra.addTransition(l0, NOK, new OutputTransition(outMapping, NOK, l0, ls, noAssign)); // IN 0 location -// ra.addTransition(l1, IN, new InputTransition(trueGuard, IN, l1, ls, noAssign)); ra.addTransition(l1, OK, new OutputTransition(outMapping, OK, l1, l2, copyAssign)); -// ra.addTransition(l1, NOK, new OutputTransition(outMapping, NOK, l1, ls, noAssign)); // IN 0 OK location ra.addTransition(l2, IN, new InputTransition(okGuard, IN, l2, l1, copyAssign)); ra.addTransition(l2, IN, new InputTransition(nokGuard, IN, l2, l3, copyAssign)); -// ra.addTransition(l2, OK, new OutputTransition(outMapping, OK, l2, ls, noAssign)); -// ra.addTransition(l2, NOK, new OutputTransition(outMapping, NOK, l2, ls, noAssign)); // IN 0 OK in 1 location -// ra.addTransition(l3, IN, new InputTransition(trueGuard, IN, l3, ls, noAssign)); -// ra.addTransition(l3, OK, new OutputTransition(outMapping, OK, l3, ls, noAssign)); ra.addTransition(l3, NOK, new OutputTransition(outMapping, NOK, l3, l2, copyAssign)); - // sink location -// ra.addTransition(ls, IN, new InputTransition(trueGuard, IN, ls, ls, noAssign)); -// ra.addTransition(ls, OK, new OutputTransition(outMapping, OK, ls, ls, noAssign)); -// ra.addTransition(ls, NOK, new OutputTransition(outMapping, NOK, ls, ls, copyAssign)); - return ra; } @@ -185,8 +168,6 @@ private static RegisterAutomaton buildAutomatonOutputParam(boolean fresh) { ra.addTransition(l2, IN, new InputTransition(nokGuard, IN, l2, l3, copyAssign)); // IN 0 OUT fresh/0 in 1 location -// ra.addTransition(l3, IN, new InputTransition(trueGuard, IN, l3, ls, noAssign)); -// ra.addTransition(l3, OK, new OutputTransition(outMapping, OK, l3, ls, noAssign)); ra.addTransition(l3, NOK, new OutputTransition(nokOutputMapping, NOK, l3, l2, copyAssign)); return ra; @@ -214,25 +195,21 @@ public void testLearnIORAWithNoOutputParams() { ConstraintSolver solver = new ConstraintSolver(); MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(ioFilter, teachers, consts, solver); - MultiTheorySDTLogicOracle mlo = new MultiTheorySDTLogicOracle(consts, solver); - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> new MultiTheoryTreeOracle(new SimulatorOracle(hyp), - teachers, consts, solver); - RaLambda ralambda = new RaLambda(mto, hypFactory, mlo, consts, true, IN, OK, NOK); + SLLambda sllambda = new SLLambda(mto, teachers, consts, true, solver, IN, OK, NOK); - ralambda.learn(); + sllambda.learn(); - RegisterAutomaton hyp = ralambda.getHypothesis(); + RegisterAutomaton hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP1: {0}", hyp); Word ce = Word.fromSymbols(new PSymbolInstance(IN, new DataValue(ID, BigDecimal.ZERO)), new PSymbolInstance(OK), new PSymbolInstance(IN, new DataValue(ID, BigDecimal.ONE)), new PSymbolInstance(NOK)); - ralambda.addCounterexample(new DefaultQuery<>(ce, model.accepts(ce))); + sllambda.addCounterexample(new DefaultQuery<>(ce, model.accepts(ce))); - ralambda.learn(); - hyp = ralambda.getHypothesis(); + sllambda.learn(); + hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP2: {0}", hyp); Assert.assertTrue(hyp.accepts(ce)); IOEquivalenceTest test = new IOEquivalenceTest(model, teachers, consts, true, IN, OK, NOK); @@ -261,29 +238,22 @@ public void testLearnIORAWithEqualOutputParam() { ConstraintSolver solver = new ConstraintSolver(); MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(ioFilter, teachers, consts, solver); - MultiTheorySDTLogicOracle mlo = new MultiTheorySDTLogicOracle(consts, solver); - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> new MultiTheoryTreeOracle(new SimulatorOracle(hyp), - teachers, consts, solver); + SLLambda sllambda = new SLLambda(mto, teachers, consts, true, solver, IN, NOK, OUT); - RaLambda ralambda = new RaLambda(mto, hypFactory, mlo, consts, true, IN, NOK, OUT); + sllambda.learn(); - ralambda.learn(); - - RegisterAutomaton hyp = ralambda.getHypothesis(); + RegisterAutomaton hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP1: {0}", hyp); -// Word ce = Word.fromSymbols(new PSymbolInstance(IN, new DataValue(ID, 0)), -// new PSymbolInstance(OUT, new DataValue(ID, 0)), new PSymbolInstance(IN, new DataValue(ID, 1)), -// new PSymbolInstance(NOK)); Word ce = Word.fromSymbols(new PSymbolInstance(IN, new DataValue(ID, BigDecimal.ZERO)), new PSymbolInstance(OUT, new DataValue(ID, BigDecimal.ZERO)), new PSymbolInstance(IN, new DataValue(ID, BigDecimal.ONE)), new PSymbolInstance(NOK)); - ralambda.addCounterexample(new DefaultQuery<>(ce, model.accepts(ce))); + sllambda.addCounterexample(new DefaultQuery<>(ce, model.accepts(ce))); - ralambda.learn(); - hyp = ralambda.getHypothesis(); + sllambda.learn(); + hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP2: {0}", hyp); Assert.assertTrue(hyp.accepts(ce)); IOEquivalenceTest test = new IOEquivalenceTest(model, teachers, consts, true, IN, NOK, OUT); @@ -312,26 +282,22 @@ public void testLearnIORAWithFreshOutputParam() { ConstraintSolver solver = new ConstraintSolver(); MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(ioFilter, teachers, consts, solver); - MultiTheorySDTLogicOracle mlo = new MultiTheorySDTLogicOracle(consts, solver); - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> new MultiTheoryTreeOracle(new SimulatorOracle(hyp), - teachers, consts, solver); - RaLambda ralambda = new RaLambda(mto, hypFactory, mlo, consts, true, IN, NOK, OUT); + SLLambda sllambda = new SLLambda(mto, teachers, consts, true, solver, IN, NOK, OUT); - ralambda.learn(); + sllambda.learn(); - RegisterAutomaton hyp = ralambda.getHypothesis(); + RegisterAutomaton hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP1: {0}", hyp); Word ce = Word.fromSymbols(new PSymbolInstance(IN, new DataValue(ID, BigDecimal.ZERO)), new PSymbolInstance(OUT, new DataValue(ID, BigDecimal.ONE)), new PSymbolInstance(IN, new DataValue(ID, BigDecimal.ONE)), new PSymbolInstance(NOK)); - ralambda.addCounterexample(new DefaultQuery<>(ce, model.accepts(ce))); + sllambda.addCounterexample(new DefaultQuery<>(ce, model.accepts(ce))); - ralambda.learn(); - hyp = ralambda.getHypothesis(); + sllambda.learn(); + hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP2: {0}", hyp); Assert.assertTrue(hyp.accepts(ce)); IOEquivalenceTest test = new IOEquivalenceTest(model, teachers, consts, true, IN, NOK, OUT); diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnABPOutputTest.java b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnABPOutputTest.java index 85489467..c70c2ba6 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnABPOutputTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnABPOutputTest.java @@ -21,12 +21,9 @@ import de.learnlib.ralib.equivalence.IOEquivalenceTest; import de.learnlib.ralib.equivalence.IORandomWalk; import de.learnlib.ralib.learning.Hypothesis; -import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; import de.learnlib.ralib.oracles.io.IOCache; import de.learnlib.ralib.oracles.io.IOFilter; import de.learnlib.ralib.oracles.io.IOOracle; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.sul.DataWordSUL; @@ -80,14 +77,8 @@ public void testLearnABPOutput() { MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( ioFilter, teachers, consts, solver); - MultiTheorySDTLogicOracle mlo = - new MultiTheorySDTLogicOracle(consts, solver); - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, consts, solver); - - RaLambda ralambda = new RaLambda(mto, hypFactory, mlo, consts, true, actions); - ralambda.setSolver(solver); + SLLambda sllambda = new SLLambda(mto, teachers, consts, true, solver, actions); IOEquivalenceTest ioEquiv = new IOEquivalenceTest( model, teachers, consts, true, actions); @@ -112,8 +103,8 @@ public void testLearnABPOutput() { inputs); for (int check = 0; check < 100; ++check) { - ralambda.learn(); - Hypothesis hyp = ralambda.getHypothesis(); + sllambda.learn(); + Hypothesis hyp = sllambda.getHypothesis(); ce = null; @@ -143,10 +134,10 @@ public void testLearnABPOutput() { Assert.assertTrue(model.accepts(ce.getInput())); Assert.assertFalse(hyp.accepts(ce.getInput())); - ralambda.addCounterexample(ce); + sllambda.addCounterexample(ce); } - RegisterAutomaton hyp = ralambda.getHypothesis(); + RegisterAutomaton hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "FINAL HYP: {0}", hyp); ce = ioEquiv.findCounterExample(hyp, null); diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnArrayListTest.java b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnArrayListTest.java new file mode 100644 index 00000000..59591859 --- /dev/null +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnArrayListTest.java @@ -0,0 +1,140 @@ +package de.learnlib.ralib.learning.ralambda; + +import static de.learnlib.ralib.example.list.ArrayListDataWordOracle.ADD; +import static de.learnlib.ralib.example.list.ArrayListDataWordOracle.REMOVE; +import static de.learnlib.ralib.example.list.ArrayListDataWordOracle.TYPE; +import static de.learnlib.ralib.example.list.ArrayListIODataWordOracle.FALSE; +import static de.learnlib.ralib.example.list.ArrayListIODataWordOracle.TRUE; +import static de.learnlib.ralib.example.list.ArrayListIODataWordOracle.VOID; + +import java.math.BigDecimal; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import de.learnlib.query.DefaultQuery; +import de.learnlib.ralib.data.Constants; +import de.learnlib.ralib.data.DataType; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.example.list.ArrayListDataWordOracle; +import de.learnlib.ralib.example.list.ArrayListIODataWordOracle; +import de.learnlib.ralib.example.list.ArrayListWrapper; +import de.learnlib.ralib.learning.Hypothesis; +import de.learnlib.ralib.oracles.DataWordOracle; +import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; +import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.theory.Theory; +import de.learnlib.ralib.tools.theories.DoubleInequalityTheory; +import de.learnlib.ralib.words.PSymbolInstance; +import net.automatalib.word.Word; + +public class LearnArrayListTest { + + @Test + public void testLearnArrayList() { + DataWordOracle oracle = new ArrayListDataWordOracle(() -> new ArrayListWrapper()); + + final Map teachers = new LinkedHashMap<>(); + teachers.put(TYPE, new DoubleInequalityTheory(TYPE)); + + ConstraintSolver solver = new ConstraintSolver(); + Constants consts = new Constants(); + MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(oracle, teachers, consts, solver); + + SLLambda learner = new SLLambda(mto, teachers, consts, false, solver, ADD, REMOVE); + learner.learn(); + + // round 1: find all locations + Word ce = Word.fromSymbols( + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.ONE)), + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.valueOf(2))), + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.valueOf(3))), + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.valueOf(4)))); + learner.addCounterexample(new DefaultQuery<>(ce, false)); + learner.learn(); + + // round 2: find remove after three adds + ce = Word.fromSymbols( + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.ONE)), + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.valueOf(2))), + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.valueOf(3))), + new PSymbolInstance(REMOVE, new DataValue(TYPE, BigDecimal.valueOf(3)))); + learner.addCounterexample(new DefaultQuery<>(ce, true)); + learner.learn(); + + // possible round 3: ordering of data values + ce = Word.fromSymbols( + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.valueOf(3))), + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.valueOf(2))), + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.ONE)), + new PSymbolInstance(REMOVE, new DataValue(TYPE, BigDecimal.valueOf(2)))); + Hypothesis hyp = learner.getHypothesis(); + if (!hyp.accepts(ce)) { + learner.addCounterexample(new DefaultQuery<>(ce, true)); + learner.learn(); + hyp = learner.getHypothesis(); + } + + Assert.assertTrue(hyp.accepts(ce)); + + Assert.assertEquals(hyp.getStates().size(), 5); + } + + @Test + public void testArrayListNonDeterminacy() { + DataWordOracle oracle = new ArrayListIODataWordOracle(() -> new ArrayListWrapper(4)); + + final Map teachers = new LinkedHashMap<>(); + teachers.put(TYPE, new DoubleInequalityTheory(TYPE)); + ConstraintSolver solver = new ConstraintSolver(); + Constants consts = new Constants(); + MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(oracle, teachers, consts, solver); + + SLLambda learner = new SLLambda(mto, teachers, consts, true, solver, ADD, REMOVE, VOID, TRUE, FALSE); + learner.learn(); + + Word ce = Word.fromSymbols( + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.ONE)), + new PSymbolInstance(VOID), + new PSymbolInstance(REMOVE, new DataValue(TYPE, BigDecimal.ONE)), + new PSymbolInstance(TRUE)); + learner.addCounterexample(new DefaultQuery<>(ce, true)); + learner.learn(); + + ce = Word.fromSymbols( + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.ONE)), + new PSymbolInstance(VOID), + new PSymbolInstance(REMOVE, new DataValue(TYPE, BigDecimal.valueOf(-1))), + new PSymbolInstance(FALSE), + new PSymbolInstance(REMOVE, new DataValue(TYPE, BigDecimal.ONE)), + new PSymbolInstance(TRUE)); + learner.addCounterexample(new DefaultQuery<>(ce, true)); + learner.learn(); + + ce = Word.fromSymbols( + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.ONE)), + new PSymbolInstance(VOID), + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.valueOf(2))), + new PSymbolInstance(VOID), + new PSymbolInstance(REMOVE, new DataValue(TYPE, BigDecimal.valueOf(2))), + new PSymbolInstance(TRUE), + new PSymbolInstance(REMOVE, new DataValue(TYPE, BigDecimal.ONE)), + new PSymbolInstance(TRUE)); + learner.addCounterexample(new DefaultQuery<>(ce, true)); + learner.learn(); + + ce = Word.fromSymbols( + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.ONE)), + new PSymbolInstance(VOID), + new PSymbolInstance(ADD, new DataValue(TYPE, BigDecimal.ZERO)), + new PSymbolInstance(VOID), + new PSymbolInstance(REMOVE, new DataValue(TYPE, BigDecimal.ZERO)), + new PSymbolInstance(TRUE)); + learner.addCounterexample(new DefaultQuery<>(ce, true)); + learner.learn(); + + Assert.assertTrue(learner.getHypothesis().accepts(ce)); + } +} diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnEchoTest.java b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnEchoTest.java index 506c2c30..c7135eed 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnEchoTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnEchoTest.java @@ -14,18 +14,14 @@ import de.learnlib.query.DefaultQuery; import de.learnlib.ralib.RaLibTestSuite; -import de.learnlib.ralib.automata.RegisterAutomaton; import de.learnlib.ralib.data.Constants; import de.learnlib.ralib.data.DataType; import de.learnlib.ralib.data.DataValue; import de.learnlib.ralib.example.repeater.RepeaterSUL; import de.learnlib.ralib.learning.Hypothesis; -import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; import de.learnlib.ralib.oracles.io.IOCache; import de.learnlib.ralib.oracles.io.IOFilter; import de.learnlib.ralib.oracles.io.IOOracle; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.sul.SULOracle; @@ -54,12 +50,8 @@ public void testLearnEcho() { ConstraintSolver solver = new ConstraintSolver(); MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(oracle, teachers, consts, solver); - MultiTheorySDTLogicOracle mlo = new MultiTheorySDTLogicOracle(consts, solver); - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, consts, solver); - - RaLambda learner = new RaLambda(mto, hypFactory, mlo, consts, true, sul.getActionSymbols()); + SLLambda learner = new SLLambda(mto, teachers, consts, true, solver, sul.getActionSymbols()); learner.learn(); Word ce = Word.fromSymbols( diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnLoginTest.java b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnLoginTest.java index f80a04f7..cc5da395 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnLoginTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnLoginTest.java @@ -27,10 +27,7 @@ import de.learnlib.ralib.learning.Measurements; import de.learnlib.ralib.learning.RaLearningAlgorithmName; import de.learnlib.ralib.oracles.DataWordOracle; -import de.learnlib.ralib.oracles.SDTLogicOracle; import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.theory.Theory; @@ -55,18 +52,12 @@ public void testLearnLogin() { MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( dwOracle, teachers, new Constants(), solver); - SDTLogicOracle slo = new MultiTheorySDTLogicOracle(consts, solver); - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, - new Constants(), solver); + SLLambda sllambda = new SLLambda(mto, teachers, + consts, false, solver, I_LOGIN, I_LOGOUT, I_REGISTER); - RaLambda ralambda = new RaLambda(mto, hypFactory, slo, - consts, I_LOGIN, I_LOGOUT, I_REGISTER); - ralambda.setSolver(solver); - - ralambda.learn(); - RegisterAutomaton hyp = ralambda.getHypothesis(); + sllambda.learn(); + RegisterAutomaton hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP1: {0}", hyp); Word ce = Word.fromSymbols( @@ -75,10 +66,10 @@ public void testLearnLogin() { new PSymbolInstance(I_LOGIN, new DataValue(T_UID, BigDecimal.ZERO), new DataValue(T_PWD, BigDecimal.ZERO))); - ralambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); + sllambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); - ralambda.learn(); - hyp = ralambda.getHypothesis(); + sllambda.learn(); + hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP2: {0}", hyp); Assert.assertEquals(hyp.getStates().size(), 3); @@ -117,28 +108,29 @@ public void testLearnLoginRandom() { measuresStar[seed] = runner.getMeasurements(); runner.resetMeasurements(); } + Assert.assertEquals(Arrays.toString(measuresLambda), - "[{TQ: 64, Resets: 2511, Inputs: 0}," + - " {TQ: 64, Resets: 2485, Inputs: 0}," + - " {TQ: 64, Resets: 1958, Inputs: 0}," + - " {TQ: 64, Resets: 1959, Inputs: 0}," + - " {TQ: 64, Resets: 1886, Inputs: 0}," + - " {TQ: 64, Resets: 2750, Inputs: 0}," + - " {TQ: 64, Resets: 2487, Inputs: 0}," + - " {TQ: 64, Resets: 1737, Inputs: 0}," + - " {TQ: 68, Resets: 1758, Inputs: 0}," + - " {TQ: 64, Resets: 1604, Inputs: 0}]"); + "[{TQ: 88, Resets: 1997, Inputs: 0}," + + " {TQ: 86, Resets: 1984, Inputs: 0}," + + " {TQ: 88, Resets: 1417, Inputs: 0}," + + " {TQ: 86, Resets: 1449, Inputs: 0}," + + " {TQ: 88, Resets: 1403, Inputs: 0}," + + " {TQ: 86, Resets: 2120, Inputs: 0}," + + " {TQ: 88, Resets: 1984, Inputs: 0}," + + " {TQ: 86, Resets: 1263, Inputs: 0}," + + " {TQ: 93, Resets: 1243, Inputs: 0}," + + " {TQ: 88, Resets: 1220, Inputs: 0}]"); Assert.assertEquals(Arrays.toString(measuresStar), - "[{TQ: 65, Resets: 2339, Inputs: 0}," + - " {TQ: 65, Resets: 2313, Inputs: 0}," + - " {TQ: 65, Resets: 2208, Inputs: 0}," + - " {TQ: 64, Resets: 2205, Inputs: 0}," + - " {TQ: 65, Resets: 2136, Inputs: 0}," + - " {TQ: 65, Resets: 2578, Inputs: 0}," + - " {TQ: 65, Resets: 2315, Inputs: 0}," + - " {TQ: 65, Resets: 2192, Inputs: 0}," + - " {TQ: 65, Resets: 3623, Inputs: 0}," + - " {TQ: 65, Resets: 2059, Inputs: 0}]"); + "[{TQ: 65, Resets: 1807, Inputs: 0}," + + " {TQ: 65, Resets: 1788, Inputs: 0}," + + " {TQ: 65, Resets: 1693, Inputs: 0}," + + " {TQ: 64, Resets: 1727, Inputs: 0}," + + " {TQ: 65, Resets: 1680, Inputs: 0}," + + " {TQ: 65, Resets: 1929, Inputs: 0}," + + " {TQ: 65, Resets: 1793, Inputs: 0}," + + " {TQ: 65, Resets: 1720, Inputs: 0}," + + " {TQ: 65, Resets: 3103, Inputs: 0}," + + " {TQ: 65, Resets: 1682, Inputs: 0}]"); } } diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnMixedIOTest.java b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnMixedIOTest.java index 6fdfdf10..3f697294 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnMixedIOTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnMixedIOTest.java @@ -39,10 +39,7 @@ import de.learnlib.ralib.equivalence.IOEquivalenceTest; import de.learnlib.ralib.equivalence.IORandomWalk; import de.learnlib.ralib.learning.Hypothesis; -import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; import de.learnlib.ralib.oracles.io.IOOracle; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.sul.DataWordSUL; @@ -101,13 +98,8 @@ public void testLearnMixedIO() { IOOracle ioOracle = new SULOracle(sul, ERROR); MultiTheoryTreeOracle mto = TestUtil.createMTO(ioOracle, teachers, consts, solver, inputs); - MultiTheorySDTLogicOracle mlo = new MultiTheorySDTLogicOracle(consts, solver); - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, consts, solver); - - RaLambda rastar = new RaLambda(mto, hypFactory, mlo, consts, true, actions); - rastar.setSolver(solver); + SLLambda sllambda = new SLLambda(mto, teachers, consts, true, solver, actions); IORandomWalk iowalk = new IORandomWalk(random, sul, @@ -127,8 +119,8 @@ public void testLearnMixedIO() { IOCounterExamplePrefixFinder pref = new IOCounterExamplePrefixFinder(ioOracle); for (int check = 0; check < 100; ++check) { - rastar.learn(); - Hypothesis hyp = rastar.getHypothesis(); + sllambda.learn(); + Hypothesis hyp = sllambda.getHypothesis(); DefaultQuery ce = iowalk.findCounterExample(hyp, null); if (ce == null) { @@ -142,10 +134,10 @@ public void testLearnMixedIO() { Assert.assertTrue(model.accepts(ce.getInput())); Assert.assertFalse(hyp.accepts(ce.getInput())); - rastar.addCounterexample(ce); + sllambda.addCounterexample(ce); } - RegisterAutomaton hyp = rastar.getHypothesis(); + RegisterAutomaton hyp = sllambda.getHypothesis(); IOEquivalenceTest checker = new IOEquivalenceTest( model, teachers, consts, true, actions); diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPQIOTest.java b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPQIOTest.java index 457fc638..7882e895 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPQIOTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPQIOTest.java @@ -40,10 +40,7 @@ import de.learnlib.ralib.equivalence.IORandomWalk; import de.learnlib.ralib.example.priority.PriorityQueueSUL; import de.learnlib.ralib.learning.Hypothesis; -import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; import de.learnlib.ralib.oracles.io.IOOracle; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.sul.SULOracle; @@ -109,13 +106,8 @@ private Hypothesis learnPQ(long seed, Map teachers, Constants MultiTheoryTreeOracle mto = TestUtil.createMTO( ioOracle, teachers, consts, solver, sul.getInputSymbols()); - MultiTheorySDTLogicOracle mlo = new MultiTheorySDTLogicOracle(consts, solver); - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) - -> new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, consts, solver); - - RaLambda rastar = new RaLambda(mto, hypFactory, mlo, - consts, true, sul.getActionSymbols()); + SLLambda sllambda = new SLLambda(mto, teachers, + consts, true, solver, sul.getActionSymbols()); IORandomWalk iowalk = new IORandomWalk(random, sul, @@ -135,11 +127,10 @@ private Hypothesis learnPQ(long seed, Map teachers, Constants IOCounterExamplePrefixFinder pref = new IOCounterExamplePrefixFinder(ioOracle); for (int check = 0; check < 100; ++check) { - rastar.learn(); - Hypothesis hyp = rastar.getHypothesis(); + sllambda.learn(); + Hypothesis hyp = sllambda.getHypothesis(); DefaultQuery ce = iowalk.findCounterExample(hyp, null); - //System.out.println("CE: " + ce); if (ce == null) { break; } @@ -147,9 +138,9 @@ private Hypothesis learnPQ(long seed, Map teachers, Constants ce = loops.optimizeCE(ce.getInput(), hyp); ce = asrep.optimizeCE(ce.getInput(), hyp); ce = pref.optimizeCE(ce.getInput(), hyp); - rastar.addCounterexample(ce); + sllambda.addCounterexample(ce); } - return rastar.getHypothesis(); + return sllambda.getHypothesis(); } } diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPQTest.java b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPQTest.java index 4498795d..3f6df81c 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPQTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPQTest.java @@ -42,10 +42,6 @@ import de.learnlib.ralib.learning.Measurements; import de.learnlib.ralib.learning.RaLearningAlgorithmName; import de.learnlib.ralib.oracles.DataWordOracle; -import de.learnlib.ralib.oracles.SDTLogicOracle; -import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.theory.Theory; @@ -74,14 +70,9 @@ public void testLearnPQ() { MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( dwOracle, teachers, new Constants(), solver); - SDTLogicOracle mlo = new MultiTheorySDTLogicOracle(consts, solver); - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, new Constants(), solver); - - RaLambda rastar = new RaLambda(mto, hypFactory, mlo, consts, OFFER, POLL); - rastar.learn(); - RegisterAutomaton hyp = rastar.getHypothesis(); + SLLambda sllambda = new SLLambda(mto, teachers, consts, false, solver, OFFER, POLL); + sllambda.learn(); + RegisterAutomaton hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP1: {0}", hyp); Word ce = Word.fromSymbols( @@ -96,10 +87,10 @@ public void testLearnPQ() { DefaultQuery ceQuery = new DefaultQuery<>(ce, true); - rastar.addCounterexample(ceQuery); + sllambda.addCounterexample(ceQuery); - rastar.learn(); - hyp = rastar.getHypothesis(); + sllambda.learn(); + hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP2: {0}", hyp); Assert.assertTrue(hyp.accepts(ceQuery.getInput())); @@ -114,9 +105,9 @@ public void testLearnPQ() { new PSymbolInstance(POLL, new DataValue(doubleType, BigDecimal.valueOf(2.0)))); DefaultQuery ce2Query = new DefaultQuery<>(ce2, true); - rastar.addCounterexample(ce2Query); - rastar.learn(); - hyp = rastar.getHypothesis(); + sllambda.addCounterexample(ce2Query); + sllambda.learn(); + hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP3: {0}", hyp); Assert.assertTrue(hyp.accepts(ce2Query.getInput())); @@ -149,7 +140,7 @@ public void testLearnPQRandom() { } // hard-coded results from first seed - Assert.assertEquals(Arrays.toString(ralambdaCount), "[{TQ: 71, Resets: 1946, Inputs: 0}]"); - Assert.assertEquals(Arrays.toString(rastarCount), "[{TQ: 55, Resets: 7033, Inputs: 0}]"); + Assert.assertEquals(Arrays.toString(ralambdaCount), "[{TQ: 88, Resets: 876, Inputs: 0}]"); + Assert.assertEquals(Arrays.toString(rastarCount), "[{TQ: 55, Resets: 5721, Inputs: 0}]"); } } diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPadlock.java b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPadlock.java index 0f3ef3a5..aa5e5568 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPadlock.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPadlock.java @@ -24,10 +24,7 @@ import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.ParameterGenerator; import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.RegisterGenerator; import de.learnlib.ralib.oracles.DataWordOracle; -import de.learnlib.ralib.oracles.SDTLogicOracle; import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.theory.Theory; @@ -111,17 +108,10 @@ public void testLearnPadlock() { MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( dwOracle, teachers, new Constants(), solver); - SDTLogicOracle slo = new MultiTheorySDTLogicOracle(consts, solver); + SLLambda sllambda = new SLLambda(mto, teachers, consts, false, solver, IN); - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, - new Constants(), solver); - - RaLambda ralambda = new RaLambda(mto, hypFactory, slo, consts, false, false, true, IN); - ralambda.setSolver(solver); - - ralambda.learn(); - RegisterAutomaton hyp = ralambda.getHypothesis(); + sllambda.learn(); + RegisterAutomaton hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP0: {0}", hyp); Word ce = Word.fromSymbols( @@ -130,10 +120,10 @@ public void testLearnPadlock() { new PSymbolInstance(IN, new DataValue(DIGIT, BigDecimal.ZERO)), new PSymbolInstance(IN, new DataValue(DIGIT, BigDecimal.ZERO))); - ralambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); + sllambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); - ralambda.learn(); - hyp = ralambda.getHypothesis(); + sllambda.learn(); + hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP1: {0}", hyp); } } diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPalindromeIOTest.java b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPalindromeIOTest.java index c54d593f..6433a492 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPalindromeIOTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPalindromeIOTest.java @@ -16,12 +16,9 @@ import de.learnlib.ralib.data.DataType; import de.learnlib.ralib.equivalence.IOEquivalenceTest; import de.learnlib.ralib.learning.Hypothesis; -import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; import de.learnlib.ralib.oracles.io.IOCache; import de.learnlib.ralib.oracles.io.IOFilter; import de.learnlib.ralib.oracles.io.IOOracle; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.sul.DataWordSUL; @@ -67,20 +64,15 @@ public void testLearnPalindromeIO() { IOFilter ioFilter = new IOFilter(ioCache, inputs); MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(ioFilter, teachers, consts, solver); - MultiTheorySDTLogicOracle mlo = new MultiTheorySDTLogicOracle(consts, solver); - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, consts, solver); - - RaLambda ralambda = new RaLambda(mto, hypFactory, mlo, consts, true, actions); - ralambda.setSolver(solver); + SLLambda sllambda = new SLLambda(mto, teachers, consts, true, solver, actions); IOEquivalenceTest ioEquiv = new IOEquivalenceTest( model, teachers, consts, true, actions); for (int check = 0; check < 10; ++check) { - ralambda.learn(); - Hypothesis hyp = ralambda.getHypothesis(); + sllambda.learn(); + Hypothesis hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP: {0}", hyp); DefaultQuery ce = ioEquiv.findCounterExample(hyp, null); @@ -92,15 +84,14 @@ public void testLearnPalindromeIO() { Assert.assertTrue(model.accepts(ce.getInput())); Assert.assertFalse(hyp.accepts(ce.getInput())); - ralambda.addCounterexample(ce); + sllambda.addCounterexample(ce); } - RegisterAutomaton hyp = ralambda.getHypothesis(); + RegisterAutomaton hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "FINAL HYP: {0}", hyp); DefaultQuery ce = ioEquiv.findCounterExample(hyp, null); Assert.assertNull(ce); -// Assert.assertEquals(hyp.getStates().size(), 5); Assert.assertEquals(hyp.getTransitions().size(), 16); } } diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnRepeaterTest.java b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnRepeaterTest.java index b38840c5..35d43924 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnRepeaterTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnRepeaterTest.java @@ -13,7 +13,6 @@ import de.learnlib.query.DefaultQuery; import de.learnlib.ralib.RaLibTestSuite; -import de.learnlib.ralib.automata.RegisterAutomaton; import de.learnlib.ralib.data.Constants; import de.learnlib.ralib.data.DataType; import de.learnlib.ralib.data.DataValue; @@ -21,12 +20,9 @@ import de.learnlib.ralib.example.repeater.RepeaterSUL; import de.learnlib.ralib.learning.Measurements; import de.learnlib.ralib.learning.QueryStatistics; -import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; import de.learnlib.ralib.oracles.io.IOCache; import de.learnlib.ralib.oracles.io.IOFilter; import de.learnlib.ralib.oracles.io.IOOracle; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.sul.SULOracle; @@ -55,17 +51,12 @@ public void testLearnRepeater() { ConstraintSolver solver = new ConstraintSolver(); MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(oracle, teachers, consts, solver); - MultiTheorySDTLogicOracle mlo = new MultiTheorySDTLogicOracle(consts, solver); - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, consts, solver); Measurements measurements = new Measurements(); QueryStatistics stats = new QueryStatistics(measurements, ioOracle); - RaLambda learner = new RaLambda(mto, hypFactory, mlo, consts, true, sul.getActionSymbols()); + SLLambda learner = new SLLambda(mto, teachers, consts, true, solver, sul.getActionSymbols()); learner.setStatisticCounter(stats); - learner.setSolver(solver); learner.learn(); @@ -87,7 +78,6 @@ public void testLearnRepeater() { learner.learn(); String str = stats.toString(); - // System.out.println(str); Assert.assertTrue(str.contains("Counterexamples: 1")); Assert.assertTrue(str.contains("CE max length: 6")); Assert.assertTrue(str.contains("CE Analysis: {TQ: 0, Resets: 7, Inputs: 0}")); diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnSipIOTest.java b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnSipIOTest.java index 7fab29c3..c0fdd967 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnSipIOTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnSipIOTest.java @@ -15,19 +15,14 @@ import de.learnlib.ralib.automata.xml.RegisterAutomatonImporter; import de.learnlib.ralib.data.Constants; import de.learnlib.ralib.data.DataType; -import de.learnlib.ralib.dt.DTLeaf; import de.learnlib.ralib.equivalence.IOCounterExamplePrefixFinder; import de.learnlib.ralib.equivalence.IOCounterExamplePrefixReplacer; import de.learnlib.ralib.equivalence.IOCounterexampleLoopRemover; import de.learnlib.ralib.equivalence.IOEquivalenceTest; import de.learnlib.ralib.learning.Hypothesis; -import de.learnlib.ralib.learning.SymbolicSuffix; -import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; import de.learnlib.ralib.oracles.io.IOCache; import de.learnlib.ralib.oracles.io.IOFilter; import de.learnlib.ralib.oracles.io.IOOracle; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.sul.DataWordSUL; @@ -38,7 +33,6 @@ import de.learnlib.ralib.tools.theories.IntegerEqualityTheory; import de.learnlib.ralib.words.PSymbolInstance; import de.learnlib.ralib.words.ParameterizedSymbol; -import net.automatalib.word.Word; public class LearnSipIOTest extends RaLibTestSuite { @Test @@ -82,22 +76,8 @@ public void testLearnSipIO() { MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( ioFilter, teachers, consts, solver); - MultiTheorySDTLogicOracle mlo = - new MultiTheorySDTLogicOracle(consts, solver); - - for (ParameterizedSymbol ps : actions) { - if (!DTLeaf.isInput(ps) && ps.getArity() > 0) { -// if (ps.getArity() > 0) { - mto.treeQuery(Word.epsilon(), new SymbolicSuffix(ps)); - break; - } - } - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, consts, solver); - RaLambda ralambda = new RaLambda(mto, hypFactory, mlo, consts, true, actions); - ralambda.setSolver(solver); + SLLambda sllambda = new SLLambda(mto, teachers, consts, true, solver, actions); IOEquivalenceTest ioEquiv = new IOEquivalenceTest( model, teachers, consts, true, actions); @@ -107,8 +87,8 @@ public void testLearnSipIO() { IOCounterExamplePrefixFinder pref = new IOCounterExamplePrefixFinder(ioOracle); for (int check = 0; check < 100; ++check) { - ralambda.learn(); - Hypothesis hyp = ralambda.getHypothesis(); + sllambda.learn(); + Hypothesis hyp = sllambda.getHypothesis(); DefaultQuery ce = ioEquiv.findCounterExample(hyp, null); if (ce == null) { @@ -122,10 +102,10 @@ public void testLearnSipIO() { Assert.assertTrue(model.accepts(ce.getInput())); Assert.assertFalse(hyp.accepts(ce.getInput())); - ralambda.addCounterexample(ce); + sllambda.addCounterexample(ce); } - RegisterAutomaton hyp = ralambda.getHypothesis(); + RegisterAutomaton hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "FINAL HYP: {0}", hyp); DefaultQuery ce = ioEquiv.findCounterExample(hyp, null); diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnStackTest.java b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnStackTest.java index 300c98ce..f0b349f4 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnStackTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnStackTest.java @@ -7,14 +7,9 @@ import java.math.BigDecimal; import java.util.Arrays; -import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Set; import java.util.logging.Level; -import java.util.stream.Collectors; - -import com.google.common.collect.ImmutableSet; import org.testng.Assert; import org.testng.annotations.Test; @@ -30,7 +25,6 @@ import de.learnlib.ralib.learning.Measurements; import de.learnlib.ralib.learning.MeasuringOracle; import de.learnlib.ralib.learning.RaLearningAlgorithmName; -import de.learnlib.ralib.learning.SymbolicSuffix; import de.learnlib.ralib.oracles.DataWordOracle; import de.learnlib.ralib.oracles.SDTLogicOracle; import de.learnlib.ralib.oracles.SimulatorOracle; @@ -49,7 +43,7 @@ public class LearnStackTest extends RaLibTestSuite { @Test public void testLearnStack() { - Constants consts = new Constants(); + Constants consts = new Constants(); RegisterAutomaton sul = AUTOMATON; DataWordOracle dwOracle = new SimulatorOracle(sul); @@ -69,21 +63,20 @@ public void testLearnStack() { new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, new Constants(), solver); - RaLambda ralambda = new RaLambda(mto, hypFactory, slo, consts, false, false, I_PUSH, I_POP); - ralambda.setSolver(solver); + SLLambda sllambda = new SLLambda(mto, teachers, consts, false, solver, I_PUSH, I_POP); - ralambda.learn(); - RegisterAutomaton hyp = ralambda.getHypothesis(); + sllambda.learn(); + RegisterAutomaton hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP0: {0}", hyp); Word ce = Word.fromSymbols( new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ZERO)), new PSymbolInstance(I_POP, new DataValue(T_INT, BigDecimal.ZERO))); - ralambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); + sllambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); - ralambda.learn(); - hyp = ralambda.getHypothesis(); + sllambda.learn(); + hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP1: {0}", hyp); ce = Word.fromSymbols( @@ -91,10 +84,10 @@ public void testLearnStack() { new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ONE)), new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(2)))); - ralambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); + sllambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); - ralambda.learn(); - hyp = ralambda.getHypothesis(); + sllambda.learn(); + hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP2: {0}", hyp); Assert.assertEquals(hyp.getStates().size(), 4); @@ -115,19 +108,12 @@ public void testLearnStackSwitchedCE() { MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( dwOracle, teachers, new Constants(), solver); - SDTLogicOracle slo = new MultiTheorySDTLogicOracle(consts, solver); - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, - new Constants(), solver); - - RaLambda ralambda = new RaLambda(mto, hypFactory, slo, consts, false, false, I_PUSH, I_POP); - ralambda.setSolver(solver); + SLLambda sllambda = new SLLambda(mto, teachers, consts, false, solver, I_PUSH, I_POP); - ralambda.learn(); - RegisterAutomaton hyp = ralambda.getHypothesis(); + sllambda.learn(); + RegisterAutomaton hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP0: {0}", hyp); - hyp = ralambda.getHypothesis(); + hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP1: {0}", hyp); Word ce = Word.fromSymbols( @@ -135,20 +121,20 @@ public void testLearnStackSwitchedCE() { new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ONE)), new PSymbolInstance(I_PUSH, new DataValue(T_INT, new BigDecimal(2)))); - ralambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); + sllambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); - ralambda.learn(); - hyp = ralambda.getHypothesis(); + sllambda.learn(); + hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP2: {0}", hyp); ce = Word.fromSymbols( new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ZERO)), new PSymbolInstance(I_POP, new DataValue(T_INT, BigDecimal.ZERO))); - ralambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); + sllambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); - ralambda.learn(); - hyp = ralambda.getHypothesis(); + sllambda.learn(); + hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP2: {0}", hyp); ce = Word.fromSymbols( @@ -156,22 +142,12 @@ public void testLearnStackSwitchedCE() { new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ONE)), new PSymbolInstance(I_POP, new DataValue(T_INT, BigDecimal.ONE))); - ralambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); - ralambda.learn(); - hyp = ralambda.getHypothesis(); - - Collection suffixes = ralambda.getDT().getSuffixes(); - Set> suffixActions = suffixes.stream().map(s -> s.getActions()).collect(Collectors.toSet()); - Set> expectedSuffixActions = ImmutableSet.of( - Word.fromSymbols(), - Word.fromSymbols(I_PUSH), - Word.fromSymbols(I_PUSH, I_PUSH), - Word.fromSymbols(I_POP), - Word.fromSymbols(I_POP, I_POP)); + sllambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); + sllambda.learn(); + hyp = sllambda.getHypothesis(); Assert.assertEquals(hyp.getStates().size(), 4); Assert.assertEquals(hyp.getTransitions().size(), 10); - Assert.assertEquals(suffixActions, expectedSuffixActions); } @Test @@ -188,19 +164,12 @@ public void testLearnStackLongCE() { MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( dwOracle, teachers, new Constants(), solver); - SDTLogicOracle slo = new MultiTheorySDTLogicOracle(consts, solver); - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, - new Constants(), solver); - - RaLambda ralambda = new RaLambda(mto, hypFactory, slo, consts, false, false, I_PUSH, I_POP); - ralambda.setSolver(solver); + SLLambda sllambda = new SLLambda(mto, teachers, consts, false, solver, I_PUSH, I_POP); - ralambda.learn(); - RegisterAutomaton hyp = ralambda.getHypothesis(); + sllambda.learn(); + RegisterAutomaton hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP0: {0}", hyp); - hyp = ralambda.getHypothesis(); + hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP1: {0}", hyp); Word ce = Word.fromSymbols( @@ -209,10 +178,10 @@ public void testLearnStackLongCE() { new PSymbolInstance(I_POP, new DataValue(T_INT, BigDecimal.ZERO)), new PSymbolInstance(I_POP, new DataValue(T_INT, BigDecimal.ZERO))); - ralambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); + sllambda.addCounterexample(new DefaultQuery<>(ce, sul.accepts(ce))); - ralambda.learn(); - hyp = ralambda.getHypothesis(); + sllambda.learn(); + hyp = sllambda.getHypothesis(); logger.log(Level.FINE, "HYP2: {0}", hyp); Assert.assertTrue(hyp.accepts(ce)); @@ -251,26 +220,26 @@ public void testLearnStackRandom() { } Assert.assertEquals(Arrays.toString(measuresLambda), - "[{TQ: 60, Resets: 1199, Inputs: 0}," + - " {TQ: 65, Resets: 1859, Inputs: 0}," + - " {TQ: 60, Resets: 1178, Inputs: 0}," + - " {TQ: 62, Resets: 1516, Inputs: 0}," + - " {TQ: 70, Resets: 2717, Inputs: 0}," + - " {TQ: 62, Resets: 1443, Inputs: 0}," + - " {TQ: 56, Resets: 1148, Inputs: 0}," + - " {TQ: 41, Resets: 1089, Inputs: 0}," + - " {TQ: 78, Resets: 2478, Inputs: 0}," + - " {TQ: 44, Resets: 1139, Inputs: 0}]"); + "[{TQ: 93, Resets: 435, Inputs: 0}," + + " {TQ: 103, Resets: 737, Inputs: 0}," + + " {TQ: 88, Resets: 441, Inputs: 0}," + + " {TQ: 98, Resets: 574, Inputs: 0}," + + " {TQ: 113, Resets: 936, Inputs: 0}," + + " {TQ: 92, Resets: 597, Inputs: 0}," + + " {TQ: 82, Resets: 446, Inputs: 0}," + + " {TQ: 58, Resets: 418, Inputs: 0}," + + " {TQ: 133, Resets: 694, Inputs: 0}," + + " {TQ: 63, Resets: 470, Inputs: 0}]"); Assert.assertEquals(Arrays.toString(measuresStar), - "[{TQ: 51, Resets: 1589, Inputs: 0}," + - " {TQ: 50, Resets: 12577, Inputs: 0}," + - " {TQ: 63, Resets: 1317, Inputs: 0}," + - " {TQ: 50, Resets: 10669, Inputs: 0}," + - " {TQ: 39, Resets: 11088, Inputs: 0}," + - " {TQ: 62, Resets: 1310, Inputs: 0}," + - " {TQ: 60, Resets: 1298, Inputs: 0}," + - " {TQ: 49, Resets: 1207, Inputs: 0}," + - " {TQ: 53, Resets: 11461, Inputs: 0}," + - " {TQ: 49, Resets: 1301, Inputs: 0}]"); + "[{TQ: 51, Resets: 838, Inputs: 0}," + + " {TQ: 50, Resets: 10681, Inputs: 0}," + + " {TQ: 63, Resets: 599, Inputs: 0}," + + " {TQ: 50, Resets: 9021, Inputs: 0}," + + " {TQ: 39, Resets: 9971, Inputs: 0}," + + " {TQ: 62, Resets: 606, Inputs: 0}," + + " {TQ: 60, Resets: 603, Inputs: 0}," + + " {TQ: 49, Resets: 520, Inputs: 0}," + + " {TQ: 53, Resets: 9344, Inputs: 0}," + + " {TQ: 49, Resets: 620, Inputs: 0}]"); } } diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/TestDistinguishingSuffixOptimization.java b/src/test/java/de/learnlib/ralib/learning/ralambda/TestDistinguishingSuffixOptimization.java index 75aa90a9..e4fc0a65 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/TestDistinguishingSuffixOptimization.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/TestDistinguishingSuffixOptimization.java @@ -5,6 +5,7 @@ import java.util.Set; import org.testng.Assert; +import org.testng.annotations.Ignore; import org.testng.annotations.Test; import de.learnlib.query.DefaultQuery; @@ -20,10 +21,7 @@ import de.learnlib.ralib.data.VarMapping; import de.learnlib.ralib.learning.SymbolicSuffix; import de.learnlib.ralib.oracles.DataWordOracle; -import de.learnlib.ralib.oracles.SDTLogicOracle; import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.theory.Theory; @@ -33,6 +31,7 @@ import gov.nasa.jpf.constraints.util.ExpressionUtil; import net.automatalib.word.Word; +@Ignore public class TestDistinguishingSuffixOptimization { private static final InputSymbol A = new InputSymbol("a"); @@ -83,14 +82,7 @@ public void testDistinguishingSuffixOptimization() { MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(dwOracle, teachers, new Constants(), solver); - SDTLogicOracle slo = new MultiTheorySDTLogicOracle(consts, solver); - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, - new Constants(), solver); - - RaLambda learner = new RaLambda(mto, hypFactory, slo, consts, false, false, A, B); - learner.setSolver(solver); + SLLambda learner = new SLLambda(mto, teachers, consts, false, solver, A, B); learner.learn(); @@ -122,7 +114,7 @@ public void testDistinguishingSuffixOptimization() { new PSymbolInstance(A))); Word b = Word.fromSymbols(new PSymbolInstance(B)); - Set suffixes = learner.getDT().getLeaf(b).getPrefix(b).getTQRs().keySet(); + Set suffixes = learner.getCT().getLeaf(b).getPrefix(b).getPath().getSDTs().keySet(); //.getTQRs().keySet(); Assert.assertFalse(suffixes.contains(suffixUnoptimized)); Assert.assertTrue(suffixes.contains(suffixOptimized)); diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/TestOutputSuffixes.java b/src/test/java/de/learnlib/ralib/learning/ralambda/TestOutputSuffixes.java index cc2e937d..9ec6fd49 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/TestOutputSuffixes.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/TestOutputSuffixes.java @@ -77,8 +77,8 @@ public void testSIPOutputSuffixesPresent() { TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, consts, solver); - RaDT radt = new RaDT(mto, hypFactory, mlo, consts, true, actions); - radt.learn(); + SLCT slct = new SLCT(mto, hypFactory, mlo, consts, true, solver, actions); + slct.learn(); String[] ces = {"IINVITE[1[int]] O100[1[int]] / true", "IINVITE[0[int]] O100[0[int]] IPRACK[0[int]] O200[0[int]] / true"}; @@ -86,8 +86,8 @@ public void testSIPOutputSuffixesPresent() { Deque> ceQueue = TestUnknownMemorable.buildSIPCEs(ces, actions); while (!ceQueue.isEmpty()) { - radt.addCounterexample(ceQueue.pop()); - radt.learn(); + slct.addCounterexample(ceQueue.pop()); + slct.learn(); } Set outputs = Sets.difference(Set.of(actions), Set.of(inputs)); @@ -96,7 +96,7 @@ public void testSIPOutputSuffixesPresent() { ceQueue = TestUnknownMemorable.buildSIPCEs(wordStr, actions); Word word = ceQueue.getFirst().getInput(); - Set suffixes = radt.getDT().getLeaf(word).getPrefix(word).getTQRs().keySet(); + Set suffixes = slct.getCT().getLeaf(word).getPrefix(word).getPath().getSDTs().keySet(); Set suffixActions = suffixes.stream() .filter(s -> s.length() == 1) .map(s -> s.getActions().firstSymbol()) diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/TestQueryCount.java b/src/test/java/de/learnlib/ralib/learning/ralambda/TestQueryCount.java index d6a79093..18e886aa 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/TestQueryCount.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/TestQueryCount.java @@ -10,7 +10,6 @@ import de.learnlib.query.DefaultQuery; import de.learnlib.ralib.RaLibTestSuite; import de.learnlib.ralib.TestUtil; -import de.learnlib.ralib.automata.RegisterAutomaton; import de.learnlib.ralib.data.Constants; import de.learnlib.ralib.data.DataType; import de.learnlib.ralib.data.DataValue; @@ -18,11 +17,7 @@ import de.learnlib.ralib.learning.Measurements; import de.learnlib.ralib.learning.MeasuringOracle; import de.learnlib.ralib.learning.QueryStatistics; -import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; import de.learnlib.ralib.oracles.io.IOOracle; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; -import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.sul.SULOracle; import de.learnlib.ralib.theory.Theory; @@ -51,14 +46,8 @@ public void testQueryCount() { ioOracle, teachers, consts, solver, sul.getInputSymbols()), measurements); - MultiTheorySDTLogicOracle mlo = new MultiTheorySDTLogicOracle(consts, solver); - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) - -> new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, consts, solver); - QueryStatistics queryStats = new QueryStatistics(measurements, ioOracle); - RaLambda learner = new RaLambda(mto, hypFactory, mlo, - consts, true, sul.getActionSymbols()); + SLLambda learner = new SLLambda(mto, teachers, consts, true, solver, sul.getActionSymbols()); learner.setStatisticCounter(queryStats); learner.learn(); @@ -77,7 +66,7 @@ public void testQueryCount() { learner.learn(); long memQueries1 = learner.getQueryStatistics().getMemQueries(); - Assert.assertEquals(memQueries1, 2); + Assert.assertEquals(memQueries1, 22); Word ce2 = Word.fromSymbols( new PSymbolInstance(PriorityQueueSUL.OFFER, new DataValue(PriorityQueueSUL.DOUBLE_TYPE, BigDecimal.ONE)), @@ -95,7 +84,7 @@ public void testQueryCount() { learner.learn(); long memQueries2 = learner.getQueryStatistics().getMemQueries(); - Assert.assertEquals(memQueries2, 27); + Assert.assertEquals(memQueries2, 35); Word ce3 = Word.fromSymbols( new PSymbolInstance(PriorityQueueSUL.OFFER, new DataValue(PriorityQueueSUL.DOUBLE_TYPE, BigDecimal.ONE)), @@ -113,6 +102,6 @@ public void testQueryCount() { learner.learn(); long memQueries3 = learner.getQueryStatistics().getMemQueries(); - Assert.assertEquals(memQueries3, 36); + Assert.assertEquals(memQueries3, 48); } } diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/TestSuffixOptimization.java b/src/test/java/de/learnlib/ralib/learning/ralambda/TestSuffixOptimization.java index fcf7c90d..2e01a0ad 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/TestSuffixOptimization.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/TestSuffixOptimization.java @@ -18,7 +18,6 @@ import de.learnlib.ralib.CacheDataWordOracle; import de.learnlib.ralib.RaLibTestSuite; import de.learnlib.ralib.TestUtil; -import de.learnlib.ralib.automata.RegisterAutomaton; import de.learnlib.ralib.data.Constants; import de.learnlib.ralib.data.DataType; import de.learnlib.ralib.data.DataValue; @@ -28,13 +27,9 @@ import de.learnlib.ralib.learning.MeasuringOracle; import de.learnlib.ralib.learning.QueryStatistics; import de.learnlib.ralib.oracles.DataWordOracle; -import de.learnlib.ralib.oracles.SDTLogicOracle; -import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; import de.learnlib.ralib.oracles.io.IOCache; import de.learnlib.ralib.oracles.io.IOFilter; import de.learnlib.ralib.oracles.io.IOOracle; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.sul.SULOracle; @@ -65,18 +60,12 @@ public void testLearnRepeaterSuffixOpt() { MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(oracle, teachers, consts, solver); - MultiTheorySDTLogicOracle mlo = - new MultiTheorySDTLogicOracle(consts, solver); - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, consts, solver); Measurements measurements = new Measurements(); QueryStatistics stats = new QueryStatistics(measurements, sul); - RaLambda learner = new RaLambda(mto, hypFactory, mlo, consts, true, sul.getActionSymbols()); + SLLambda learner = new SLLambda(mto, teachers, consts, true, solver, sul.getActionSymbols()); learner.setStatisticCounter(stats); - learner.setSolver(solver); learner.learn(); @@ -120,13 +109,8 @@ public void testLearnPQSuffixOpt() { ConstraintSolver solver = TestUtil.getZ3Solver(); QueryStatistics stats = new QueryStatistics(m, ioCache); MeasuringOracle mto = new MeasuringOracle(new MultiTheoryTreeOracle(ioCache, teachers, consts, solver), m); - SDTLogicOracle mlo = new MultiTheorySDTLogicOracle(consts, solver); - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, new Constants(), solver); - RaLambda learner = new RaLambda(mto, hypFactory, mlo, consts, OFFER, POLL); - learner.setSolver(solver); + SLLambda learner = new SLLambda(mto, teachers, consts, false, solver, OFFER, POLL); learner.setStatisticCounter(stats); learner.learn(); @@ -146,9 +130,9 @@ public void testLearnPQSuffixOpt() { String str = stats.toString(); Assert.assertTrue(str.contains("Counterexamples: 1")); Assert.assertTrue(str.contains("CE max length: 4")); - Assert.assertTrue(str.contains("CE Analysis: {TQ: 33, Resets: 339, Inputs: 0}")); - Assert.assertTrue(str.contains("Processing / Refinement: {TQ: 27, Resets: 815, Inputs: 0}")); - Assert.assertTrue(str.contains("Other: {TQ: 7, Resets: 7, Inputs: 0}")); - Assert.assertTrue(str.contains("Total: {TQ: 67, Resets: 1161, Inputs: 0}")); + Assert.assertTrue(str.contains("CE Analysis: {TQ: 81, Resets: 92, Inputs: 0}")); + Assert.assertTrue(str.contains("Processing / Refinement: {TQ: 32, Resets: 510, Inputs: 0}")); + Assert.assertTrue(str.contains("Other: {TQ: 10, Resets: 5, Inputs: 0}")); + Assert.assertTrue(str.contains("Total: {TQ: 123, Resets: 607, Inputs: 0}")); } } diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/TestSymmetry.java b/src/test/java/de/learnlib/ralib/learning/ralambda/TestSymmetry.java index ba0adc81..33fa1e58 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/TestSymmetry.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/TestSymmetry.java @@ -23,10 +23,7 @@ import de.learnlib.ralib.data.util.SymbolicDataValueGenerator; import de.learnlib.ralib.learning.Hypothesis; import de.learnlib.ralib.oracles.DataWordOracle; -import de.learnlib.ralib.oracles.SDTLogicOracle; import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.theory.Theory; @@ -59,20 +56,9 @@ public void testLearnSymmetryExampleCT2() { MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(dwOracle, teachers, new Constants(), solver); - SDTLogicOracle slo = new MultiTheorySDTLogicOracle(consts, solver); - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, new Constants(), solver); - - // System.out.println(sul); - // System.out.println("---------------------------------"); - - RaLambda learner = new RaLambda(mto, hypFactory, slo, consts, false, false, A, B); - learner.setSolver(solver); + SLLambda learner = new SLLambda(mto, teachers, consts, false, solver, A, B); learner.learn(); - // System.out.println(learner.getHypothesis().toString()); - Word ce = Word.fromSymbols(new PSymbolInstance(A, new DataValue(T_INT, BigDecimal.ONE)), new PSymbolInstance(A, new DataValue(T_INT, new BigDecimal(2) )), @@ -80,11 +66,7 @@ public void testLearnSymmetryExampleCT2() { learner.addCounterexample(new DefaultQuery<>(ce, true)); learner.learn(); - // System.out.println(learner.getHypothesis().toString()); - Hypothesis hyp = learner.getHypothesis(); - // System.out.println(hyp.toString()); - // System.out.println(learner.getDT()); ce = Word.fromSymbols(new PSymbolInstance(A, new DataValue(T_INT, BigDecimal.ONE)), new PSymbolInstance(A, new DataValue(T_INT, new BigDecimal(2))), @@ -99,9 +81,7 @@ public void testLearnSymmetryExampleCT2() { learner.addCounterexample(new DefaultQuery<>(ce, true)); learner.learn(); - // System.out.println(learner.getHypothesis().toString()); - // System.out.println(learner.getDT()); - Assert.assertTrue(learner.getDTHyp().accepts(ce)); + Assert.assertTrue(learner.getHypothesis().accepts(ce)); } private RegisterAutomaton buildCT2() { @@ -197,13 +177,7 @@ public void testLearnSymmetryExampleCT1() { MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(dwOracle, teachers, new Constants(), solver); - SDTLogicOracle slo = new MultiTheorySDTLogicOracle(consts, solver); - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, new Constants(), solver); - - RaLambda learner = new RaLambda(mto, hypFactory, slo, consts, false, false, A, B); - learner.setSolver(solver); + SLLambda learner = new SLLambda(mto, teachers, consts, false, solver, A, B); learner.learn(); diff --git a/src/test/java/de/learnlib/ralib/learning/ralambda/TestUnknownMemorable.java b/src/test/java/de/learnlib/ralib/learning/ralambda/TestUnknownMemorable.java index 064998e9..a2bdbc04 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/TestUnknownMemorable.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/TestUnknownMemorable.java @@ -32,12 +32,9 @@ import de.learnlib.ralib.data.SymbolicDataValue.Register; import de.learnlib.ralib.data.VarMapping; import de.learnlib.ralib.data.util.SymbolicDataValueGenerator; -import de.learnlib.ralib.oracles.SimulatorOracle; -import de.learnlib.ralib.oracles.TreeOracleFactory; import de.learnlib.ralib.oracles.io.IOCache; import de.learnlib.ralib.oracles.io.IOFilter; import de.learnlib.ralib.oracles.io.IOOracle; -import de.learnlib.ralib.oracles.mto.MultiTheorySDTLogicOracle; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.sul.DataWordSUL; @@ -189,14 +186,10 @@ public void testUnknownMemorable() { IOFilter oracle = new IOFilter(ioCache, inputSymbols); MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(oracle, teachers, consts, solver); - MultiTheorySDTLogicOracle mlo = new MultiTheorySDTLogicOracle(consts, solver); - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, consts, solver); - RaLambda ralambda = new RaLambda(mto, hypFactory, mlo, consts, true, actions); - ralambda.setSolver(solver); + SLLambda sllambda = new SLLambda(mto, teachers, consts, true, solver, actions); - ralambda.learn(); + sllambda.learn(); Word ce = Word.fromSymbols( new PSymbolInstance(IPUT, new DataValue(T_INT, BigDecimal.ZERO)), @@ -205,9 +198,9 @@ public void testUnknownMemorable() { new PSymbolInstance(OECHO, new DataValue(T_INT, BigDecimal.ONE)), new PSymbolInstance(IQUERY), new PSymbolInstance(OYES, new DataValue(T_INT, BigDecimal.ONE))); - ralambda.addCounterexample(new DefaultQuery(ce, true)); + sllambda.addCounterexample(new DefaultQuery(ce, true)); - ralambda.learn(); + sllambda.learn(); ce = Word.fromSymbols( new PSymbolInstance(IPUT, new DataValue(T_INT, BigDecimal.ZERO)), @@ -218,10 +211,10 @@ public void testUnknownMemorable() { new PSymbolInstance(OECHO, new DataValue(T_INT, BigDecimal.ZERO)), new PSymbolInstance(IQUERY), new PSymbolInstance(ONO, new DataValue(T_INT, BigDecimal.ZERO))); - boolean acc = ralambda.getHypothesis().accepts(ce); + boolean acc = sllambda.getHypothesis().accepts(ce); if (!acc) { - ralambda.addCounterexample(new DefaultQuery(ce, true)); - ralambda.learn(); + sllambda.addCounterexample(new DefaultQuery(ce, true)); + sllambda.learn(); } // if learning reaches this point without assertion violations, the test is passed @@ -260,15 +253,9 @@ public void testSkippingMemorable() { MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( ioFilter, teachers, consts, solver); - MultiTheorySDTLogicOracle mlo = - new MultiTheorySDTLogicOracle(consts, solver); - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, consts, solver); - - RaLambda ralambda = new RaLambda(mto, hypFactory, mlo, consts, true, actions); - ralambda.setSolver(solver); - ralambda.learn(); + SLLambda sllambda = new SLLambda(mto, teachers, consts, true, solver, actions); + sllambda.learn(); String[] ces = {"IPRACK[0[int]] Otimeout[] IINVITE[1[int]] Otimeout[] / true", "Inil[] Otimeout[] IINVITE[0[int]] O100[0[int]] / true", @@ -280,8 +267,8 @@ public void testSkippingMemorable() { Deque> ceQueue = buildSIPCEs(ces, actions); while (!ceQueue.isEmpty()) { - ralambda.addCounterexample(ceQueue.pop()); - ralambda.learn(); + sllambda.addCounterexample(ceQueue.pop()); + sllambda.learn(); } Assert.assertTrue(true); @@ -319,15 +306,9 @@ public void testSkippingMemorable2() { MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( ioFilter, teachers, consts, solver); - MultiTheorySDTLogicOracle mlo = - new MultiTheorySDTLogicOracle(consts, solver); - - TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> - new MultiTheoryTreeOracle(new SimulatorOracle(hyp), teachers, consts, solver); - RaLambda ralambda = new RaLambda(mto, hypFactory, mlo, consts, true, actions); - ralambda.setSolver(solver); - ralambda.learn(); + SLLambda sllambda = new SLLambda(mto, teachers, consts, true, solver, actions); + sllambda.learn(); String[] ces = {"IINVITE[0[int]] O100[0[int]] / true", "IACK[0[int]] Otimeout[] IINVITE[0[int]] Otimeout[] / true", @@ -343,8 +324,8 @@ public void testSkippingMemorable2() { Deque> ceQueue = buildSIPCEs(ces, actions); while (!ceQueue.isEmpty()) { - ralambda.addCounterexample(ceQueue.pop()); - ralambda.learn(); + sllambda.addCounterexample(ceQueue.pop()); + sllambda.learn(); } Assert.assertTrue(true); diff --git a/src/test/java/de/learnlib/ralib/theory/TestSDTEquivalence.java b/src/test/java/de/learnlib/ralib/theory/TestSDTEquivalence.java new file mode 100644 index 00000000..48ebb274 --- /dev/null +++ b/src/test/java/de/learnlib/ralib/theory/TestSDTEquivalence.java @@ -0,0 +1,49 @@ +package de.learnlib.ralib.theory; + +import java.math.BigDecimal; +import java.util.Map; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import de.learnlib.ralib.data.Bijection; +import de.learnlib.ralib.data.DataType; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.SDTRelabeling; +import de.learnlib.ralib.data.SymbolicDataValue.Register; +import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; +import de.learnlib.ralib.theory.SDTGuard.EqualityGuard; +import de.learnlib.ralib.theory.SDTGuard.IntervalGuard; + +public class TestSDTEquivalence { + + @Test + public void testSelfEquivalenceIneqTheory() { + DataType type = new DataType("double"); + SuffixValue s1 = new SuffixValue(type, 1); + Register r1 = new Register(type, 1); + Register r2 = new Register(type, 2); + + SDT sdt = new SDT(Map.of( + IntervalGuard.greaterGuard(s1, r2), SDTLeaf.REJECTING, + new EqualityGuard(s1, r2), SDTLeaf.ACCEPTING, + new IntervalGuard(s1, r1, r2), SDTLeaf.REJECTING, + new EqualityGuard(s1, r1), SDTLeaf.ACCEPTING, + IntervalGuard.lessGuard(s1, r1), SDTLeaf.REJECTING)); + + Assert.assertTrue(SDT.equivalentUnderId(sdt, sdt)); + + DataValue d0 = new DataValue(type, BigDecimal.ZERO); + DataValue d1 = new DataValue(type, BigDecimal.ONE); + SDTRelabeling relabeling = new SDTRelabeling(); + relabeling.put(r1, d0); + relabeling.put(r2, d1); + Bijection expected = new Bijection<>(); + expected.put(d0, d0); + expected.put(d1, d1); + + SDT sdtDv = sdt.relabel(relabeling); + Bijection actual = SDT.equivalentUnderBijection(sdtDv, sdtDv); + Assert.assertEquals(actual, expected); + } +} diff --git a/src/test/java/de/learnlib/ralib/theory/inequality/IneqGuardMergeTest.java b/src/test/java/de/learnlib/ralib/theory/inequality/IneqGuardMergeTest.java index 04e5895e..c0fc718c 100644 --- a/src/test/java/de/learnlib/ralib/theory/inequality/IneqGuardMergeTest.java +++ b/src/test/java/de/learnlib/ralib/theory/inequality/IneqGuardMergeTest.java @@ -12,6 +12,7 @@ import de.learnlib.ralib.RaLibTestSuite; import de.learnlib.ralib.data.DataType; import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.SuffixValuation; import de.learnlib.ralib.data.SymbolicDataValue.Register; import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.RegisterGenerator; @@ -81,7 +82,7 @@ public void testIntervalMerge() { expected1.put(new SDTGuard.IntervalGuard(s1, r1, r3), SDTLeaf.REJECTING); expected1.put(SDTGuard.IntervalGuard.greaterOrEqualGuard(s1, r3), SDTLeaf.ACCEPTING); - Map actual1 = dit.mergeGuards(sdts1, equivClasses, new ArrayList()); + Map actual1 = dit.mergeGuards(sdts1, equivClasses, new ArrayList(), new SuffixValuation()); Assert.assertEquals(actual1.size(), expected1.size()); Assert.assertTrue(actual1.entrySet().containsAll(expected1.entrySet())); @@ -101,7 +102,7 @@ public void testIntervalMerge() { expected2.put(new SDTGuard.IntervalGuard(s1, r2, r3, true, true), SDTLeaf.REJECTING); expected2.put(g6, SDTLeaf.ACCEPTING); - Map actual2 = dit.mergeGuards(sdts2, equivClasses, new ArrayList()); + Map actual2 = dit.mergeGuards(sdts2, equivClasses, new ArrayList(), new SuffixValuation()); Assert.assertEquals(actual2.size(), expected2.size()); Assert.assertTrue(actual2.entrySet().containsAll(expected2.entrySet())); @@ -122,7 +123,7 @@ public void testIntervalMerge() { expected3.put(new SDTGuard.IntervalGuard(s1, r1, r2, false, true), SDTLeaf.ACCEPTING); expected3.put(SDTGuard.IntervalGuard.greaterGuard(s1, r2), SDTLeaf.REJECTING); - Map actual3 = dit.mergeGuards(sdts3, equivClasses, new ArrayList()); + Map actual3 = dit.mergeGuards(sdts3, equivClasses, new ArrayList(), new SuffixValuation()); Assert.assertEquals(actual3.size(), expected3.size()); Assert.assertTrue(actual3.entrySet().containsAll(expected3.entrySet())); @@ -145,7 +146,7 @@ public void testIntervalMerge() { expected4.put(g5, SDTLeaf.ACCEPTING); expected4.put(g6, SDTLeaf.REJECTING); - Map actual4 = dit.mergeGuards(sdts4, equivClasses, new ArrayList()); + Map actual4 = dit.mergeGuards(sdts4, equivClasses, new ArrayList(), new SuffixValuation()); Assert.assertEquals(actual4.size(), expected4.size()); Assert.assertTrue(actual4.entrySet().containsAll(expected4.entrySet())); @@ -193,7 +194,7 @@ public void testTrueGuard() { sdts1.put(g3, SDTLeaf.ACCEPTING); sdts1.put(g4, SDTLeaf.ACCEPTING); - Map merged = dit.mergeGuards(sdts1, equivClasses, new ArrayList<>()); + Map merged = dit.mergeGuards(sdts1, equivClasses, new ArrayList<>(), new SuffixValuation()); Assert.assertEquals(merged.size(), 1); Assert.assertTrue(merged.containsKey(new SDTGuard.SDTTrueGuard(s1))); @@ -256,7 +257,7 @@ public void testFilteredDataValues() { expected.put(new SDTGuard.IntervalGuard(s1, r1, r2, true, false), SDTLeaf.ACCEPTING); expected.put(g5, SDTLeaf.REJECTING); - Map actual = dit.mergeGuards(sdts, equivClasses, filtered); + Map actual = dit.mergeGuards(sdts, equivClasses, filtered, new SuffixValuation()); Assert.assertEquals(actual.size(), expected.size()); Assert.assertTrue(actual.entrySet().containsAll(expected.entrySet())); @@ -318,7 +319,7 @@ public void testSDTSubtree() { expected.put(new SDTGuard.IntervalGuard(s1, r1, r2, false, true), subSdt2); expected.put(g4, subSdt3); - Map actual = dit.mergeGuards(sdts, equivClasses, new ArrayList<>()); + Map actual = dit.mergeGuards(sdts, equivClasses, new ArrayList<>(), new SuffixValuation()); Assert.assertEquals(actual.size(), expected.size()); Assert.assertTrue(actual.entrySet().containsAll(expected.entrySet())); @@ -372,7 +373,7 @@ public void testDisequalityGuard() { Map expected1 = new LinkedHashMap<>(); expected1.put(new SDTGuard.SDTTrueGuard(s1), SDTLeaf.REJECTING); - Map actual1 = dit.mergeGuards(sdts1, equivClasses, filteredOut); + Map actual1 = dit.mergeGuards(sdts1, equivClasses, filteredOut, new SuffixValuation()); Assert.assertEquals(actual1.size(), expected1.size()); Assert.assertTrue(actual1.entrySet().containsAll(expected1.entrySet())); @@ -383,7 +384,7 @@ public void testDisequalityGuard() { Map expected2 = new LinkedHashMap<>(); expected2.put(new SDTGuard.DisequalityGuard(s1, r1), SDTLeaf.REJECTING); expected2.put(g1, SDTLeaf.ACCEPTING); - Map actual2 = dit.mergeGuards(sdts2, equivClasses, new ArrayList<>()); + Map actual2 = dit.mergeGuards(sdts2, equivClasses, new ArrayList<>(), new SuffixValuation()); Assert.assertEquals(actual2.size(), expected2.size()); Assert.assertTrue(actual2.entrySet().containsAll(expected2.entrySet())); @@ -411,7 +412,7 @@ public void testDisequalityGuard() { expected3.put(new SDTGuard.IntervalGuard(s1, null, r2), sdt1); expected3.put(g3, sdt2); expected3.put(g4, sdt3); - Map actual3 = dit.mergeGuards(sdts3, equivClasses, new ArrayList<>()); + Map actual3 = dit.mergeGuards(sdts3, equivClasses, new ArrayList<>(), new SuffixValuation()); Assert.assertEquals(actual3.size(), expected3.size()); Assert.assertTrue(actual3.entrySet().containsAll(expected3.entrySet()));