diff --git a/src/main/java/de/learnlib/ralib/automata/RARun.java b/src/main/java/de/learnlib/ralib/automata/RARun.java index 2d2cd8bde..030f57f98 100644 --- a/src/main/java/de/learnlib/ralib/automata/RARun.java +++ b/src/main/java/de/learnlib/ralib/automata/RARun.java @@ -22,6 +22,7 @@ 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; /** * Data structure containing the locations, register valuations, symbol instances @@ -135,6 +136,18 @@ private Expression outputGuard(OutputTransition t) { return ExpressionUtil.and(expressions); } + public Word getPrefix(int id) { + return Word.fromArray(symbols, 0, id); + } + + public Word getSuffix(int id) { + return Word.fromArray(symbols, id, symbols.length - id); + } + + public Word getWord() { + return getPrefix(symbols.length); + } + @Override public String toString() { if (locations.length == 0) { diff --git a/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinder.java b/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinder.java index a2607686b..97b4d928e 100644 --- a/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinder.java +++ b/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinder.java @@ -1,6 +1,7 @@ package de.learnlib.ralib.ceanalysis; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; @@ -21,6 +22,8 @@ import de.learnlib.ralib.data.DataValue; import de.learnlib.ralib.data.Mapping; import de.learnlib.ralib.data.RegisterValuation; +import de.learnlib.ralib.data.SDTGuardElement; +import de.learnlib.ralib.data.SDTRelabeling; import de.learnlib.ralib.data.SymbolicDataValue; import de.learnlib.ralib.data.SymbolicDataValue.Parameter; import de.learnlib.ralib.data.SymbolicDataValue.Register; @@ -34,6 +37,7 @@ import de.learnlib.ralib.smt.ReplacingValuesVisitor; import de.learnlib.ralib.theory.SDT; import de.learnlib.ralib.theory.Theory; +import de.learnlib.ralib.words.DataWords; import de.learnlib.ralib.words.PSymbolInstance; import de.learnlib.ralib.words.ParameterizedSymbol; import gov.nasa.jpf.constraints.api.Expression; @@ -57,17 +61,17 @@ public enum ResultType { */ public record Result(Word prefix, ResultType result) {}; - private final CTHypothesis hyp; - private final ClassificationTree ct; + protected final CTHypothesis hyp; + protected final ClassificationTree ct; - private final TreeOracle sulOracle; - private final Map teachers; + protected final TreeOracle sulOracle; + protected final Map teachers; - private final SymbolicSuffixRestrictionBuilder restrBuilder; + protected final SymbolicSuffixRestrictionBuilder restrBuilder; - private final ConstraintSolver solver; + protected final ConstraintSolver solver; - private final Constants consts; + protected final Constants consts; public PrefixFinder(TreeOracle sulOracle, CTHypothesis hyp, ClassificationTree ct, Map teachers, SymbolicSuffixRestrictionBuilder restrBuilder, @@ -101,9 +105,8 @@ public Result analyzeCounterExample(Word ce) { 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()) { + Expression gHyp = getHypGuard(run, i, u); SDT sdt = sulOracle.treeQuery(u, v); Set uVals = hyp.getLeaf(loc).getPrefix(u).getRegisters(); @@ -188,7 +191,7 @@ private Set> extendedValuationRenamings(SDT uSDT, DataValue[] sdtValsArr = sdtVals.toArray(new DataValue[sdtVals.size()]); // gather data values from prefix of run at index id - List runVals = new ArrayList<>(); + ArrayList runVals = new ArrayList<>(); for (int i = 1; i <= id-1; i++) { for (DataValue d : run.getTransitionSymbol(i).getParameterValues()) { runVals.add(d); @@ -231,7 +234,7 @@ private Set> extendedValuationRenamings(SDT uSDT, * @param d * @return array containing data values of {@code list}, with one occurrence of {@code d} removed */ - private List removeFirst(List list, DataValue d) { + private ArrayList removeFirst(ArrayList list, DataValue d) { ArrayList ret = new ArrayList<>(); ret.addAll(list); for (int i = 0; i < list.size(); i++) { @@ -270,13 +273,15 @@ private Optional checkTransition(RALocation loc, // instantiate a representative data value for the conjunction DataType[] types = action.getPtypes(); DataValue[] reprDataVals = new DataValue[types.length]; + List prior = new ArrayList<>(); for (int i = 0; i < types.length; i++) { - Optional reprDataVal = teachers.get(types[i]).instantiate(u, action, conjunction, i+1, consts, solver); + Optional reprDataVal = teachers.get(types[i]).instantiate(u, action, conjunction, i+1, prior, consts, solver); if (reprDataVal.isEmpty()) { // guard unsat return Optional.empty(); } reprDataVals[i] = reprDataVal.get(); + prior.add(reprDataVals[i]); } PSymbolInstance psi = new PSymbolInstance(action, reprDataVals); Word uExtSUL = u.append(psi); @@ -292,7 +297,7 @@ private Optional checkTransition(RALocation loc, SDT uExtHypSDT = sulOracle.treeQuery(uExtHyp, v).toRegisterSDT(uExtHyp, consts); SDT uExtSULSDT = sulOracle.treeQuery(uExtSUL, v).toRegisterSDT(uExtSUL, consts); - if (SDT.equivalentUnderId(uExtHypSDT, uExtSULSDT)) { + if (this.equivalentSDTsWithEqualityMapping(uExtSULSDT, uExtHypSDT, uExtSUL)) { return Optional.empty(); // there is an equivalent extension, so no discrepancy } } @@ -372,4 +377,55 @@ private boolean isGuardSatisfied(Expression guard, Mapping getHypGuard(RARun run, int i, Word u) { + RegisterValuation runVal = run.getValuation(i - 1); + RegisterValuation uVal = hyp.getRun(u).getValuation(u.size()); + Mapping renaming = new Mapping<>(); + for (Map.Entry runValEntry : runVal.entrySet()) { + DataValue replace = runValEntry.getValue(); + DataValue by = uVal.get(runValEntry.getKey()); + renaming.put(replace, by); + } + + ReplacingValuesVisitor rvv = new ReplacingValuesVisitor(); + Expression guard = run.getGuard(i, consts); + return rvv.apply(guard, renaming); + } + + /** + * For each register ri in {@code sdtElse} that is not present in {@code sdtIf}, check whether data value di of {@code uIf} is equal to some other register in {@code sdtIf}. + * If so, maps ri to that register and returns {@code true} if {@code sdtIf} and {@code sdtElse} are equivalent under that remapping. + * + * @param sdtIf + * @param sdtElse + * @param uIf + * @param uElse + * @return {@code true} if and only if {@code sdtIf} and {@code sdtElse} are equivalent when collapsing equivalent registers of {@code uIf} + */ + private boolean equivalentSDTsWithEqualityMapping(SDT sdtIf, SDT sdtElse, Word uIf) { + ArrayList uIfVals = new ArrayList<>(Arrays.asList(DataWords.valsOf(uIf))); + Mapping renaming = new Mapping<>(); + for (SDTGuardElement elem : sdtElse.getVariables()) { + if (elem instanceof Register r && !sdtIf.getVariables().contains(r)) { + DataValue d = uIfVals.get(r.getId() - 1); + assert d != null : "Incompatible SDTs"; + int index = uIfVals.indexOf(d); + if (index >= 0) { + Register rEq = new Register(d.getDataType(), index + 1); + renaming.put(r, rEq); + } + } + } + return sdtIf.isEquivalent(sdtElse, SDTRelabeling.fromMapping(renaming)); + } } diff --git a/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinderEq.java b/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinderEq.java new file mode 100644 index 000000000..35f5a6086 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinderEq.java @@ -0,0 +1,342 @@ +package de.learnlib.ralib.ceanalysis; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +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.ParameterValuation; +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.Register; +import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; +import de.learnlib.ralib.data.VarMapping; +import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.ParameterGenerator; +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.SLLambdaEqRestrictionBuilder; +import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.smt.ReplacingVarsVisitor; +import de.learnlib.ralib.smt.VarsValuationVisitor; +import de.learnlib.ralib.theory.AbstractSuffixValueRestriction; +import de.learnlib.ralib.theory.SDT; +import de.learnlib.ralib.theory.Theory; +import de.learnlib.ralib.theory.equality.EqualityTheory; +import de.learnlib.ralib.words.DataWords; +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; + +public class PrefixFinderEq extends PrefixFinder { + + public PrefixFinderEq(TreeOracle sulOracle, CTHypothesis hyp, ClassificationTree ct, Map teachers, + SLLambdaEqRestrictionBuilder restrBuilder, ConstraintSolver solver, Constants consts) { + super(sulOracle, hyp, ct, teachers, restrBuilder, solver, consts); + if (!isEqTheory(teachers)) { + throw new RuntimeException("PrefixFinderEq onlu supports theories of type EqualityTheory"); + } + } + + @Override + public Result analyzeCounterExample(Word ce) { + RARun run = hyp.getRun(ce); + for (int i = ce.length(); i >= 1; i--) { + RALocation loc = run.getLocation(i - 1); + CTLeaf leaf = hyp.getLeaf(loc); + for (ShortPrefix u : leaf.getShortPrefixes()) { + Optional result = checkTransition(u, run, i); + if (result.isEmpty()) { + result = checkLocation(u, run, i); + } + if (result.isPresent()) { + return result.get(); + } + } + } + throw new IllegalStateException("Found no counterexample in " + ce); + } + + private SLLambdaEqRestrictionBuilder getRestrBuilder() { + return (SLLambdaEqRestrictionBuilder) restrBuilder; + } + + /** + * Check for a transition discrepancy. This is done by checking whether there exists no + * {@code action}-extension of {@code u} in the leaf of the location of {@code run} at + * index {@code id} that is equivalent to the {@code (hypGuard && sulGuard)} extension + * of {@code u} after the symbolic suffix derived from the remaining transitions of + * {@code v}. + * + * @param run counterexample run on the hypothesis + * @param id index of {@code run} being searched + * @param u short prefix from leaf of {@code loc} + * @param action the symbol of the next transition + * @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(ShortPrefix u, RARun run, int i) { + int arity = run.getTransitionSymbol(i).getBaseSymbol().getArity(); + if (arity == 0) { + return Optional.empty(); + } + return checkTransition(new DataValue[arity], 0, u, run, i); + } + + /** + * For each possible set of data values the action may take according to the concrete suffix, + * check whether the resulting (prefix+action) word is inequivalent to an existing prefix + * extension. The prefix and suffix are taken from {@code run} at index {@code i-1} (for prefix) + * and {@code i} (for suffix). + * + * @param dvals already generated values + * @param did index of next value not yet generated + * @param u short prefix matching {@code run.getPrefix(i-1)} + * @param run run of hypothesis over counterexample + * @param i index of run to check + * @return {@code Optional} enclosing new transition, if one is found, otherwise {@code Optional.empty()} + */ + private Optional checkTransition(DataValue[] dvals, int did, ShortPrefix u, RARun run, int i) { + Word prefix = run.getPrefix(i - 1); + Word prefixNext = run.getPrefix(i); + Word suffixNext = run.getSuffix(i); + RegisterValuation prefixValuation = run.getValuation(i - 1); + RegisterValuation prefixExtValuation = run.getValuation(i); + + PSymbolInstance action = run.getTransitionSymbol(i); + DataValue d = action.getParameterValues()[did]; + + // find the indices of data values in u that parameter with index did may be equal to + EqualityTheory et = (EqualityTheory) teachers.get(d.getDataType()); + RegisterValuation uValuation = hyp.getRun(u).getValuation(u.length()); + Map potmap = et.potmap(u, uValuation, prefix, prefixValuation, d.getDataType()); + Set potmatch = et.potmatch(prefix, d, u, uValuation, potmap); + + if (potmatch.isEmpty()) { + // not equal to a data value in the prefix, could equal a constant or a prior data value in the action + Word suffix = run.getSuffix(i - 1); + SymbolicSuffix v = getRestrBuilder().constructRestrictedSuffix(prefix, suffix, u, prefixValuation, uValuation); + SymbolicSuffix vHyp = SLLambdaEqRestrictionBuilder.concretize(v, uValuation, ParameterValuation.fromPSymbolWord(u), consts); + SDT sdt = sulOracle.treeQuery(u, vHyp); + Branching branching = sulOracle.getInitialBranching(u, action.getBaseSymbol(), sdt); + Set> guards = branching.guardSet(); + Set> sulExtensions = instantiateGuards(guards, vHyp, u, hyp.getRun(u).getValuation(u.length()).keySet(), action.getBaseSymbol()); + Set> hypExtensions = ct.getExtensions(u, action.getBaseSymbol()); + // check for any extension on the sul not already covered by the hyp + for (Word uExt : sulExtensions) { + if (!hypExtensions.contains(uExt)) { + return Optional.of(new Result(uExt, ResultType.TRANSITION)); + } + } + + } + + // for each data value in action allowed by the potmatch, check if (prefix+action) is equivalent to an existing extension + DataValue[] uVals = DataWords.valsOf(u); + POTMATCH: for (int l : potmatch) { + DataValue dPot = uVals[l - 1]; + dvals[did] = dPot; + if (did + 1 < action.getBaseSymbol().getArity()) { + // not final index, check each potmatch for next index + Optional res = checkTransition(dvals, did + 1, u, run, i); + if (res.isPresent()) { + return res; + } + } else { + // final index, construct (prefix+action) symbol and check equivalence with prefixNext + PSymbolInstance psi = new PSymbolInstance(action.getBaseSymbol(), dvals); + Word uExtSul = u.append(psi); + Set> extensions = ct.getExtensions(u, action.getBaseSymbol()); + if (extensions.contains(uExtSul)) { + continue; + } + for (Word uExtHyp : extensions) { + RegisterValuation uExtSulValuation = hyp.getRun(uExtSul).getValuation(uExtSul.length()); + RegisterValuation uExtHypValuation = hyp.getRun(uExtHyp).getValuation(uExtHyp.length()); + SymbolicSuffix v = getRestrBuilder().constructRestrictedSuffix(prefixNext, suffixNext, uExtSul, prefixExtValuation, uExtSulValuation); + SymbolicSuffix vSul = SLLambdaEqRestrictionBuilder.concretize(v, uExtSulValuation, ParameterValuation.fromPSymbolWord(uExtSul), consts); + SymbolicSuffix vHyp = SLLambdaEqRestrictionBuilder.concretize(v, uExtHypValuation, ParameterValuation.fromPSymbolWord(uExtHyp), consts); + + SDT sdtSul = sulOracle.treeQuery(uExtSul, vSul).toRegisterSDT(uExtSul, consts); + SDT sdtHyp = sulOracle.treeQuery(uExtHyp, vHyp).toRegisterSDT(uExtHyp, consts); + if (SDT.equalUnderActionRemapping(sdtSul, sdtHyp, uExtSul, uExtHyp)) { + continue POTMATCH; + } + } + return Optional.of(new Result(uExtSul, ResultType.TRANSITION)); + } + } + return Optional.empty(); + } + + /** + * Check whether any extension of {@code u} is inequivalent to a short prefix in location + * {@code run.getLocation(i)}. + * + * @param u + * @param run + * @param i + * @return {@code Optional} enclosing an inequivalent transition, if one exists, otherwise {@code Optional.empty()} + */ + private Optional checkLocation(ShortPrefix u, RARun run, int i) { + Word prefix = run.getPrefix(i); + Word suffix = run.getSuffix(i); + RegisterValuation prefixValuation = run.getValuation(i); + CTLeaf leafNext = hyp.getLeaf(run.getLocation(i)); + PSymbolInstance action = prefix.lastSymbol(); + + Iterator extensions = ct.getExtensions(u, action.getBaseSymbol()) + .stream() + .filter(w -> leafNext.getPrefixes().contains(w)) + .map(w -> leafNext.getPrefix(w)) + .iterator(); + EXTENSIONS: while (extensions.hasNext()) { + Prefix uExt = extensions.next(); + RegisterValuation uExtValuation = hyp.getRun(uExt).getValuation(uExt.length()); + Bijection uExtBijection = uExt.getRpBijection(); + for (ShortPrefix uNext : leafNext.getShortPrefixes()) { + RegisterValuation uNextValuation = hyp.getRun(uNext).getValuation(uNext.length()); + SymbolicSuffix v = getRestrBuilder().constructRestrictedSuffix(prefix, suffix, uExt, uNext, prefixValuation, uExtValuation, uNextValuation); + + SymbolicSuffix vuExt = SLLambdaEqRestrictionBuilder.concretize(v, uExtValuation, ParameterValuation.fromPSymbolWord(uExt), consts); + SymbolicSuffix vuNext = SLLambdaEqRestrictionBuilder.concretize(v, uNextValuation, ParameterValuation.fromPSymbolWord(uNext), consts); + + Bijection uNextBijection = uNext.getRpBijection(); + Bijection gamma = uNextBijection.compose(uExtBijection.inverse()); + SDT uExtSDT = sulOracle.treeQuery(uExt, vuExt); + SDT uNextSDT = sulOracle.treeQuery(uNext, vuNext); + if (SDT.equivalentUnderBijection(uNextSDT, uExtSDT, gamma) != null) { + continue EXTENSIONS; + } + } + return Optional.of(new Result(uExt, ResultType.LOCATION)); + } + return Optional.empty(); + } + + /** + * For each guard in {@code guards}, instantiate an action whose values satisfy the guard + * and the restrictions of {@code suffix}, and return a set of words formed by appending + * the actions to {@code u}. + * + * @param guards + * @param suffix + * @param u + * @param regs + * @param action + * @return + */ + private Set> instantiateGuards(Set> guards, SymbolicSuffix suffix, Word u, Set regs, ParameterizedSymbol action) { + Set> extensions = new LinkedHashSet<>(); + for (Expression guard : guards) { + Expression con = conjunctionWithRestriction(guard, suffix, u, regs, consts); + List vals = new ArrayList<>(); + DataValue[] valsArr = new DataValue[action.getArity()]; + for (int i = 0; i < action.getArity(); i++) { + DataType t = action.getPtypes()[i]; + Theory theory = teachers.get(t); + assert theory instanceof EqualityTheory; + EqualityTheory et = (EqualityTheory) theory; + Optional dOpt = et.instantiate(u, action, con, i + 1, vals, consts, solver); + assert dOpt.isPresent(); + vals.add(dOpt.get()); + valsArr[i] = dOpt.get(); + } + PSymbolInstance psi = new PSymbolInstance(action, valsArr); + extensions.add(u.append(psi)); + } + return extensions; + } + + /** + * Compute conjunction of {@code guard} and the restrictions of {@code suffix}. + * + * @param guard + * @param suffix + * @param u + * @param regs + * @param consts + * @return + */ + private Expression conjunctionWithRestriction(Expression guard, SymbolicSuffix suffix, Word u, Set regs, Constants consts) { + DataType[] types = null; + for (ParameterizedSymbol ps : suffix.getActions()) { + if (ps.getArity() > 0) { + types = ps.getPtypes(); + break; + } + } + if (types == null) { + return guard; + } + SuffixValueGenerator sgen = new SuffixValueGenerator(); + + Set vals = new LinkedHashSet<>(); + DataValue[] uVals = DataWords.valsOf(u); + ParameterGenerator pgen = new ParameterGenerator(); + ParameterValuation pmap = new ParameterValuation(); + for (int i = 0; i < uVals.length; i++) { + Parameter p = pgen.next(uVals[i].getDataType()); + vals.add(p); + pmap.put(p, uVals[i]); + } + vals.addAll(regs); + vals.addAll(consts.keySet()); + + List> restrictionExpressions = new ArrayList<>(); + VarMapping paramMapping = new VarMapping<>(); + for (int i = 0; i < types.length; i++) { + if (!teachers.containsKey(types[i]) || !teachers.get(types[i]).isUsingSuffixOptimization()) { + continue; + } + + SuffixValue s = sgen.next(types[i]); + Parameter p = new Parameter(s.getDataType(), s.getId()); + AbstractSuffixValueRestriction r = suffix.getRestriction(s); + Expression expr = r.toGuardExpression(vals); + + VarsValuationVisitor vvv = new VarsValuationVisitor(); + expr = vvv.apply(expr, pmap); + + ReplacingVarsVisitor rvv = new ReplacingVarsVisitor(); + paramMapping.put(s, p); + Expression renamedExpr = rvv.apply(expr, paramMapping); + restrictionExpressions.add(renamedExpr); + } + restrictionExpressions.add(guard); + Expression con = ExpressionUtil.and(restrictionExpressions.toArray(new Expression[restrictionExpressions.size()])); + return con; + } + + /** + * @param teachers + * @return {@code true} if and only if all data types of {@code teachers} are associated with {@code EqualityTheory} + */ + private static boolean isEqTheory(Map teachers) { + for (Map.Entry t : teachers.entrySet()) { + if (t.getKey() == null || !(t.getValue() instanceof EqualityTheory)) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinderFactory.java b/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinderFactory.java new file mode 100644 index 000000000..8100f8eac --- /dev/null +++ b/src/main/java/de/learnlib/ralib/ceanalysis/PrefixFinderFactory.java @@ -0,0 +1,49 @@ +package de.learnlib.ralib.ceanalysis; + +import java.util.Map; + +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.oracles.TreeOracle; +import de.learnlib.ralib.oracles.mto.SLLambdaEqRestrictionBuilder; +import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; +import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.theory.Theory; + +public class PrefixFinderFactory { + public enum PrefixFinderType { + Default, + Eq + }; + + private PrefixFinderType type = PrefixFinderType.Default; + + private final TreeOracle sulOracle; + private final Map teachers; + private final SymbolicSuffixRestrictionBuilder restrBuilder; + private final ConstraintSolver solver; + private final Constants consts; + + public PrefixFinderFactory(TreeOracle sulOracle, Map teachers, + SymbolicSuffixRestrictionBuilder restrBuilder, ConstraintSolver solver, Constants consts) { + this.sulOracle = sulOracle; + this.teachers = teachers; + this.restrBuilder = restrBuilder; + this.solver = solver; + this.consts = consts; + } + + public void setPrefixFinderType(PrefixFinderType type) { + this.type = type; + } + + public PrefixFinder create(CTHypothesis hyp, ClassificationTree ct) { + if (type == PrefixFinderType.Eq) { + assert restrBuilder instanceof SLLambdaEqRestrictionBuilder; + return new PrefixFinderEq(sulOracle, hyp, ct, teachers, (SLLambdaEqRestrictionBuilder) restrBuilder, solver, consts); + } + return new PrefixFinder(sulOracle, hyp, ct, teachers, restrBuilder, solver, consts); + } +} diff --git a/src/main/java/de/learnlib/ralib/ct/CTInnerNode.java b/src/main/java/de/learnlib/ralib/ct/CTInnerNode.java index 66cedc311..519d09e44 100644 --- a/src/main/java/de/learnlib/ralib/ct/CTInnerNode.java +++ b/src/main/java/de/learnlib/ralib/ct/CTInnerNode.java @@ -10,8 +10,8 @@ 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.theory.ConcretizingTreeOracle; import de.learnlib.ralib.words.PSymbolInstance; import de.learnlib.ralib.words.ParameterizedSymbol; import net.automatalib.word.Word; @@ -28,7 +28,7 @@ public class CTInnerNode extends CTNode { private final SymbolicSuffix suffix; private final List branches; - public CTInnerNode(CTNode parent, SymbolicSuffix suffix) { + public CTInnerNode(CTInnerNode parent, SymbolicSuffix suffix) { super(parent); this.suffix = suffix; branches = new ArrayList<>(); @@ -52,20 +52,23 @@ protected CTBranch getBranch(CTNode child) { } @Override - protected CTLeaf sift(Prefix prefix, TreeOracle oracle, ConstraintSolver solver, boolean ioMode) { - CTPath path = CTPath.computePath(oracle, prefix, getSuffixes(), ioMode); + protected CTLeaf sift(Prefix prefix, ConcretizingTreeOracle oracle, ConstraintSolver solver, boolean ioMode) { + List suffixes = getSuffixes(); + CTPath path = CTPath.computePath(oracle, prefix, suffixes, 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); + prefix = new Prefix(prefix, vars, path, prefix.getBijections()); + prefix.putBijection(getSuffix(), vars); return b.getChild().sift(prefix, oracle, solver, ioMode); } } - // no child with equivalent SDTs, create a new leaf + // no child with equivalent SDTs, create a new leaf prefix = new Prefix(prefix, path); + prefix.putBijection(suffix); CTLeaf leaf = new CTLeaf(prefix, this); CTBranch branch = new CTBranch(path, leaf); branches.add(branch); @@ -86,11 +89,14 @@ protected CTLeaf sift(Prefix prefix, TreeOracle oracle, ConstraintSolver solver, * @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) { + protected Map, CTLeaf> refine(CTLeaf leaf, SymbolicSuffix suffix, ConcretizingTreeOracle oracle, ConstraintSolver solver, boolean ioMode, ParameterizedSymbol[] inputs) { CTBranch b = getBranch(leaf); assert b != null : "Node is not the parent of leaf " + leaf; + List suffixes = getSuffixes(); 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); @@ -110,18 +116,24 @@ protected Map, CTLeaf> refine(CTLeaf leaf, SymbolicSuffix 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; + if (getParent() != null) { + suffixes.addAll(getParent().getSuffixes()); } - - suffixes.addAll(getParent().getSuffixes()); + suffixes.add(suffix); return suffixes; } diff --git a/src/main/java/de/learnlib/ralib/ct/CTLeaf.java b/src/main/java/de/learnlib/ralib/ct/CTLeaf.java index 417639860..e139cb999 100644 --- a/src/main/java/de/learnlib/ralib/ct/CTLeaf.java +++ b/src/main/java/de/learnlib/ralib/ct/CTLeaf.java @@ -14,6 +14,7 @@ import de.learnlib.ralib.oracles.Branching; import de.learnlib.ralib.oracles.TreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.theory.ConcretizingTreeOracle; import de.learnlib.ralib.words.PSymbolInstance; import de.learnlib.ralib.words.ParameterizedSymbol; import net.automatalib.word.Word; @@ -32,7 +33,7 @@ public class CTLeaf extends CTNode implements LocationComponent { private final Set shortPrefixes; private final Set prefixes; - public CTLeaf(Prefix rp, CTNode parent) { + public CTLeaf(Prefix rp, CTInnerNode parent) { super(parent); if (parent == null) { throw new IllegalArgumentException("A leaf must have a parent"); @@ -118,7 +119,8 @@ public boolean isAccepting() { * @return {@code this} */ @Override - protected CTLeaf sift(Prefix prefix, TreeOracle oracle, ConstraintSolver solver, boolean ioMode) { + protected CTLeaf sift(Prefix prefix, ConcretizingTreeOracle oracle, ConstraintSolver solver, boolean ioMode) { + prefixes.add(prefix); prefixes.add(prefix); if (prefix instanceof ShortPrefix sp) { shortPrefixes.add(sp); diff --git a/src/main/java/de/learnlib/ralib/ct/CTNode.java b/src/main/java/de/learnlib/ralib/ct/CTNode.java index ec9c9a39d..07e872ff6 100644 --- a/src/main/java/de/learnlib/ralib/ct/CTNode.java +++ b/src/main/java/de/learnlib/ralib/ct/CTNode.java @@ -5,6 +5,7 @@ import de.learnlib.ralib.learning.SymbolicSuffix; import de.learnlib.ralib.oracles.TreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.theory.ConcretizingTreeOracle; /** * Node of a {@link ClassificationTree}. @@ -12,9 +13,9 @@ * @author fredrik */ public abstract class CTNode { - private final CTNode parent; + private final CTInnerNode parent; - public CTNode(CTNode parent) { + public CTNode(CTInnerNode parent) { this.parent = parent; } @@ -22,7 +23,7 @@ public CTNode(CTNode parent) { * * @return immediate ancestor of this node */ - public CTNode getParent() { + public CTInnerNode getParent() { return parent; } @@ -52,5 +53,5 @@ public CTNode getParent() { * @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); + protected abstract CTLeaf sift(Prefix prefix, ConcretizingTreeOracle 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 index 8b9247f7d..a6b1a0205 100644 --- a/src/main/java/de/learnlib/ralib/ct/CTPath.java +++ b/src/main/java/de/learnlib/ralib/ct/CTPath.java @@ -1,5 +1,8 @@ package de.learnlib.ralib.ct; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -10,9 +13,13 @@ 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.AbstractSuffixValueRestriction; +import de.learnlib.ralib.theory.ConcretizingTreeOracle; +import de.learnlib.ralib.theory.ElementRestriction; import de.learnlib.ralib.theory.SDT; +import de.learnlib.ralib.words.PSymbolInstance; +import gov.nasa.jpf.constraints.api.Expression; /** * This data structure stores the SDTs from tree queries for a prefix along a path @@ -26,6 +33,7 @@ public class CTPath { private final Map sdts; private final MemorableSet memorable; + private final List suffixes; private boolean ioMode; @@ -33,12 +41,14 @@ public CTPath(boolean ioMode) { this.sdts = new LinkedHashMap<>(); this.memorable = new MemorableSet(); this.ioMode = ioMode; + this.suffixes = new ArrayList<>(); } public void putSDT(SymbolicSuffix suffix, SDT sdt) { assert !sdts.containsKey(suffix); sdts.put(suffix, sdt); memorable.addAll(sdt.getDataValues()); + suffixes.add(suffix); } public MemorableSet getMemorable() { @@ -53,6 +63,21 @@ public Map getSDTs() { return sdts; } + /** + * @param suffix + * @return the symbolic suffix one node above {@code suffix} + */ + public SymbolicSuffix getPrior(SymbolicSuffix suffix) { + int index = suffixes.indexOf(suffix); + if (index < 0) { + throw new IllegalArgumentException("No occurrence of " + suffix); + } + if (index == 0) { + return RaStar.EMPTY_SUFFIX; + } + return suffixes.get(index - 1); + } + public boolean isAccepting() { SDT s = sdts.get(RaStar.EMPTY_SUFFIX); return s.isAccepting(); @@ -134,25 +159,55 @@ private static boolean equalTypeSizes(Set s1, Set s2) { * @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) { + public static CTPath computePath(ConcretizingTreeOracle 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); + SymbolicSuffix prevSuffix = RaStar.EMPTY_SUFFIX; for (SymbolicSuffix s : suffixes) { + if (s.equals(RaStar.EMPTY_SUFFIX)) { + continue; + } + // relabel restrictions in symbolic suffix + Bijection renaming = prefix.getBijection(prevSuffix); + SymbolicSuffix sRelabeled = s.relabel(renaming.inverse().toVarMapping()); + PSymbolInstance action = prefix.size() > 0 ? prefix.lastSymbol() : null; + assert noUnmapped(action, sRelabeled, r.getMemorable()) : "Equality with unmapped data value"; sdt = prefix.getSDT(s); if (sdt == null) { - sdt = oracle.treeQuery(prefix, s); + sdt = oracle.treeQuery(prefix, sRelabeled, r.getMemorable()); } if (r.getSDT(s) == null) { r.putSDT(s, sdt); } + prevSuffix = s; } return r; } + /** + * @param action + * @param suffix + * @param memorable + * @return {@code true} if and only if the restrictions for {@code suffix} contain any data value not in {@code memorable} or {@code action} + */ + private static boolean noUnmapped(PSymbolInstance action, SymbolicSuffix suffix, Set memorable) { + List actionVals = action == null ? new ArrayList<>() : Arrays.asList(action.getParameterValues()); + for (AbstractSuffixValueRestriction r : suffix.getRestrictions().values()) { + if (r instanceof ElementRestriction er) { + for (Expression e : er.getElements()) { + if (e instanceof DataValue d && !memorable.contains(d) && !actionVals.contains(d)) { + return false; + } + } + } + } + return true; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/de/learnlib/ralib/ct/ClassificationTree.java b/src/main/java/de/learnlib/ralib/ct/ClassificationTree.java index b611595fb..4bf670059 100644 --- a/src/main/java/de/learnlib/ralib/ct/ClassificationTree.java +++ b/src/main/java/de/learnlib/ralib/ct/ClassificationTree.java @@ -1,6 +1,8 @@ package de.learnlib.ralib.ct; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -10,6 +12,8 @@ import java.util.Set; import java.util.stream.Collectors; +import com.google.common.collect.Sets; + import de.learnlib.ralib.data.Bijection; import de.learnlib.ralib.data.Constants; import de.learnlib.ralib.data.DataValue; @@ -17,7 +21,6 @@ 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; @@ -25,9 +28,11 @@ 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.SLLambdaEqRestrictionBuilder; import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.smt.ReplacingValuesVisitor; +import de.learnlib.ralib.theory.ConcretizingTreeOracle; import de.learnlib.ralib.theory.SDT; import de.learnlib.ralib.words.DataWords; import de.learnlib.ralib.words.OutputSymbol; @@ -49,7 +54,7 @@ public class ClassificationTree { private final Set> shortPrefixes; private final ConstraintSolver solver; - private final TreeOracle oracle; + private final ConcretizingTreeOracle oracle; private final SymbolicSuffixRestrictionBuilder restrBuilder; private final OptimizedSymbolicSuffixBuilder suffixBuilder; @@ -67,7 +72,7 @@ public ClassificationTree(TreeOracle oracle, Constants consts, boolean ioMode, ParameterizedSymbol ... inputs) { - this.oracle = oracle; + this.oracle = new ConcretizingTreeOracle(oracle, consts); this.solver = solver; this.ioMode = ioMode; this.inputs = inputs; @@ -337,25 +342,29 @@ public boolean checkRegisterClosedness() { continue; } Word u = ua.prefix(ua.size() - 1); + Prefix u_pref = getLeaf(u).getPrefix(u); 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 + Set missingRegs = missingMemorable(ua_mem, u_mem, u); + if (!missingRegs.isEmpty()) { 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); + Set v_mem = ua_pref.getSDT(v).getDataValues(); + Set vMissingRegs = missingMemorable(v_mem, u_mem, u); + if (!vMissingRegs.isEmpty()) { + // found a suffix with missing memorables + SymbolicSuffix av = extendSuffixRegister(ua, v, vMissingRegs); + SDT sdt = oracle.treeQuery(u, av.relabel(u_pref.getRpBijection().inverse().toVarMapping()), u_pref.getRegisters()); + if (Collections.disjoint(vMissingRegs, sdt.getDataValues())) { + continue; + } refine(u_leaf, av); - break; + return false; } } - return false; } } return true; @@ -404,7 +413,7 @@ public boolean checkLocationConsistency() { if (uExtLeaf != uOtherExtLeaf) { // inconsistent, refine leaf with extended suffix SymbolicSuffix v = lca(uExtLeaf, uOtherExtLeaf).getSuffix(); - SymbolicSuffix av = extendSuffix(uExtension, uOtherExtension.get(), v); + SymbolicSuffix av = extendSuffixLocation(uExtension, uOtherExtension.get(), v); refine(l, av); return false; } @@ -434,23 +443,23 @@ public boolean checkTransitionConsistency() { for (ParameterizedSymbol action : inputs) { Set> extensions = getExtensions(u, action); for (Map.Entry, Expression> e : u.getBranching(action).getBranches().entrySet()) { - Word uA = e.getKey(); + Word uElse = e.getKey(); Expression g = e.getValue(); - for (Word uB : extensions) { - if (uB.equals(uA)) { + for (Word uIf : extensions) { + if (uIf.equals(uElse)) { continue; } // check if guard for uA is satisfiable under mapping of uB Mapping mapping = new Mapping<>(); - mapping.putAll(actionValuation(uB)); + mapping.putAll(actionValuation(uIf)); mapping.putAll(consts); if (solver.isSatisfiable(g, mapping)) { // check transition consistency A - Optional av = transitionConsistentA(uA, uB); + Optional av = transitionConsistentA(uIf, uElse); if (av.isEmpty()) { // check transition consistency B - av = transitionConsistentB(uA, uB); + av = transitionConsistentB(uIf, uElse); } if (av.isPresent()) { refine(getLeaf(u), av.get()); @@ -468,32 +477,30 @@ private Optional transitionConsistentA(Word uA, Word u = uA.prefix(uA.length() - 1); CTLeaf uALeaf = getLeaf(uA); CTLeaf uBLeaf = getLeaf(uB); - if (uALeaf != uBLeaf) { + if (! uALeaf.equals(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); + SymbolicSuffix av = extendSuffixTransition(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)); + private Optional transitionConsistentB(Word uIf, Word uElse) { + Prefix pA = getLeaf(uIf).getPrefix(uIf); + Prefix pB = getLeaf(uElse).getPrefix(uElse); + for (SymbolicSuffix v : getLeaf(uElse).getSuffixes()) { + SDT sdtA = pA.getSDT(v).toRegisterSDT(uIf, consts); + SDT sdtB = pB.getSDT(v).toRegisterSDT(uElse, consts); + if (!SDT.equivalentUnderId(sdtA, sdtB) && !SDT.equalUnderActionRemapping(sdtA, sdtB, pA, pB)) { + CTLeaf uLeaf = getLeaf(uIf.prefix(uIf.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)))) { + SymbolicSuffix av = extendSuffixTransition(uIf, uElse, v); + Word u = uIf.prefix(uIf.length() - 1); + ShortPrefix uSp = (ShortPrefix) uLeaf.getPrefix(u); + if (suffixRevealsNewGuard(av, uSp)) { return Optional.of(av); } } @@ -536,8 +543,7 @@ public boolean checkRegisterConsistency() { 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); + SymbolicSuffix av = new SymbolicSuffix(DataWords.concatenate(Word.fromSymbols(uExtended.lastSymbol().getBaseSymbol()), v.getActions())); refine(getLeaf(u), av); return false; } @@ -597,48 +603,18 @@ private int height(CTNode n) { } /** - * * @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} + * @param u + * @return the set of data values of {@code u} that are present in {@code ua_mem} but not in {@code u_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()]); + private Set missingMemorable(Set ua_mem, Set u_mem, Word u) { + Set uVals = new LinkedHashSet<>(); + uVals.addAll(Arrays.asList(DataWords.valsOf(u))); + Set diff = new LinkedHashSet<>(ua_mem); + diff.removeAll(u_mem); + uVals.removeAll(u_mem); + return Sets.intersection(uVals, diff); } /** @@ -650,10 +626,10 @@ private DataValue[] missingRegisters(Set s_mem, Set u_mem, * @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) { + private SymbolicSuffix extendSuffixRegister(Word ua, SymbolicSuffix v, Set missingRegs) { + Word u = ua.prefix(ua.length() - 1); 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); } @@ -661,7 +637,13 @@ private SymbolicSuffix extendSuffix(Word ua, SymbolicSuffix 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); + if (restrBuilder instanceof SLLambdaEqRestrictionBuilder sllambdaRestrBuilder) { + Prefix uPref = getLeaf(u).getPrefix(u); + Prefix uExtPref = getLeaf(ua).getPrefix(ua); + return sllambdaRestrBuilder.extendSuffix(uPref, uExtPref, v, u_sdt); + } + + return suffixBuilder.extendSuffix(ua, u_sdt, v, missingRegs.toArray(new DataValue[missingRegs.size()])); } /** @@ -672,12 +654,13 @@ private SymbolicSuffix extendSuffix(Word ua, SymbolicSuffix v, * @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); + private boolean suffixRevealsNewGuard(SymbolicSuffix av, ShortPrefix u) { + if (restrBuilder instanceof SLLambdaEqRestrictionBuilder rBuilder && rBuilder.hasUnmappedRestrictionValue(av, u.getRegisters())) { + return false; + } + SDT sdt = oracle.treeQuery(u, av.relabel(u.getRpBijection().inverse().toVarMapping()), u.getRegisters()); ParameterizedSymbol a = av.getActions().firstSymbol(); - Branching branching = leaf.getBranching(a); + Branching branching = u.getBranching(a); Branching newBranching = oracle.updateBranching(u, a, branching, sdt); for (Expression guard : newBranching.getBranches().values()) { if (!branching.getBranches().values().contains(guard)) { @@ -688,48 +671,59 @@ private boolean suffixRevealsNewGuard(SymbolicSuffix av, CTLeaf leaf) { } /** - * Convert {@code Bijection} to {@code Bijection} using the - * data values of {@code prefix} to determine register ids. - * - * @param bijection - * @param prefix - * @return - */ - private Bijection rpRegBijection(Bijection bijection, Word prefix) { - return Bijection.dvToRegBijection(bijection, prefix, getLeaf(prefix).getRepresentativePrefix()); - } - - /** - * Convert array of {@code Register} to array of {@code DataValue} by matching {@link Register#getId()} - * values to data value positions in {@code prefix}. + * 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 regs - * @param prefix + * @param u1 + * @param u2 + * @param v * @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]; + private SymbolicSuffix extendSuffixLocation(Word u1Ext, Word u2Ext, SymbolicSuffix v) { + SDT sdt1 = getLeaf(u1Ext).getPrefix(u1Ext).getSDT(v); + SDT sdt2 = getLeaf(u2Ext).getPrefix(u2Ext).getSDT(v); + if (restrBuilder != null && restrBuilder instanceof SLLambdaEqRestrictionBuilder sllambdaRestrBuilder) { + Word u1 = u1Ext.prefix(u1Ext.size() - 1); + Word u2 = u2Ext.prefix(u2Ext.size() - 1); + CTLeaf leaf = getLeaf(u1); + assert leaf == getLeaf(u2); + Prefix u1Pref = leaf.getPrefix(u1); + Prefix u2Pref = leaf.getPrefix(u2); + Prefix u1ExtPref = getLeaf(u1Ext).getPrefix(u1Ext); + Prefix u2ExtPref = getLeaf(u2Ext).getPrefix(u2Ext); + return sllambdaRestrBuilder.extendSuffix(u1Pref, u1ExtPref, u2Pref, u2ExtPref, v, sdt1, sdt2); } - return ret; + + return suffixBuilder.extendDistinguishingSuffix(u1Ext, sdt1, u2Ext, sdt2, v); } /** - * 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. + * Form a {@code SymbolicSuffix} by prepending {@code v} by the last symbol of {@code uIf}. + * The new suffix will be optimized for revealing the guard of {@code uIf}. + * Note that the last symbols of {@code uIf} and {@code uElse} must have the same base symbol. * - * @param u1 - * @param u2 + * @param uIf + * @param uElse * @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); + private SymbolicSuffix extendSuffixTransition(Word uIf, Word uElse, SymbolicSuffix v) { + CTLeaf leafIf = getLeaf(uIf); + CTLeaf leafElse = getLeaf(uElse); + Prefix uIfPref = leafIf.getPrefix(uIf); + Prefix uElsePref = leafElse.getPrefix(uElse); + SDT sdtIf = uIfPref.getSDT(v); + SDT sdtElse = uElsePref.getSDT(v); + if (restrBuilder != null && restrBuilder instanceof SLLambdaEqRestrictionBuilder sllambdaRestrBuilder) { + Word u = uIf.prefix(uIf.size() - 1); + CTLeaf uLeaf = getLeaf(u); + Prefix uPref = uLeaf.getPrefix(u); + boolean sameLeaf = (leafIf == leafElse); + return sllambdaRestrBuilder.extendSuffix(uPref, uIfPref, uElsePref, v, sdtIf, sdtElse, sameLeaf); + } + + return suffixBuilder.extendDistinguishingSuffix(uIf, sdtIf, uElse, sdtElse, v); } /** @@ -749,28 +743,6 @@ private ParameterValuation actionValuation(Word ua) { 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(); diff --git a/src/main/java/de/learnlib/ralib/ct/Prefix.java b/src/main/java/de/learnlib/ralib/ct/Prefix.java index 79cf96fd6..23af928ff 100644 --- a/src/main/java/de/learnlib/ralib/ct/Prefix.java +++ b/src/main/java/de/learnlib/ralib/ct/Prefix.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Spliterator; @@ -37,29 +38,66 @@ public class Prefix extends Word implements PrefixContainer { private final Word prefix; private Bijection rpBijection; private final CTPath path; + public final Map> pathBijections; // tracks bijections to the RP at each ancestor node public Prefix(Word u, Bijection rpRenaming, CTPath path) { this.prefix = u instanceof Prefix prefix ? prefix.getPrefix() : u; this.rpBijection = rpRenaming; this.path = path; + pathBijections = new LinkedHashMap<>(); + } + + public Prefix(Word u, Bijection rpRenaming, CTPath path, Map> pathBijections) { + this(u, rpRenaming, path); + this.pathBijections.putAll(pathBijections); } public Prefix(Word prefix, CTPath path) { this(prefix, Bijection.identity(path.getMemorable()), path); + if (prefix instanceof Prefix p) { + pathBijections.putAll(p.pathBijections); + } } public Prefix(Prefix prefix, Bijection rpRenaming) { - this(prefix.prefix, rpRenaming, prefix.path); - } - - public Prefix(Prefix other) { - this(other.prefix, other.rpBijection, other.path); + this(prefix.prefix, rpRenaming, prefix.path, prefix.pathBijections); } public void setRpBijection(Bijection rpBijection) { this.rpBijection = rpBijection; } + /** + * Add bijection mapping {@code this} to the representative prefix of the node containing {@code suffix}. + * + * @param suffix + * @param bijection + */ + public void putBijection(SymbolicSuffix suffix, Bijection bijection) { + pathBijections.put(suffix, bijection); + } + + public Map> getBijections() { + return pathBijections; + } + + /** + * Add identity bijection mapping {@code this} to the representative prefix of the node containing {@code suffix} + * + * @param suffix + */ + public void putBijection(SymbolicSuffix suffix) { + putBijection(suffix, Bijection.identity(getRegisters())); + } + + /** + * @param suffix + * @return bijection mapping {@code this} to the representative prefix of the ancestor node containing {@code suffix} + */ + public Bijection getBijection(SymbolicSuffix suffix) { + return pathBijections.get(suffix); + } + public SDT[] getSDTs(ParameterizedSymbol ps) { List list = new ArrayList<>(); for (Map.Entry e : path.getSDTs().entrySet()) { diff --git a/src/main/java/de/learnlib/ralib/data/Bijection.java b/src/main/java/de/learnlib/ralib/data/Bijection.java index a3c40b893..1a79067f8 100644 --- a/src/main/java/de/learnlib/ralib/data/Bijection.java +++ b/src/main/java/de/learnlib/ralib/data/Bijection.java @@ -56,6 +56,14 @@ public T get(Object key) { return injection.get(key); } + public T getValue(Object key) { + return get(key); + } + + public T getKey(Object value) { + return surjection.get(value); + } + @Override public T remove(Object key) { T val = get(key); diff --git a/src/main/java/de/learnlib/ralib/data/RegisterValuation.java b/src/main/java/de/learnlib/ralib/data/RegisterValuation.java index e0d2083bf..f82c3dedb 100644 --- a/src/main/java/de/learnlib/ralib/data/RegisterValuation.java +++ b/src/main/java/de/learnlib/ralib/data/RegisterValuation.java @@ -16,6 +16,14 @@ */ package de.learnlib.ralib.data; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Set; + +import de.learnlib.ralib.data.SymbolicDataValue.Register; +import de.learnlib.ralib.words.DataWords; +import de.learnlib.ralib.words.PSymbolInstance; +import net.automatalib.word.Word; /** * A valuation of registers. @@ -32,4 +40,18 @@ public static RegisterValuation copyOf(RegisterValuation other) { return copy; } + public static RegisterValuation fromMemorable(Word prefix, Set memorable) { + ArrayList vals = new ArrayList<>(); + vals.addAll(Arrays.asList(DataWords.valsOf(prefix))); + + RegisterValuation regs = new RegisterValuation(); + for (DataValue d : memorable) { + int id = vals.indexOf(d) + 1; + assert id > 0 : "Memorable is not in prefix"; + Register r = new Register(d.getDataType(), id); + regs.put(r, d); + } + return regs; + } + } diff --git a/src/main/java/de/learnlib/ralib/data/SDTGuardElement.java b/src/main/java/de/learnlib/ralib/data/SDTGuardElement.java index b5086aa4a..7eaeac8e0 100644 --- a/src/main/java/de/learnlib/ralib/data/SDTGuardElement.java +++ b/src/main/java/de/learnlib/ralib/data/SDTGuardElement.java @@ -24,4 +24,11 @@ static boolean isRegister(SDTGuardElement e) { Expression asExpression(); + public static Expression castToExpression(SDTGuardElement e) { + if (isConstant(e) || isDataValue(e) || isSuffixValue(e) || isRegister(e)) { + return (Expression) e; + } + throw new IllegalArgumentException("Unknown SDT guard element class: " + e.getClass()); + } + } diff --git a/src/main/java/de/learnlib/ralib/data/SDTRelabeling.java b/src/main/java/de/learnlib/ralib/data/SDTRelabeling.java index 010431c72..4133e28a2 100644 --- a/src/main/java/de/learnlib/ralib/data/SDTRelabeling.java +++ b/src/main/java/de/learnlib/ralib/data/SDTRelabeling.java @@ -8,7 +8,7 @@ public static SDTRelabeling fromBijection(Bijection in) { return ret; } - public static SDTRelabeling fromMapping(Mapping mapping) { + public static SDTRelabeling fromMapping(Mapping mapping) { SDTRelabeling ret = new SDTRelabeling(); ret.putAll(mapping); return ret; diff --git a/src/main/java/de/learnlib/ralib/learning/MeasuringOracle.java b/src/main/java/de/learnlib/ralib/learning/MeasuringOracle.java index a259ff2e0..bff37222d 100644 --- a/src/main/java/de/learnlib/ralib/learning/MeasuringOracle.java +++ b/src/main/java/de/learnlib/ralib/learning/MeasuringOracle.java @@ -1,6 +1,5 @@ package de.learnlib.ralib.learning; -import java.util.Map; import de.learnlib.ralib.oracles.Branching; import de.learnlib.ralib.oracles.TreeOracle; @@ -43,12 +42,6 @@ public Branching updateBranching(Word prefix, ParameterizedSymb return oracle.updateBranching(prefix, ps, current, sdts); } - @Override - public Map, Boolean> instantiate(Word prefix, SymbolicSuffix suffix, - SDT sdt) { - return oracle.instantiate(prefix, suffix, sdt); - } - @Override public SymbolicSuffixRestrictionBuilder getRestrictionBuilder() { return oracle.getRestrictionBuilder(); diff --git a/src/main/java/de/learnlib/ralib/learning/RaLearningAlgorithmName.java b/src/main/java/de/learnlib/ralib/learning/RaLearningAlgorithmName.java index a56aab231..5d643cee3 100644 --- a/src/main/java/de/learnlib/ralib/learning/RaLearningAlgorithmName.java +++ b/src/main/java/de/learnlib/ralib/learning/RaLearningAlgorithmName.java @@ -3,5 +3,6 @@ public enum RaLearningAlgorithmName { RASTAR, RALAMBDA, - RADT + RADT, + RALAMBDAEQ } diff --git a/src/main/java/de/learnlib/ralib/learning/SymbolicSuffix.java b/src/main/java/de/learnlib/ralib/learning/SymbolicSuffix.java index d7a388b31..90b32b3ff 100644 --- a/src/main/java/de/learnlib/ralib/learning/SymbolicSuffix.java +++ b/src/main/java/de/learnlib/ralib/learning/SymbolicSuffix.java @@ -27,11 +27,14 @@ 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.SymbolicDataValue.SuffixValue; +import de.learnlib.ralib.data.TypedValue; import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.SuffixValueGenerator; import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; +import de.learnlib.ralib.theory.AbstractSuffixValueRestriction; import de.learnlib.ralib.theory.FreshSuffixValue; -import de.learnlib.ralib.theory.SuffixValueRestriction; +import de.learnlib.ralib.theory.TrueRestriction; import de.learnlib.ralib.theory.UnrestrictedSuffixValue; import de.learnlib.ralib.theory.equality.EqualRestriction; import de.learnlib.ralib.words.DataWords; @@ -55,7 +58,7 @@ public class SymbolicSuffix { /** * restrictions on suffix values */ - private final Map restrictions; + private final Map restrictions; /** * are generic suffix optimizations (fresh, equal to prior suffix value or unrestricted) used @@ -72,7 +75,7 @@ public SymbolicSuffix(SymbolicSuffix s) { actions = Word.fromWords(s.actions); restrictions = new LinkedHashMap<>(); - for (Map.Entry r : s.restrictions.entrySet()) + for (Map.Entry r : s.restrictions.entrySet()) restrictions.put(r.getKey(), r.getValue()); } @@ -99,7 +102,7 @@ public SymbolicSuffix(Word prefix, SuffixValueGenerator svgen = new SuffixValueGenerator(); for (DataValue dv : DataWords.valsOf(suffix)) { SuffixValue sv = svgen.next(dv.getDataType()); - SuffixValueRestriction restriction = SuffixValueRestriction.genericRestriction(sv, prefix, suffix, consts); + AbstractSuffixValueRestriction restriction = AbstractSuffixValueRestriction.genericRestriction(sv, prefix, suffix, consts); restrictions.put(sv, restriction); } } @@ -132,7 +135,7 @@ public SymbolicSuffix(Word actions) { for (ParameterizedSymbol ps : actions) { for (DataType t : ps.getPtypes()) { SuffixValue sv = valgen.next(t); - restrictions.put(sv, new UnrestrictedSuffixValue(sv)); + restrictions.put(sv, new TrueRestriction(sv)); } } } @@ -156,14 +159,14 @@ public SymbolicSuffix(Word prefix, SuffixValueGenerator svgen = new SuffixValueGenerator(); for (DataValue dv : DataWords.valsOf(suffix)) { SuffixValue sv = svgen.next(dv.getDataType()); - SuffixValueRestriction restriction = SuffixValueRestriction.genericRestriction(sv, prefix, suffix, consts); + AbstractSuffixValueRestriction restriction = AbstractSuffixValueRestriction.genericRestriction(sv, prefix, suffix, consts); restrictions.put(sv, restriction); } int actionArity = suffix.firstSymbol().getBaseSymbol().getArity(); - for (Map.Entry e : symSuffix.restrictions.entrySet()) { + for (Map.Entry e : symSuffix.restrictions.entrySet()) { SuffixValue sv = e.getKey(); - SuffixValueRestriction restriction = e.getValue(); + AbstractSuffixValueRestriction restriction = e.getValue(); SuffixValue s = new SuffixValue(sv.getDataType(), sv.getId()+actionArity); restrictions.put(s, restriction.shift(actionArity)); } @@ -180,9 +183,9 @@ public SymbolicSuffix(Word prefix, SymbolicSuffix symSuffix, Sy this.restrictions = restrictionBuilder.restrictSuffix(prefix, suffix); int actionArity = suffix.firstSymbol().getBaseSymbol().getArity(); - for (Map.Entry e : symSuffix.restrictions.entrySet()) { + for (Map.Entry e : symSuffix.restrictions.entrySet()) { SuffixValue sv = e.getKey(); - SuffixValueRestriction restriction = e.getValue(); + AbstractSuffixValueRestriction restriction = e.getValue(); SuffixValue s = new SuffixValue(sv.getDataType(), sv.getId()+actionArity); restrictions.put(s, restriction.shift(actionArity)); } @@ -216,16 +219,20 @@ public SymbolicSuffix(Word actions, Map actions, Map restrictions) { + public SymbolicSuffix(Word actions, Map restrictions) { this.genericOptimizations = false; this.actions = actions; - this.restrictions = restrictions; + this.restrictions = new LinkedHashMap<>(restrictions); } - public SuffixValueRestriction getRestriction(SuffixValue sv) { + public AbstractSuffixValueRestriction getRestriction(SuffixValue sv) { return restrictions.get(sv); } + public Map getRestrictions() { + return restrictions; + } + public SuffixValue getSuffixValue(int i) { for (SuffixValue sv : restrictions.keySet()) { if (sv.getId() == i) @@ -244,7 +251,7 @@ public Set getDataValues() { public Set getFreeValues() { Set freeValues = new LinkedHashSet<>(); - for (Map.Entry restr : restrictions.entrySet()) { + for (Map.Entry restr : restrictions.entrySet()) { if (restr.getValue() instanceof UnrestrictedSuffixValue) freeValues.add(restr.getKey()); } @@ -263,17 +270,25 @@ public Word getActions() { public SymbolicSuffix concat(SymbolicSuffix other) { Word actions = this.getActions().concat(other.actions); - Map concatRestr = new LinkedHashMap<>(); + Map concatRestr = new LinkedHashMap<>(); int arity = restrictions.size(); concatRestr.putAll(restrictions); - for (Map.Entry e : other.restrictions.entrySet()) { + for (Map.Entry e : other.restrictions.entrySet()) { SuffixValue sv = new SuffixValue(e.getKey().getDataType(), e.getKey().getId()+arity); - SuffixValueRestriction restr = e.getValue().shift(arity); + AbstractSuffixValueRestriction restr = e.getValue().shift(arity); concatRestr.put(sv, restr); } return new SymbolicSuffix(actions, concatRestr); } + public SymbolicSuffix relabel(Mapping renaming) { + Map renamed = new LinkedHashMap<>(); + for (Map.Entry e : restrictions.entrySet()) { + renamed.put(e.getKey(), e.getValue().relabel(renaming)); + } + return new SymbolicSuffix(actions, renamed); + } + public int length() { return actions.length(); } diff --git a/src/main/java/de/learnlib/ralib/learning/ralambda/SLLambda.java b/src/main/java/de/learnlib/ralib/learning/ralambda/SLLambda.java index 097d0c001..6314c6983 100644 --- a/src/main/java/de/learnlib/ralib/learning/ralambda/SLLambda.java +++ b/src/main/java/de/learnlib/ralib/learning/ralambda/SLLambda.java @@ -7,6 +7,7 @@ import de.learnlib.query.DefaultQuery; import de.learnlib.ralib.ceanalysis.PrefixFinder; import de.learnlib.ralib.ceanalysis.PrefixFinder.Result; +import de.learnlib.ralib.ceanalysis.PrefixFinderFactory; import de.learnlib.ralib.ct.CTAutomatonBuilder; import de.learnlib.ralib.ct.CTHypothesis; import de.learnlib.ralib.ct.ClassificationTree; @@ -27,20 +28,16 @@ public class SLLambda implements RaLearningAlgorithm { - private final ClassificationTree ct; + private final ClassificationTree ct; - private final Constants consts; + 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 SymbolicSuffixRestrictionBuilder restrictionBuilder; private QueryStatistics queryStats; @@ -48,22 +45,30 @@ public class SLLambda implements RaLearningAlgorithm { private final ConstraintSolver solver; + protected final PrefixFinderFactory prefixFinderFactory; + public SLLambda(TreeOracle sulOracle, Map teachers, Constants consts, boolean ioMode, ConstraintSolver solver, + SymbolicSuffixRestrictionBuilder restrBuilder, ParameterizedSymbol ... inputs) { - this.sulOracle = sulOracle; - this.teachers = teachers; this.consts = consts; this.ioMode = ioMode; this.solver = solver; - restrictionBuilder = sulOracle.getRestrictionBuilder(); + restrictionBuilder = restrBuilder; suffixBuilder = new OptimizedSymbolicSuffixBuilder(consts, restrictionBuilder); - counterexamples = new ArrayDeque<>(); + counterexamples = new ArrayDeque<>(); hyp = null; - ct = new ClassificationTree(sulOracle, solver, restrictionBuilder, suffixBuilder, consts, ioMode, inputs); + prefixFinderFactory = new PrefixFinderFactory(sulOracle, teachers, restrictionBuilder, solver, consts); + ct = new ClassificationTree(sulOracle, solver, restrBuilder, suffixBuilder, consts, ioMode, inputs); ct.initialize(); } + public SLLambda(TreeOracle sulOracle, Map teachers, + Constants consts, boolean ioMode, ConstraintSolver solver, + ParameterizedSymbol ... inputs) { + this(sulOracle, teachers, consts, ioMode, solver, new SymbolicSuffixRestrictionBuilder(consts, teachers), inputs); + } + @Override public void learn() { if (hyp == null) { @@ -112,6 +117,10 @@ private void buildHypothesis() { hyp = ab.buildHypothesis(); } + protected PrefixFinder createPrefixFinder() { + return prefixFinderFactory.create(hyp, ct); + } + private boolean analyzeCounterExample() { if (counterexamples.isEmpty()) { return false; @@ -135,13 +144,7 @@ private boolean analyzeCounterExample() { queryStats.analyzeCE(ceWord); } - PrefixFinder prefixFinder = new PrefixFinder(sulOracle, - hyp, - ct, - teachers, - restrictionBuilder, - solver, - consts); + PrefixFinder prefixFinder = createPrefixFinder(); Result res = prefixFinder.analyzeCounterExample(ceWord); diff --git a/src/main/java/de/learnlib/ralib/learning/ralambda/SLLambdaEq.java b/src/main/java/de/learnlib/ralib/learning/ralambda/SLLambdaEq.java new file mode 100644 index 000000000..bfe9bf7e4 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/learning/ralambda/SLLambdaEq.java @@ -0,0 +1,26 @@ +package de.learnlib.ralib.learning.ralambda; + +import java.util.Map; + +import de.learnlib.ralib.ceanalysis.PrefixFinderFactory; +import de.learnlib.ralib.data.Constants; +import de.learnlib.ralib.data.DataType; +import de.learnlib.ralib.oracles.TreeOracle; +import de.learnlib.ralib.oracles.mto.SLLambdaEqRestrictionBuilder; +import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.theory.Theory; +import de.learnlib.ralib.words.ParameterizedSymbol; + +public class SLLambdaEq extends SLLambda { + + public SLLambdaEq(TreeOracle sulOracle, Map teachers, Constants consts, boolean ioMode, + ConstraintSolver solver, boolean improvedRegClosed, ParameterizedSymbol ... inputs) { + super(sulOracle, teachers, consts, ioMode, solver, new SLLambdaEqRestrictionBuilder(consts, teachers, solver, improvedRegClosed), inputs); + prefixFinderFactory.setPrefixFinderType(PrefixFinderFactory.PrefixFinderType.Eq); + } + + public SLLambdaEq(TreeOracle sulOracle, Map teachers, Constants consts, boolean ioMode, + ConstraintSolver solver, ParameterizedSymbol ... inputs) { + this(sulOracle, teachers, consts, ioMode, solver, false, inputs); + } +} diff --git a/src/main/java/de/learnlib/ralib/oracles/TreeOracle.java b/src/main/java/de/learnlib/ralib/oracles/TreeOracle.java index ea9629bc6..8fb6cf9d2 100644 --- a/src/main/java/de/learnlib/ralib/oracles/TreeOracle.java +++ b/src/main/java/de/learnlib/ralib/oracles/TreeOracle.java @@ -16,7 +16,6 @@ */ package de.learnlib.ralib.oracles; -import java.util.Map; import de.learnlib.ralib.learning.SymbolicSuffix; import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; @@ -69,9 +68,6 @@ Branching getInitialBranching(Word prefix, Branching updateBranching(Word prefix, ParameterizedSymbol ps, Branching current, SDT... sdts); - Map, Boolean> instantiate(Word prefix, - SymbolicSuffix suffix, SDT sdt); - SymbolicSuffixRestrictionBuilder getRestrictionBuilder(); } 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 8b142b7f2..cfb168527 100644 --- a/src/main/java/de/learnlib/ralib/oracles/mto/MultiTheoryTreeOracle.java +++ b/src/main/java/de/learnlib/ralib/oracles/mto/MultiTheoryTreeOracle.java @@ -16,7 +16,6 @@ */ package de.learnlib.ralib.oracles.mto; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -24,7 +23,6 @@ import java.util.LinkedHashSet; 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; @@ -44,7 +42,6 @@ import de.learnlib.ralib.data.SymbolicDataValue; import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; import de.learnlib.ralib.data.WordValuation; -import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.ParameterGenerator; import de.learnlib.ralib.learning.SymbolicSuffix; import de.learnlib.ralib.oracles.Branching; import de.learnlib.ralib.oracles.DataWordOracle; @@ -93,7 +90,7 @@ public MultiTheoryTreeOracle(DataWordOracle oracle, Map teache @Override public SDT treeQuery(Word prefix, SymbolicSuffix suffix) { if (!isValid(prefix)) { - return makeRejectingSDT(suffix); + return SDT.makeRejectingSDT(suffix); } SDT sdt = treeQuery(prefix, suffix, new WordValuation(), constants, new SuffixValuation()); //System.out.println(sdt); @@ -395,50 +392,6 @@ private Mapping buildValuation(SuffixValuation suf return valuation; } - @Override - public Map, Boolean> instantiate(Word prefix, SymbolicSuffix suffix, - SDT sdt) { - - Map, Boolean> words = new LinkedHashMap, Boolean>(); - instantiate(words, prefix, suffix, sdt, 0, 0, - new SuffixValuation(), new ParameterGenerator(), new SuffixValuation(), new ParameterGenerator()); - return words; - } - - private void instantiate(Map, Boolean> words, Word prefix, - SymbolicSuffix suffix, SDT sdt, int aidx, int pidx, - SuffixValuation pval, ParameterGenerator pgen, SuffixValuation gpval, ParameterGenerator gpgen) { - if (aidx == suffix.getActions().length()) { - words.put(prefix, sdt.isAccepting()); - } else { - ParameterizedSymbol ps = suffix.getActions().getSymbol(aidx); - if (ps.getArity() == pidx) { - DataValue[] vals = pval.values().toArray(new DataValue [] {}); - PSymbolInstance psi = new PSymbolInstance(ps, vals); - Word newPrefix = prefix.append(psi); - instantiate(words, newPrefix, suffix, sdt, aidx+1, 0, new SuffixValuation(), new ParameterGenerator(), gpval, gpgen); - } else { - SuffixValue p = new SuffixValue(ps.getPtypes()[pidx], pgen.next(ps.getPtypes()[pidx]).getId()); - SuffixValue gp = new SuffixValue( ps.getPtypes()[pidx], gpgen.next(ps.getPtypes()[pidx]).getId() ); - Theory t = teachers.get(ps.getPtypes()[pidx]); - for (Map.Entry entry : sdt.getChildren().entrySet()) { - DataValue val = t.instantiate(prefix, ps, gpval, constants, entry.getKey(), p, Collections.emptySet()); - SuffixValuation newPval = new SuffixValuation(); - newPval.putAll(pval); - newPval.put(p, val); - SuffixValuation newGpval = new SuffixValuation(); - newGpval.putAll(gpval); - newGpval.put(gp, val); - ParameterGenerator newPgen = new ParameterGenerator(); - newPgen.set(pgen); - ParameterGenerator newGpgen = new ParameterGenerator(); - newGpgen.set(gpgen); - instantiate(words, prefix, suffix, entry.getValue(), aidx, pidx+1, newPval, newPgen, newGpval, newGpgen); - } - } - } - } - /** * This method computes the initial branching for an SDT. It reuses existing * valuations where possible. @@ -497,29 +450,4 @@ private boolean isValid(Word word) { return true; } - - private SDT makeRejectingSDT(SymbolicSuffix suffix) { - Queue types = new ArrayDeque<>(); - 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/oracles/mto/OptimizedSymbolicSuffixBuilder.java b/src/main/java/de/learnlib/ralib/oracles/mto/OptimizedSymbolicSuffixBuilder.java index 83259939f..700224928 100644 --- a/src/main/java/de/learnlib/ralib/oracles/mto/OptimizedSymbolicSuffixBuilder.java +++ b/src/main/java/de/learnlib/ralib/oracles/mto/OptimizedSymbolicSuffixBuilder.java @@ -11,13 +11,15 @@ import de.learnlib.ralib.data.SymbolicDataValue; import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; import de.learnlib.ralib.data.util.SymbolicDataValueGenerator; +import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.SuffixValueGenerator; import de.learnlib.ralib.learning.SymbolicSuffix; import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.theory.AbstractSuffixValueRestriction; import de.learnlib.ralib.theory.SDT; import de.learnlib.ralib.theory.SDTGuard; import de.learnlib.ralib.theory.SDTLeaf; -import de.learnlib.ralib.theory.SuffixValueRestriction; import de.learnlib.ralib.theory.UnrestrictedSuffixValue; +import de.learnlib.ralib.theory.equality.EqualRestriction; import de.learnlib.ralib.words.DataWords; import de.learnlib.ralib.words.PSymbolInstance; import de.learnlib.ralib.words.ParameterizedSymbol; @@ -54,16 +56,15 @@ public OptimizedSymbolicSuffixBuilder(Constants consts, SymbolicSuffixRestrictio * @return a new suffix formed by prepending suffix with the last symbol of prefix */ public SymbolicSuffix extendSuffix(Word prefix, SDT sdt, SymbolicSuffix suffix, DataValue... values) { - Word suffixActions = suffix.getActions(); if (values.length > 0) { - SymbolicSuffix s = extendSuffixRevealingRegisters(prefix, sdt, suffixActions, values); + SymbolicSuffix s = extendSuffixRevealingRegisters(prefix, sdt, suffix, values); return s; } Set> paths = sdt.getAllPaths(new ArrayList<>()).keySet(); SymbolicSuffix coalesced = null; for (List path : paths) { - SymbolicSuffix extended = extendSuffix(prefix, path, suffixActions); + SymbolicSuffix extended = extendSuffix(prefix, path, suffix); if (coalesced == null) { coalesced = extended; } else { @@ -73,23 +74,24 @@ public SymbolicSuffix extendSuffix(Word prefix, SDT sdt, Symbol return coalesced; } - private SymbolicSuffix extendSuffixRevealingRegisters(Word prefix, SDT sdt, Word suffixActions, DataValue[] registers) { + private SymbolicSuffix extendSuffixRevealingRegisters(Word prefix, SDT sdt, SymbolicSuffix suffix, DataValue[] registers) { SDT prunedSDT = pruneSDT(sdt, registers); Set> paths = prunedSDT.getAllPaths(new ArrayList<>()).keySet(); assert paths.size() > 0 : "All paths in SDT were pruned"; - SymbolicSuffix suffix = null; + SymbolicSuffix extendedSuffix = null; for (List path : paths) { - SymbolicSuffix extended = extendSuffix(prefix, path, suffixActions); - if (suffix == null) { - suffix = extended; + SymbolicSuffix extended = extendSuffix(prefix, path, suffix); + if (extendedSuffix == null) { + extendedSuffix = extended; } else { - suffix = mergeSuffixes(extended, suffix); + extendedSuffix = mergeSuffixes(extended, extendedSuffix); } } - return suffix; + return extendedSuffix; } - SymbolicSuffix extendSuffix(Word prefix, List sdtPath, Word suffixActions) { + SymbolicSuffix extendSuffix(Word prefix, List sdtPath, SymbolicSuffix suffix) { + Word suffixActions = suffix.getActions(); Word sub = prefix.prefix(prefix.length()-1); PSymbolInstance action = prefix.lastSymbol(); ParameterizedSymbol actionSymbol = action.getBaseSymbol(); @@ -97,7 +99,7 @@ SymbolicSuffix extendSuffix(Word prefix, List sdtPath int actionArity = actionSymbol.getArity(); int subArity = DataWords.paramValLength(sub); - Map restrictions = new LinkedHashMap<>(); + Map restrictions = new LinkedHashMap<>(); for (SuffixValue sv : actionSuffix.getDataValues()) { restrictions.put(sv, actionSuffix.getRestriction(sv)); } @@ -122,7 +124,13 @@ SymbolicSuffix extendSuffix(Word prefix, List sdtPath SuffixValue newSV = new SuffixValue(oldSV.getDataType(), oldSV.getId()+actionArity); renaming.put(oldSV, newSV); SDTGuard renamedGuard = SDTGuard.relabel(guard, renaming); - SuffixValueRestriction restr = restrictionBuilder.restrictSuffixValue(renamedGuard, restrictions); + if (guard instanceof SDTGuard.SDTTrueGuard) { + if (suffix.getRestrictions().get(oldSV) instanceof EqualRestriction er) { + SuffixValue equalParam = er.getEqualParameter(); + renamedGuard = SDTGuard.shift(new SDTGuard.EqualityGuard(oldSV, equalParam), actionArity); + } + } + AbstractSuffixValueRestriction restr = restrictionBuilder.restrictSuffixValue(renamedGuard, restrictions); restrictions.put(newSV, restr); } @@ -203,10 +211,10 @@ private boolean guardOnRegisters(SDTGuard guard, DataValue[] registers) { private SymbolicSuffix mergeSuffixes(SymbolicSuffix suffix1, SymbolicSuffix suffix2) { assert suffix1.getActions().equals(suffix2.getActions()); - Map restrictions = new LinkedHashMap<>(); + Map restrictions = new LinkedHashMap<>(); for (SuffixValue sv : suffix1.getDataValues()) { - SuffixValueRestriction restr1 = suffix1.getRestriction(sv); - SuffixValueRestriction restr2 = suffix2.getRestriction(sv); + AbstractSuffixValueRestriction restr1 = suffix1.getRestriction(sv); + AbstractSuffixValueRestriction restr2 = suffix2.getRestriction(sv); if (restr1.equals(restr2)) { restrictions.put(sv, restr1); } else { @@ -329,15 +337,15 @@ public SymbolicSuffix extendDistinguishingSuffix(Word prefix1, * based on the SDTs that revealed the source of the inequivalence. */ public SymbolicSuffix distinguishingSuffixFromSDTs(Word prefix1, SDT sdt1, - Word prefix2, SDT sdt2, Word suffixActions, ConstraintSolver solver) { + Word prefix2, SDT sdt2, SymbolicSuffix suffix, ConstraintSolver solver) { Mapping valuation = buildValuation(consts); - SymbolicSuffix suffix = distinguishingSuffixFromSDTs(prefix1, sdt1, prefix2, sdt2, valuation, suffixActions, solver); - return suffix; + SymbolicSuffix extendedSuffix = distinguishingSuffixFromSDTs(prefix1, sdt1, prefix2, sdt2, valuation, suffix, solver); + return extendedSuffix; } private SymbolicSuffix distinguishingSuffixFromSDTs(Word prefix1, SDT sdt1, Word prefix2, SDT sdt2, - Mapping valuation, Word suffixActions, ConstraintSolver solver) { + Mapping valuation, SymbolicSuffix suffix, ConstraintSolver solver) { SymbolicSuffix best = null; for (boolean b : new boolean [] {true, false}) { // we check for paths @@ -348,8 +356,8 @@ private SymbolicSuffix distinguishingSuffixFromSDTs(Word prefix for (List pathSdt2 : pathsSdt2) { Expression expr2 = toGuardExpression(pathSdt2); if (solver.isSatisfiable(ExpressionUtil.and(expr1, expr2), valuation)) { - SymbolicSuffix suffix = buildOptimizedSuffix(prefix1, pathSdt1, prefix2, pathSdt2, suffixActions); - best = pickBest(best, suffix); + SymbolicSuffix extendedSuffix = buildOptimizedSuffix(prefix1, pathSdt1, prefix2, pathSdt2, suffix); + best = pickBest(best, extendedSuffix); } } } @@ -360,25 +368,25 @@ private SymbolicSuffix distinguishingSuffixFromSDTs(Word prefix private SymbolicSuffix buildOptimizedSuffix(Word prefix1, List pathSdt1, Word prefix2, List pathSdt2, - Word suffixActions) { - SymbolicSuffix suffix1 = extendSuffix(prefix1, pathSdt1, suffixActions); - SymbolicSuffix suffix2 = extendSuffix(prefix2, pathSdt2, suffixActions); + SymbolicSuffix suffix) { + SymbolicSuffix extendedSuffix1 = extendSuffix(prefix1, pathSdt1, suffix); + SymbolicSuffix extendedSuffix2 = extendSuffix(prefix2, pathSdt2, suffix); - return coalesceSuffixes(suffix1, suffix2); + return coalesceSuffixes(extendedSuffix1, extendedSuffix2); } SymbolicSuffix coalesceSuffixes(SymbolicSuffix suffix1, SymbolicSuffix suffix2) { assert suffix1.getActions().equals(suffix2.getActions()); - Map restrictions = new LinkedHashMap<>(); + Map restrictions = new LinkedHashMap<>(); SymbolicDataValueGenerator.SuffixValueGenerator sgen = new SymbolicDataValueGenerator.SuffixValueGenerator(); for (int i = 0; i < DataWords.paramLength(suffix1.getActions()); i++) { DataType type = suffix1.getDataValue(i+1).getDataType(); SuffixValue sv = sgen.next(type); - SuffixValueRestriction restr1 = suffix1.getRestriction(sv); - SuffixValueRestriction restr2 = suffix2.getRestriction(sv); - SuffixValueRestriction restr = restr1.merge(restr2, restrictions); + AbstractSuffixValueRestriction restr1 = suffix1.getRestriction(sv); + AbstractSuffixValueRestriction restr2 = suffix2.getRestriction(sv); + AbstractSuffixValueRestriction restr = restr1.merge(restr2, restrictions); restrictions.put(sv, restr); } @@ -415,4 +423,16 @@ private Mapping buildValuation(Constants constants constants.forEach((c, dv) -> valuation.put(c, dv)); return valuation; } + + public SymbolicSuffix unrestrictedSuffix(Word actions) { + Map restrs = new LinkedHashMap<>(); + SuffixValueGenerator sgen = new SuffixValueGenerator(); + for (ParameterizedSymbol action : actions) { + for (DataType t : action.getPtypes()) { + SuffixValue s = sgen.next(t); + restrs.put(s, new UnrestrictedSuffixValue(s)); + } + } + return new SymbolicSuffix(actions, restrs); + } } diff --git a/src/main/java/de/learnlib/ralib/oracles/mto/SLLambdaEqRestrictionBuilder.java b/src/main/java/de/learnlib/ralib/oracles/mto/SLLambdaEqRestrictionBuilder.java new file mode 100644 index 000000000..4f365f3f5 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/oracles/mto/SLLambdaEqRestrictionBuilder.java @@ -0,0 +1,1198 @@ +package de.learnlib.ralib.oracles.mto; + +import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +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.ct.Prefix; +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.SDTGuardElement; +import de.learnlib.ralib.data.SDTRelabeling; +import de.learnlib.ralib.data.SymbolicDataValue; +import de.learnlib.ralib.data.SymbolicDataValue.Constant; +import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; +import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.SuffixValueGenerator; +import de.learnlib.ralib.learning.SymbolicSuffix; +import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.theory.AbstractSuffixValueRestriction; +import de.learnlib.ralib.theory.DisjunctionRestriction; +import de.learnlib.ralib.theory.ElementRestriction; +import de.learnlib.ralib.theory.FreshSuffixValue; +import de.learnlib.ralib.theory.SDT; +import de.learnlib.ralib.theory.SDTGuard; +import de.learnlib.ralib.theory.SuffixValueRestriction; +import de.learnlib.ralib.theory.Theory; +import de.learnlib.ralib.theory.TrueRestriction; +import de.learnlib.ralib.theory.equality.EqualityRestriction; +import de.learnlib.ralib.theory.equality.EqualityTheory; +import de.learnlib.ralib.theory.equality.UnmappedEqualityRestriction; +import de.learnlib.ralib.words.DataWords; +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; + +public class SLLambdaEqRestrictionBuilder extends SymbolicSuffixRestrictionBuilder { + + private boolean useImprovedRegClosedOpt = false; + + protected final ConstraintSolver solver; + + public SLLambdaEqRestrictionBuilder(SymbolicSuffixRestrictionBuilder restrBuilder, ConstraintSolver solver) { + this(restrBuilder.consts, restrBuilder.teachers, solver); + } + + public SLLambdaEqRestrictionBuilder(Constants consts, Map teachers, ConstraintSolver solver) { + super(consts, teachers); + if (teachers == null) { + throw new IllegalArgumentException("Non-null argument expected"); + } + this.solver = solver; + } + + public SLLambdaEqRestrictionBuilder(Constants consts, Map teachers, ConstraintSolver solver, boolean useImprovedRegClosedOpt) { + this(consts, teachers, solver); + this.useImprovedRegClosedOpt = useImprovedRegClosedOpt; + } + + /** + * Restrict suffix value by examining relation between corresponding data values in {@code suffix} + * and values in {@code prefix} and {@code u} during counterexample analysis. + *
+ * Note that restrictions computed by this method are specific to the counterexample and should + * not be used for suffixes added to the classification tree. + *

+ * This method is currently only implemented for the {@link EqualityTheory} + * + * @param prefix prefix of counterexample + * @param suffix suffix of counterexample + * @param u short prefix in classification tree corresponding to {@code prefix} + * @param prefixValuation valuation after a run of the hypothesis over {@code prefix} + * @param uValuation valuation after a run of the hypothesis over {@code u} + * @return + */ + public Map restrictSuffix(Word prefix, + Word suffix, + Word u, + RegisterValuation prefixValuation, + RegisterValuation uValuation) { + Map restrs = new LinkedHashMap<>(); + DataValue[] suffixVals = DataWords.valsOf(suffix); + for (int i = 0; i < suffixVals.length; i++) { + SuffixValue sv = new SuffixValue(suffixVals[i].getDataType(), i+1); + assert teachers != null; + Theory theory = teachers.get(suffixVals[i].getDataType()); + restrs.put(sv, theory.restrictSuffixValue(sv, prefix, suffix, u, prefixValuation, uValuation, consts)); + } + return restrs; + } + + /** + * Construct a restricted symbolic suffix with restrictions derived by examining relations + * between data values in {@code suffix} and data values in {@code prefix} and {@code u} + * during counterexample analysis. + * Note that restrictions computed by this method are specific to the counterexample and should + * not be used for suffixes added to the classification tree. + *

+ * This method is currently only implemented for the {@link EqualityTheory}. + * + * @param prefix prefix of counterexample + * @param suffix suffix of counterexample + * @param u short prefix in classification tree corresponding to {@code prefix} + * @param prefixValuation valuation after a run of the hypothesis over {@code prefix} + * @param uValuation valuation after a run of the hypothesis over {@code u} + * @return symbolic suffix with restrictions respecting the relations between data values in counterexample + */ + public SymbolicSuffix constructRestrictedSuffix(Word prefix, + Word suffix, + Word u, + RegisterValuation prefixValuation, + RegisterValuation uValuation) { + return new SymbolicSuffix(DataWords.actsOf(suffix), + restrictSuffix(prefix, suffix, u, prefixValuation, uValuation)); + } + + /** + * Construct a restricted symbolic suffix with restrictions derived by examining relations + * between data values in {@code suffix} and data values in {@code prefix} and {@code u} + * during counterexample analysis. + * Note that restrictions computed by this method are specific to the counterexample and should + * not be used for suffixes added to the classification tree. + *

+ * This method is currently only implemented for the {@link EqualityTheory}. + * + * @param prefix prefix of counterexample + * @param suffix suffix of counterexample + * @param u1 short prefix in classification tree corresponding to {@code prefix} + * @param u2 other short prefix in same leaf as {@code u1} + * @param prefixValuation valuation after a run of the hypothesis over {@code prefix} + * @param u1Valuation valuation after a run of the hypothesis over {@code u1} + * @param u2Valuation valuation after a run of the hypothesis over {@code u2} + * @return symbolic suffix with restrictions respecting the relations between data values in counterexample + */ + public SymbolicSuffix constructRestrictedSuffix(Word prefix, + Word suffix, + Word u1, + Word u2, + RegisterValuation prefixValuation, + RegisterValuation u1Valuation, + RegisterValuation u2Valuation) { + Map restr1 = restrictSuffix(prefix, suffix, u1, prefixValuation, u1Valuation); + Map restr2 = restrictSuffix(prefix, suffix, u2, prefixValuation, u2Valuation); + Map restr = new LinkedHashMap<>(); + for (SuffixValue s : restr1.keySet()) { + AbstractSuffixValueRestriction r1 = restr1.get(s); + AbstractSuffixValueRestriction r2 = restr2.get(s); + if (!r1.equals(r2)) { + restr.put(s, DisjunctionRestriction.create(s, r1, r2)); + } else { + restr.put(s, r1); + } + } + return new SymbolicSuffix(DataWords.actsOf(suffix), restr); + } + + /** + * Concretize the restrictions of {@code suffix} according to {@code valuations}. A concretized + * restriction is constructed for a specific prefix, and will usually be expressed as + * guard relations between suffix values and data values. + * + * @param suffix restricted symbolic suffix + * @param valuations valuations of registers and prefix parameters + * @return {@code suffix} with conretized restrictions + */ + @SafeVarargs + public static SymbolicSuffix concretize(SymbolicSuffix suffix, Mapping ... valuations) { + Mapping mapping = new Mapping<>(); + for (Mapping m : valuations) { + mapping.putAll(m); + } + return concretize(suffix, mapping); + } + + /** + * Concretize the restrictions of {@code suffix} according to {@code mapping}. A concretized + * restriction is constructed for a specific prefix, and will usually be expressed as + * guard relations between suffix values and data values. + * + * @param suffix restricted symbolic suffix + * @param mapping mapping of registers and prefix parameters + * @return {@code suffix} with conretized restrictions + */ + public static SymbolicSuffix concretize(SymbolicSuffix suffix, Mapping mapping) { + Map newRestrs = new LinkedHashMap<>(); + for (SuffixValue s : suffix.getValues()) { + AbstractSuffixValueRestriction restr = suffix.getRestriction(s); + AbstractSuffixValueRestriction concrRestr = restr.concretize(mapping); + newRestrs.put(s, concrRestr); + } + return new SymbolicSuffix(suffix.getActions(), newRestrs); + } + + /** + * Checks whether {@code av} has a restriction on an unmapped data value + * + * @param av + * @param mem + * @return {@code true} if and only if the restrictions of {@code av} contain any data values not in {@code mem} + */ + public boolean hasUnmappedRestrictionValue(SymbolicSuffix av, Set mem) { + Set restrVals = getDataValueElements(av.getRestrictions()); + for (DataValue d : restrVals) { + if (teachers != null && teachers.get(d.getDataType()) instanceof EqualityTheory && !mem.contains(d)) { + return true; + } + } + return false; + } + + /** + * Extend {@code suffix} by prepending it with the last symbol of {@code u1Extended} (hereafter + * known as the action). Note that the last symbol of {@code u2Extended} must have the same + * base symbol. The extended symbolic suffix will be restricted in such a way that the + * restrictions of the action respect all possible relations between its data values and + * data values in the prefix. Restrictions for the {@code suffix} part of the extended suffix + * will be restricted such that the extended suffix will be able to separate {@code u1} and + * {@code u2}. Any data values in the restrictions will be mapped to the representative + * prefix of the leaf containing {@code u1} and {@code u2}. + *

+ * This method assumes the following: + *

    + *
  • {@code u1Extended} and {@code u2Extended} are one-symbol extensions of {@code u1} + * and {@code u2}, respectively, with the same base symbol
  • + *
  • {@code sdt1} and {@code sdt2} were constructed from a tree query with + * {@code u1Extended} and {@code u2Extended}, respectively, and {@code suffix}
  • + *
  • {@code sdt1} and {@code sdt2} are not equivalent under any bijection (thereby + * separating {@code u1Extended} and {@code u2Extended})
  • + *
  • {@code u1} and {@code u2} are in the same leaf
  • + *
+ *

+ * Note that restrictions are currently only implemented for the {@link EqualityTheory}. + * + * @param u1 a short prefix + * @param u1Extended one-symbol extension of {@code u1} + * @param u2 a different short prefix in the same leaf as {@code u1} + * @param u2Extended one-symbol extension of {@code u2} + * @param suffix restricted symbolic suffix separating {@code u1Extended} and {@code u2Extended} + * @param sdt1 SDT from a tree query with {@code u1Extended} and {@code suffix} + * @param sdt2 SDT from a tree query with {@code u2Extended} and {@code suffix} + * @return restricted symbolic suffix separating {@code u1} and {@code u2} + */ + public SymbolicSuffix extendSuffix(Prefix u1, Prefix u1Extended, Prefix u2, Prefix u2Extended, SymbolicSuffix suffix, SDT sdt1, SDT sdt2) { + ParameterizedSymbol action = u1Extended.lastSymbol().getBaseSymbol(); + Word suffixActions = suffix.getActions(); + + if (!isEqualityTheory(DataWords.typesOf(suffixActions))) { + throw new IllegalArgumentException("Only supported for equality theory"); + } + + SuffixValueGenerator sgen = new SuffixValueGenerator(); + + if (teachers == null) { + return unrestricted(action, suffix); + } + + // compute restrictions for the suffix values of the action + Map actionRestrictions = new LinkedHashMap<>(); + for (DataType type : action.getPtypes()) { + SuffixValue s = sgen.next(type); + Theory theory = teachers.get(type); + if (theory instanceof EqualityTheory) { + AbstractSuffixValueRestriction r = restrictSuffixValue(s, u1, u1Extended.lastSymbol(), u1.getRegisters(), consts); + actionRestrictions.put(s, r); + } else { + actionRestrictions.put(s, new TrueRestriction(s)); + } + } + // relabel to the representative prefix of u1 (and u2) + actionRestrictions = AbstractSuffixValueRestriction.relabel(actionRestrictions, u1.getRpBijection().toVarMapping()); + + // restrictions for suffix + Map suffixRestrictions = restrictionFromSDTs(sdt1, sdt2, + u1Extended, u2Extended, + u1.getRpBijection(), u2.getRpBijection(), + false, consts, suffix, solver); + suffixRestrictions = AbstractSuffixValueRestriction.relabel(suffixRestrictions, u1.getRpBijection().toVarMapping()); + + Map restrictions = new LinkedHashMap<>(); + restrictions.putAll(actionRestrictions); + restrictions.putAll(suffixRestrictions); + + Word actions = DataWords.concatenate(Word.fromSymbols(action), suffixActions); + return new SymbolicSuffix(actions, restrictions); + } + + /** + * Extend {@code suffix} by prepending it with the last symbol of {@code uIf} (hereafter + * known as the action). Note that the last symbol of {@code uElse} must have the same + * base symbol. The extended symbolic suffix will be restricted in such a way that the + * restrictions of the action respect all possible relations between its data values and + * data values in the prefix. Restrictions for the {@code suffix} part of the extended suffix + * will be restricted such that the extended suffix will be able to separate {@code u1} and + * {@code u2}. Any data values in the restrictions will be mapped to the representative + * prefix of the leaf containing {@code u}. + *

+ * This method assumes the following: + *

    + * + *
  • {@code uIf} and {@code uElse} are one-symbol extensions of {@code u}, specifically + *
      + *
    • {@code uIf} is the one-symbol extension of the "if-guard", i.e., an equality + * guard on data values in {@code u}
    • + *
    • {@code uElse} is the one-symbol extension of the "else-guard", i.e., the guard + * corresponding to a fresh data value
    • + *
    + *
  • + *
  • {@code sdtIf} and {@code sdtElse} were constructed from a tree query with + * {@code uIf} and {@code uElse}, respectively, and {@code suffix}
  • + *
  • {@code sdtIf} and {@code sdtElse} are not equivalent
  • + *
+ *

+ * Note that restrictions are currently only implemented for the {@link EqualityTheory}. + * + * @param u a short prefix + * @param uIf one-symbol extension of {@code u} corresponding to an if-guard + * @param uElse one-symbol extension of {@code u} corresponding to an else-guard + * @param suffix restricted symbolic suffix separating {@code uIf} and {@code uElse} + * @param sdtIf SDT from a tree query with {@code uIf} and {@code suffix} + * @param sdtElse SDT from a tree query with {@code uElse} and {@code suffix} + * @return restricted symbolic suffix, extended from {@code suffix}, which reveals the if-guard of {@code uIf} + */ + public SymbolicSuffix extendSuffix(Prefix u, Prefix uIf, Prefix uElse, SymbolicSuffix suffix, SDT sdtIf, SDT sdtElse, boolean sameLeaf) { + PSymbolInstance symbol = uIf.lastSymbol(); + ParameterizedSymbol action = symbol.getBaseSymbol(); + assert uElse.lastSymbol().getBaseSymbol().equals(action) : "Extensions do not match"; + Word suffixActions = suffix.getActions(); + + if (!isEqualityTheory(DataWords.typesOf(suffixActions))) { + throw new IllegalArgumentException("Only supported for equality theory"); + } + + SuffixValueGenerator sgen = new SuffixValueGenerator(); + + if (teachers == null) { + return unrestricted(action, suffix); + } + + // compute restrictions for action + Map actionRestrictions = new LinkedHashMap<>(); + for (DataType type : action.getPtypes()) { + SuffixValue s = sgen.next(type); + Theory theory = teachers.get(type); + if (theory instanceof EqualityTheory) { + AbstractSuffixValueRestriction rIf = restrictSuffixValue(s, u, symbol, u.getRegisters(), consts); + // must include fresh in order to allow extended suffix to reveal guard + AbstractSuffixValueRestriction r = DisjunctionRestriction.create(s, rIf, new FreshSuffixValue(s)); + actionRestrictions.put(s, r); + } else { + actionRestrictions.put(s, new TrueRestriction(s)); + } + } + // relabel to representative prefix of u + actionRestrictions = AbstractSuffixValueRestriction.relabel(actionRestrictions, u.getRpBijection().toVarMapping()); + + // compute restrictions for suffix part + Map suffixRestrictions = restrictionFromSDTs(sdtIf, sdtElse, + uIf, uElse, + u.getRpBijection(), u.getRpBijection(), + sameLeaf, consts, suffix, solver); + suffixRestrictions = AbstractSuffixValueRestriction.relabel(suffixRestrictions, u.getRpBijection().toVarMapping()); + + Map restrictions = new LinkedHashMap<>(); + restrictions.putAll(actionRestrictions); + restrictions.putAll(suffixRestrictions); + + Word actions = DataWords.concatenate(Word.fromSymbols(action), suffixActions); + return new SymbolicSuffix(actions, restrictions); + } + + /** + * Extend {@code suffix} by prepending it with the last symbol of {@code uExtended} (hereafter + * known as the action). The extended symbolic suffix will be restricted in such a way that the + * restrictions of the action respect all possible relations between its data values and + * data values in the prefix. Restrictions for the {@code suffix} part of the extended suffix + * will be restricted such that the extended suffix reveals all data values in {@code sdt} + * which are not memorable in {@code u}. + * + * @param u a short prefix + * @param uExtended a one-symbol extension of {@code u} + * @param suffix a restricted symbolic suffix revealing data values in {@code uExtended} that are not memorable in {@code u} + * @param sdt SDT from a tree query with {@code uExtended} and {@code suffix} + * @return a restricted suffix, extended from {@code suffix}, which reveals unmapped data values in {@code u} + */ + public SymbolicSuffix extendSuffix(Prefix u, Prefix uExtended, SymbolicSuffix suffix, SDT sdt) { + ParameterizedSymbol action = uExtended.lastSymbol().getBaseSymbol(); + Word suffixActions = suffix.getActions(); + List uVals = Arrays.asList(DataWords.valsOf(u)); + + if (!isEqualityTheory(DataWords.typesOf(suffixActions))) { + throw new IllegalArgumentException("Only supported for equality theory"); + } + + if (teachers == null) { + return unrestricted(action, suffix); + } + + Set missingRegisters = new LinkedHashSet<>(sdt.getDataValues()); + missingRegisters.removeAll(u.getRegisters()); + + SuffixValueGenerator sgen = new SuffixValueGenerator(); + + // compute restrictions for action + Map actionRestrictions = new LinkedHashMap<>(); + for (DataValue d : uExtended.lastSymbol().getParameterValues()) { + DataType type = d.getDataType(); + SuffixValue s = sgen.next(type); + Theory theory = teachers.get(type); + if (theory instanceof EqualityTheory) { + AbstractSuffixValueRestriction restr = uVals.contains(d) ? + (u.getRegisters().contains(d) ? new EqualityRestriction(s, Set.of(d)) : + DisjunctionRestriction.create(s, new UnmappedEqualityRestriction(s), new FreshSuffixValue(s))) : + new FreshSuffixValue(s); + actionRestrictions.put(s, restr); + } else { + actionRestrictions.put(s, new TrueRestriction(s)); + } + } + // relabel to representative prefix of u + actionRestrictions = AbstractSuffixValueRestriction.relabel(actionRestrictions, u.getRpBijection().toVarMapping()); + + // compute restrictions for the suffix part + Map suffixRestrictions = restrictionFromSDT(sdt, u, uExtended, u.getRpBijection(), consts, suffix, solver, useImprovedRegClosedOpt); + suffixRestrictions = AbstractSuffixValueRestriction.relabel(suffixRestrictions, u.getRpBijection().toVarMapping()); + + Map restrictions = new LinkedHashMap<>(); + restrictions.putAll(actionRestrictions); + restrictions.putAll(suffixRestrictions); + + Word actions = DataWords.concatenate(Word.fromSymbols(action), suffixActions); + return new SymbolicSuffix(actions, restrictions); + } + + /** + * @param action + * @param suffix + * @return unrestricted symbolic suffix constructed by prepending {@code suffix} with {@code action} + */ + private SymbolicSuffix unrestricted(ParameterizedSymbol action, SymbolicSuffix suffix) { + DataType[] actionTypes = action.getPtypes(); + + SuffixValueGenerator sgen = new SuffixValueGenerator(); + Map restrictions = new LinkedHashMap<>(); + + for (int i = 0; i < actionTypes.length; i++) { + SuffixValue s = sgen.next(actionTypes[i]); + restrictions.put(s, new TrueRestriction(s)); + } + + for (Map.Entry e : suffix.getRestrictions().entrySet()) { + SuffixValue s = sgen.next(e.getKey().getDataType()); + restrictions.put(s, new TrueRestriction(s)); + } + + Word actions = DataWords.concatenate(Word.fromSymbols(action), suffix.getActions()); + return new SymbolicSuffix(actions, restrictions); + } + + /** + * @param types + * @return {@code true} if and only if all data types of {@code types} are associated with the {@link EqualityTheory} + */ + private boolean isEqualityTheory(DataType[] types) { + for (DataType type : types) { + Theory theory = teachers.get(type); + if (theory == null || !(theory instanceof EqualityTheory)) { + return false; + } + } + return true; + } + + public void setUseImprovedRegClosedOpt(boolean useImprovedRegClosedOpt) { + this.useImprovedRegClosedOpt = useImprovedRegClosedOpt; + } + + + /** + * Compute restriction on {@code suffixValue} by examining the relationship between its + * corresponding data value in {@code action} and data values in {@code u}. + * + * @param suffixValue + * @param u + * @param action + * @param memorable + * @param consts + * @return + */ + private AbstractSuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, Word u, PSymbolInstance action, Set memorable, Constants consts) { + List uVals = Arrays.asList(DataWords.valsOf(u)); + DataValue[] actionVals = action.getParameterValues(); + int index = suffixValue.getId() - 1; + + if (consts.containsValue(actionVals[index])) { + // we never have accidental equality with constants, so restriction can be equality with constant + return SuffixValueRestriction.equalityRestriction(suffixValue, consts.getAllKeysForValue(actionVals[index])); + } + + Set prior = new LinkedHashSet<>(); + for (int i = 0; i < index; i++) { + if (actionVals[index].equals(actionVals[i])) { + SuffixValue s = new SuffixValue(actionVals[i].getDataType(), i + 1); + prior.add(s); + } + } + + AbstractSuffixValueRestriction unmappedWithFreshRestr = DisjunctionRestriction.create(suffixValue, new UnmappedEqualityRestriction(suffixValue), new FreshSuffixValue(suffixValue)); + + // determine type of equality with data values in u + AbstractSuffixValueRestriction eq = uVals.contains(actionVals[index]) ? + ((memorable.contains(actionVals[index])) ? + SuffixValueRestriction.equalityRestriction(suffixValue, actionVals[index]) : + unmappedWithFreshRestr) : + null; + + if (prior.isEmpty()) { + return eq == null ? new FreshSuffixValue(suffixValue) : eq; + } + + AbstractSuffixValueRestriction eqPrior = SuffixValueRestriction.equalityRestriction(suffixValue, prior); + + if (eq == null) { + return eqPrior; + } + + return DisjunctionRestriction.create(suffixValue, eq, eqPrior); + } + + /** + * Compute restrictions separating {@code sdt1} and {@code sdt2} from a common separating path. + * + * @param sdt1 SDT for prefix 1 + * @param sdt2 SDT for prefix 2 + * @param oldRestrictions + * @param mappedInPrefix memorable data values in the common prefix of prefix 1 and 2 + * @param action1Vals data values in the last symbol of prefix 1 + * @param action2Vals data values in the last symbol of prefix 2 + * @param solver + * @return + */ + private static Map restrictionsFromPruning(SDT sdt1, SDT sdt2, Map oldRestrictions, Set mappedInPrefix, List action1Vals, List action2Vals, ConstraintSolver solver) { + Optional>> pathsOpt = prune(sdt1, sdt2, solver); + assert pathsOpt.isPresent(); + List> paths = pathsOpt.get(); + List path = pathConjunction(paths); + return pathToRestrictions(path, oldRestrictions, mappedInPrefix, action1Vals, action2Vals, false); + } + + /** + * Compute restrictions that reveal the unmapped data values of {@code sdt}. + * + * @param sdt SDT for a prefix + * @param missingRegs unmapped data values of {@code sdt} + * @param oldRestrictions + * @param mappedInPrefix memorable data values in last symbol of prefix + * @param actionVals all data values of last symbol of prefix + * @param solver + * @return + */ + private static Map restrictionsFromPruning(SDT sdt, Set missingRegs, Map oldRestrictions, Set mappedInPrefix, List actionVals, ConstraintSolver solver) { + List> paths = pruneRegClosed(sdt, missingRegs, solver); + List path = pathConjunction(paths); + return pathToRestrictions(path, oldRestrictions, mappedInPrefix, actionVals, Arrays.asList(), true); + } + + /** + * Forms the conjunction of each pair in {@code paths}. + * This method assumes that valid conjunctions can be formed, i.e., that the conjunction + * of the guard expressions of each pair is satisfiable. + * If a pair consists of two equality guards, this method assumes that the guards are on + * the same register. + * + * @param paths + * @return a list containing the conjunctions of each pair of {@code paths} + */ + private static List pathConjunction(List> paths) { + List path = new ArrayList<>(); + for (Map.Entry pair : paths) { + SDTGuard left = pair.getKey(); + SDTGuard right = pair.getValue(); + assert left.getParameter().equals(right.getParameter()) : "Non-matching guards"; + SDTGuard.EqualityGuard eg = left instanceof SDTGuard.EqualityGuard ? + (SDTGuard.EqualityGuard) left : ( + right instanceof SDTGuard.EqualityGuard ? + (SDTGuard.EqualityGuard) right : + null); + if (eg != null) { + path.add(eg); + } else { + path.add(new SDTGuard.SDTTrueGuard(left.getParameter())); + } + } + return path; + } + + /** + * Convert a path of SDT guards into a set of restrictions, with calls to {@link guardToRestriction}. + * + * @param path + * @param oldRestrictions + * @param mappedInPrefix + * @param action1Vals + * @param action2Vals + * @param isRegClosed + * @return + */ + private static Map pathToRestrictions(List path, Map oldRestrictions, Set mappedInPrefix, List action1Vals, List action2Vals, boolean isRegClosed) { + int arity = action1Vals.size(); + Map restr = new LinkedHashMap<>(); + for (Map.Entry old : oldRestrictions.entrySet()) { + SuffixValue sv = old.getKey(); + AbstractSuffixValueRestriction oldRestr = old.getValue(); + SDTGuard guard = path.get(sv.getId() - arity - 1); + restr.put(sv, guardToRestriction(guard, oldRestr, mappedInPrefix, action1Vals, action2Vals, isRegClosed)); + } + return restr; + } + + /** + * Convert {@code guard} into a restriction. This method assumes the existence of a prefix + * and action (and a potential action for an potential additional prefix). + * If encountering an equality with a data value, translates it to an equality restriction. + * This equality will be on the same data value, if it is memorable in the prefix (i.e., + * present in {@code mappedInPrefix}. Otherwise it will be an equality with any suffix value + * corresponding to the action which is of the same data type. + * If the guard is not an equality guard, return but the old restriction is an equality + * restriction, return that restriction (with data values not in {@code mappedInPrefix} + * replaced with action suffix values of matching type, similarly to above). + * Otherwise, return fresh restriction. + * + * @param guard + * @param oldRestriction previous restriction for the parameter of {@code guard} + * @param mappedInPrefix memorable data values in prefix + * @param action1Vals values in the action of prefix 1 + * @param action2Vals values in the action of prefix 2 + * @param isRegClosed {@code true} if constructing restrictions for Register Closedness special case + * @return + */ + private static AbstractSuffixValueRestriction guardToRestriction(SDTGuard guard, AbstractSuffixValueRestriction oldRestriction, Set mappedInPrefix, List action1Vals, List action2Vals, boolean isRegClosed) { + SuffixValue suffixValue = guard.getParameter(); + if (guard instanceof SDTGuard.EqualityGuard eg) { + SDTGuardElement element = eg.register(); + if (element instanceof DataValue d) { + if (mappedInPrefix.contains(d)) { + // mapped data value in prefix, so can be used in restriction + return new EqualityRestriction(suffixValue, Set.of(d)); + } + // not a mapped data value, so is instead an equality with a parameter in the action + // (or an unmapped data value, if restriction is for Register Closedness special case) + Set potentiallyEqualSuffixValues = potentiallyEqualSuffixValues(d, action1Vals); + if (isRegClosed) { + if (action1Vals.contains(d) || action2Vals.contains(d)) { + // can be an unmapped data value, a fresh data value or any action suffix value of matching type + return DisjunctionRestriction.create(suffixValue, + new UnmappedEqualityRestriction(suffixValue), + new EqualityRestriction(suffixValue, potentiallyEqualSuffixValues), + new FreshSuffixValue(suffixValue)); + } + // not present in action so must be unmapped or fresh + return DisjunctionRestriction.create(suffixValue, + new UnmappedEqualityRestriction(suffixValue), + new FreshSuffixValue(suffixValue)); + } + return new EqualityRestriction(suffixValue, potentiallyEqualSuffixValues); + } else if (element instanceof SuffixValue sv) { + return new EqualityRestriction(suffixValue, Set.of(sv)); + } else if (element instanceof Constant c) { + return new EqualityRestriction(suffixValue, Set.of(c)); + } else { + throw new IllegalArgumentException("Invalid value in equality: " + eg.register()); + } + } + + // not equality guard, check old restriction + if (oldRestriction instanceof EqualityRestriction er) { + Set suffixVals = new LinkedHashSet<>(); + for (SDTGuardElement elem : er.getGuardElements()) { + if (elem instanceof DataValue d) { + if (mappedInPrefix.contains(d)) { + // mapped data value, can use in restriction + return new EqualityRestriction(suffixValue, Set.of(d)); + } + // not mapped, so must be referring to action parameter + Set potentiallyEqualSuffixValues = potentiallyEqualSuffixValues(d, action1Vals); + return new EqualityRestriction(suffixValue, potentiallyEqualSuffixValues); + } else if (elem instanceof Constant c) { + return new EqualityRestriction(suffixValue, Set.of(c)); + } else if (elem instanceof SuffixValue) { + suffixVals.add(elem); + } + } + assert !suffixVals.isEmpty() : "Invalid equality restriction: " + er; + return new EqualityRestriction(suffixValue, suffixVals); + } + + // if not equality restriction, must be fresh + assert oldRestriction.containsFresh() : "Restriction invalid at this point: " + oldRestriction; + return new FreshSuffixValue(suffixValue); + } + + /** + * Find a "common" path in {@code sdt1} and {@code sdt2} (i.e., a path in {@code sdt1} and + * another path in {@code sdt2} such that the conjunction of these two paths is satisfiable) + * with different outcomes. + * + * @param sdt1 + * @param sdt2 + * @param restrictions + * @param solver + * @return {@code Optional} containing a "common" path in {@code sdt1} and {@code sdt2}, if such a path exists + */ + private static Optional>> prune(SDT sdt1, SDT sdt2, ConstraintSolver solver) { + Map, Boolean> paths1 = sdt1.getAllPaths(new ArrayList<>()); + Map, Boolean> paths2 = sdt2.getAllPaths(new ArrayList<>()); + for (Map.Entry, Boolean> e1 : paths1.entrySet()) { + for (Map.Entry, Boolean> e2 : paths2.entrySet()) { + if (!e1.getValue().equals(e2.getValue())) { + // paths have different outcomes + List path1 = e1.getKey(); + List path2 = e2.getKey(); + int n = path1.size(); + assert path2.size() == n : "SDTs are not compatible"; + Expression[] exprs = new Expression[n + n]; + Iterator it1 = path1.iterator(); + Iterator it2 = path2.iterator(); + for (int i = 0; i < n; i++) { + exprs[i] = SDTGuard.toExpr(it1.next()); + exprs[i+n] = SDTGuard.toExpr(it2.next()); + } + Expression expr = ExpressionUtil.and(exprs); + if (solver.isSatisfiable(expr, new Mapping<>())) { + // common path + List sorted1 = new ArrayList<>(path1); + List sorted2 = new ArrayList<>(path2); + // sort paths in ascending suffix value order + sorted1.sort((g1, g2) -> Integer.compare(g1.getParameter().getId(), g2.getParameter().getId())); + sorted2.sort((g1, g2) -> Integer.compare(g1.getParameter().getId(), g2.getParameter().getId())); + + List> ret = new ArrayList<>(); + Iterator pathIt1 = sorted1.iterator(); + Iterator pathIt2 = sorted2.iterator(); + while (pathIt1.hasNext()) { + assert pathIt2.hasNext(); + ret.add(new SimpleEntry<>(pathIt1.next(), pathIt2.next())); + } + + return Optional.of(ret); + } + } + } + } + return Optional.empty(); + } + + private static List> pruneRegClosed(SDT sdt, Set missingRegs, ConstraintSolver solver) { + return pruneRegClosed(new ArrayList<>(), sdt, missingRegs, solver); + } + + /** + * Find a pair of paths in {@code sdt} which reveal a missing register. + * + * @param path + * @param sdt + * @param missingRegs + * @param solver + * @return + */ + private static List> pruneRegClosed(List> path, SDT sdt, Set missingRegs, ConstraintSolver solver) { + if (sdt.getChildren() == null) { + return new ArrayList<>(); + } + + Map children = sdt.getChildren(); + for (Map.Entry child : children.entrySet()) { + SDTGuard guard = child.getKey(); + if (guard instanceof SDTGuard.EqualityGuard ifGuard) { + SDTGuardElement element = ifGuard.register(); + if (element instanceof DataValue d && missingRegs.contains(d)) { + SDTGuard elseGuard = findElseGuard(children.keySet()); + SDT ifSdt = child.getValue(); + SDT elseSdt = children.get(elseGuard); + Optional>> prunedPathsOpt = prune(ifSdt, elseSdt, solver); + assert prunedPathsOpt.isPresent(); + List> prunedPaths = prunedPathsOpt.get(); + + path.add(Map.entry(ifGuard, elseGuard)); + path.addAll(prunedPaths); + return path; + } + } + + path.add(Map.entry(guard, guard)); + List> potPath = pruneRegClosed(path, child.getValue(), missingRegs, solver); + if (!potPath.isEmpty()) { + return potPath; + } + } + return new ArrayList<>(); + } + + private static SDTGuard findElseGuard(Set guards) { + for (SDTGuard guard : guards) { + if (isElseGuard(guard)) { + return guard; + } + } + throw new IllegalStateException("No else guard to corresponding equality guard"); + } + + /** + * @param g + * @return {@code true} if and only if {@code g} is an else guard + */ + private static boolean isElseGuard(SDTGuard g) { + if (g instanceof SDTGuard.SDTTrueGuard) { + return true; + } + if (g instanceof SDTGuard.DisequalityGuard) { + return true; + } + if (g instanceof SDTGuard.SDTAndGuard andGuard) { + for (SDTGuard conjunct : andGuard.conjuncts()) { + if (!isElseGuard(conjunct)) { + return false; + } + } + return true; + } + return false; + } + + /** + * Derive restrictions for an extended symbolic suffix, i.e., {@code suffix} prepended by + * the last symbol of {@code uExt1}. The new restrictions are derived by examining the paths + * of {@code sdt1} and {@code sdt2} to find a "common" path in {@code sdt1} and {@code sdt2} + * with different outcomes. The restrictions will have data values mapped to {@code uExt1}. + * + * @param sdt1 + * @param sdt2 + * @param uExt1 + * @param uExt2 + * @param u1RpBijection + * @param u2RpBijection + * @param consts + * @param suffix + * @param solver + * @return + */ + private static Map restrictionFromSDTs(SDT sdt1, SDT sdt2, Prefix uExt1, Prefix uExt2, Bijection u1RpBijection, Bijection u2RpBijection, boolean sameLeaf, Constants consts, SymbolicSuffix suffix, ConstraintSolver solver) { + PSymbolInstance symb1 = uExt1.lastSymbol(); + PSymbolInstance symb2 = uExt2.lastSymbol(); + if (!symb1.getBaseSymbol().equals(symb2.getBaseSymbol())) { + throw new IllegalArgumentException("One-symbol extensions do not match"); + } + int arity = symb1.getBaseSymbol().getArity(); + + // shift parameters + Map oldRestr = suffix.getRestrictions(); + Map oldRestrShifted = AbstractSuffixValueRestriction.shift(oldRestr, arity); + SDT sdt1Shifted = sdt1.shift(arity); + SDT sdt2Shifted = sdt2.shift(arity); + + // remap old restrictions from the RP of the immediate ancestor node of u1 to match u1 + // u1 will be used as the base prefix, so all data values must be mapped to u1 + Bijection uExt1FromAncestorRenaming = uExt1.getBijection(uExt1.getPath().getPrior(suffix)).inverse(); + Map oldRestrShiftedRenamed = AbstractSuffixValueRestriction.relabel(oldRestrShifted, uExt1FromAncestorRenaming.toVarMapping()); + + // data values in the action will become suffix values, so map values in the action to their corresponding suffix values + Mapping actionRenaming1 = actionValueToSuffixValue(uExt1); + Mapping actionRenaming2 = actionValueToSuffixValue(uExt2); + SDT sdt1ActionRenamed = sdt1Shifted.relabel(SDTRelabeling.fromMapping(actionRenaming1)); + SDT sdt2ActionRenamed = sdt2Shifted.relabel(SDTRelabeling.fromMapping(actionRenaming2)); + Map oldRestrActionRenamed = AbstractSuffixValueRestriction.relabel(oldRestrShiftedRenamed, actionRenaming1); + + // remap data values of uExt2 to uExt1 in such a way that there are no collisions for data values in uExt2 that have no correlation to uExt1 + Bijection uExt2Renaming = collisionFreeRenaming(uExt1, uExt2, u1RpBijection, u2RpBijection, suffix, sameLeaf); + SDT sdt2Renamed = sdt2ActionRenamed.relabel(SDTRelabeling.fromBijection(uExt2Renaming)); + + // get memorable data values of u1 and data values in the actions of uExt1 and uExt2 + Set mappedInPrefix = u1RpBijection.keySet(); + List action1Vals = Arrays.asList(symb1.getParameterValues()); + List action2Vals = new ArrayList<>(); + // map uExt2 action data values to uExt1 + renameCollection(action2Vals, Arrays.asList(symb2.getParameterValues()), uExt2Renaming); + + // derive restrictions + Map restrPruned = restrictionsFromPruning(sdt1ActionRenamed, sdt2Renamed, oldRestrActionRenamed, mappedInPrefix, action1Vals, action2Vals, solver); + + // replace restrictions on data values in the actions of uExt2 with their corresponding suffix values + Bijection uExt2FromAncestorRenaming = uExt2.getBijection(uExt2.getPath().getPrior(suffix)).inverse(); + Map restrElseParams = addActionParameter(restrPruned, actionRenaming2, uExt1FromAncestorRenaming.inverse(), uExt2FromAncestorRenaming); + + return restrElseParams; + } + + private static void renameCollection(Collection dest, Collection col, Bijection renaming) { + for (DataValue d : col) { + if (renaming.containsKey(d)) { + dest.add(renaming.get(d)); + } + } + } + + /** + * Derive restrictions for an extended symbolic suffix, i.e., {@code suffix} prepended by + * the last symbol of {@code uExt}. The restrictions are derived by examining paths of + * {@code sdt} to find and isolate paths that reveal unmapped data values. Each suffix value + * with a guard on unmapped data value will have a {@link TrueRestriction}, while any other + * will have a restriction given by the conjunction of existing restrictions in {@code suffix} + * and restrictions derived from the paths in {@code sdt} which reveal the unmapped data + * values. + * + * @param sdt + * @param uExt + * @param rp + * @param consts + * @param suffix + * @param solver + * @return + */ + private static Map restrictionFromSDT(SDT sdt, Prefix u, Prefix uExt, Bijection rp, Constants consts, SymbolicSuffix suffix, ConstraintSolver solver, boolean useImprovedRegClosed) { + PSymbolInstance symb = uExt.lastSymbol(); + int arity = symb.getBaseSymbol().getArity(); + List actionVals = Arrays.asList(symb.getParameterValues()); + + Set missingRegs = new LinkedHashSet<>(sdt.getDataValues()); + missingRegs.removeAll(rp.keySet()); + + if (!Collections.disjoint(actionVals, missingRegs) || !useImprovedRegClosed) { + return transferRestriction(sdt, u, uExt, rp, consts, suffix, solver); + } + + Bijection ancestorRenaming = uExt.getBijection(uExt.getPath().getPrior(suffix)).inverse(); + Map oldRestr = suffix.getRestrictions(); + Map oldRestrRenamed = AbstractSuffixValueRestriction.relabel(oldRestr, ancestorRenaming.toVarMapping()); + + SDT sdtShifted = sdt.shift(arity); + Map oldRestrRenamedShifted = AbstractSuffixValueRestriction.shift(oldRestrRenamed, arity); + + Set mappedInPrefix = rp.keySet(); + + return restrictionsFromPruning(sdtShifted, missingRegs, oldRestrRenamedShifted, mappedInPrefix, actionVals, solver); + } + + /** + * Shift restrictions one action-arity to the right. + * If there are any equality restrictions on a data value that is not memorable in {@code u}, + * replace that with an equality restriction on any suffix value in the action that is + * of a matching type to that data value. + * + * @param sdt + * @param u + * @param uExt + * @param rp + * @param consts + * @param suffix + * @param solver + * @return + */ + private static Map transferRestriction(SDT sdt, Prefix u, Prefix uExt, Bijection rp, Constants consts, SymbolicSuffix suffix, ConstraintSolver solver) { + PSymbolInstance symb = uExt.lastSymbol(); + ArrayList symbVals = new ArrayList<>(Arrays.asList(symb.getParameterValues())); + + Set missingRegs = new LinkedHashSet<>(sdt.getDataValues()); + missingRegs.removeAll(rp.keySet()); + + Map ret = AbstractSuffixValueRestriction.shift(suffix.getRestrictions(), symb.getBaseSymbol().getArity()); + sdt = sdt.shift(symb.getBaseSymbol().getArity()); + + Bijection ancestorRenaming = uExt.getBijection(uExt.getPath().getPrior(suffix)); + + // find missing registers in restriction and replace those that are in the action with suffix params + Mapping suffixValueRenaming = new Mapping<>(); + for (DataValue r : missingRegs) { + if (symbVals.contains(r) && ancestorRenaming.containsKey(r)) { + SuffixValue s = new SuffixValue(r.getDataType(), symbVals.indexOf(r)); + suffixValueRenaming.put(r, s); + } + } + ret = AbstractSuffixValueRestriction.relabel(ret, suffixValueRenaming); + + // replace unmapped restriction depending on sdt guards + ret = replaceUnmappedRestriction(ret, u, uExt, sdt); + + return AbstractSuffixValueRestriction.relabel(ret, ancestorRenaming.inverse().toVarMapping()); + } + + /** + * Check old unmapped restrictions have discovered a new mapped value, or discovered that + * a prior unmapped value is a value in the action and therefore now a suffix value. + * + * @param restr + * @param u + * @param uExt + * @param sdt + * @return + */ + private static Map replaceUnmappedRestriction(Map restr, Prefix u, Prefix uExt, SDT sdt) { + Map ret = new LinkedHashMap<>(); + List symbVals = Arrays.asList(uExt.lastSymbol().getParameterValues()); + Set uMem = u.getRegisters(); + + Set unmappedSuffixVals = AbstractSuffixValueRestriction.unmappedSuffixValues(restr); + for (Map.Entry e : restr.entrySet()) { + SuffixValue sv = e.getKey(); + if (!unmappedSuffixVals.contains(sv)) { + ret.put(sv, e.getValue()); + continue; + } + List disjuncts = new ArrayList<>(); + disjuncts.add(new FreshSuffixValue(sv)); + Set eqElems = new LinkedHashSet<>(); + for (SDTGuard g : sdt.getGuards(sv)) { + if (g instanceof SDTGuard.EqualityGuard eg) { + SDTGuardElement element = eg.register(); + if (SDTGuardElement.isDataValue(element)) { + DataValue d = (DataValue) element; + if (uMem.contains(d)) { + eqElems.add(element); + } else { + disjuncts.add(new UnmappedEqualityRestriction(sv)); + } + if (symbVals.contains(d)) { + eqElems.addAll(potentiallyEqualSuffixValues(d, symbVals)); + } + } else if (SDTGuardElement.isSuffixValue(element)) { + eqElems.add(element); + } + } + } + if (!eqElems.isEmpty()) { + disjuncts.add(new EqualityRestriction(sv, eqElems)); + } + ret.put(sv, DisjunctionRestriction.create(sv, disjuncts)); + } + return ret; + } + + /** + * Find a mapping for the data values from {@code uExt2} to {@code uExt1}. This mapping + * ensures that the mappings of data values from {@code uExt1} and {@code uExt2} to their + * ancestor node is adhered to. Data values of {@code uExt2} that have no mapping to the + * ancestor node are renamed to ensure there is no collision with data values in + * {@code uExt1} that are also not mapped to the ancestor node. + * + * @param uExt1 + * @param uExt2 + * @param u1RpBijection + * @param u2RpBijection + * @param suffix + * @param sameLeaf + * @return + */ + private static Bijection collisionFreeRenaming(Prefix uExt1, Prefix uExt2, Bijection u1RpBijection, Bijection u2RpBijection, SymbolicSuffix suffix, boolean sameLeaf) { + Set usedVals = DataWords.valSet(uExt1); + Bijection freshRenaming = new Bijection<>(); + for (DataValue d : DataWords.valsOf(uExt2)) { + DataValue fresh = EqualityTheory.getFreshValue(usedVals, d.getDataType()); + freshRenaming.put(d, fresh); + usedVals.add(fresh); + } + + Bijection uExt1AncestorRenaming = uExt1.getBijection(uExt1.getPath().getPrior(suffix)); + Bijection uExt2AncestorRenaming = uExt2.getBijection(uExt2.getPath().getPrior(suffix)); + Bijection uExt1RpBijection = uExt1.getRpBijection(); + Bijection uExt2RpBijection = uExt2.getRpBijection(); + + List> bijections = new ArrayList<>(); + + bijections.add(uExt2AncestorRenaming.compose(uExt1AncestorRenaming.inverse())); + bijections.add(u2RpBijection.compose(u1RpBijection.inverse())); + if (sameLeaf) { + bijections.add(uExt2RpBijection.compose(uExt1RpBijection.inverse())); + } + + Bijection renaming = new Bijection<>(freshRenaming); + for (Bijection b : bijections) { + renaming = updateRenaming(renaming, b); + } + return renaming; + } + + /** + * @param renaming + * @param b + * @return {@code b} added to {@code renaming} + */ + private static Bijection updateRenaming(Bijection renaming, Bijection b) { + Bijection ret = new Bijection<>(renaming); + for (Map.Entry e : b.entrySet()) { + ret.put(e.getKey(), e.getValue()); + } + return ret; + } + + /** + * Map data values in the last symbol of {@code uExt} (the action) to their corresponding + * suffix values when the action is made symbolic. + * + * @param uExt + * @return + */ + private static Mapping actionValueToSuffixValue(Word uExt) { + List uVals = Arrays.asList(DataWords.valsOf(uExt.prefix(uExt.length() - 1))); + DataValue[] actionVals = uExt.lastSymbol().getParameterValues(); + + Mapping ret = new Mapping<>(); + for (int i = 0; i < actionVals.length; i++) { + DataValue d = actionVals[i]; + if (!uVals.contains(d)) { + SuffixValue sv = new SuffixValue(d.getDataType(), i + 1); + ret.put(d, sv); + } + } + return ret; + } + + /** + * Compute set of suffix values corresponding to each data value in {@code vals} with the + * same type as {@code d}. The id for each suffix value is given by the position of its + * corresponding value in {@code vals}. + * + * @param d + * @param vals + * @return set of suffix values corresponding to {@code vals} with the same type as {@code d} + */ + private static Set potentiallyEqualSuffixValues(DataValue d, List vals) { + Set ret = new LinkedHashSet<>(); + for (int i = 0; i < vals.size(); i++) { + if (vals.get(i).getDataType().equals(d.getDataType())) { + ret.add(new SuffixValue(d.getDataType(), i + 1)); + } + } + return ret; + } + + /** + * @param restr + * @return the set of {@code DataValue} elements of {@code restr} + */ + private static Set getDataValueElements(Map restr) { + return AbstractSuffixValueRestriction.getElements(restr) + .stream() + .filter(e -> e instanceof DataValue) + .map(d -> (DataValue) d) + .collect(Collectors.toSet()); + } + + /** + * Given a prefix {@code ua}, where {@code a} (the action) is a one-symbol extension, + * checks for any equality restriction with a data value in {@code a}. If such an equality + * restriction is found, add to it an equality restriction with the value's corresponding + * suffix value, as given by {@code actionRenaming}. + * + * @param restr + * @param actionRenaming mapping from values in action to corresponding suffix value + * @param toAncestorRenaming mapping of data values from {@code restr} to ancestor node + * @param fromAncestorToExtRenaming mapping of data values from ancestor node to prefix + * @return + */ + private static Map addActionParameter(Map restr, Mapping actionRenaming, Bijection toAncestorRenaming, Bijection fromAncestorToExtRenaming) { + Map ret = restr; + Set restrVals = getDataValueElements(restr); + for (DataValue d : restrVals) { + if (toAncestorRenaming.containsKey(d)) { + DataValue dAncestor = toAncestorRenaming.get(d); + DataValue dExt = fromAncestorToExtRenaming.get(dAncestor); + assert dExt != null : "Data value of ancestor node not present in bijection"; + if (actionRenaming.containsKey(dExt)) { + for (ElementRestriction er : AbstractSuffixValueRestriction.getRestrictionsOnElement(restr, d)) { + SuffixValue sv = er.cast().getParameter(); + assert er instanceof EqualityRestriction : "Unsupported restriction type"; + EqualityRestriction replace = (EqualityRestriction) er; + Set elems = new LinkedHashSet<>(replace.getGuardElements()); + elems.add(actionRenaming.get(dExt)); + EqualityRestriction by = new EqualityRestriction(sv, elems); + ret = AbstractSuffixValueRestriction.replaceRestriction(ret, replace, by); + } + } + } + } + return ret; + } +} diff --git a/src/main/java/de/learnlib/ralib/oracles/mto/SymbolicSuffixRestrictionBuilder.java b/src/main/java/de/learnlib/ralib/oracles/mto/SymbolicSuffixRestrictionBuilder.java index 005517b73..afe8fa771 100644 --- a/src/main/java/de/learnlib/ralib/oracles/mto/SymbolicSuffixRestrictionBuilder.java +++ b/src/main/java/de/learnlib/ralib/oracles/mto/SymbolicSuffixRestrictionBuilder.java @@ -8,11 +8,12 @@ import de.learnlib.ralib.data.Constants; import de.learnlib.ralib.data.DataType; +import de.learnlib.ralib.data.RegisterValuation; import de.learnlib.ralib.data.SymbolicDataValue; import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.SuffixValueGenerator; +import de.learnlib.ralib.theory.AbstractSuffixValueRestriction; import de.learnlib.ralib.theory.SDTGuard; -import de.learnlib.ralib.theory.SuffixValueRestriction; import de.learnlib.ralib.theory.Theory; import de.learnlib.ralib.words.DataWords; import de.learnlib.ralib.words.PSymbolInstance; @@ -20,18 +21,17 @@ public class SymbolicSuffixRestrictionBuilder { - private final Map teachers; + protected final Map teachers; - private final Constants consts; + protected final Constants consts; - public SymbolicSuffixRestrictionBuilder(Constants consts) { - this.consts = consts; - this.teachers = null; + public SymbolicSuffixRestrictionBuilder(Constants consts, Map teachers) { + this.consts = consts; + this.teachers = teachers; } - public SymbolicSuffixRestrictionBuilder(Constants consts, Map teachers) { - this.consts = consts; - this.teachers = teachers; + public SymbolicSuffixRestrictionBuilder(Constants consts) { + this(consts, null); } public SymbolicSuffixRestrictionBuilder(Map teachers) { @@ -39,20 +39,41 @@ public SymbolicSuffixRestrictionBuilder(Map teachers) { } public SymbolicSuffixRestrictionBuilder() { - this(new Constants()); + this(new Constants(), null); } + public Map restrictSuffix(Word prefix, + Word suffix, + Word u, + RegisterValuation wValuation, + RegisterValuation uValuation) { + DataType[] types = DataWords.typesOf(DataWords.actsOf(suffix)); + Map restrictions = new LinkedHashMap<>(); + SuffixValueGenerator svgen = new SuffixValueGenerator(); + for (DataType t : types) { + SuffixValue sv = svgen.next(t); + AbstractSuffixValueRestriction restr; + if (teachers == null) { + restr = AbstractSuffixValueRestriction.genericRestriction(sv, prefix, suffix, consts); + } else { + Theory theory = teachers.get(t); + restr = theory.restrictSuffixValue(sv, prefix, suffix, u, wValuation, uValuation, consts); + } + restrictions.put(sv, restr); + } + return restrictions; + } - public Map restrictSuffix(Word prefix, Word suffix) { + public Map restrictSuffix(Word prefix, Word suffix) { DataType[] types = DataWords.typesOf(DataWords.actsOf(suffix)); - Map restrictions = new LinkedHashMap<>(); + Map restrictions = new LinkedHashMap<>(); SuffixValueGenerator svgen = new SuffixValueGenerator(); for (DataType t : types) { SuffixValue sv = svgen.next(t); - SuffixValueRestriction restr; + AbstractSuffixValueRestriction restr; if (teachers == null) { // use standard restrictions - restr = SuffixValueRestriction.genericRestriction(sv, prefix, suffix, consts); + restr = AbstractSuffixValueRestriction.genericRestriction(sv, prefix, suffix, consts); } else { // theory-specific restrictions Theory theory = teachers.get(t); @@ -63,9 +84,9 @@ public Map restrictSuffix(Word prior) { + public AbstractSuffixValueRestriction restrictSuffixValue(SDTGuard guard, Map prior) { if (teachers == null) - return SuffixValueRestriction.genericRestriction(guard, prior); + return AbstractSuffixValueRestriction.genericRestriction(guard, prior); Theory theory = teachers.get(guard.getParameter().getDataType()); return theory.restrictSuffixValue(guard, prior); } diff --git a/src/main/java/de/learnlib/ralib/theory/AbstractSuffixValueRestriction.java b/src/main/java/de/learnlib/ralib/theory/AbstractSuffixValueRestriction.java new file mode 100644 index 000000000..cb102d399 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/theory/AbstractSuffixValueRestriction.java @@ -0,0 +1,270 @@ +package de.learnlib.ralib.theory; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import de.learnlib.ralib.data.*; +import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; +import de.learnlib.ralib.theory.equality.EqualRestriction; +import de.learnlib.ralib.theory.equality.UnmappedEqualityRestriction; +import de.learnlib.ralib.words.DataWords; +import de.learnlib.ralib.words.PSymbolInstance; +import gov.nasa.jpf.constraints.api.Expression; +import net.automatalib.word.Word; + +public abstract class AbstractSuffixValueRestriction { + protected final SuffixValue parameter; + + public AbstractSuffixValueRestriction(SuffixValue parameter) { + this.parameter = parameter; + } + + public AbstractSuffixValueRestriction(AbstractSuffixValueRestriction other) { + parameter = new SuffixValue(other.parameter.getDataType(), other.parameter.getId()); + } + + public AbstractSuffixValueRestriction(AbstractSuffixValueRestriction other, int shift) { + parameter = new SuffixValue(other.parameter.getDataType(), other.parameter.getId()+shift); + } + + public SuffixValue getParameter() { + return parameter; + } + + public abstract AbstractSuffixValueRestriction shift(int shiftStep); + + public abstract AbstractSuffixValueRestriction concretize(Mapping mapping); + + public abstract Expression toGuardExpression(Set vals); + + public abstract AbstractSuffixValueRestriction merge(AbstractSuffixValueRestriction other, Map prior); + + public abstract boolean revealsRegister(SymbolicDataValue r); + + public abstract AbstractSuffixValueRestriction relabel(Mapping renaming); + + /** + * Generate a generic restriction using Fresh, Unrestricted and Equal restriction types + * + * @param sv + * @param prefix + * @param suffix + * @param consts + * @return + */ + public static AbstractSuffixValueRestriction genericRestriction(SuffixValue sv, Word prefix, Word suffix, Constants consts) { + DataValue[] prefixVals = DataWords.valsOf(prefix); + DataValue[] suffixVals = DataWords.valsOf(suffix); + DataType[] prefixTypes = DataWords.typesOf(DataWords.actsOf(prefix)); + DataType[] suffixTypes = DataWords.typesOf(DataWords.actsOf(suffix)); + DataValue val = suffixVals[sv.getId()-1]; + int firstSymbolArity = suffix.length() > 0 ? suffix.getSymbol(0).getBaseSymbol().getArity() : 0; + + boolean unrestricted = false; + for (int i = 0; i < prefixVals.length; i++) { + DataValue dv = prefixVals[i]; + DataType dt = prefixTypes[i]; + if (dt.equals(sv.getDataType()) && dv.equals(val)) { + unrestricted = true; + break; + } + } + if (consts.containsValue(val)) { + unrestricted = true; + } + boolean equalsSuffixValue = false; + int equalSV = -1; + for (int i = 0; i < sv.getId()-1 && !equalsSuffixValue; i++) { + DataType dt = suffixTypes[i]; + if (dt.equals(sv.getDataType()) && suffixVals[i].equals(val)) { + if (sv.getId() <= firstSymbolArity) { + unrestricted = true; + } else { + equalsSuffixValue = true; + equalSV = i; + } + } + } + + // case equal to previous suffix value + if (equalsSuffixValue && !unrestricted) { + AbstractSuffixValueRestriction restr = new EqualRestriction(sv, new SuffixValue(suffixVals[equalSV].getDataType(), equalSV+1)); + return restr; + } + // case fresh + else if (!equalsSuffixValue && !unrestricted) { + return new FreshSuffixValue(sv); + } + // case unrestricted + else { + return new UnrestrictedSuffixValue(sv); + } + } + + public abstract boolean isTrue(); + + public abstract boolean isFalse(); + + public abstract boolean containsFresh(); + + @Override + public int hashCode() { + return Objects.hash(parameter); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AbstractSuffixValueRestriction other = (AbstractSuffixValueRestriction) obj; + return Objects.equals(parameter, other.parameter); + } + + public static AbstractSuffixValueRestriction genericRestriction(SDTGuard guard, Map prior) { + SuffixValue suffixValue = guard.getParameter(); + // case fresh + if (guard instanceof SDTGuard.SDTTrueGuard || guard instanceof SDTGuard.DisequalityGuard) { + return new FreshSuffixValue(suffixValue); + // case equal to previous suffix value + } else if (guard instanceof SDTGuard.EqualityGuard equalityGuard) { + SDTGuardElement param = equalityGuard.register(); + if (param instanceof SuffixValue suffixValueParam) { + AbstractSuffixValueRestriction restr = prior.get(param); + if (restr instanceof FreshSuffixValue) { + return new EqualRestriction(suffixValue, suffixValueParam); + } else if (restr instanceof EqualRestriction equalRestriction) { + return new EqualRestriction(suffixValue, equalRestriction.getEqualParameter()); + } else { + return new UnrestrictedSuffixValue(suffixValue); + } + } else { + return new UnrestrictedSuffixValue(suffixValue); + } + // case unrestricted + } else { + return new UnrestrictedSuffixValue(suffixValue); + } + } + + /** + * Shift suffix values in {@code restrictions} by {@code shift} steps. Applies to both the + * suffix value of the restrictions themselves, and to any suffix value elements of the + * restrictions. For example, an equality restriction {@code (s2 == s1)} that is + * shifted by 2 will become {@code (s4 == s3)}. + * + * @param restrictions + * @param shift + * @return + */ + public static Map shift(Map restrictions, int shift) { + Map ret = new LinkedHashMap<>(); + for (Map.Entry e : restrictions.entrySet()) { + SuffixValue s = new SuffixValue(e.getKey().getDataType(), e.getKey().getId() + shift); + AbstractSuffixValueRestriction r = e.getValue().shift(shift); + ret.put(s, r); + } + return ret; + } + + public static Map replaceRestriction(Map restrictions, AbstractSuffixValueRestriction replace, AbstractSuffixValueRestriction by) { + SuffixValue param = replace.getParameter(); + if (!by.getParameter().equals(param)) { + throw new IllegalArgumentException("Restriction parameters do not match"); + } + + Map replaced = new LinkedHashMap<>(); + + for (Map.Entry e : restrictions.entrySet()) { + AbstractSuffixValueRestriction r = e.getValue(); + if (e.getKey().equals(param)) { + if (r.equals(replace)) { + replaced.put(e.getKey(), by); + } else if (r instanceof RestrictionContainer rc) { + replaced.put(e.getKey(), rc.replace(replace, by)); + } else { + replaced.put(e.getKey(), r); + } + } else { + replaced.put(e.getKey(), r); + } + } + + return replaced; + } + + public static Map relabel(Map restrictions, Mapping renaming) { + Map renamed = new LinkedHashMap<>(); + for (Map.Entry e : restrictions.entrySet()) { + renamed.put(e.getKey(), e.getValue().relabel(renaming)); + } + return renamed; + } + + /** + * @param restrictions + * @param element + * @return {@code true} if and only if {@code restrictions} contains a restriction on {@code element} + */ + public static boolean containsElement(Map restrictions, Expression element) { + for (AbstractSuffixValueRestriction r : restrictions.values()) { + if (r instanceof ElementRestriction er && er.containsElement(element)) { + return true; + } + } + return false; + } + + /** + * @param restrictions + * @return set of all variables in {@code restrictions} + */ + public static Set> getElements(Map restrictions) { + Set> ret = new LinkedHashSet<>(); + for (AbstractSuffixValueRestriction r : restrictions.values()) { + if (r instanceof ElementRestriction er) { + ret.addAll(er.getElements()); + } + } + return ret; + } + + /** + * @param restrictions + * @param element + * @return list of all restrictions on {@code element} in {@code restrictions} + */ + public static List getRestrictionsOnElement(Map restrictions, Expression element) { + List ret = new ArrayList<>(); + for (Map.Entry e : restrictions.entrySet()) { + if (e.getValue() instanceof ElementRestriction er && er.containsElement(element)) { + ret.addAll(er.getRestrictions(element)); + } + } + return ret; + } + + /** + * @param restrictions + * @return set of all suffix values with an {@link UnmappedEqualityRestriction} + */ + public static Set unmappedSuffixValues(Map restrictions) { + Set ret = new LinkedHashSet<>(); + for (Map.Entry e : restrictions.entrySet()) { + if (e.getValue() instanceof UnmappedEqualityRestriction || + (e.getValue() instanceof RestrictionContainer rc && rc.containsUnmapped())) { + ret.add(e.getKey()); + } + } + return ret; + } +} diff --git a/src/main/java/de/learnlib/ralib/theory/ConcretizingTreeOracle.java b/src/main/java/de/learnlib/ralib/theory/ConcretizingTreeOracle.java new file mode 100644 index 000000000..054ef5977 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/theory/ConcretizingTreeOracle.java @@ -0,0 +1,62 @@ +package de.learnlib.ralib.theory; + +import java.util.Set; + +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.RegisterValuation; +import de.learnlib.ralib.data.SymbolicDataValue; +import de.learnlib.ralib.learning.SymbolicSuffix; +import de.learnlib.ralib.oracles.Branching; +import de.learnlib.ralib.oracles.TreeOracle; +import de.learnlib.ralib.oracles.mto.SLLambdaEqRestrictionBuilder; +import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; +import de.learnlib.ralib.words.PSymbolInstance; +import de.learnlib.ralib.words.ParameterizedSymbol; +import net.automatalib.word.Word; + +public class ConcretizingTreeOracle implements TreeOracle { + + private final TreeOracle oracle; + private final Constants consts; + + public ConcretizingTreeOracle(TreeOracle oracle, Constants consts) { + this.oracle = oracle; + this.consts = consts; + } + + @Override + public SDT treeQuery(Word prefix, SymbolicSuffix suffix) { + return oracle.treeQuery(prefix, suffix); + } + + public SDT treeQuery(Word prefix, SymbolicSuffix suffix, Set memorable) { + RegisterValuation regs = RegisterValuation.fromMemorable(prefix, memorable); + ParameterValuation params = ParameterValuation.fromPSymbolWord(prefix); + Mapping mapping = new Mapping<>(); + mapping.putAll(regs); + mapping.putAll(params); + mapping.putAll(consts); + SymbolicSuffix concreteSuffix = SLLambdaEqRestrictionBuilder.concretize(suffix, mapping); + return oracle.treeQuery(prefix, concreteSuffix); + } + + @Override + public Branching getInitialBranching(Word prefix, ParameterizedSymbol ps, SDT... sdts) { + return oracle.getInitialBranching(prefix, ps, sdts); + } + + @Override + public Branching updateBranching(Word prefix, ParameterizedSymbol ps, Branching current, + SDT... sdts) { + return oracle.updateBranching(prefix, ps, current, sdts); + } + + @Override + public SymbolicSuffixRestrictionBuilder getRestrictionBuilder() { + return oracle.getRestrictionBuilder(); + } + +} diff --git a/src/main/java/de/learnlib/ralib/theory/ConjunctionRestriction.java b/src/main/java/de/learnlib/ralib/theory/ConjunctionRestriction.java new file mode 100644 index 000000000..5240d28e9 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/theory/ConjunctionRestriction.java @@ -0,0 +1,289 @@ +package de.learnlib.ralib.theory; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +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.data.TypedValue; +import de.learnlib.ralib.theory.equality.UnmappedEqualityRestriction; +import gov.nasa.jpf.constraints.api.Expression; +import gov.nasa.jpf.constraints.util.ExpressionUtil; + +public class ConjunctionRestriction extends AbstractSuffixValueRestriction implements RestrictionContainer, ElementRestriction { + + private Collection conjuncts; + + public ConjunctionRestriction(SuffixValue parameter, Collection conjuncts) { + super(parameter); + this.conjuncts = new ArrayList<>(); + boolean hasFalse = false; + for (AbstractSuffixValueRestriction restr : conjuncts) { + if (restr instanceof ConjunctionRestriction cr) { + for (AbstractSuffixValueRestriction r : cr.conjuncts) { + if (!this.conjuncts.contains(r)) { + this.conjuncts.add(r); + } + } + } + if (this.conjuncts.contains(restr)) { + continue; + } + if (restr.isFalse()) { + hasFalse = true; + break; + } else if (!(restr.isTrue())) { + this.conjuncts.add(restr); + } + } + if (hasFalse) { + this.conjuncts.clear(); + } + } + + public ConjunctionRestriction(SuffixValue parameter, AbstractSuffixValueRestriction ... conjuncts) { + this(parameter, Arrays.asList(conjuncts)); + } + + public ConjunctionRestriction(ConjunctionRestriction other, int shift) { + super(other, shift); + conjuncts = new ArrayList<>(); + other.conjuncts.forEach(r -> conjuncts.add(r.shift(shift))); + } + + protected Collection getConjuncts() { + return conjuncts; + } + + @Override + public ConjunctionRestriction shift(int shiftStep) { + return new ConjunctionRestriction(this, shiftStep); + } + + @Override + public AbstractSuffixValueRestriction concretize(Mapping mapping) { + Collection conc = new ArrayList<>(); + conjuncts.forEach(r -> conc.add(r.concretize(mapping))); + return create(parameter, conc); + } + + @Override + public Expression toGuardExpression(Set vals) { + Expression[] exprs = new Expression[conjuncts.size()]; + int i = 0; + for (AbstractSuffixValueRestriction r : conjuncts) { + exprs[i++] = r.toGuardExpression(vals); + } + return ExpressionUtil.and(exprs); + } + + @Override + public boolean isTrue() { + return conjuncts.isEmpty(); + } + + @Override + public boolean isFalse() { + return !conjuncts.isEmpty(); + } + + @Override + public boolean containsFresh() { + return conjuncts.stream().filter(AbstractSuffixValueRestriction::containsFresh).findAny().isPresent(); + } + + @Override + public AbstractSuffixValueRestriction merge(AbstractSuffixValueRestriction other, + Map prior) { + throw new RuntimeException("Unsupported operation"); + } + + @Override + public boolean revealsRegister(SymbolicDataValue r) { + return conjuncts.stream().filter(c -> c.revealsRegister(r)).findAny().isPresent(); + } + + @Override + public AbstractSuffixValueRestriction relabel(Mapping renaming) { + Collection relabeled = new ArrayList<>(); + conjuncts.forEach(r -> relabeled.add(r.relabel(renaming))); + return create(parameter, relabeled); + } + + @Override + public List getRestrictions(Expression element) { + List restrictions = new ArrayList<>(); + for (AbstractSuffixValueRestriction r : conjuncts) { + if (r instanceof ElementRestriction er && er.containsElement(element)) { + restrictions.addAll(er.getRestrictions(element)); + } + } + return restrictions; + } + + @Override + public boolean containsElement(Expression element) { + for (AbstractSuffixValueRestriction r : conjuncts) { + if (r instanceof ElementRestriction er && er.containsElement(element)) { + return true; + } + } + return false; + } + + @Override + public Set> getElements() { + Set> ret = new LinkedHashSet<>(); + for (AbstractSuffixValueRestriction r : conjuncts) { + if (r instanceof ElementRestriction er) { + ret.addAll(er.getElements()); + } + } + return ret; + } + + @Override + public AbstractSuffixValueRestriction replaceElement(Expression replace, Expression by) { + Collection replaced = new ArrayList<>(); + for (AbstractSuffixValueRestriction r : conjuncts) { + if (r instanceof ElementRestriction er && er.containsElement(replace)) { + replaced.add(er.replaceElement(replace, by)); + } else { + replaced.add(r); + } + } + return create(getParameter(), replaced); + } + + @Override + public boolean contains(AbstractSuffixValueRestriction restr) { + for (AbstractSuffixValueRestriction r : conjuncts) { + if (r.equals(restr)) { + return true; + } + if (r instanceof RestrictionContainer rc && rc.contains(restr)) { + return true; + } + } + return false; + } + + @Override + public boolean containsUnmapped() { + for (AbstractSuffixValueRestriction r : conjuncts) { + if (r instanceof UnmappedEqualityRestriction) { + return true; + } + } + return false; + } + + @Override + public AbstractSuffixValueRestriction replace(AbstractSuffixValueRestriction replace, AbstractSuffixValueRestriction with) { + Collection replaced = new ArrayList<>(); + for (AbstractSuffixValueRestriction r : conjuncts) { + if (r.equals(replace)) { + if (with instanceof ConjunctionRestriction cr) { + replaced.addAll(cr.conjuncts); + } else { + replaced.add(with); + } + } else if (r instanceof RestrictionContainer rc && rc.contains(replace)) { + AbstractSuffixValueRestriction nrc = rc.replace(replace, with); + if (nrc instanceof ConjunctionRestriction cr) { + replaced.addAll(cr.getConjuncts()); + } else { + replaced.add(nrc); + } + } else { + replaced.add(r); + } + } + return create(getParameter(), replaced); + } + + @Override + public ConjunctionRestriction cast() { + return this; + } + + @Override + public String toString() { + Iterator it = conjuncts.iterator(); + String str = "("; + while (it.hasNext()) { + str = str + it.next().toString(); + if (it.hasNext()) { + str = str + " AND "; + } + } + return str + ")"; + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) { + return false; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ConjunctionRestriction other = (ConjunctionRestriction) obj; + return other.conjuncts.equals(conjuncts); + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 61 * hash + conjuncts.hashCode(); + return hash; + } + + public static AbstractSuffixValueRestriction create(SuffixValue parameter, Collection conjuncts) { + if (conjuncts != null) { + conjuncts = flattenConjuncts(conjuncts); + } else { + conjuncts = new ArrayList<>(); + } + boolean isTrue = conjuncts.stream().filter(d -> d.isTrue()).findAny().isPresent(); + if (conjuncts.isEmpty() || isTrue) { + return new TrueRestriction(parameter); + } + if (conjuncts.size() == 1) { + return conjuncts.iterator().next(); + } + ConjunctionRestriction conjunction = new ConjunctionRestriction(parameter, conjuncts); + if (conjunction.isTrue()) { + return new TrueRestriction(parameter); + } + return conjunction; + } + + public static AbstractSuffixValueRestriction create(SuffixValue parameter, AbstractSuffixValueRestriction ... conjuncts) { + return create(parameter, Arrays.asList(conjuncts)); + } + + private static Set flattenConjuncts(Collection conjuncts) { + Set dis = new LinkedHashSet<>(); + for (AbstractSuffixValueRestriction r : conjuncts) { + if (r instanceof ConjunctionRestriction cr) { + dis.addAll(flattenConjuncts(cr.conjuncts)); + } else { + dis.add(r); + } + } + return dis; + } +} diff --git a/src/main/java/de/learnlib/ralib/theory/DisjunctionRestriction.java b/src/main/java/de/learnlib/ralib/theory/DisjunctionRestriction.java new file mode 100644 index 000000000..fa86d4dc1 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/theory/DisjunctionRestriction.java @@ -0,0 +1,289 @@ +package de.learnlib.ralib.theory; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +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.data.TypedValue; +import de.learnlib.ralib.theory.equality.UnmappedEqualityRestriction; +import gov.nasa.jpf.constraints.api.Expression; +import gov.nasa.jpf.constraints.util.ExpressionUtil; + +public class DisjunctionRestriction extends AbstractSuffixValueRestriction implements RestrictionContainer, ElementRestriction { + + private Collection disjuncts; + + public DisjunctionRestriction(SuffixValue parameter, Collection disjuncts) { + super(parameter); + this.disjuncts = new ArrayList<>(); + boolean hasTrue = false; + for (AbstractSuffixValueRestriction restr : disjuncts) { + if (restr instanceof DisjunctionRestriction cr) { + for (AbstractSuffixValueRestriction r : cr.disjuncts) { + if (!this.disjuncts.contains(r)) { + this.disjuncts.add(r); + } + } + } + if (this.disjuncts.contains(restr)) { + continue; + } + if (restr.isTrue()) { + hasTrue = true; + break; + } else if (!(restr.isFalse())) { + this.disjuncts.add(restr); + } + } + if (hasTrue) { + this.disjuncts.clear(); + } + } + + public DisjunctionRestriction(SuffixValue parameter, AbstractSuffixValueRestriction ... disjuncts) { + this(parameter, Arrays.asList(disjuncts)); + } + + public DisjunctionRestriction(DisjunctionRestriction other, int shift) { + super(other, shift); + disjuncts = new ArrayList<>(); + other.disjuncts.forEach(r -> disjuncts.add(r.shift(shift))); + } + + protected Collection getDisjuncts() { + return disjuncts; + } + + @Override + public DisjunctionRestriction shift(int shiftStep) { + return new DisjunctionRestriction(this, shiftStep); + } + + @Override + public AbstractSuffixValueRestriction concretize(Mapping mapping) { + Collection conc = new ArrayList<>(); + disjuncts.forEach(r -> conc.add(r.concretize(mapping))); + return create(parameter, conc); + } + + @Override + public Expression toGuardExpression(Set vals) { + Expression[] exprs = new Expression[disjuncts.size()]; + int i = 0; + for (AbstractSuffixValueRestriction r : disjuncts) { + exprs[i++] = r.toGuardExpression(vals); + } + return ExpressionUtil.or(exprs); + } + + @Override + public boolean isTrue() { + return disjuncts.isEmpty(); + } + + @Override + public boolean isFalse() { + return !disjuncts.isEmpty(); + } + + @Override + public boolean containsFresh() { + return disjuncts.stream().filter(AbstractSuffixValueRestriction::containsFresh).findAny().isPresent(); + } + + @Override + public AbstractSuffixValueRestriction merge(AbstractSuffixValueRestriction other, + Map prior) { + throw new RuntimeException("Unsupported operation"); + } + + @Override + public boolean revealsRegister(SymbolicDataValue r) { + return disjuncts.stream().filter(d -> d.revealsRegister(r)).findAny().isPresent(); + } + + @Override + public AbstractSuffixValueRestriction relabel(Mapping renaming) { + Collection relabeled = new ArrayList<>(); + disjuncts.forEach(r -> relabeled.add(r.relabel(renaming))); + return create(parameter, relabeled); + } + + @Override + public List getRestrictions(Expression element) { + List restrictions = new ArrayList<>(); + for (AbstractSuffixValueRestriction r : disjuncts) { + if (r instanceof ElementRestriction er && er.containsElement(element)) { + restrictions.addAll(er.getRestrictions(element)); + } + } + return restrictions; + } + + @Override + public boolean containsElement(Expression element) { + for (AbstractSuffixValueRestriction r : disjuncts) { + if (r instanceof ElementRestriction er && er.containsElement(element)) { + return true; + } + } + return false; + } + + @Override + public Set> getElements() { + Set> ret = new LinkedHashSet<>(); + for (AbstractSuffixValueRestriction r : disjuncts) { + if (r instanceof ElementRestriction er) { + ret.addAll(er.getElements()); + } + } + return ret; + } + + @Override + public AbstractSuffixValueRestriction replaceElement(Expression replace, Expression by) { + Collection replaced = new ArrayList<>(); + for (AbstractSuffixValueRestriction r : disjuncts) { + if (r instanceof ElementRestriction er && er.containsElement(replace)) { + replaced.add(er.replaceElement(replace, by)); + } else { + replaced.add(r); + } + } + return create(getParameter(), replaced); + } + + @Override + public boolean contains(AbstractSuffixValueRestriction restr) { + for (AbstractSuffixValueRestriction r : disjuncts) { + if (r.equals(restr)) { + return true; + } + if (r instanceof RestrictionContainer rc && rc.contains(restr)) { + return true; + } + } + return false; + } + + @Override + public boolean containsUnmapped() { + for (AbstractSuffixValueRestriction r : disjuncts) { + if (r instanceof UnmappedEqualityRestriction) { + return true; + } + } + return false; + } + + @Override + public AbstractSuffixValueRestriction replace(AbstractSuffixValueRestriction replace, AbstractSuffixValueRestriction with) { + Collection replaced = new ArrayList<>(); + for (AbstractSuffixValueRestriction r : disjuncts) { + if (r.equals(replace)) { + if (with instanceof DisjunctionRestriction dr) { + replaced.addAll(dr.disjuncts); + } else { + replaced.add(with); + } + } else if (r instanceof RestrictionContainer rc && rc.contains(replace)) { + AbstractSuffixValueRestriction nrc = rc.replace(replace, with); + if (nrc instanceof DisjunctionRestriction dr) { + replaced.addAll(dr.getDisjuncts()); + } else { + replaced.add(nrc); + } + } else { + replaced.add(r); + } + } + return create(getParameter(), replaced); + } + + @Override + public DisjunctionRestriction cast() { + return this; + } + + @Override + public String toString() { + Iterator it = disjuncts.iterator(); + String str = "("; + while (it.hasNext()) { + str = str + it.next().toString(); + if (it.hasNext()) { + str = str + " OR "; + } + } + return str + ")"; + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) { + return false; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + DisjunctionRestriction other = (DisjunctionRestriction) obj; + return other.disjuncts.equals(disjuncts); + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 61 * hash + disjuncts.hashCode(); + return hash; + } + + public static AbstractSuffixValueRestriction create(SuffixValue parameter, Collection disjuncts) { + if (disjuncts != null) { + disjuncts = flattenDisjuncts(disjuncts); + } else { + disjuncts = new ArrayList<>(); + } + boolean isTrue = disjuncts.stream().filter(d -> d.isTrue()).findAny().isPresent(); + if (disjuncts.isEmpty() || isTrue) { + return new TrueRestriction(parameter); + } + if (disjuncts.size() == 1) { + return disjuncts.iterator().next(); + } + DisjunctionRestriction disjunction = new DisjunctionRestriction(parameter, disjuncts); + if (disjunction.isTrue()) { + return new TrueRestriction(parameter); + } + return disjunction; + } + + public static AbstractSuffixValueRestriction create(SuffixValue parameter, AbstractSuffixValueRestriction ... disjuncts) { + return create(parameter, Arrays.asList(disjuncts)); + } + + private static Set flattenDisjuncts(Collection disjuncts) { + Set dis = new LinkedHashSet<>(); + for (AbstractSuffixValueRestriction r : disjuncts) { + if (r instanceof DisjunctionRestriction dr) { + dis.addAll(flattenDisjuncts(dr.disjuncts)); + } else { + dis.add(r); + } + } + return dis; + } +} diff --git a/src/main/java/de/learnlib/ralib/theory/ElementRestriction.java b/src/main/java/de/learnlib/ralib/theory/ElementRestriction.java new file mode 100644 index 000000000..5ab14072e --- /dev/null +++ b/src/main/java/de/learnlib/ralib/theory/ElementRestriction.java @@ -0,0 +1,20 @@ +package de.learnlib.ralib.theory; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Set; + +import gov.nasa.jpf.constraints.api.Expression; + +public interface ElementRestriction { + + public boolean containsElement(Expression element); + + public Set> getElements(); + + public AbstractSuffixValueRestriction replaceElement(Expression replace, Expression by); + + public List getRestrictions(Expression element); + + public AbstractSuffixValueRestriction cast(); +} diff --git a/src/main/java/de/learnlib/ralib/theory/EquivalenceClassFilter.java b/src/main/java/de/learnlib/ralib/theory/EquivalenceClassFilter.java index 35a732eda..3f0314408 100644 --- a/src/main/java/de/learnlib/ralib/theory/EquivalenceClassFilter.java +++ b/src/main/java/de/learnlib/ralib/theory/EquivalenceClassFilter.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; +import de.learnlib.ralib.data.Constants; import de.learnlib.ralib.data.DataType; import de.learnlib.ralib.data.DataValue; import de.learnlib.ralib.data.Mapping; @@ -28,8 +29,9 @@ public EquivalenceClassFilter(List equivClasses, boolean useOptimizat this.useOptimization = useOptimization; } - public List toList(SuffixValueRestriction restr, - Word prefix, Word suffix, WordValuation valuation) { + public List toList(AbstractSuffixValueRestriction restr, + Word prefix, Word suffix, + WordValuation valuation, Constants consts) { if (!useOptimization) { return equivClasses; @@ -60,6 +62,7 @@ public List toList(SuffixValueRestriction restr, } } } + mapping.putAll(consts); Expression expr = restr.toGuardExpression(mapping.keySet()); for (DataValue ec : equivClasses) { diff --git a/src/main/java/de/learnlib/ralib/theory/FalseRestriction.java b/src/main/java/de/learnlib/ralib/theory/FalseRestriction.java new file mode 100644 index 000000000..67831345e --- /dev/null +++ b/src/main/java/de/learnlib/ralib/theory/FalseRestriction.java @@ -0,0 +1,81 @@ +package de.learnlib.ralib.theory; + +import java.util.Objects; + +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 gov.nasa.jpf.constraints.util.ExpressionUtil; + +public class FalseRestriction extends SuffixValueRestriction { + + public FalseRestriction(SuffixValue parameter) { + super(parameter, ExpressionUtil.FALSE); + } + + public FalseRestriction(FalseRestriction other, int shift) { + super(other, shift); + } + + @Override + public SuffixValueRestriction concretize(Mapping mapping) { + return this; + } + + @Override + public SuffixValueRestriction concretize(Mapping ... valuations) { + return this; + } + + @Override + public boolean isTrue() { + return false; + } + + @Override + public boolean isFalse() { + return true; + } + + @Override + public boolean containsFresh() { + return false; + } + + @Override + public SuffixValueRestriction or(SuffixValueRestriction other) { + return other; + } + + @Override + public SuffixValueRestriction and(SuffixValueRestriction other) { + return this; + } + + @Override + public FalseRestriction shift(int shiftStep) { + return new FalseRestriction(this, shiftStep); + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) { + return false; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 37 * hash + Objects.hashCode(getClass()); + return hash; + } +} diff --git a/src/main/java/de/learnlib/ralib/theory/FreshSuffixValue.java b/src/main/java/de/learnlib/ralib/theory/FreshSuffixValue.java index 2bc59b7ce..b614c72c7 100644 --- a/src/main/java/de/learnlib/ralib/theory/FreshSuffixValue.java +++ b/src/main/java/de/learnlib/ralib/theory/FreshSuffixValue.java @@ -3,16 +3,20 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; 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.data.TypedValue; 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; -public class FreshSuffixValue extends SuffixValueRestriction { +public class FreshSuffixValue extends AbstractSuffixValueRestriction { public FreshSuffixValue(SuffixValue param) { super(param); } @@ -34,12 +38,17 @@ public Expression toGuardExpression(Set vals) { } @Override - public SuffixValueRestriction shift(int shiftStep) { + public AbstractSuffixValueRestriction shift(int shiftStep) { return new FreshSuffixValue(this, shiftStep); } @Override - public SuffixValueRestriction merge(SuffixValueRestriction other, Map prior) { + public AbstractSuffixValueRestriction concretize(Mapping mapping) { + return this; + } + + @Override + public AbstractSuffixValueRestriction merge(AbstractSuffixValueRestriction other, Map prior) { if (other instanceof FreshSuffixValue) { return this; } @@ -55,4 +64,45 @@ public String toString() { public boolean revealsRegister(SymbolicDataValue r) { return false; } + + @Override + public boolean isTrue() { + return false; + } + + @Override + public boolean isFalse() { + return false; + } + + @Override + public AbstractSuffixValueRestriction relabel(Mapping renaming) { + return this; + } + + @Override + public boolean containsFresh() { + return true; + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) { + return false; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 37 * hash + Objects.hashCode(getClass()); + return hash; + } } diff --git a/src/main/java/de/learnlib/ralib/theory/RestrictionContainer.java b/src/main/java/de/learnlib/ralib/theory/RestrictionContainer.java new file mode 100644 index 000000000..765773fd2 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/theory/RestrictionContainer.java @@ -0,0 +1,12 @@ +package de.learnlib.ralib.theory; + +public interface RestrictionContainer { + + public boolean contains(AbstractSuffixValueRestriction restr); + + public boolean containsUnmapped(); + + public AbstractSuffixValueRestriction replace(AbstractSuffixValueRestriction replace, AbstractSuffixValueRestriction with); + + public AbstractSuffixValueRestriction cast(); +} diff --git a/src/main/java/de/learnlib/ralib/theory/SDT.java b/src/main/java/de/learnlib/ralib/theory/SDT.java index f829c4434..9931b9cfb 100644 --- a/src/main/java/de/learnlib/ralib/theory/SDT.java +++ b/src/main/java/de/learnlib/ralib/theory/SDT.java @@ -22,12 +22,15 @@ import de.learnlib.ralib.data.*; import de.learnlib.ralib.data.SymbolicDataValue.Register; +import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; import de.learnlib.ralib.data.util.RemappingIterator; import de.learnlib.ralib.data.util.SymbolicDataValueGenerator; +import de.learnlib.ralib.learning.SymbolicSuffix; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.smt.SMTUtil; import de.learnlib.ralib.words.DataWords; 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.Negation; import gov.nasa.jpf.constraints.util.ExpressionUtil; @@ -127,6 +130,21 @@ public Set getVariables() { return variables; } + public Set getGuards(SuffixValue sv) { + Set guards = new LinkedHashSet<>(); + if (children == null) { + return guards; + } + for (Map.Entry child : children.entrySet()) { + SDTGuard g = child.getKey(); + if (g.getParameter().equals(sv)) { + guards.add(g); + } + guards.addAll(child.getValue().getGuards(sv)); + } + return guards; + } + public Set getSuffixValues() { Set values = new LinkedHashSet<>(); if (this instanceof SDTLeaf) @@ -413,6 +431,25 @@ public Map, Boolean> getAllPaths(List path) { return ret; } + /** + * Add {@code shift} to each suffix value id in SDT, including any potential guard elements + * + * @param shift + * @return + */ + public SDT shift(int shift) { + if (this.children == null) { + return this; + } + Map children = new LinkedHashMap<>(); + for (Map.Entry child : this.children.entrySet()) { + SDT sdt = child.getValue().shift(shift); + SDTGuard guard = SDTGuard.shift(child.getKey(), shift); + children.put(guard, sdt); + } + return new SDT(children); + } + private static SDT findFinest(int i, List sdts, SDT curr) { i++; if (sdts.size() == i) { @@ -521,4 +558,56 @@ public static boolean equivalentUnderId(SDT sdt1, SDT sdt2) { return sdt1.isEquivalentUnderCondition(sdt2, ExpressionUtil.TRUE); } + public static boolean equalUnderActionRemapping(SDT sdtIf, SDT sdtElse, Word uIf, Word uElse) { + assert DataWords.actsOf(uIf).equals(DataWords.actsOf(uElse)) : "Action mismatch"; + Word u = uIf.prefix(uIf.length() - 1); + int arity = uIf.lastSymbol().getBaseSymbol().getArity(); + int prefixArity = DataWords.paramValLength(uIf) - arity; + DataValue[] uIfVals = uIf.lastSymbol().getParameterValues(); + ArrayList uVals = new ArrayList<>(Arrays.asList(DataWords.valsOf(u))); + + Mapping renaming = new Mapping<>(); + for (SDTGuardElement e : sdtElse.getVariables()) { + if (!SDTGuardElement.isRegister(e)) { + continue; + } + Register r = (Register) e; + if (r.getId() > prefixArity) { + int paramId = r.getId() - prefixArity; + DataValue d = uIfVals[paramId - 1]; + int uId = uVals.indexOf(d); + if (uId < 0) { + continue; + } + Register nr = new Register(d.getDataType(), uId + 1); + renaming.put(r, nr); + } + } + return sdtIf.isEquivalent(sdtElse, SDTRelabeling.fromMapping(renaming)); + } + + public static SDT makeRejectingSDT(SymbolicSuffix suffix) { + Queue types = new ArrayDeque<>(); + for (ParameterizedSymbol ps : suffix.getActions()) { + for (DataType type : ps.getPtypes()) { + types.offer(type); + } + } + return makeRejectingSDT(1, types); + } + + public static 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/theory/SDTGuard.java b/src/main/java/de/learnlib/ralib/theory/SDTGuard.java index 4f99643f9..929e204cf 100644 --- a/src/main/java/de/learnlib/ralib/theory/SDTGuard.java +++ b/src/main/java/de/learnlib/ralib/theory/SDTGuard.java @@ -348,4 +348,38 @@ static SDTGuard toDeqGuard(SDTGuard in) { throw new RuntimeException("not refactored yet"); }; } + + static SDTGuard shift(SDTGuard in, int shift) { + SuffixValue p = new SuffixValue(in.getParameter().getDataType(), in.getParameter().getId() + shift); + switch (in) { + case SDTGuard.EqualityGuard guard: + return new SDTGuard.EqualityGuard(p, shiftRegister(guard.register, shift)); + case SDTGuard.DisequalityGuard guard: + return new SDTGuard.DisequalityGuard(p, shiftRegister(guard.register, shift)); + case SDTGuard.IntervalGuard guard: + return new SDTGuard.IntervalGuard(p, + shiftRegister(guard.smallerElement, shift), + shiftRegister(guard.greaterElement, shift), + guard.smallerEqual, guard.greaterEqual); + case SDTGuard.SDTTrueGuard guard: + return new SDTGuard.SDTTrueGuard(p); + case SDTGuard.SDTAndGuard guard: + List conjuncts = new ArrayList<>(); + guard.conjuncts.forEach(g -> conjuncts.add(shift(g, shift))); + return new SDTGuard.SDTAndGuard(p, conjuncts); + case SDTGuard.SDTOrGuard guard : + List disjuncts = new ArrayList<>(); + guard.disjuncts.forEach(g -> disjuncts.add(shift(g, shift))); + return new SDTGuard.SDTOrGuard(p, disjuncts); + default: + throw new RuntimeException("should not be reachable"); + } + } + + private static SDTGuardElement shiftRegister(SDTGuardElement r, int shift) { + if (r instanceof SuffixValue s) { + return new SuffixValue(s.getDataType(), s.getId() + shift); + } + return r; + } } diff --git a/src/main/java/de/learnlib/ralib/theory/SuffixValueRestriction.java b/src/main/java/de/learnlib/ralib/theory/SuffixValueRestriction.java index c50777ba0..8120de6da 100644 --- a/src/main/java/de/learnlib/ralib/theory/SuffixValueRestriction.java +++ b/src/main/java/de/learnlib/ralib/theory/SuffixValueRestriction.java @@ -1,142 +1,291 @@ package de.learnlib.ralib.theory; +import java.math.BigDecimal; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; import java.util.Set; -import de.learnlib.ralib.data.*; +import de.learnlib.ralib.data.DataType; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.Mapping; +import de.learnlib.ralib.data.SDTGuardElement; +import de.learnlib.ralib.data.SymbolicDataValue; import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; -import de.learnlib.ralib.theory.equality.EqualRestriction; -import de.learnlib.ralib.words.DataWords; -import de.learnlib.ralib.words.PSymbolInstance; +import de.learnlib.ralib.data.TypedValue; +import de.learnlib.ralib.data.VarMapping; +import de.learnlib.ralib.smt.ReplacingValuesVisitor; +import de.learnlib.ralib.smt.ReplacingVarsVisitor; +import de.learnlib.ralib.smt.SMTUtil; +import de.learnlib.ralib.smt.VarsValuationVisitor; +import de.learnlib.ralib.theory.equality.EqualityRestriction; import gov.nasa.jpf.constraints.api.Expression; -import net.automatalib.word.Word; +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.types.BuiltinTypes; +import gov.nasa.jpf.constraints.util.DuplicatingVisitor; +import gov.nasa.jpf.constraints.util.ExpressionUtil; -public abstract class SuffixValueRestriction { - protected final SuffixValue parameter; +public class SuffixValueRestriction extends AbstractSuffixValueRestriction { - public SuffixValueRestriction(SuffixValue parameter) { - this.parameter = parameter; - } + protected final Expression expr; + + protected class DummyVisitor extends DuplicatingVisitor, ? extends Variable>> { + @Override + public Expression visit(Variable v, Map, ? extends Variable> data) { + Variable newVar = data.get(v); + return (newVar != null) ? newVar : v; + } + + public Expression apply(Expression expr, Map, ? extends Variable> rename) { + return visit(expr, rename).requireAs(expr.getType()); + } + }; + + protected class DummyDataValue extends Variable { + int id; + DataType type; + + public DummyDataValue(DataType type, int id) { + super(BuiltinTypes.DECIMAL, "dummy" + id); + this.type = type; + this.id = id; + } + + public DataType getDataType() { + return type; + } - public SuffixValueRestriction(SuffixValueRestriction other) { - parameter = new SuffixValue(other.parameter.getDataType(), other.parameter.getId()); + public int getId() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final DummyDataValue other = (DummyDataValue) obj; + if (!Objects.equals(this.type, other.type)) { + return false; + } + return this.id == other.id; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 97 * hash + Objects.hashCode(this.id); + hash = 97 * hash + Objects.hashCode(this.type); + hash = 97 * hash + Objects.hashCode(this.getClass()); + return hash; + } + }; + + public SuffixValueRestriction(SuffixValue parameter, Expression expr) { + super(parameter); + this.expr = expr; } public SuffixValueRestriction(SuffixValueRestriction other, int shift) { - parameter = new SuffixValue(other.parameter.getDataType(), other.parameter.getId()+shift); - } - - public SuffixValue getParameter() { - return parameter; - } - - public abstract SuffixValueRestriction shift(int shiftStep); - - public abstract Expression toGuardExpression(Set vals); - - public abstract SuffixValueRestriction merge(SuffixValueRestriction other, Map prior); - - public abstract boolean revealsRegister(SymbolicDataValue r); - - /** - * Generate a generic restriction using Fresh, Unrestricted and Equal restriction types - * - * @param sv - * @param prefix - * @param suffix - * @param consts - * @return - */ - public static SuffixValueRestriction genericRestriction(SuffixValue sv, Word prefix, Word suffix, Constants consts) { - DataValue[] prefixVals = DataWords.valsOf(prefix); - DataValue[] suffixVals = DataWords.valsOf(suffix); - DataType[] prefixTypes = DataWords.typesOf(DataWords.actsOf(prefix)); - DataType[] suffixTypes = DataWords.typesOf(DataWords.actsOf(suffix)); - DataValue val = suffixVals[sv.getId()-1]; - int firstSymbolArity = suffix.length() > 0 ? suffix.getSymbol(0).getBaseSymbol().getArity() : 0; - - boolean unrestricted = false; - for (int i = 0; i < prefixVals.length; i++) { - DataValue dv = prefixVals[i]; - DataType dt = prefixTypes[i]; - if (dt.equals(sv.getDataType()) && dv.equals(val)) { - unrestricted = true; - break; - } - } - if (consts.containsValue(val)) { - unrestricted = true; - } - boolean equalsSuffixValue = false; - int equalSV = -1; - for (int i = 0; i < sv.getId()-1 && !equalsSuffixValue; i++) { - DataType dt = suffixTypes[i]; - if (dt.equals(sv.getDataType()) && suffixVals[i].equals(val)) { - if (sv.getId() <= firstSymbolArity) { - unrestricted = true; - } else { - equalsSuffixValue = true; - equalSV = i; - } - } + super(other, shift); + Set suffixVals = new LinkedHashSet<>(); + SMTUtil.getSymbolicDataValues(other.expr) + .stream() + .filter(s -> s.isSuffixValue()) + .forEach(s -> suffixVals.add((SuffixValue)s)); + Map toDummy = new LinkedHashMap<>(); + Map fromDummy = new LinkedHashMap<>(); + suffixVals.stream().forEach(s -> toDummy.put(s, new DummyDataValue(s.getDataType(), s.getId()))); + toDummy.values().stream().forEach(d -> fromDummy.put(d, new SuffixValue(d.getDataType(), d.getId() + shift))); + DummyVisitor visitor = new DummyVisitor(); + Expression dummyExpr = visitor.apply(other.expr, toDummy); + this.expr = visitor.apply(dummyExpr, fromDummy); + } + + @Override + public AbstractSuffixValueRestriction shift(int shiftStep) { + return new SuffixValueRestriction(this, shiftStep); + } + + @Override + public AbstractSuffixValueRestriction concretize(Mapping mapping) { + VarsValuationVisitor vvv = new VarsValuationVisitor(); + Expression expr = vvv.apply(this.expr, mapping); + return new SuffixValueRestriction(parameter, expr); + } + + public SuffixValueRestriction or(SuffixValueRestriction other) { + if (!this.parameter.equals(other.parameter)) { + throw new IllegalArgumentException("Mismatched parameters: " + this.parameter + ", " + other.parameter); } + return new SuffixValueRestriction(parameter, ExpressionUtil.or(this.expr, other.expr)); + } - // case equal to previous suffix value - if (equalsSuffixValue && !unrestricted) { - SuffixValueRestriction restr = new EqualRestriction(sv, new SuffixValue(suffixVals[equalSV].getDataType(), equalSV+1)); - return restr; + public SuffixValueRestriction and(SuffixValueRestriction other) { + if (!this.parameter.equals(other.parameter)) { + throw new IllegalArgumentException("Mismatched parameters: " + this.parameter + ", " + other.parameter); } - // case fresh - else if (!equalsSuffixValue && !unrestricted) { - return new FreshSuffixValue(sv); + return new SuffixValueRestriction(parameter, ExpressionUtil.and(this.expr, other.expr)); + } + + @Override + public Expression toGuardExpression(Set vals) { + return expr; + } + + public SuffixValueRestriction concretize(Mapping ... valuations) { + VarsValuationVisitor vvv = new VarsValuationVisitor(); + Mapping valuation = new Mapping<>(); + for (Mapping v : valuations) { + valuation.putAll(v); + } + Expression expr = vvv.apply(this.expr, valuation); + return new SuffixValueRestriction(parameter, expr); + } + + @Override + public AbstractSuffixValueRestriction merge(AbstractSuffixValueRestriction other, + Map prior) { + throw new RuntimeException("Not supported for this type of restriction"); + } + + @Override + public boolean revealsRegister(SymbolicDataValue r) { + throw new RuntimeException("Not supported for this type of restriction"); + } + + @Override + public boolean isTrue() { + return expr.equals(ExpressionUtil.TRUE); + } + + @Override + public boolean isFalse() { + return expr.equals(ExpressionUtil.FALSE); + } + + @Override + public boolean containsFresh() { + throw new RuntimeException("Not supported for this type of restriction"); + } + + @Override + public AbstractSuffixValueRestriction relabel(Mapping renaming) { + if (renaming.isEmpty()) { + return this; } - // case unrestricted - else { - return new UnrestrictedSuffixValue(sv); + K firstKey = renaming.keySet().iterator().next(); + V firstValue = renaming.values().iterator().next(); + if (firstKey instanceof DataValue && firstValue instanceof SDTGuardElement) { + ReplacingValuesVisitor rvv = new ReplacingValuesVisitor(); + Mapping map = new Mapping<>(); + renaming.forEach((k,v) -> map.put((DataValue) k, (SDTGuardElement) v)); + Expression expr = rvv.apply(this.expr, map); + return new SuffixValueRestriction(parameter, expr); + } else if (firstKey instanceof SymbolicDataValue) { + if (firstValue instanceof SymbolicDataValue) { + ReplacingVarsVisitor rvv = new ReplacingVarsVisitor(); + VarMapping map = new VarMapping<>(); + renaming.forEach((k,v) -> map.put((SymbolicDataValue) k, (SymbolicDataValue) v)); + Expression expr = rvv.apply(this.expr, map); + return new SuffixValueRestriction(parameter, expr); + } else if (firstValue instanceof DataValue) { + VarsValuationVisitor vvv = new VarsValuationVisitor(); + Mapping map = new Mapping<>(); + renaming.forEach((k,v) -> map.put((SymbolicDataValue) k, (DataValue) v)); + Expression expr = vvv.apply(this.expr, map); + return new SuffixValueRestriction(parameter, expr); + } } + throw new RuntimeException("Unsupported parameter type"); } @Override - public int hashCode() { - return Objects.hash(parameter); + public String toString() { + return expr.toString(); } @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } SuffixValueRestriction other = (SuffixValueRestriction) obj; - return Objects.equals(parameter, other.parameter); - } - - public static SuffixValueRestriction genericRestriction(SDTGuard guard, Map prior) { - SuffixValue suffixValue = guard.getParameter(); - // case fresh - if (guard instanceof SDTGuard.SDTTrueGuard || guard instanceof SDTGuard.DisequalityGuard) { - return new FreshSuffixValue(suffixValue); - // case equal to previous suffix value - } else if (guard instanceof SDTGuard.EqualityGuard equalityGuard) { - SDTGuardElement param = equalityGuard.register(); - if (param instanceof SuffixValue suffixValueParam) { - SuffixValueRestriction restr = prior.get(param); - if (restr instanceof FreshSuffixValue) { - return new EqualRestriction(suffixValue, suffixValueParam); - } else if (restr instanceof EqualRestriction equalRestriction) { - return new EqualRestriction(suffixValue, equalRestriction.getEqualParameter()); - } else { - return new UnrestrictedSuffixValue(suffixValue); - } - } else { - return new UnrestrictedSuffixValue(suffixValue); - } - // case unrestricted - } else { - return new UnrestrictedSuffixValue(suffixValue); - } + if (!Objects.equals(parameter, other.parameter)) { + return false; + } + return other.expr.equals(expr); + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 89 * hash + (expr == null ? 0 : Objects.hashCode(expr)); + return hash; + } + + public static AbstractSuffixValueRestriction equalityRestriction(SuffixValue s, Expression ... elements) { + if (elements.length == 0) { + return new FalseRestriction(s); + } + + boolean isSDTGuardElement = true; + Expression[] eqs = new Expression[elements.length]; + Set regs = new LinkedHashSet<>(); + for (int i = 0; i < elements.length; i++) { + if (elements[i] instanceof SDTGuardElement e) { + regs.add(e); + } else { + isSDTGuardElement = false; + } + eqs[i] = new NumericBooleanExpression(s, NumericComparator.EQ, elements[i]); + } + if (isSDTGuardElement) { + return new EqualityRestriction(s, regs); + } + return new SuffixValueRestriction(s, ExpressionUtil.or(eqs)); + } + + public static AbstractSuffixValueRestriction equalityRestriction(SuffixValue s, Collection elements) { + Expression[] elems = new Expression[elements.size()]; + int i = 0; + for (Expression e : elements) { + elems[i++] = e; + } + return equalityRestriction(s, elems); + } + + public static SuffixValueRestriction disequalityRestriction(SuffixValue s, Expression ... elements) { + if (elements.length == 0) { + return new TrueRestriction(s); + } + Expression[] eqs = new Expression[elements.length]; + for (int i = 0; i < elements.length; i++) { + eqs[i] = new NumericBooleanExpression(s, NumericComparator.NE, elements[i]); + } + return new SuffixValueRestriction(s, ExpressionUtil.and(eqs)); + } + + public static SuffixValueRestriction disequalityRestriction(SuffixValue s, Collection elements) { + Expression[] elems = new Expression[elements.size()]; + int i = 0; + for (Expression e : elements) { + elems[i++] = e; + } + return disequalityRestriction(s, elems); + } + + public static SuffixValueRestriction fresh(SuffixValue s, Collection elements) { + return disequalityRestriction(s, elements); } } diff --git a/src/main/java/de/learnlib/ralib/theory/Theory.java b/src/main/java/de/learnlib/ralib/theory/Theory.java index bc7023285..e168ee7d4 100644 --- a/src/main/java/de/learnlib/ralib/theory/Theory.java +++ b/src/main/java/de/learnlib/ralib/theory/Theory.java @@ -24,6 +24,7 @@ import de.learnlib.ralib.data.Constants; import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.RegisterValuation; import de.learnlib.ralib.data.SuffixValuation; import de.learnlib.ralib.data.SymbolicDataValue; import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; @@ -115,12 +116,37 @@ DataValue instantiate(Word prefix, */ public Optional instantiate(Word prefix, ParameterizedSymbol ps, Expression guard, int param, - Constants constants, ConstraintSolver solver); + List prior, Constants constants, ConstraintSolver solver); - SuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, Word prefix, Word suffix, Constants consts); + /** + * Restrict suffix value by examining relation between corresponding data value in {@code suffix} + * and values in {@code prefix} and {@code u} during counterexample analysis. + *
+ * Note that restrictions computed by this method are specific to the counterexample and should + * not be used for suffixes added to the data structure. + * + * @param suffixValue suffix value to compute restriction for + * @param prefix prefix of counterexample + * @param suffix suffix of counterexample + * @param u prefix in data structure corresponding to {@code prefix} + * @param prefixValuation valuation after a run of {@code prefix} over the hypothesis + * @param uValuation valuation after a run of {@code u} over the hypothesis + * @param consts constants + * @return + */ + public AbstractSuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, + Word prefix, + Word suffix, + Word u, + RegisterValuation prefixValuation, + RegisterValuation uValuation, + Constants consts); + + AbstractSuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, Word prefix, Word suffix, Constants consts); - SuffixValueRestriction restrictSuffixValue(SDTGuard guard, Map prior); + AbstractSuffixValueRestriction restrictSuffixValue(SDTGuard guard, Map prior); boolean guardRevealsRegister(SDTGuard guard, SymbolicDataValue registers); + boolean isUsingSuffixOptimization(); } diff --git a/src/main/java/de/learnlib/ralib/theory/TrueRestriction.java b/src/main/java/de/learnlib/ralib/theory/TrueRestriction.java new file mode 100644 index 000000000..37cec235d --- /dev/null +++ b/src/main/java/de/learnlib/ralib/theory/TrueRestriction.java @@ -0,0 +1,86 @@ +package de.learnlib.ralib.theory; + +import java.util.Objects; + +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.data.TypedValue; +import gov.nasa.jpf.constraints.util.ExpressionUtil; + +public class TrueRestriction extends SuffixValueRestriction { + + public TrueRestriction(SuffixValue parameter) { + super(parameter, ExpressionUtil.TRUE); + } + + public TrueRestriction(TrueRestriction other, int shift) { + super(other, shift); + } + + @Override + public TrueRestriction shift(int shiftStep) { + return new TrueRestriction(this, shiftStep); + } + @Override + public SuffixValueRestriction concretize(Mapping mapping) { + return this; + } + + @Override + public SuffixValueRestriction concretize(Mapping ... valuations) { + return this; + } + + @Override + public boolean isTrue() { + return true; + } + + @Override + public boolean isFalse() { + return false; + } + + @Override + public boolean containsFresh() { + return true; + } + + @Override + public SuffixValueRestriction or(SuffixValueRestriction other) { + return this; + } + + @Override + public SuffixValueRestriction and(SuffixValueRestriction other) { + return other; + } + + @Override + public AbstractSuffixValueRestriction relabel(Mapping renaming) { + return this; + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) { + return false; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 37 * hash + Objects.hashCode(getClass()); + return hash; + } +} diff --git a/src/main/java/de/learnlib/ralib/theory/UnrestrictedSuffixValue.java b/src/main/java/de/learnlib/ralib/theory/UnrestrictedSuffixValue.java index 4cb1dde96..e6020a6c4 100644 --- a/src/main/java/de/learnlib/ralib/theory/UnrestrictedSuffixValue.java +++ b/src/main/java/de/learnlib/ralib/theory/UnrestrictedSuffixValue.java @@ -1,14 +1,18 @@ package de.learnlib.ralib.theory; import java.util.Map; +import java.util.Objects; 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.data.TypedValue; import gov.nasa.jpf.constraints.api.Expression; import gov.nasa.jpf.constraints.util.ExpressionUtil; -public class UnrestrictedSuffixValue extends SuffixValueRestriction { +public class UnrestrictedSuffixValue extends AbstractSuffixValueRestriction { public UnrestrictedSuffixValue(SuffixValue parameter) { super(parameter); @@ -18,23 +22,48 @@ public UnrestrictedSuffixValue(UnrestrictedSuffixValue other, int shift) { super(other, shift); } + @Override + public AbstractSuffixValueRestriction concretize(Mapping mapping) { + return this; + } + @Override public Expression toGuardExpression(Set vals) { return ExpressionUtil.TRUE; } @Override - public SuffixValueRestriction shift(int shiftStep) { + public AbstractSuffixValueRestriction shift(int shiftStep) { return new UnrestrictedSuffixValue(this, shiftStep); } + @Override + public boolean isTrue() { + return false; + } + + @Override + public boolean isFalse() { + return false; + } + + @Override + public boolean containsFresh() { + return true; + } + + @Override + public AbstractSuffixValueRestriction relabel(Mapping renaming) { + return this; + } + @Override public String toString() { return "Unrestricted(" + parameter.toString() + ")"; } @Override - public SuffixValueRestriction merge(SuffixValueRestriction other, Map prior) { + public AbstractSuffixValueRestriction merge(AbstractSuffixValueRestriction other, Map prior) { return this; } @@ -42,4 +71,25 @@ public SuffixValueRestriction merge(SuffixValueRestriction other, Map toGuardExpression(Set vals) { } @Override - public SuffixValueRestriction shift(int shiftStep) { + public AbstractSuffixValueRestriction shift(int shiftStep) { return new EqualRestriction(this, shiftStep); } @Override - public SuffixValueRestriction merge(SuffixValueRestriction other, Map prior) { + public AbstractSuffixValueRestriction concretize(Mapping mapping) { + return this; + } + + @Override + public AbstractSuffixValueRestriction merge(AbstractSuffixValueRestriction other, Map prior) { assert other.getParameter().equals(parameter); if (prior.get(equalParam) instanceof FreshSuffixValue) { if (other instanceof EqualRestriction equalRestriction && @@ -60,6 +73,38 @@ public SuffixValueRestriction merge(SuffixValueRestriction other, Map element) { + return equalParam.equals(element); + } + + @Override + public Set> getElements() { + return Set.of(equalParam); + } + + @Override + public AbstractSuffixValueRestriction replaceElement(Expression replace, Expression by) { + if (!(by instanceof SuffixValue)) { + throw new IllegalArgumentException("Not a valid type for this restriction"); + } + + if (equalParam.asExpression().equals(replace)) { + return new EqualRestriction(getParameter(), (SuffixValue) by); + } + return this; + } + + @Override + public List getRestrictions(Expression element) { + return Arrays.asList(this); + } + + @Override + public EqualRestriction cast() { + return this; + } + @Override public String toString() { return "(" + parameter.toString() + "=" + equalParam.toString() + ")"; @@ -86,4 +131,30 @@ public SuffixValue getEqualParameter() { public boolean revealsRegister(SymbolicDataValue r) { return equalParam.equals(r); } + + @Override + public boolean isTrue() { + return false; + } + + @Override + public boolean isFalse() { + return false; + } + + @Override + public boolean containsFresh() { + return false; + } + + @Override + public AbstractSuffixValueRestriction relabel(Mapping renaming) { + for (Map.Entry e : renaming.entrySet()) { + if (e.getKey().equals(equalParam)) { + assert e.getValue() instanceof SDTGuardElement; + return new EqualityRestriction(parameter, Set.of((SDTGuardElement) e.getValue())); + } + } + return this; + } } diff --git a/src/main/java/de/learnlib/ralib/theory/equality/EqualityRestriction.java b/src/main/java/de/learnlib/ralib/theory/equality/EqualityRestriction.java new file mode 100644 index 000000000..c916a8ec0 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/theory/equality/EqualityRestriction.java @@ -0,0 +1,212 @@ +package de.learnlib.ralib.theory.equality; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.Mapping; +import de.learnlib.ralib.data.SDTGuardElement; +import de.learnlib.ralib.data.SymbolicDataValue; +import de.learnlib.ralib.data.SymbolicDataValue.Register; +import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; +import de.learnlib.ralib.data.TypedValue; +import de.learnlib.ralib.theory.AbstractSuffixValueRestriction; +import de.learnlib.ralib.theory.ElementRestriction; +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; + +public class EqualityRestriction extends AbstractSuffixValueRestriction implements ElementRestriction { + + private Set regs; + + public EqualityRestriction(SuffixValue parameter, Set regs) { + super(parameter); + for (SDTGuardElement r : regs) { + assert r != null; + } + this.regs = regs; + } + + public EqualityRestriction(EqualityRestriction other, int shift) { + super(other, shift); + regs = new LinkedHashSet<>(); + for (SDTGuardElement r : other.regs) { + if (r instanceof SuffixValue s) { + regs.add(new SuffixValue(s.getDataType(), s.getId() + shift)); + } else { + regs.add(r); + } + } + } + + @Override + public AbstractSuffixValueRestriction shift(int shiftStep) { + return new EqualityRestriction(this, shiftStep); + } + + @Override + public AbstractSuffixValueRestriction concretize(Mapping mapping) { + Set regs = new LinkedHashSet<>(); + for (SDTGuardElement r : this.regs) { + if (r instanceof SymbolicDataValue s && mapping.containsKey(s)) { + regs.add(mapping.get(s)); + } else if (!(r instanceof Register)) { + regs.add(r); + } + } + return new EqualityRestriction(parameter, regs); + } + + @Override + public Expression toGuardExpression(Set vals) { + Expression[] exprs = new Expression[regs.size()]; + int i = 0; + for (SDTGuardElement r : regs) { + if (r instanceof DataValue d) { + exprs[i] = new NumericBooleanExpression(parameter, NumericComparator.EQ, d); + } else if (r instanceof SymbolicDataValue s) { + exprs[i] = new NumericBooleanExpression(parameter, NumericComparator.EQ, s); + } else { + throw new RuntimeException("Unknown SDT guard element class: " + r.getClass()); + } + i++; + } + return ExpressionUtil.or(exprs); + } + + @Override + public AbstractSuffixValueRestriction merge(AbstractSuffixValueRestriction other, + Map prior) { + return null; + } + + @Override + public boolean revealsRegister(SymbolicDataValue r) { + return regs.stream().filter(e -> e.equals(r)).findAny().isPresent(); + } + + @Override + public EqualityRestriction relabel(Mapping renaming) { + Set regs = new LinkedHashSet<>(); + for (SDTGuardElement r : this.regs) { + if (renaming.containsKey(r)) { + TypedValue t = renaming.get(r); + assert t instanceof SDTGuardElement; + SDTGuardElement casted = (SDTGuardElement) t; + regs.add(casted); + } else { + regs.add(r); + } + } + return new EqualityRestriction(parameter, regs); + } + + @Override + public boolean isTrue() { + return false; + } + + @Override + public boolean isFalse() { + return false; + } + + @Override + public boolean containsFresh() { + return false; + } + + @Override + public boolean containsElement(Expression element) { + for (SDTGuardElement e : regs) { + Expression cast = SDTGuardElement.castToExpression(e); + if (element.equals(cast)) { + return true; + } + } + return false; + } + + @Override + public Set> getElements() { + Set> ret = new LinkedHashSet<>(); + regs.forEach(r -> ret.add(r.asExpression())); + return ret; + } + + public Set getGuardElements() { + return new LinkedHashSet<>(regs); + } + + @Override + public AbstractSuffixValueRestriction replaceElement(Expression replace, Expression by) { + if (!(by instanceof SDTGuardElement)) { + throw new IllegalArgumentException("Not a valid type for this restriction"); + } + Set nregs = new LinkedHashSet<>(); + for (SDTGuardElement e : regs) { + if (e.asExpression().equals(replace)) { + nregs.add((SDTGuardElement) by); + } + } + return new EqualityRestriction(getParameter(), nregs); + } + + @Override + public List getRestrictions(Expression element) { + return Arrays.asList(this); + } + + @Override + public EqualityRestriction cast() { + return this; + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) { + return false; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + if (!regs.equals(((EqualityRestriction) obj).regs)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 37 * hash + ((regs == null) ? 0 : regs.hashCode()); + return hash; + } + + @Override + public String toString() { + if (regs.isEmpty()) { + return ""; + } + + String str = ""; + int i = 0; + for (SDTGuardElement r : regs) { + str = str + "(" + parameter + " == " + r.toString() + ")"; + i++; + if (i < regs.size()) { + str = str + " OR "; + } + } + return str; + } +} 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 379beadc7..55aca95fc 100644 --- a/src/main/java/de/learnlib/ralib/theory/equality/EqualityTheory.java +++ b/src/main/java/de/learnlib/ralib/theory/equality/EqualityTheory.java @@ -16,20 +16,26 @@ */ package de.learnlib.ralib.theory.equality; +import java.math.BigDecimal; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; 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 java.util.Optional; +import java.util.Queue; import java.util.Set; import java.util.stream.Collectors; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,13 +44,12 @@ import de.learnlib.ralib.data.SymbolicDataValue.Parameter; import de.learnlib.ralib.data.SymbolicDataValue.Register; import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; +import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.ParameterGenerator; 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; import de.learnlib.ralib.words.DataWords; import de.learnlib.ralib.words.OutputSymbol; import de.learnlib.ralib.words.PSymbolInstance; @@ -57,6 +62,8 @@ */ public abstract class EqualityTheory implements Theory { + protected boolean useSuffixOpt = false; + protected boolean useNonFreeOptimization; protected boolean freshValues = false; @@ -78,232 +85,140 @@ public EqualityTheory() { this(false); } - public List getPotential(List vals) { - return vals; + @Override + public boolean isUsingSuffixOptimization() { + return useSuffixOpt; } - // given a map from guards to SDTs, merge guards based on whether they can - // use another SDT. Base case: always add the 'else' guard first. - private Map mergeGuards(Map eqs, SDTGuard.SDTAndGuard deqGuard, SDT deqSdt) { - Map retMap = new LinkedHashMap<>(); - List deqList = new ArrayList<>(); - List eqList = new ArrayList<>(); - for (Map.Entry e : eqs.entrySet()) { - SDT eqSdt = e.getValue(); - SDTGuard.EqualityGuard eqGuard = e.getKey(); - LOGGER.trace("comparing guards: " + eqGuard.toString() + " to " + deqGuard.toString() - + "\nSDT : " + eqSdt.toString() + "\nto SDT : " + deqSdt.toString()); - List ds = new ArrayList<>(); - ds.add(eqGuard); - LOGGER.trace("remapping: " + ds); - if (! eqSdt.isEquivalentUnder(deqSdt, ds)) { - LOGGER.trace("--> not eq."); - deqList.add(SDTGuard.toDeqGuard(eqGuard)); - eqList.add(eqGuard); - } else { - LOGGER.trace("--> equivalent"); - } - - } - if (eqList.isEmpty()) { - retMap.put(new SDTGuard.SDTTrueGuard(deqGuard.getParameter()), deqSdt); - } else if (eqList.size() == 1) { - SDTGuard.EqualityGuard q = eqList.get(0); - retMap.put(q, eqs.get(q)); - retMap.put(SDTGuard.toDeqGuard(q), deqSdt); - } else if (eqList.size() > 1) { - for (SDTGuard.EqualityGuard q : eqList) { - retMap.put(q, eqs.get(q)); - } - retMap.put(new SDTGuard.SDTAndGuard(deqGuard.getParameter(), deqList), deqSdt); - } - assert !retMap.isEmpty(); - - return retMap; + public List getPotential(List vals) { + return vals; } - // process a tree query @Override public SDT treeQuery(Word prefix, SymbolicSuffix suffix, WordValuation values, - Constants constants, SuffixValuation suffixValues, MultiTheoryTreeOracle oracle) { - - int pId = values.size() + 1; - - SuffixValue currentParam = suffix.getSuffixValue(pId); - DataType type = currentParam.getDataType(); - - Map tempKids = new LinkedHashMap<>(); - - Collection potSet = DataWords.joinValsToSet(constants.values(type), - DataWords.valSet(prefix, type), suffixValues.values(type)); - - List potList = new ArrayList<>(potSet); - List potential = getPotential(potList); - - DataValue fresh = getFreshValue(potential); - - List equivClasses = new ArrayList<>(potSet); - equivClasses.add(fresh); - //System.out.println(" prefix: " + prefix); - //System.out.println(" potential: " + potential); - //System.out.println(" eqs " + Arrays.toString(equivClasses.toArray())); - EquivalenceClassFilter eqcFilter = new EquivalenceClassFilter(equivClasses, useNonFreeOptimization); - List filteredEquivClasses = eqcFilter.toList(suffix.getRestriction(currentParam), prefix, suffix.getActions(), values); - assert filteredEquivClasses.size() > 0; - - // TODO: integrate fresh-value optimization with restrictions - // special case: fresh values in outputs - if (freshValues) { - - ParameterizedSymbol ps = computeSymbol(suffix, pId); - - if (ps instanceof OutputSymbol && ps.getArity() > 0) { - - int idx = computeLocalIndex(suffix, pId); - Word query = buildQuery(prefix, suffix, values); - Word trace = ioOracle.trace(query); - - if (!trace.isEmpty() && trace.lastSymbol().getBaseSymbol().equals(ps)) { - - DataValue d = trace.lastSymbol().getParameterValues()[idx]; - - if (d instanceof FreshValue) { - d = getFreshValue(potential); - values.put(pId, d); - WordValuation trueValues = new WordValuation(); - trueValues.putAll(values); - SuffixValuation trueSuffixValues = new SuffixValuation(); - trueSuffixValues.putAll(suffixValues); - trueSuffixValues.put(currentParam, d); - SDT sdt = oracle.treeQuery(prefix, suffix, trueValues, constants, trueSuffixValues); - - LOGGER.trace(" single deq SDT : " + sdt.toString()); - - Map merged = mergeGuards(tempKids, new SDTGuard.SDTAndGuard(currentParam, List.of()), sdt); - - LOGGER.trace("temporary guards = " + tempKids.keySet()); - LOGGER.trace("merged guards = " + merged.keySet()); - - return new SDT(merged); - } - } else { - int maxSufIndex = DataWords.paramLength(suffix.getActions()) + 1; - SDT rejSdt = makeRejectingBranch(currentParam.getId() + 1, maxSufIndex, type); - SDTGuard.SDTTrueGuard trueGuard = new SDTGuard.SDTTrueGuard(currentParam); - Map merged = new LinkedHashMap<>(); - merged.put(trueGuard, rejSdt); - return new SDT(merged); - } - } - } - - LOGGER.trace("potential " + potential.toString()); - - // process each 'if' case - // prepare by picking up the prefix values - List prefixValues = Arrays.asList(DataWords.valsOf(prefix)); - - LOGGER.trace("prefix list " + prefixValues); - - List diseqList = new ArrayList<>(); - for (DataValue newDv : potential) { - if (filteredEquivClasses.contains(newDv)) { - LOGGER.trace(newDv.toString()); - - // this is the valuation of the suffixvalues in the suffix - SuffixValuation ifSuffixValues = new SuffixValuation(); - ifSuffixValues.putAll(suffixValues); // copy the suffix valuation - - SDTGuard.EqualityGuard eqGuard = pickupDataValue(newDv, prefixValues, currentParam, values, constants); - LOGGER.trace("eqGuard is: " + eqGuard); - diseqList.add(new SDTGuard.DisequalityGuard(currentParam, eqGuard.register())); - // construct the equality guard - // find the data value in the prefix - // this is the valuation of the positions in the suffix - WordValuation ifValues = new WordValuation(); - ifValues.putAll(values); - ifValues.put(pId, newDv); - SDT eqOracleSdt = oracle.treeQuery(prefix, suffix, ifValues, constants, ifSuffixValues); - - tempKids.put(eqGuard, eqOracleSdt); - } - } - - Map merged; - - // process the 'else' case - if (filteredEquivClasses.contains(fresh)) { - // this is the valuation of the positions in the suffix - WordValuation elseValues = new WordValuation(); - elseValues.putAll(values); - elseValues.put(pId, fresh); - - // this is the valuation of the suffixvalues in the suffix - SuffixValuation elseSuffixValues = new SuffixValuation(); - elseSuffixValues.putAll(suffixValues); - elseSuffixValues.put(currentParam, fresh); - - SDT elseOracleSdt = oracle.treeQuery(prefix, suffix, elseValues, constants, elseSuffixValues); + Constants consts, SuffixValuation suffixValues, MultiTheoryTreeOracle oracle) { + int currentId = values.size() + 1; + + SuffixValue suffixValue = suffix.getSuffixValue(currentId); + + Map pot = getPotential(suffixValue.getDataType(), prefix, suffixValues, consts); + List potVals = new ArrayList<>(); + pot.keySet().forEach(d -> potVals.add(d)); + DataValue fresh = getFreshValue(potVals); + + List equivClasses = new ArrayList<>(potVals); + equivClasses.add(fresh); + EquivalenceClassFilter eqcFilter = new EquivalenceClassFilter(equivClasses, useSuffixOpt); + List filteredEquivClasses = eqcFilter.toList(suffix.getRestriction(suffixValue), prefix, suffix.getActions(), values, consts); + + if (freshValues) { + ParameterizedSymbol act = computeSymbol(suffix, currentId); + if (act.getArity() > 0 && act instanceof OutputSymbol) { + int idx = computeLocalIndex(suffix, currentId); + Word query = buildQuery(prefix, suffix, values); + Word trace = ioOracle.trace(query); + + if (!trace.isEmpty() && trace.lastSymbol().getBaseSymbol().equals(act)) { + DataValue d = trace.lastSymbol().getParameterValues()[idx]; + if (d instanceof FreshValue) { + filteredEquivClasses = Arrays.asList(fresh); + } + } else { + Queue types = new LinkedList<>(); + DataType[] suffixTypes = DataWords.typesOf(suffix.getActions()); + for (int i = currentId - 1; i < suffixTypes.length; i++) { + types.offer(suffixTypes[i]); + } + return SDT.makeRejectingSDT(currentId, types); + } + } + } + + if (!filteredEquivClasses.contains(fresh)) { + fresh = Collections.max(filteredEquivClasses, (d1,d2) -> d1.compareTo(d2)); + } + + Map ifSdts = new LinkedHashMap<>(); + SDT elseSdt = null; + for (DataValue d : filteredEquivClasses) { + WordValuation nextValuation = new WordValuation(); + nextValuation.putAll(values); + nextValuation.put(currentId, d); + SuffixValuation nextSuffixValuation = new SuffixValuation(); + nextSuffixValuation.putAll(suffixValues); + nextSuffixValuation.put(suffixValue, d); + + SDT sdt = oracle.treeQuery(prefix, suffix, nextValuation, consts, nextSuffixValuation); + + if (d.equals(fresh)) { + elseSdt = sdt; + } else { + ifSdts.put(d, sdt); + } + } - SDTGuard.SDTAndGuard deqGuard = new SDTGuard.SDTAndGuard(currentParam, diseqList); - LOGGER.trace("diseq guard = " + deqGuard); + Map eqChildren = getIfGuards(suffixValue, ifSdts, pot, elseSdt); + SDTGuard elseGuard = getElseGuard(suffixValue, eqChildren.keySet()); - // merge the guards - merged = mergeGuards(tempKids, deqGuard, elseOracleSdt); - } else { - // if no else case, we can only have a true guard - // TODO: add support for multiple equalities with same outcome - assert tempKids.size() == 1; - - Iterator> it = tempKids.entrySet().iterator(); - Map.Entry e = it.next(); - merged = new LinkedHashMap(); - merged.put(e.getKey(), e.getValue()); - } + Map children = new LinkedHashMap<>(); + children.putAll(eqChildren); + children.put(elseGuard, elseSdt); + return new SDT(children); + } - // only keep registers that are referenced by the merged guards - //pir.putAll(keepMem(merged)); + private Map getPotential(DataType type, Word prefix, SuffixValuation suffixValues, Constants consts) { + Map pot = new LinkedHashMap<>(); - LOGGER.trace("temporary guards = " + tempKids.keySet()); - LOGGER.trace("merged guards = " + merged.keySet()); + DataValue[] vals = DataWords.valsOf(prefix); + for (DataValue val : vals) { + if (val.getDataType().equals(type) && !consts.containsValue(val)) { + pot.put(val, val); + } + } - // clear the temporary map of children - tempKids.clear(); + for (Map.Entry e : suffixValues.entrySet()) { + DataValue d = e.getValue(); + if (d != null && d.getDataType().equals(type) && !pot.containsKey(d)) { + pot.put(d, e.getKey()); + } + } - for (SDTGuard g : merged.keySet()) { - assert !(g == null); - } + for (Map.Entry e : consts.entrySet()) { + DataValue d = e.getValue(); + if (d != null && d.getDataType().equals(type)) { + pot.put(d, e.getKey()); + } + } - SDT returnSDT = new SDT(merged); - return returnSDT; + return pot; + } + private Map getIfGuards(SuffixValue suffixValue, Map sdts, Map pot, SDT elseSdt) { + Map ifGuards = new LinkedHashMap<>(); + for (Map.Entry e : sdts.entrySet()) { + DataValue d = e.getKey(); + SDT sdt = e.getValue(); + SDTGuard.EqualityGuard eq = new SDTGuard.EqualityGuard(suffixValue, pot.get(d)); + List eqList = new ArrayList<>(); + eqList.add(eq); + if (!sdt.isEquivalentUnder(elseSdt, eqList)) { + ifGuards.put(eq, sdt); + } + } + return ifGuards; } - // construct equality guard by picking up a data value from the prefix - private SDTGuard.EqualityGuard pickupDataValue(DataValue newDv, List prefixValues, SuffixValue currentParam, - WordValuation ifValues, Constants constants) { - DataType type = currentParam.getDataType(); - int newDv_i; - for (Map.Entry entry : constants.entrySet()) { - if (entry.getValue().equals(newDv)) { - return new SDTGuard.EqualityGuard(currentParam, entry.getKey()); - } - } - if (prefixValues.contains(newDv)) { - // first index of the data value in the prefixvalues list - newDv_i = prefixValues.indexOf(newDv) + 1; - Register newDv_r = new Register(type, newDv_i); - LOGGER.trace("current param = " + currentParam); - LOGGER.trace("New register = " + newDv_r); - return new SDTGuard.EqualityGuard(currentParam, newDv); - - } // if the data value isn't in the prefix, - // it is somewhere earlier in the suffix - else { - - int smallest = Collections.min(ifValues.getAllKeysForValue(newDv)); - return new SDTGuard.EqualityGuard(currentParam, new SuffixValue(type, smallest)); - } + private SDTGuard getElseGuard(SuffixValue suffixValue, Set eqGuards) { + if (eqGuards.isEmpty()) { + return new SDTGuard.SDTTrueGuard(suffixValue); + } + if (eqGuards.size() == 1) { + SDTGuard.EqualityGuard eq = eqGuards.iterator().next(); + return new SDTGuard.DisequalityGuard(suffixValue, eq.register()); + } + List deqList = new ArrayList<>(); + eqGuards.forEach(eq -> deqList.add(new SDTGuard.DisequalityGuard(suffixValue, eq.register()))); + return new SDTGuard.SDTAndGuard(suffixValue, deqList); } @Override @@ -394,42 +309,25 @@ private Word buildQuery(Word prefix, SymbolicS return query; } - /** - * Creates a "unary tree" of depth maxIndex - nextSufIndex which leads to a - * rejecting Leaf. Edges are of type {@link SDTTrueGuard}. Used to shortcut - * output processing. - */ - private SDT makeRejectingBranch(int nextSufIndex, int maxIndex, DataType type) { - if (nextSufIndex == maxIndex) { - // map.put(guard, SDTLeaf.REJECTING); - return SDTLeaf.REJECTING; - } else { - Map map = new LinkedHashMap<>(); - SDTGuard.SDTTrueGuard trueGuard = new SDTGuard.SDTTrueGuard(new SuffixValue(type, nextSufIndex)); - map.put(trueGuard, makeRejectingBranch(nextSufIndex + 1, maxIndex, type)); - SDT sdt = new SDT(map); - return sdt; - } - } - @Override public Optional instantiate(Word prefix, ParameterizedSymbol ps, Expression guard, int param, - Constants constants, ConstraintSolver solver) { + List prior, 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 ArrayList<>(vals)); + vals.addAll(prior); + DataValue fresh = getFreshValue(new LinkedList<>(vals)); - if (isSatisfiableWithEquality(guard, p, fresh, solver, constants)) { + if (isSatisfiableWithEquality(guard, p, fresh, prior, solver, constants)) { return Optional.of(fresh); } for (DataValue val : vals) { - if (isSatisfiableWithEquality(guard, p, val, solver, constants)) { + if (isSatisfiableWithEquality(guard, p, val, prior, solver, constants)) { return Optional.of(val); } } @@ -437,23 +335,202 @@ public Optional instantiate(Word prefix, return Optional.empty(); } - private boolean isSatisfiableWithEquality(Expression guard, Parameter p, DataValue val, ConstraintSolver solver, Constants consts) { + private boolean isSatisfiableWithEquality(Expression guard, Parameter p, DataValue val, List prior, ConstraintSolver solver, Constants consts) { Mapping valuation = new Mapping<>(); + ParameterGenerator pgen = new ParameterGenerator(); + for (DataValue d : prior) { + Parameter param = pgen.next(d.getDataType()); + valuation.put(param, d); + } valuation.put(p, val); valuation.putAll(consts); return solver.isSatisfiable(guard, valuation); } @Override - public SuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, Word prefix, Word suffix, Constants consts) { + public AbstractSuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, Word prefix, Word suffix, Constants consts) { // for now, use generic restrictions with equality theory - return SuffixValueRestriction.genericRestriction(suffixValue, prefix, suffix, consts); + return AbstractSuffixValueRestriction.genericRestriction(suffixValue, prefix, suffix, consts); + } + + /** + * @param u + * @param type + * @return position-injective potential of {@code u} matching data type {@code type} + */ + private BiMap pot(Word u, DataType type) { + BiMap pot = HashBiMap.create(); + DataValue[] vals = DataWords.valsOf(u); + for (int i = 0; i < vals.length; i++) { + if (vals[i].getDataType().equals(type) && !pot.values().contains(vals[i])) { + pot.put(i+1, vals[i]); + } + } + return pot; + } + + /** + * Mapping of indices in the potential of {@code u} to data values in {@code w} such that + * for each index {@code l}, the data value at position {@code l} in {@code u} maps to + * the same register in {@code uValuation} as the corresponding data value in {@code w} + * does in {@code wValuation}. + * + * @param u + * @param uValuation + * @param w + * @param wValuation + * @param type + * @return + */ + public Map potmap(Word u, RegisterValuation uValuation, Word w, RegisterValuation wValuation, DataType type) { + BiMap pot = pot(u, type).inverse(); + Map map = new LinkedHashMap<>(); + for (Map.Entry uEntry : uValuation.entrySet()) { + DataValue wVal = wValuation.get(uEntry.getKey()); + if (wVal != null && wVal.getDataType().equals(type)) { + int id = pot.get(uEntry.getValue()); + map.put(id, wVal); + } + } + return map; + } + + /** + * The indices {@code l} of {@code u} such that if a hypothesis reaches {@code wValuation} + * after a run over {@code w}, then there is a position-injective extension of + * {@code uValuation} under which a data value {@code d} at index {@code l} of {@code u} will + * satisfy an equality guard {@code (s == d)}. + * + * @param w + * @param d + * @param u + * @param uValuation + * @param potmap + * @return + */ + public Set potmatch(Word w, DataValue d, Word u, RegisterValuation uValuation, Map potmap) { + List wVals = new ArrayList<>(Arrays.asList(DataWords.valsOf(w, d.getDataType()))); + Set indices = new LinkedHashSet<>(); + + // add indices for each mapped occurrence of d + for (Map.Entry potmapEntry : potmap.entrySet()) { + if (potmapEntry.getValue().equals(d)) { + indices.add(potmapEntry.getKey()); + wVals.remove(d); + } + } + + // if there are more occurrences of d than the unmapped, add all indices of unmapped data values + if (wVals.contains(d)) { + BiMap pot = pot(u, d.getDataType()); + pot.forEach((i,dv) -> {if (!uValuation.containsValue(dv)) indices.add(i);}); + } + + return indices; + } + + @Override + public AbstractSuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, + Word prefix, + Word suffix, + Word u, + RegisterValuation prefixValuation, + RegisterValuation uValuation, + Constants consts) { + int index = suffixValue.getId() - 1; + DataValue[] suffixVals = DataWords.valsOf(suffix); + Collection prefixVals = Arrays.asList(DataWords.valsOf(prefix)); + DataValue[] uVals = DataWords.valsOf(u); + DataValue d = suffixVals[index]; + + // find data values in u that the current suffix value may equal + List eqList = new ArrayList<>(); + Map potmap = potmap(u, uValuation, prefix, prefixValuation, d.getDataType()); + potmatch(prefix, d, u, uValuation, potmap).forEach(i -> eqList.add(uVals[i-1])); + + // find prior suffix values that the current suffix value may equal + List suffixEqList = new ArrayList<>(); + List priorSuffixes = new ArrayList<>(); + for (int i = 0; i < index; i++) { + SuffixValue s = new SuffixValue(d.getDataType(), i+1); + priorSuffixes.add(s); + if (suffixVals[i].equals(d)) { + suffixEqList.add(s); + } + } + + // find constants the current suffix value may equal + Set constEqList = new LinkedHashSet<>(consts.getAllKeysForValue(d)); + + // find registers in u that the current suffix value may equal + Collection regsEqList = dataValueToRegister(eqList, uValuation); + + // collect unmapped data values in u that the current suffix value may equal + List unmappedEqList = new ArrayList<>(eqList); + for (SuffixValue s : suffixEqList) { + DataValue dv = suffixVals[s.getId()-1]; + if (!prefixVals.contains(dv)) { + unmappedEqList.remove(dv); + } + } + constEqList.forEach(c -> unmappedEqList.remove(consts.get(c))); + regsEqList.forEach(r -> unmappedEqList.remove(uValuation.get(r))); + + FreshSuffixValue restrrFresh = new FreshSuffixValue(suffixValue); + UnmappedEqualityRestriction eqRestrUnmapped = new UnmappedEqualityRestriction(suffixValue); + AbstractSuffixValueRestriction eqRestrSuffix = SuffixValueRestriction.equalityRestriction(suffixValue, suffixEqList); + AbstractSuffixValueRestriction eqRestrReg = SuffixValueRestriction.equalityRestriction(suffixValue, regsEqList); + AbstractSuffixValueRestriction eqRestrConst = SuffixValueRestriction.equalityRestriction(suffixValue, constEqList); + + if (unmappedEqList.isEmpty()) { + // no unmapped equality + if (regsEqList.size() == 1 && /*suffixEqList.isEmpty() &&*/ constEqList.isEmpty()) { + // equals one register + AbstractSuffixValueRestriction eqr = SuffixValueRestriction.equalityRestriction(suffixValue, regsEqList); + return eqr; + } + if (regsEqList.isEmpty() && suffixEqList.size() > 0 && constEqList.isEmpty()) { + // equals prior suffix values + return SuffixValueRestriction.equalityRestriction(suffixValue, suffixEqList.get(0)); + } + if (regsEqList.isEmpty() && suffixEqList.isEmpty() && constEqList.size() == 1) { + // equals one constant + return SuffixValueRestriction.equalityRestriction(suffixValue, constEqList); + } + if (regsEqList.isEmpty() && suffixEqList.isEmpty() && constEqList.isEmpty()) { + // equals nothing + return restrrFresh; + } + // equals any number of register, constant, prior suffix value, but no unmapped + return DisjunctionRestriction.create(suffixValue, restrrFresh, eqRestrSuffix, eqRestrReg, eqRestrConst); + } else if (regsEqList.isEmpty() && suffixEqList.isEmpty() && constEqList.isEmpty()) { + // equals only unmapped + return DisjunctionRestriction.create(suffixValue, eqRestrUnmapped, restrrFresh); + } + + // all classes of equality, collect all data values the current suffix value can not equal + List regsDiseqList = new ArrayList<>(uValuation.keySet()); + regsDiseqList.removeAll(regsEqList); + List constDiseqList = new ArrayList<>(consts.keySet()); + constDiseqList.removeAll(constEqList); + List suffixDiseqList = new ArrayList<>(priorSuffixes); + suffixDiseqList.removeAll(suffixEqList); + SuffixValueRestriction diseqRestrRegs = SuffixValueRestriction.disequalityRestriction(suffixValue, regsDiseqList); + SuffixValueRestriction diseqRestrConst = SuffixValueRestriction.disequalityRestriction(suffixValue, constDiseqList); + SuffixValueRestriction diseqRestrSuffix = SuffixValueRestriction.disequalityRestriction(suffixValue, suffixDiseqList); + return DisjunctionRestriction.create(suffixValue, diseqRestrRegs, diseqRestrConst, diseqRestrSuffix); + } + + private Collection dataValueToRegister(Collection vals, RegisterValuation valuation) { + Collection regs = new ArrayList<>(); + valuation.forEach((r, v) -> {if (vals.contains(v)) regs.add(r);}); + return regs; } @Override - public SuffixValueRestriction restrictSuffixValue(SDTGuard guard, Map prior) { + public AbstractSuffixValueRestriction restrictSuffixValue(SDTGuard guard, Map prior) { // for now, use generic restrictions with equality theory - return SuffixValueRestriction.genericRestriction(guard, prior); + return AbstractSuffixValueRestriction.genericRestriction(guard, prior); } @Override @@ -476,4 +553,18 @@ public boolean guardRevealsRegister(SDTGuard guard, SymbolicDataValue register) return revealsGuard; } return false; } + + /** + * @param vals + * @param type + * @return fresh data value of type {@code type} not present in {@code vals} + */ + public static DataValue getFreshValue(Collection vals, DataType type) { + BigDecimal dv = new BigDecimal("-1"); + for (DataValue d : vals) { + dv = dv.max(d.getValue()); + } + + return new DataValue(type, BigDecimal.ONE.add(dv)); + } } diff --git a/src/main/java/de/learnlib/ralib/theory/equality/UnmappedEqualityRestriction.java b/src/main/java/de/learnlib/ralib/theory/equality/UnmappedEqualityRestriction.java new file mode 100644 index 000000000..213758276 --- /dev/null +++ b/src/main/java/de/learnlib/ralib/theory/equality/UnmappedEqualityRestriction.java @@ -0,0 +1,101 @@ +package de.learnlib.ralib.theory.equality; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +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.data.TypedValue; +import de.learnlib.ralib.theory.AbstractSuffixValueRestriction; +import de.learnlib.ralib.theory.SuffixValueRestriction; +import gov.nasa.jpf.constraints.api.Expression; + +public class UnmappedEqualityRestriction extends AbstractSuffixValueRestriction { + + public UnmappedEqualityRestriction(SuffixValue parameter) { + super(parameter); + } + + public UnmappedEqualityRestriction(UnmappedEqualityRestriction other, int shift) { + super(other, shift); + } + + @Override + public AbstractSuffixValueRestriction shift(int shiftStep) { + return new UnmappedEqualityRestriction(this, shiftStep); + } + + @Override + public AbstractSuffixValueRestriction concretize(Mapping mapping) { + Set params = new LinkedHashSet<>(); + mapping.forEach((s,d) -> {if (s.isParameter()) params.add(d);}); + mapping.forEach((s,d) -> {if (!s.isParameter()) params.remove(d);}); + return SuffixValueRestriction.equalityRestriction(parameter, params); + } + + @Override + public Expression toGuardExpression(Set vals) { + throw new RuntimeException("Not supported for this type of restrictions"); + } + + @Override + public AbstractSuffixValueRestriction merge(AbstractSuffixValueRestriction other, + Map prior) { + throw new RuntimeException("Not supported for this type of restriction"); + } + + @Override + public boolean revealsRegister(SymbolicDataValue r) { + return false; + } + + @Override + public boolean isTrue() { + return false; + } + + @Override + public boolean isFalse() { + return false; + } + + @Override + public boolean containsFresh() { + return false; + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) { + return false; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 37 * hash + Objects.hashCode(getClass()); + return hash; + } + + @Override + public AbstractSuffixValueRestriction relabel(Mapping renaming) { + return this; + } + + @Override + public String toString() { + return "Unmapped(" + parameter + ")"; + } +} diff --git a/src/main/java/de/learnlib/ralib/theory/inequality/GreaterSuffixValue.java b/src/main/java/de/learnlib/ralib/theory/inequality/GreaterSuffixValue.java index 206e209de..8ce558634 100644 --- a/src/main/java/de/learnlib/ralib/theory/inequality/GreaterSuffixValue.java +++ b/src/main/java/de/learnlib/ralib/theory/inequality/GreaterSuffixValue.java @@ -1,12 +1,18 @@ package de.learnlib.ralib.theory.inequality; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; 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.data.TypedValue; +import de.learnlib.ralib.theory.AbstractSuffixValueRestriction; import de.learnlib.ralib.theory.FreshSuffixValue; import de.learnlib.ralib.theory.SuffixValueRestriction; import de.learnlib.ralib.theory.UnrestrictedSuffixValue; @@ -16,7 +22,7 @@ import gov.nasa.jpf.constraints.expressions.NumericComparator; import gov.nasa.jpf.constraints.util.ExpressionUtil; -public class GreaterSuffixValue extends SuffixValueRestriction { +public class GreaterSuffixValue extends AbstractSuffixValueRestriction { public GreaterSuffixValue(SuffixValue param) { super(param); @@ -27,7 +33,7 @@ public GreaterSuffixValue(GreaterSuffixValue other, int shift) { } @Override - public SuffixValueRestriction shift(int shiftStep) { + public AbstractSuffixValueRestriction shift(int shiftStep) { return new GreaterSuffixValue(this, shiftStep); } @@ -44,7 +50,17 @@ public Expression toGuardExpression(Set vals) { } @Override - public SuffixValueRestriction merge(SuffixValueRestriction other, Map prior) { + public AbstractSuffixValueRestriction concretize(Mapping mapping) { + if (mapping.isEmpty()) { + return this; + } + DataValue d = Collections.max(mapping.values()); + Expression expr = new NumericBooleanExpression(parameter, NumericComparator.GT, d); + return new SuffixValueRestriction(parameter, expr); + } + + @Override + public AbstractSuffixValueRestriction merge(AbstractSuffixValueRestriction other, Map prior) { if (other instanceof GreaterSuffixValue || other instanceof FreshSuffixValue) { return this; } @@ -59,6 +75,47 @@ public boolean revealsRegister(SymbolicDataValue r) { return false; } + @Override + public boolean isTrue() { + return false; + } + + @Override + public boolean isFalse() { + return false; + } + + @Override + public boolean containsFresh() { + return true; + } + + @Override + public AbstractSuffixValueRestriction relabel(Mapping renaming) { + return this; + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) { + return false; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 37 * hash + Objects.hashCode(getClass()); + return hash; + } + @Override public String toString() { return "Greater(" + parameter.toString() + ")"; 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 f0fb4fe88..2bf3fad35 100644 --- a/src/main/java/de/learnlib/ralib/theory/inequality/InequalityTheoryWithEq.java +++ b/src/main/java/de/learnlib/ralib/theory/inequality/InequalityTheoryWithEq.java @@ -35,11 +35,11 @@ import de.learnlib.ralib.learning.SymbolicSuffix; import de.learnlib.ralib.oracles.mto.MultiTheoryTreeOracle; import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.theory.AbstractSuffixValueRestriction; import de.learnlib.ralib.theory.EquivalenceClassFilter; import de.learnlib.ralib.theory.FreshSuffixValue; import de.learnlib.ralib.theory.SDT; import de.learnlib.ralib.theory.SDTGuard; -import de.learnlib.ralib.theory.SuffixValueRestriction; import de.learnlib.ralib.theory.Theory; import de.learnlib.ralib.theory.UnrestrictedSuffixValue; import de.learnlib.ralib.words.DataWords; @@ -63,6 +63,11 @@ public abstract class InequalityTheoryWithEq implements Theory { boolean useSuffixOpt = false; + @Override + public boolean isUsingSuffixOptimization() { + return useSuffixOpt; + } + /** * Given a potential, generate data values for each equivalence class. * @@ -154,11 +159,12 @@ private Map filterEquivClasses(Map val Word prefix, SymbolicSuffix suffix, SuffixValue suffixValue, + Constants consts, WordValuation values) { List equivClasses = new ArrayList<>(); equivClasses.addAll(valueGuards.keySet()); EquivalenceClassFilter eqcFilter = new EquivalenceClassFilter(equivClasses, useSuffixOpt); - List filteredEquivClasses = eqcFilter.toList(suffix.getRestriction(suffixValue), prefix, suffix.getActions(), values); + List filteredEquivClasses = eqcFilter.toList(suffix.getRestriction(suffixValue), prefix, suffix.getActions(), values, consts); Map ret = new LinkedHashMap<>(); for (Map.Entry e : valueGuards.entrySet()) { @@ -421,7 +427,7 @@ public SDT treeQuery(Word prefix, Map pot = getPotential(prefix, suffixValues, consts); Map equivClasses = generateEquivClasses(currentParam, pot, consts); - Map filteredEquivClasses = filterEquivClasses(equivClasses, prefix, suffix, currentParam, values); + Map filteredEquivClasses = filterEquivClasses(equivClasses, prefix, suffix, currentParam, consts, values); Map children = new LinkedHashMap<>(); for (Map.Entry ec : filteredEquivClasses.entrySet()) { @@ -598,15 +604,15 @@ public DataValue instantiate( return returnThis; } - @Override public Optional instantiate(Word prefix, ParameterizedSymbol ps, Expression guard, int param, - Constants constants, ConstraintSolver solver) { + List prior, 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())); + vals.addAll(prior); DataValue fresh = getFreshValue(new ArrayList<>(vals)); if (isSatisfiableWithEquality(guard, p, fresh, solver)) { @@ -645,7 +651,7 @@ public void useSuffixOptimization(boolean useSuffixOpt) { } @Override - public SuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, Word prefix, Word suffix, Constants consts) { + public AbstractSuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, Word prefix, Word suffix, Constants consts) { int firstActionArity = suffix.size() > 0 ? suffix.getSymbol(0).getBaseSymbol().getArity() : 0; if (suffixValue.getId() <= firstActionArity) { return new UnrestrictedSuffixValue(suffixValue); @@ -702,8 +708,18 @@ public SuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, Word< return greater ? new GreaterSuffixValue(suffixValue) : new LesserSuffixValue(suffixValue); } + public AbstractSuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, + Word prefix, + Word suffix, + Word u, + RegisterValuation prefixValuation, + RegisterValuation uValuation, + Constants consts) { + return this.restrictSuffixValue(suffixValue, prefix, suffix, consts); + } + @Override - public SuffixValueRestriction restrictSuffixValue(SDTGuard guard, Map prior) { + public AbstractSuffixValueRestriction restrictSuffixValue(SDTGuard guard, Map prior) { SuffixValue sv = guard.getParameter(); if (guard instanceof SDTGuard.IntervalGuard ig) { @@ -713,7 +729,7 @@ public SuffixValueRestriction restrictSuffixValue(SDTGuard guard, Map toGuardExpression(Set vals) { } @Override - public SuffixValueRestriction merge(SuffixValueRestriction other, Map prior) { + public AbstractSuffixValueRestriction concretize(Mapping mapping) { + if (mapping.isEmpty()) { + return this; + } + DataValue d = Collections.min(mapping.values()); + Expression expr = new NumericBooleanExpression(parameter, NumericComparator.LT, d); + return new SuffixValueRestriction(parameter, expr); + } + + @Override + public AbstractSuffixValueRestriction merge(AbstractSuffixValueRestriction other, Map prior) { if (other instanceof LesserSuffixValue || other instanceof FreshSuffixValue) { return this; } @@ -58,6 +74,47 @@ public boolean revealsRegister(SymbolicDataValue r) { return false; } + @Override + public boolean isTrue() { + return false; + } + + @Override + public boolean isFalse() { + return false; + } + + @Override + public boolean containsFresh() { + return false; + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) { + return false; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 37 * hash + Objects.hashCode(getClass()); + return hash; + } + + @Override + public AbstractSuffixValueRestriction relabel(Mapping renaming) { + return this; + } + @Override public String toString() { return "Lesser(" + parameter.toString() + ")"; diff --git a/src/main/java/de/learnlib/ralib/tools/AbstractToolWithRandomWalk.java b/src/main/java/de/learnlib/ralib/tools/AbstractToolWithRandomWalk.java index 5e4bcf59e..3b6d35868 100644 --- a/src/main/java/de/learnlib/ralib/tools/AbstractToolWithRandomWalk.java +++ b/src/main/java/de/learnlib/ralib/tools/AbstractToolWithRandomWalk.java @@ -42,6 +42,7 @@ public abstract class AbstractToolWithRandomWalk implements RaLibTool { public static final String LEARNER_SLLAMBDA = "sllambda"; public static final String LEARNER_SLSTAR = "slstar"; public static final String LEARNER_RADT = "sldt"; + public static final String LEARNER_SLLAMBDAEQ = "slleq"; protected static final ConfigurationOption.StringOption OPTION_LEARNER = new ConfigurationOption.StringOption("learner", @@ -88,6 +89,10 @@ public Level parse(Configuration c) throws ConfigurationException { = new ConfigurationOption.BooleanOption("use.suffixopt", "Do only use fresh values for non-free suffix values", Boolean.FALSE, true); + protected static final ConfigurationOption.BooleanOption OPTION_OPTIMIZE_REGCLOSED + = new ConfigurationOption.BooleanOption("suffixopt.reg.closed", + "Use improved optimizations for register closedness (" + LEARNER_SLLAMBDAEQ + " only)", Boolean.FALSE, true); + protected static final ConfigurationOption.LongOption OPTION_TIMEOUT = new ConfigurationOption.LongOption("max.time.millis", "Maximal run time for experiment in milliseconds", -1L, true); diff --git a/src/main/java/de/learnlib/ralib/tools/ClassAnalyzer.java b/src/main/java/de/learnlib/ralib/tools/ClassAnalyzer.java index f9e546ab8..e192970c4 100644 --- a/src/main/java/de/learnlib/ralib/tools/ClassAnalyzer.java +++ b/src/main/java/de/learnlib/ralib/tools/ClassAnalyzer.java @@ -39,6 +39,7 @@ import de.learnlib.ralib.learning.RaLearningAlgorithm; import de.learnlib.ralib.learning.ralambda.SLCT; import de.learnlib.ralib.learning.ralambda.SLLambda; +import de.learnlib.ralib.learning.ralambda.SLLambdaEq; import de.learnlib.ralib.learning.rastar.RaStar; import de.learnlib.ralib.oracles.DataWordOracle; import de.learnlib.ralib.oracles.SimulatorOracle; @@ -244,6 +245,7 @@ public TreeOracle createTreeOracle(RegisterAutomaton hyp) { } }; + boolean useImprovedRegClosed = OPTION_OPTIMIZE_REGCLOSED.parse(config); this.rastar = switch (this.learner) { case AbstractToolWithRandomWalk.LEARNER_SLSTAR -> new RaStar(mto, hypFactory, mlo, consts, true, actions); @@ -251,6 +253,8 @@ public TreeOracle createTreeOracle(RegisterAutomaton hyp) { new SLLambda(mto, teachers, consts, true, solver, actions); case AbstractToolWithRandomWalk.LEARNER_RADT -> new SLCT(mto, hypFactory, mlo, consts, true, solver, actions); + case AbstractToolWithRandomWalk.LEARNER_SLLAMBDAEQ -> + new SLLambdaEq(mto, teachers, consts, true, solver, useImprovedRegClosed, actions); 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 c8711fdf5..856275339 100644 --- a/src/main/java/de/learnlib/ralib/tools/IOSimulator.java +++ b/src/main/java/de/learnlib/ralib/tools/IOSimulator.java @@ -39,6 +39,7 @@ import de.learnlib.ralib.learning.RaLearningAlgorithm; import de.learnlib.ralib.learning.ralambda.SLCT; import de.learnlib.ralib.learning.ralambda.SLLambda; +import de.learnlib.ralib.learning.ralambda.SLLambdaEq; import de.learnlib.ralib.learning.rastar.RaStar; import de.learnlib.ralib.oracles.DataWordOracle; import de.learnlib.ralib.oracles.SimulatorOracle; @@ -213,6 +214,7 @@ public TreeOracle createTreeOracle(RegisterAutomaton hyp) { } }; + boolean useImprovedRegClosed = OPTION_OPTIMIZE_REGCLOSED.parse(config); this.rastar = switch (this.learner) { case AbstractToolWithRandomWalk.LEARNER_SLSTAR -> new RaStar(mto, hypFactory, mlo, consts, true, actions); @@ -220,6 +222,8 @@ public TreeOracle createTreeOracle(RegisterAutomaton hyp) { new SLLambda(mto, teachers, consts, true, solver, actions); case AbstractToolWithRandomWalk.LEARNER_RADT -> new SLCT(mto, hypFactory, mlo, consts, true, solver, actions); + case AbstractToolWithRandomWalk.LEARNER_SLLAMBDAEQ -> + new SLLambdaEq(mto, teachers, consts, true, solver, useImprovedRegClosed, actions); default -> throw new ConfigurationException("Unknown Learning algorithm: " + this.learner); }; diff --git a/src/main/java/de/learnlib/ralib/tools/theories/IntegerEqualityTheory.java b/src/main/java/de/learnlib/ralib/tools/theories/IntegerEqualityTheory.java index 30b9d4222..605adaf67 100644 --- a/src/main/java/de/learnlib/ralib/tools/theories/IntegerEqualityTheory.java +++ b/src/main/java/de/learnlib/ralib/tools/theories/IntegerEqualityTheory.java @@ -60,7 +60,7 @@ public void setType(DataType type) { @Override public void setUseSuffixOpt(boolean useit) { - this.useNonFreeOptimization = useit; + this.useSuffixOpt = useit; } @Override 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 af7fd0268..24adb72dd 100644 --- a/src/main/java/de/learnlib/ralib/tools/theories/UniqueIntegerEqualityTheory.java +++ b/src/main/java/de/learnlib/ralib/tools/theories/UniqueIntegerEqualityTheory.java @@ -10,12 +10,13 @@ import de.learnlib.ralib.data.Constants; import de.learnlib.ralib.data.DataType; import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.RegisterValuation; 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.AbstractSuffixValueRestriction; 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; @@ -70,18 +71,17 @@ public Collection getAllNextValues(List vals) { @Override public Optional instantiate(Word prefix, ParameterizedSymbol ps, Expression guard, int param, - Constants constants, ConstraintSolver solver) { + List prior, Constants constants, ConstraintSolver solver) { throw new RuntimeException("Not implemented"); } @Override - public SuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, Word prefix, - Word suffix, Constants consts) { + public AbstractSuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, Word prefix, Word suffix, Constants consts) { return new UnrestrictedSuffixValue(suffixValue); } @Override - public SuffixValueRestriction restrictSuffixValue(SDTGuard guard, Map prior) { + public AbstractSuffixValueRestriction restrictSuffixValue(SDTGuard guard, Map prior) { return new UnrestrictedSuffixValue(guard.getParameter()); } @@ -90,4 +90,16 @@ public boolean guardRevealsRegister(SDTGuard guard, SymbolicDataValue register) // not yet implemented for inequality theory return false; } + + @Override + public AbstractSuffixValueRestriction restrictSuffixValue(SuffixValue suffixValue, Word prefix, + Word suffix, Word u, RegisterValuation prefixValuation, + RegisterValuation uValuation, Constants consts) { + return this.restrictSuffixValue(suffixValue, prefix, suffix, consts); + } + + @Override + public boolean isUsingSuffixOptimization() { + return false; + } } diff --git a/src/main/java/de/learnlib/ralib/words/DataWords.java b/src/main/java/de/learnlib/ralib/words/DataWords.java index 6408e648e..ce1e0ea96 100644 --- a/src/main/java/de/learnlib/ralib/words/DataWords.java +++ b/src/main/java/de/learnlib/ralib/words/DataWords.java @@ -35,6 +35,17 @@ */ public final class DataWords { + @SafeVarargs + public static Word concatenate(Word ... words) { + Word con = Word.epsilon(); + for (Word word : words) { + for (T symbol : word) { + con = con.append(symbol); + } + } + return con; + } + /** * returns sequence of data values of a specific type in a data word. * diff --git a/src/test/java/de/learnlib/ralib/RaLibLearningExperimentRunner.java b/src/test/java/de/learnlib/ralib/RaLibLearningExperimentRunner.java index 73f6420d9..c95239dcf 100644 --- a/src/test/java/de/learnlib/ralib/RaLibLearningExperimentRunner.java +++ b/src/test/java/de/learnlib/ralib/RaLibLearningExperimentRunner.java @@ -20,6 +20,7 @@ import de.learnlib.ralib.learning.RaLearningAlgorithmName; import de.learnlib.ralib.learning.ralambda.SLCT; import de.learnlib.ralib.learning.ralambda.SLLambda; +import de.learnlib.ralib.learning.ralambda.SLLambdaEq; import de.learnlib.ralib.learning.rastar.RaStar; import de.learnlib.ralib.oracles.DataWordOracle; import de.learnlib.ralib.oracles.TreeOracleFactory; @@ -120,6 +121,8 @@ public Hypothesis run(RaLearningAlgorithmName algorithmName, DataWordOracle data new SLLambda(mto, teachers, consts, ioMode, solver, actionSymbols); case RADT -> new SLCT(mto, hypFactory, mlo, consts, ioMode, solver, actionSymbols); + case RALAMBDAEQ -> + new SLLambdaEq(mto, teachers, consts, ioMode, solver, actionSymbols); default -> throw new UnsupportedOperationException(String.format("Algorithm %s not supported", algorithmName)); }; diff --git a/src/test/java/de/learnlib/ralib/ct/CTConsistencyTest.java b/src/test/java/de/learnlib/ralib/ct/CTConsistencyTest.java index c874fa506..5c752065a 100644 --- a/src/test/java/de/learnlib/ralib/ct/CTConsistencyTest.java +++ b/src/test/java/de/learnlib/ralib/ct/CTConsistencyTest.java @@ -25,6 +25,7 @@ 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.SymbolicDataValue.SuffixValue; import de.learnlib.ralib.data.VarMapping; import de.learnlib.ralib.learning.SymbolicSuffix; import de.learnlib.ralib.learning.rastar.RaStar; @@ -34,7 +35,9 @@ 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.AbstractSuffixValueRestriction; import de.learnlib.ralib.theory.Theory; +import de.learnlib.ralib.theory.TrueRestriction; import de.learnlib.ralib.tools.theories.DoubleInequalityTheory; import de.learnlib.ralib.tools.theories.IntegerEqualityTheory; import de.learnlib.ralib.words.InputSymbol; @@ -101,7 +104,8 @@ public void testConsistencyStack() { Assert.assertTrue(closed); ct.refine(ct.getLeaf(pu0pu1), s1); - boolean consistent = ct.checkLocationConsistency(); + boolean consistent; + consistent = ct.checkLocationConsistency(); Assert.assertFalse(consistent); ct.sift(pu0po0); @@ -254,6 +258,9 @@ public void testRegisterConsistency() { DataValue dv1 = new DataValue(T_INT, BigDecimal.ONE); DataValue dv2 = new DataValue(T_INT, BigDecimal.valueOf(2)); + SuffixValue s1 = new SuffixValue(T_INT, 1); + SuffixValue s2 = new SuffixValue(T_INT, 2); + Word b0 = Word.fromSymbols(new PSymbolInstance(BETA, dv0)); Word a0 = Word.fromSymbols(new PSymbolInstance(ALPHA, dv0)); Word a0a1 = Word.fromSymbols( @@ -270,10 +277,14 @@ public void testRegisterConsistency() { new PSymbolInstance(BETA, dv2), new PSymbolInstance(BETA, dv1)); + Map sbbRestr = new LinkedHashMap<>(); + sbbRestr.put(s1, new TrueRestriction(s1)); + sbbRestr.put(s2, new TrueRestriction(s2)); + 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); + SymbolicSuffix sbb = new SymbolicSuffix(Word.fromSymbols(BETA, BETA), sbbRestr); ClassificationTree ct = new ClassificationTree(mto, solver, restrBuilder, suffixBuilder, consts, false, ALPHA, BETA); ct.initialize(); diff --git a/src/test/java/de/learnlib/ralib/example/palindrome/Palindrome.java b/src/test/java/de/learnlib/ralib/example/palindrome/Palindrome.java new file mode 100644 index 000000000..138df94d3 --- /dev/null +++ b/src/test/java/de/learnlib/ralib/example/palindrome/Palindrome.java @@ -0,0 +1,46 @@ +package de.learnlib.ralib.example.palindrome; + +import java.util.ArrayList; +import java.util.List; + +public class Palindrome { + public static final int DEFAULT_MAX = 3; + + private final int max; + + private List vals; + + public Palindrome(int max) { + this.max = max; + vals = new ArrayList<>(); + } + + public Palindrome() { + this(DEFAULT_MAX); + } + + public void reset() { + vals.clear(); + } + + public boolean in(int d) { + vals.add(d); + if (vals.size() > max) { + return false; + } + return isPalindrome(); + } + + private boolean isPalindrome() { + int left = 0; + int right = vals.size() - 1; + while (left < right) { + if (!vals.get(left).equals(vals.get(right))) { + return false; + } + left++; + right--; + } + return true; + } +} diff --git a/src/test/java/de/learnlib/ralib/example/palindrome/PalindromeGenerator.java b/src/test/java/de/learnlib/ralib/example/palindrome/PalindromeGenerator.java new file mode 100644 index 000000000..b4acf2ad4 --- /dev/null +++ b/src/test/java/de/learnlib/ralib/example/palindrome/PalindromeGenerator.java @@ -0,0 +1,309 @@ +package de.learnlib.ralib.example.palindrome; + +import static de.learnlib.ralib.example.palindrome.PalindromeOracle.IN; +import static de.learnlib.ralib.example.palindrome.PalindromeOracle.TYPE; + +import java.math.BigDecimal; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Queue; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import de.learnlib.ralib.automata.Assignment; +import de.learnlib.ralib.automata.MutableRegisterAutomaton; +import de.learnlib.ralib.automata.RALocation; +import de.learnlib.ralib.automata.RegisterAutomaton; +import de.learnlib.ralib.automata.Transition; +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.data.util.SymbolicDataValueGenerator; +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.util.ExpressionUtil; +import net.automatalib.word.WordBuilder; + +public class PalindromeGenerator { + + /** + * Tree structure for compactly specifying {@link RegisterAutomaton} instances with equality. + * Nodes in the tree are converted to locations, edges to transitions. + */ + public static class Node { + private Integer maxMemV; + private BiMap children; + private Node parent; + private boolean accept; + + public Node(Node parent, int maxMemV) { + this.parent = parent; + this.maxMemV = maxMemV; + this.children = HashBiMap.create(); + } + + public List getPrefix() { + Node parent = this.parent, child = this; + List prefix = new LinkedList(); + while (parent != null) { + Integer trans = parent.children.inverse().get(child); + prefix.addFirst(trans); + child = parent; + parent = parent.parent; + } + return prefix; + } + + public void setAccept(boolean accept) { + this.accept = accept; + } + + public Node createChildIfAbsent(Integer edge) { + Node child = children.get(edge); + if (child == null) { + child = new Node(this, maxMemV); + children.put(edge, child); + } + return child; + } + + public Integer determineMaxDownstreamEdge() { + Integer maxEdge = 0; + for (Entry entry : children.entrySet()) { + maxEdge = Math.max(maxEdge, entry.getValue().determineMaxDownstreamEdge()); + maxEdge = Math.max(maxEdge, entry.getKey()); + } + return maxEdge; + } + + private Integer determineMaxMemV () { + Integer futureMaxMemV = this.determineMaxDownstreamEdge(); + for (Entry entry : children.entrySet()) { + entry.getValue().determineMaxMemV(); + } + Integer pastMaxMemV = parent == null? 0 : Collections.max(this.getPrefix()); + maxMemV = Math.min(futureMaxMemV, pastMaxMemV); + return maxMemV; + } + + public Integer getMaxMemV() { + return maxMemV; + } + + public String toString() { + return toString(0, 2); + } + + public Map getChildren() { + return children; + } + + public boolean getAccept() { + return accept; + } + + public String toString(int indent, int indentIncrease) { + StringBuilder builder = new StringBuilder(); + builder.append(getPrefix()). + append(", maxMemV: ").append(maxMemV).append(", acc: ").append(accept).append("{").append(System.lineSeparator()); + for (Entry entry : children.entrySet()) { + builder.repeat(" ", indent).append(entry.getKey()).append(" -> ").append(entry.getValue() + .toString(indent + indentIncrease, indentIncrease)); + } + builder.append("}"); + return builder.toString(); + } + } + + + static record WorkItem (Node node) {}; + + + private static boolean isPalindrome(List word) { + for (int i=0; i q = new ArrayDeque(); + Map map = new HashMap<>(); + q.add(root); + map.put(root, ra.addState(root.getAccept())); + ra.setInitialState(map.get(root)); + RALocation sink = ra.addState(false); + while (!q.isEmpty()) { + Node srcNode = q.poll(); + RALocation srcLoc = map.get(srcNode); + + // create srcNode registers and parameter + SymbolicDataValueGenerator.RegisterGenerator srcRgen = new SymbolicDataValueGenerator.RegisterGenerator(); + Register [] srcRegs = new Register [srcNode.getMaxMemV()]; + for (int i=0; i eqRegs = new ArrayList<>(); + for (int edge=1; edge<=srcNode.getMaxMemV() + 1; edge++) { + Node destNode = srcNode.getChildren().get(edge); + if (destNode == null) { + continue; + } + RALocation destLoc = ra.addState(destNode.getAccept()); + map.put(destNode, destLoc); + + // create assignment + SymbolicDataValueGenerator.RegisterGenerator destRgen = new SymbolicDataValueGenerator.RegisterGenerator(); + VarMapping assignmentMapping = new VarMapping<>(); + for (int i=1; i<=srcNode.getMaxMemV(); i++) { + if (destNode.getMaxMemV() >= i) { + assignmentMapping.put(destRgen.next(type), srcRegs[i-1]); + } + } + if (destNode.getMaxMemV() > srcNode.getMaxMemV()) { + assignmentMapping.put(destRgen.next(type), param); + } + + // create guard + Expression guard = null; + if (edge <= srcNode.getMaxMemV()) { // equality case + guard = new NumericBooleanExpression(srcRegs[edge-1], NumericComparator.EQ, param); + // update list of registers over which we have equality + eqRegs.add(srcRegs[edge-1]); + } else { // fresh case + List> conjuncts = new ArrayList<>(srcRegs.length); + for (Register r : eqRegs) { + conjuncts.add(new NumericBooleanExpression(r, NumericComparator.NE, param)); + } + guard = ExpressionUtil.and(conjuncts); + } + + Transition transition = new Transition(in, guard, srcLoc, destLoc, new Assignment(assignmentMapping)); + ra.addTransition(srcLoc, in, transition); + q.add(destNode); + } + + // add sink transition to sink + if (!srcNode.getChildren().containsKey(srcNode.getMaxMemV()+1)) { + VarMapping assignmentMapping = new VarMapping<>(); + // create guard for the fresh case + List> conjuncts = new ArrayList<>(srcRegs.length); + for (Register r : eqRegs) { + conjuncts.add(new NumericBooleanExpression(r, NumericComparator.NE, param)); + } + Expression guard = ExpressionUtil.and(conjuncts); + Transition transition = new Transition(in, guard, srcLoc, sink, new Assignment(assignmentMapping)); + ra.addTransition(srcLoc, in, transition); + } + } + + // add sink self-loop transition + VarMapping assignmentMapping = new VarMapping<>(); + Expression guard = ExpressionUtil.TRUE; + Transition transition = new Transition(in, guard, sink, sink, new Assignment(assignmentMapping)); + ra.addTransition(sink, in, transition); + + return ra; + } + + /** + * Returns a {@link RegisterAutomaton} of a language which accepts palindromes up to a length {code maxLen}. + * @param maxLen The maximum length of the palindromes + * @return the constructed RegisterAutomaton + */ + public static RegisterAutomaton generate(int maxLen) { + Node root = new Node(null, 0); + Queue q = new ArrayDeque(); + q.add(new WorkItem(root)); + while (!q.isEmpty()) { + WorkItem i = q.poll(); + Node node = i.node; + List p = node.getPrefix(); + if (2*p.size() <= maxLen) { + Node last = node; + for (int idx = p.size()-1; idx>=0; idx--) { + last = last.createChildIfAbsent(p.get(idx)); + q.add(new WorkItem(last)); + } + last.setAccept(true); + if (2*p.size() + 1 <= maxLen) { + Integer maxVal = p.stream().max((i1, i2) -> i1.compareTo(i2)).orElse(0); + for (int middleVal = 1; middleVal <= maxVal+1; middleVal++) { + last = node.createChildIfAbsent(middleVal); + q.add(new WorkItem(last)); + for (int idx = p.size()-1; idx>=0; idx--) { + last = last.createChildIfAbsent(p.get(idx)); + q.add(new WorkItem(last)); + } + last.setAccept(true); + } + } + } + } + root.determineMaxMemV(); + RegisterAutomaton ra = convertToRA(root, IN); + return ra; + } + + private static int TEST_MAX_LEN = 8; + /* + * Tests generator for numbers up to TEST_MAX_LEN size. Note that this method scales badly with increasing TEST_MAX_LEN. + */ + + @Test(enabled=false) + public void testPalindromeGenerator() { + RegisterAutomaton ra = generate(TEST_MAX_LEN); + Queue> q = new ArrayDeque<>(); + q.add(Collections.emptyList()); + while (!q.isEmpty()) { + List valWord = q.poll(); + WordBuilder wb = new WordBuilder(); + valWord.forEach(d -> + wb.add(new PSymbolInstance(IN, + new DataValue(TYPE, BigDecimal.valueOf(d))))); + Assert.assertEquals(ra.accepts(wb.toWord()), isPalindrome(valWord), "Mismatch for word " + valWord); + if (valWord.size() < TEST_MAX_LEN) { + if (valWord.isEmpty()) { + q.add(Arrays.asList(1)); + } else { + Integer maxVal = valWord.stream().max((i1, i2) -> i1.compareTo(i2)).get(); + for (int i=1; i<= maxVal+1; i++) { + List next = new ArrayList<>(valWord); + next.add(i); + q.add(next); + } + } + } + } + } +} diff --git a/src/test/java/de/learnlib/ralib/example/palindrome/PalindromeOracle.java b/src/test/java/de/learnlib/ralib/example/palindrome/PalindromeOracle.java new file mode 100644 index 000000000..caf06a7f8 --- /dev/null +++ b/src/test/java/de/learnlib/ralib/example/palindrome/PalindromeOracle.java @@ -0,0 +1,46 @@ +package de.learnlib.ralib.example.palindrome; + +import java.util.Collection; + +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 net.automatalib.word.Word; + +public class PalindromeOracle implements DataWordOracle { + + public static final DataType TYPE = new DataType("int"); + public static final InputSymbol IN = new InputSymbol("in", new DataType[] {TYPE}); + + private final Palindrome pal; + + public PalindromeOracle(Palindrome pal) { + this.pal = pal; + } + + @Override + public void processQueries(Collection> queries) { + for (Query q : queries) { + q.answer(answer(q.getInput())); + } + } + + private boolean answer(Word word) { + pal.reset(); + boolean isPalindrome = true; + for (PSymbolInstance psi : word) { + isPalindrome = answer(psi); + } + return isPalindrome; + } + + private boolean answer(PSymbolInstance psi) { + if (!psi.getBaseSymbol().equals(IN)) { + return false; + } + int d = psi.getParameterValues()[0].getValue().intValue(); + return pal.in(d); + } +} diff --git a/src/test/java/de/learnlib/ralib/example/sdts/LoginExampleTreeOracle.java b/src/test/java/de/learnlib/ralib/example/sdts/LoginExampleTreeOracle.java index 6714119a0..ce9e00fa5 100644 --- a/src/test/java/de/learnlib/ralib/example/sdts/LoginExampleTreeOracle.java +++ b/src/test/java/de/learnlib/ralib/example/sdts/LoginExampleTreeOracle.java @@ -204,12 +204,6 @@ public Branching updateBranching(Word prefix, return getInitialBranching(prefix, ps, sdts); } - @Override - public Map, Boolean> instantiate(Word prefix, - SymbolicSuffix suffix, SDT sdt) { - throw new UnsupportedOperationException("Not implemented"); - } - @Override public SymbolicSuffixRestrictionBuilder getRestrictionBuilder() { return new SymbolicSuffixRestrictionBuilder(new Constants()); diff --git a/src/test/java/de/learnlib/ralib/learning/LearnPalindromeTest.java b/src/test/java/de/learnlib/ralib/learning/LearnPalindromeTest.java new file mode 100644 index 000000000..7eef02747 --- /dev/null +++ b/src/test/java/de/learnlib/ralib/learning/LearnPalindromeTest.java @@ -0,0 +1,134 @@ +package de.learnlib.ralib.learning; + +import static de.learnlib.ralib.example.palindrome.PalindromeOracle.IN; +import static de.learnlib.ralib.example.palindrome.PalindromeOracle.TYPE; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.testng.annotations.Test; + +import de.learnlib.query.DefaultQuery; +import de.learnlib.ralib.CacheDataWordOracle; +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.equivalence.RAEquivalenceTest; +import de.learnlib.ralib.example.palindrome.Palindrome; +import de.learnlib.ralib.example.palindrome.PalindromeGenerator; +import de.learnlib.ralib.example.palindrome.PalindromeOracle; +import de.learnlib.ralib.learning.ralambda.SLLambda; +import de.learnlib.ralib.learning.ralambda.SLLambdaEq; +import de.learnlib.ralib.learning.rastar.RaStar; +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; +import de.learnlib.ralib.words.PSymbolInstance; + +public class LearnPalindromeTest extends RaLibTestSuite { + + // TODO Create a system for managing configuration of tests. + + // The size of the palindrome + private static final int PALINDROME_SIZE = Integer.parseInt(System.getProperty("palindrome.size", "6")); + // Whether the test is run in a benchmark setting + private static final boolean PALINDROME_BENCHMARK = Boolean.parseBoolean(System.getProperty("palindrome.benchmark", "false")); + + private void printHeader() { + System.out.println("======================="); + System.out.println("========== " + PALINDROME_SIZE + " =========="); + System.out.println("======================="); + } + + @Test(enabled = true) + public void testLearnPalindromeSLLambda() { + testLearnPalindrome(PALINDROME_SIZE, RaLearningAlgorithmName.RALAMBDA); + } + + @Test(enabled = true) + public void testLearnPalindromeSLLEq() { + testLearnPalindrome(PALINDROME_SIZE, RaLearningAlgorithmName.RALAMBDAEQ); + } + + @Test(enabled = true) + public void testLearnPalindromeSLStar() { + testLearnPalindrome(PALINDROME_SIZE, RaLearningAlgorithmName.RASTAR); + } + + public void testLearnPalindrome(int size, RaLearningAlgorithmName name) { + RegisterAutomaton model = PalindromeGenerator.generate(size); + + RaLearningAlgorithm algorithm = makeLearner(name); + if (PALINDROME_BENCHMARK) { + printHeader(); + } + + Map teachers = new LinkedHashMap<>(); + teachers.put(TYPE, new IntegerEqualityTheory(TYPE)); + RAEquivalenceTest checker = new RAEquivalenceTest(model, teachers, new Constants(), true, IN); + learn(algorithm, checker, name); + } + + private RaLearningAlgorithm makeLearner(RaLearningAlgorithmName name) { + Constants consts = new Constants(); + ConstraintSolver solver = new ConstraintSolver(); + + Palindrome pal = new Palindrome(PALINDROME_SIZE); + DataWordOracle oracle = new PalindromeOracle(pal); + CacheDataWordOracle cacheOracle = new CacheDataWordOracle(oracle); + + Map teachers = new LinkedHashMap<>(); + IntegerEqualityTheory iet = new IntegerEqualityTheory(TYPE); + iet.setUseSuffixOpt(true); + teachers.put(TYPE, iet); + + MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(cacheOracle, teachers, consts, solver); + SDTLogicOracle slo = new MultiTheorySDTLogicOracle(consts, solver); + TreeOracleFactory hypFactory = (RegisterAutomaton hyp) -> new MultiTheoryTreeOracle(new SimulatorOracle(hyp), + teachers, consts, solver); + + Measurements mes = new Measurements(); + QueryStatistics queryStats = new QueryStatistics(mes, cacheOracle); + + RaLearningAlgorithm learner = null; + switch (name) { + case RALAMBDA -> learner = new SLLambda(mto, teachers, consts, false, solver, IN); + case RALAMBDAEQ -> learner = new SLLambdaEq(mto, teachers, consts, false, solver, IN); + case RASTAR -> learner = new RaStar(mto, hypFactory, slo, consts, false, IN); + default -> throw new RuntimeException("Unsupported algorithm %s".formatted(name.name())); + } + learner.setStatisticCounter(queryStats); + + return learner; + } + + private void learn(RaLearningAlgorithm learner, RAEquivalenceTest checker, RaLearningAlgorithmName name) { + learner.learn(); + DefaultQuery ce = checker.findCounterExample(learner.getHypothesis(), null); + while (ce != null) { + learner.addCounterexample(ce); + learner.learn(); + ce = checker.findCounterExample(learner.getHypothesis(), null); + } + + if (PALINDROME_BENCHMARK) { + Hypothesis hyp = learner.getHypothesis(); + System.out.println(hyp); + System.out.println(learner.getQueryStatistics()); + System.out.println("Hyp. Locations: " + hyp.getStates().size()); + System.out.println("Hyp. Transitions: " + hyp.getTransitions().size()); + + // input locations + transitions + System.out.println("Hyp. Input Locations: " + hyp.getInputStates().size()); + System.out.println("Hyp. Input Transitions: " + hyp.getInputTransitions().size()); + System.out.println("Hyp. Registers: " + hyp.getRegisters().size()); + } + } +} 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 c70c2ba67..5ab1b7d12 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnABPOutputTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnABPOutputTest.java @@ -143,4 +143,111 @@ public void testLearnABPOutput() { Assert.assertNull(ce); } + + @Test + public void testLearnABPOutputSLLEq() { + + long seed = -1297170870937649002L; + final Random random = new Random(seed); + + RegisterAutomatonImporter loader = TestUtil.getLoader( + "/de/learnlib/ralib/automata/xml/abp.output.xml"); + + RegisterAutomaton model = loader.getRegisterAutomaton(); + + ParameterizedSymbol[] inputs = loader.getInputs().toArray( + new ParameterizedSymbol[]{}); + + ParameterizedSymbol[] actions = loader.getActions().toArray( + new ParameterizedSymbol[]{}); + + final Constants consts = loader.getConstants(); + + + final Map teachers = new LinkedHashMap<>(); + loader.getDataTypes().stream().forEach((t) -> { + IntegerEqualityTheory theory = new IntegerEqualityTheory(t); + theory.setUseSuffixOpt(true); + teachers.put(t, theory); + }); + + DataWordSUL sul = new SimulatorSUL(model, teachers, consts); + IOOracle ioOracle = new SULOracle(sul, ERROR); + IOCache ioCache = new IOCache(ioOracle); + IOFilter ioFilter = new IOFilter(ioCache, inputs); + + teachers.values().stream().forEach((t) -> { + ((EqualityTheory)t).setFreshValues(true, ioCache); + }); + + ConstraintSolver solver = new ConstraintSolver(); + + MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( + ioFilter, teachers, consts, solver); + + SLLambda sllambda = new SLLambdaEq(mto, teachers, consts, true, solver, actions); + + IOEquivalenceTest ioEquiv = new IOEquivalenceTest( + model, teachers, consts, true, actions); + + IOCounterexampleLoopRemover loops = new IOCounterexampleLoopRemover(ioOracle); + IOCounterExamplePrefixReplacer asrep = new IOCounterExamplePrefixReplacer(ioOracle); + IOCounterExamplePrefixFinder pref = new IOCounterExamplePrefixFinder(ioOracle); + + DefaultQuery ce = null; + + IORandomWalk randomWalk = new IORandomWalk(random, + sul, + false, + 0.1, + 0.8, + 10000, + 100, + consts, + false, + false, + teachers, + inputs); + + for (int check = 0; check < 100; ++check) { + sllambda.learn(); + Hypothesis hyp = sllambda.getHypothesis(); + + ce = null; + + boolean nullCe = false; + for (int i = 0; i < 3; i++) { + DefaultQuery ce2 = null; + + ce2 = randomWalk.findCounterExample(hyp, null); + if (ce2 == null) { + nullCe = true; + break; + } + + ce2 = loops.optimizeCE(ce2.getInput(), hyp); + ce2 = asrep.optimizeCE(ce2.getInput(), hyp); + ce2 = pref.optimizeCE(ce2.getInput(), hyp); + ce = (ce == null || ce.getInput().length() > ce2.getInput().length()) ? + ce2 : ce; + } + + if (nullCe) { + ce = ioEquiv.findCounterExample(hyp, null); + if (ce == null) + break; + } + + Assert.assertTrue(model.accepts(ce.getInput())); + Assert.assertFalse(hyp.accepts(ce.getInput())); + + sllambda.addCounterexample(ce); + } + + RegisterAutomaton hyp = sllambda.getHypothesis(); + logger.log(Level.FINE, "FINAL HYP: {0}", hyp); + ce = ioEquiv.findCounterExample(hyp, null); + + Assert.assertNull(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 c7135eed5..194417ef1 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnEchoTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnEchoTest.java @@ -74,4 +74,47 @@ public void testLearnEcho() { Assert.assertEquals(hyp.getStates().size(), 11); Assert.assertTrue(hyp.accepts(ce)); } + + @Test + public void testLearnEchoSLLEq() { + + Constants consts = new Constants(); + + final Map teachers = new LinkedHashMap<>(); + IntegerEqualityTheory theory = new IntegerEqualityTheory(TINT); + theory.setUseSuffixOpt(true); + teachers.put(TINT, theory); + + RepeaterSUL sul = new RepeaterSUL(-1, 4); + IOOracle ioOracle = new SULOracle(sul, RepeaterSUL.ERROR); + IOCache ioCache = new IOCache(ioOracle); + IOFilter oracle = new IOFilter(ioCache, sul.getInputSymbols()); + + ConstraintSolver solver = new ConstraintSolver(); + + MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(oracle, teachers, consts, solver); + + SLLambda learner = new SLLambdaEq(mto, teachers, consts, true, solver, sul.getActionSymbols()); + learner.learn(); + + Word ce = Word.fromSymbols( + new PSymbolInstance(IPUT, new DataValue(TINT, BigDecimal.ZERO)), + new PSymbolInstance(OECHO, new DataValue(TINT, BigDecimal.ZERO)), + new PSymbolInstance(IPUT, new DataValue(TINT, BigDecimal.ONE)), + new PSymbolInstance(OECHO, new DataValue(TINT, BigDecimal.ONE)), + new PSymbolInstance(IPUT, new DataValue(TINT, new BigDecimal(2))), + new PSymbolInstance(OECHO, new DataValue(TINT, new BigDecimal(2))), + new PSymbolInstance(IPUT, new DataValue(TINT, new BigDecimal(3))), + new PSymbolInstance(OECHO, new DataValue(TINT, new BigDecimal(3))), + new PSymbolInstance(IPUT, new DataValue(TINT, new BigDecimal(4))), + new PSymbolInstance(ONOK)); + + learner.addCounterexample(new DefaultQuery<>(ce, true)); + learner.learn(); + + Hypothesis hyp = learner.getHypothesis(); + + Assert.assertEquals(hyp.getStates().size(), 11); + Assert.assertTrue(hyp.accepts(ce)); + } } 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 cc5da3950..253727894 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnLoginTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnLoginTest.java @@ -110,16 +110,16 @@ public void testLearnLoginRandom() { } Assert.assertEquals(Arrays.toString(measuresLambda), - "[{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}]"); + "[{TQ: 74, Resets: 2102, Inputs: 0}," + + " {TQ: 74, Resets: 2089, Inputs: 0}," + + " {TQ: 74, Resets: 1422, Inputs: 0}," + + " {TQ: 74, Resets: 1479, Inputs: 0}," + + " {TQ: 74, Resets: 1433, Inputs: 0}," + + " {TQ: 74, Resets: 2225, Inputs: 0}," + + " {TQ: 74, Resets: 2089, Inputs: 0}," + + " {TQ: 74, Resets: 1268, Inputs: 0}," + + " {TQ: 79, Resets: 1277, Inputs: 0}," + + " {TQ: 74, Resets: 1225, Inputs: 0}]"); Assert.assertEquals(Arrays.toString(measuresStar), "[{TQ: 65, Resets: 1807, Inputs: 0}," + " {TQ: 65, Resets: 1788, Inputs: 0}," + 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 3f6df81cc..9162ff12e 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPQTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPQTest.java @@ -140,7 +140,7 @@ public void testLearnPQRandom() { } // hard-coded results from first seed - Assert.assertEquals(Arrays.toString(ralambdaCount), "[{TQ: 88, Resets: 876, Inputs: 0}]"); + Assert.assertEquals(Arrays.toString(ralambdaCount), "[{TQ: 77, Resets: 441, Inputs: 0}]"); Assert.assertEquals(Arrays.toString(rastarCount), "[{TQ: 55, Resets: 5721, Inputs: 0}]"); } } 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 6433a4920..89b613df5 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPalindromeIOTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnPalindromeIOTest.java @@ -94,4 +94,67 @@ public void testLearnPalindromeIO() { Assert.assertNull(ce); Assert.assertEquals(hyp.getTransitions().size(), 16); } + + @Test + public void testLearnPalindromeIOSLLEq() { + + RegisterAutomatonImporter loader = TestUtil.getLoader( + "/de/learnlib/ralib/automata/xml/palindrome.xml"); + + RegisterAutomaton model = loader.getRegisterAutomaton(); + logger.log(Level.FINE, "SYS: {0}", model); + + ParameterizedSymbol[] inputs = loader.getInputs().toArray( + new ParameterizedSymbol[]{}); + + ParameterizedSymbol[] actions = loader.getActions().toArray( + new ParameterizedSymbol[]{}); + + Constants consts = loader.getConstants(); + + final Map teachers = new LinkedHashMap<>(); + loader.getDataTypes().stream().forEach((t) -> { + TypedTheory theory = new IntegerEqualityTheory(t); + theory.setUseSuffixOpt(true); + teachers.put(t, theory); + }); + + ConstraintSolver solver = new ConstraintSolver(); + + DataWordSUL sul = new SimulatorSUL(model, teachers, consts); + IOOracle ioOracle = new SULOracle(sul, ERROR); + IOCache ioCache = new IOCache(ioOracle); + IOFilter ioFilter = new IOFilter(ioCache, inputs); + + MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(ioFilter, teachers, consts, solver); + + SLLambda sllambda = new SLLambdaEq(mto, teachers, consts, true, solver, actions); + + IOEquivalenceTest ioEquiv = new IOEquivalenceTest( + model, teachers, consts, true, actions); + + for (int check = 0; check < 10; ++check) { + sllambda.learn(); + Hypothesis hyp = sllambda.getHypothesis(); + logger.log(Level.FINE, "HYP: {0}", hyp); + + DefaultQuery ce = ioEquiv.findCounterExample(hyp, null); + logger.log(Level.FINE, "CE: {0}", ce); + if (ce == null) { + break; + } + + Assert.assertTrue(model.accepts(ce.getInput())); + Assert.assertFalse(hyp.accepts(ce.getInput())); + + sllambda.addCounterexample(ce); + } + + RegisterAutomaton hyp = sllambda.getHypothesis(); + logger.log(Level.FINE, "FINAL HYP: {0}", hyp); + DefaultQuery ce = ioEquiv.findCounterExample(hyp, null); + + Assert.assertNull(ce); + 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 35d439241..61e0a3fbc 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnRepeaterTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnRepeaterTest.java @@ -85,4 +85,57 @@ public void testLearnRepeater() { Assert.assertTrue(str.contains("Other: {TQ: 0, Resets: 1, Inputs: 0}")); Assert.assertTrue(str.contains("Total: {TQ: 0, Resets: 13, Inputs: 0}")); } + + @Test + public void testLearnRepeaterSLLEQ() { + + Constants consts = new Constants(); + + final Map teachers = new LinkedHashMap<>(); + IntegerEqualityTheory theory = new IntegerEqualityTheory(TINT); + theory.setUseSuffixOpt(true); + teachers.put(TINT, theory); + + RepeaterSUL sul = new RepeaterSUL(); + IOOracle ioOracle = new SULOracle(sul, RepeaterSUL.ERROR); + IOCache ioCache = new IOCache(ioOracle); + IOFilter oracle = new IOFilter(ioCache, sul.getInputSymbols()); + + ConstraintSolver solver = new ConstraintSolver(); + + MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle(oracle, teachers, consts, solver); + + Measurements measurements = new Measurements(); + QueryStatistics stats = new QueryStatistics(measurements, ioOracle); + + SLLambda learner = new SLLambdaEq(mto, teachers, consts, true, solver, sul.getActionSymbols()); + learner.setStatisticCounter(stats); + + learner.learn(); + + Repeater repeater = new Repeater(); + Assert.assertEquals(repeater.repeat(0), (Integer)0); + Assert.assertEquals(repeater.repeat(0), (Integer)0); + Assert.assertNull(repeater.repeat(0)); + + Word ce = + Word.fromSymbols(new PSymbolInstance(IPUT, new DataValue(TINT, BigDecimal.ZERO)), + new PSymbolInstance(OECHO, new DataValue(TINT, BigDecimal.ZERO)), + new PSymbolInstance(IPUT, new DataValue(TINT, BigDecimal.ZERO)), + new PSymbolInstance(OECHO, new DataValue(TINT, BigDecimal.ZERO)), + new PSymbolInstance(IPUT, new DataValue(TINT, BigDecimal.ZERO)), + new PSymbolInstance(OECHO, new DataValue(TINT, BigDecimal.ZERO))); + + learner.addCounterexample(new DefaultQuery(ce, false)); + + learner.learn(); + + String str = stats.toString(); + 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}")); + Assert.assertTrue(str.contains("Processing / Refinement: {TQ: 0, Resets: 3, Inputs: 0}")); + Assert.assertTrue(str.contains("Other: {TQ: 0, Resets: 1, Inputs: 0}")); + Assert.assertTrue(str.contains("Total: {TQ: 0, Resets: 11, 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 8100dc878..0b24fc934 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnSipIOTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnSipIOTest.java @@ -60,7 +60,7 @@ public void testLearnSipIO() { final Map teachers = new LinkedHashMap<>(); loader.getDataTypes().stream().forEach((t) -> { IntegerEqualityTheory theory = new IntegerEqualityTheory(t); - theory.setUseSuffixOpt(false); + theory.setUseSuffixOpt(true); teachers.put(t, theory); }); @@ -112,4 +112,80 @@ public void testLearnSipIO() { Assert.assertNull(ce); } + + @Test + public void testLearnSipIOSLLEq() { + + long seed = -1386796323025681754L; + //long seed = (new Random()).nextLong(); + logger.log(Level.FINE, "SEED={0}", seed); + + RegisterAutomatonImporter loader = TestUtil.getLoader( + "/de/learnlib/ralib/automata/xml/sip.xml"); + + RegisterAutomaton model = loader.getRegisterAutomaton(); + + ParameterizedSymbol[] inputs = loader.getInputs().toArray( + new ParameterizedSymbol[]{}); + + ParameterizedSymbol[] actions = loader.getActions().toArray( + new ParameterizedSymbol[]{}); + + final Constants consts = loader.getConstants(); + + final Map teachers = new LinkedHashMap<>(); + loader.getDataTypes().stream().forEach((t) -> { + IntegerEqualityTheory theory = new IntegerEqualityTheory(t); + theory.setUseSuffixOpt(true); + teachers.put(t, theory); + }); + + DataWordSUL sul = new SimulatorSUL(model, teachers, consts); + IOOracle ioOracle = new SULOracle(sul, ERROR); + IOCache ioCache = new IOCache(ioOracle); + IOFilter ioFilter = new IOFilter(ioCache, inputs); + + teachers.values().stream().forEach((t) -> { + ((EqualityTheory)t).setFreshValues(true, ioCache); + }); + + ConstraintSolver solver = new ConstraintSolver(); + + MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( + ioFilter, teachers, consts, solver); + + SLLambda sllambda = new SLLambdaEq(mto, teachers, consts, true, solver, actions); + + IOEquivalenceTest ioEquiv = new IOEquivalenceTest( + model, teachers, consts, true, actions); + + IOCounterexampleLoopRemover loops = new IOCounterexampleLoopRemover(ioOracle); + IOCounterExamplePrefixReplacer asrep = new IOCounterExamplePrefixReplacer(ioOracle); + IOCounterExamplePrefixFinder pref = new IOCounterExamplePrefixFinder(ioOracle); + + for (int check = 0; check < 100; ++check) { + sllambda.learn(); + Hypothesis hyp = sllambda.getHypothesis(); + + DefaultQuery ce = ioEquiv.findCounterExample(hyp, null); + if (ce == null) { + break; + } + + ce = loops.optimizeCE(ce.getInput(), hyp); + ce = asrep.optimizeCE(ce.getInput(), hyp); + ce = pref.optimizeCE(ce.getInput(), hyp); + + Assert.assertTrue(model.accepts(ce.getInput())); + Assert.assertFalse(hyp.accepts(ce.getInput())); + + sllambda.addCounterexample(ce); + } + + RegisterAutomaton hyp = sllambda.getHypothesis(); + logger.log(Level.FINE, "FINAL HYP: {0}", hyp); + DefaultQuery ce = ioEquiv.findCounterExample(hyp, null); + + Assert.assertNull(ce); + } } 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 9ad25f09e..1555abea6 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/LearnStackTest.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/LearnStackTest.java @@ -211,16 +211,16 @@ public void testLearnStackRandom() { } Assert.assertEquals(Arrays.toString(measuresLambda), - "[{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}]"); + "[{TQ: 83, Resets: 435, Inputs: 0}," + + " {TQ: 93, Resets: 737, Inputs: 0}," + + " {TQ: 78, Resets: 441, Inputs: 0}," + + " {TQ: 88, Resets: 574, Inputs: 0}," + + " {TQ: 103, Resets: 936, Inputs: 0}," + + " {TQ: 82, Resets: 597, Inputs: 0}," + + " {TQ: 72, Resets: 446, Inputs: 0}," + + " {TQ: 48, Resets: 418, Inputs: 0}," + + " {TQ: 123, Resets: 694, Inputs: 0}," + + " {TQ: 53, Resets: 470, Inputs: 0}]"); Assert.assertEquals(Arrays.toString(measuresStar), "[{TQ: 51, Resets: 838, Inputs: 0}," + " {TQ: 50, Resets: 10681, Inputs: 0}," + 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 2e01a0ad4..d6968c366 100644 --- a/src/test/java/de/learnlib/ralib/learning/ralambda/TestSuffixOptimization.java +++ b/src/test/java/de/learnlib/ralib/learning/ralambda/TestSuffixOptimization.java @@ -18,29 +18,62 @@ import de.learnlib.ralib.CacheDataWordOracle; 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.ct.CTPath; +import de.learnlib.ralib.ct.Prefix; +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.SymbolicDataValue.Parameter; +import de.learnlib.ralib.data.SymbolicDataValue.Register; +import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; +import de.learnlib.ralib.data.VarMapping; +import de.learnlib.ralib.equivalence.RAEquivalenceTest; import de.learnlib.ralib.example.repeater.RepeaterSUL; import de.learnlib.ralib.learning.Hypothesis; import de.learnlib.ralib.learning.Measurements; import de.learnlib.ralib.learning.MeasuringOracle; import de.learnlib.ralib.learning.QueryStatistics; +import de.learnlib.ralib.learning.SymbolicSuffix; import de.learnlib.ralib.oracles.DataWordOracle; +import de.learnlib.ralib.oracles.SimulatorOracle; 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.MultiTheoryTreeOracle; +import de.learnlib.ralib.oracles.mto.SLLambdaEqRestrictionBuilder; import de.learnlib.ralib.smt.ConstraintSolver; import de.learnlib.ralib.sul.SULOracle; +import de.learnlib.ralib.theory.AbstractSuffixValueRestriction; +import de.learnlib.ralib.theory.DisjunctionRestriction; +import de.learnlib.ralib.theory.FreshSuffixValue; +import de.learnlib.ralib.theory.SDT; +import de.learnlib.ralib.theory.SDTGuard; +import de.learnlib.ralib.theory.SDTLeaf; +import de.learnlib.ralib.theory.SuffixValueRestriction; import de.learnlib.ralib.theory.Theory; +import de.learnlib.ralib.theory.equality.UnmappedEqualityRestriction; 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 TestSuffixOptimization extends RaLibTestSuite { + private static final InputSymbol A = new InputSymbol("a", TINT); + private static final InputSymbol B = new InputSymbol("b", TINT, TINT); + private static final InputSymbol C = new InputSymbol("c"); + @Test public void testLearnRepeaterSuffixOpt() { @@ -93,6 +126,58 @@ public void testLearnRepeaterSuffixOpt() { Assert.assertTrue(str.contains("Total: {TQ: 0, Resets: 4, Inputs: 10}")); } + @Test + public void testLearnRepeaterSuffixOptSLLEq() { + + Constants consts = new Constants(); + + final Map teachers = new LinkedHashMap<>(); + IntegerEqualityTheory theory = new IntegerEqualityTheory(TINT); + theory.setUseSuffixOpt(true); + teachers.put(TINT, theory); + + RepeaterSUL sul = new RepeaterSUL(-1, 2); + IOOracle ioOracle = new SULOracle(sul, RepeaterSUL.ERROR); + IOCache ioCache = new IOCache(ioOracle); + IOFilter oracle = new IOFilter(ioCache, sul.getInputSymbols()); + + ConstraintSolver solver = new ConstraintSolver(); + + MultiTheoryTreeOracle mto = + new MultiTheoryTreeOracle(oracle, teachers, consts, solver); + + Measurements measurements = new Measurements(); + QueryStatistics stats = new QueryStatistics(measurements, sul); + + SLLambda learner = new SLLambdaEq(mto, teachers, consts, true, solver, sul.getActionSymbols()); + learner.setStatisticCounter(stats); + + learner.learn(); + + Word ce = + Word.fromSymbols(new PSymbolInstance(IPUT, new DataValue(TINT, BigDecimal.ZERO)), + new PSymbolInstance(OECHO, new DataValue(TINT, BigDecimal.ZERO)), + new PSymbolInstance(IPUT, new DataValue(TINT, BigDecimal.ONE)), + new PSymbolInstance(OECHO, new DataValue(TINT, BigDecimal.ONE)), + new PSymbolInstance(IPUT, new DataValue(TINT, new BigDecimal(2))), + new PSymbolInstance(OECHO, new DataValue(TINT, new BigDecimal(2)))); + + learner.addCounterexample(new DefaultQuery(ce, false)); + + learner.learn(); + + Hypothesis hyp = learner.getHypothesis(); + Assert.assertEquals(hyp.getStates().size(), 7); + + String str = stats.toString(); + Assert.assertTrue(str.contains("Counterexamples: 1")); + Assert.assertTrue(str.contains("CE max length: 6")); + Assert.assertTrue(str.contains("CE Analysis: {TQ: 0, Resets: 2, Inputs: 5}")); + Assert.assertTrue(str.contains("Processing / Refinement: {TQ: 0, Resets: 1, Inputs: 4}")); + Assert.assertTrue(str.contains("Other: {TQ: 0, Resets: 1, Inputs: 1}")); + Assert.assertTrue(str.contains("Total: {TQ: 0, Resets: 4, Inputs: 10}")); + } + @Test public void testLearnPQSuffixOpt() { @@ -131,8 +216,189 @@ public void testLearnPQSuffixOpt() { Assert.assertTrue(str.contains("Counterexamples: 1")); Assert.assertTrue(str.contains("CE max length: 4")); 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}")); + Assert.assertTrue(str.contains("Processing / Refinement: {TQ: 27, Resets: 514, Inputs: 0}")); + Assert.assertTrue(str.contains("Other: {TQ: 5, Resets: 5, Inputs: 0}")); + Assert.assertTrue(str.contains("Total: {TQ: 113, Resets: 611, Inputs: 0}")); + } + + @Test + public void testExtendSuffixLocation() { + IntegerEqualityTheory iet = new IntegerEqualityTheory(TINT); + iet.setUseSuffixOpt(true); + Map teachers = Map.of(TINT, iet); + + SLLambdaEqRestrictionBuilder builder = new SLLambdaEqRestrictionBuilder(new Constants(), teachers, new ConstraintSolver()); + + SuffixValue s1 = new SuffixValue(TINT, 1); + SuffixValue s2 = new SuffixValue(TINT, 2); + SuffixValue s3 = new SuffixValue(TINT, 3); + SuffixValue s4 = new SuffixValue(TINT, 4); + SuffixValue s5 = new SuffixValue(TINT, 5); + + DataValue d0 = new DataValue(TINT, BigDecimal.ZERO); + DataValue d1 = new DataValue(TINT, BigDecimal.ONE); + DataValue d2 = new DataValue(TINT, BigDecimal.valueOf(2)); + + PSymbolInstance a1 = new PSymbolInstance(A, d1); + PSymbolInstance a2 = new PSymbolInstance(A, d2); + PSymbolInstance b12 = new PSymbolInstance(B, d1, d2); + PSymbolInstance b21 = new PSymbolInstance(B, d2, d1); + + SDT u1ExtSdt = new SDT(Map.of( + new SDTGuard.EqualityGuard(s1, d2), new SDT(Map.of( + new SDTGuard.EqualityGuard(s2, d2), new SDT(Map.of( + new SDTGuard.SDTTrueGuard(s3), SDTLeaf.REJECTING)), + new SDTGuard.DisequalityGuard(s2, d2), new SDT(Map.of( + new SDTGuard.SDTTrueGuard(s3), SDTLeaf.ACCEPTING)))), + new SDTGuard.DisequalityGuard(s1, d2), new SDT(Map.of( + new SDTGuard.SDTTrueGuard(s2), new SDT(Map.of( + new SDTGuard.SDTTrueGuard(s3), SDTLeaf.REJECTING)))))); + SDT u2ExtSdt = new SDT(Map.of( + new SDTGuard.SDTTrueGuard(s1), new SDT(Map.of( + new SDTGuard.SDTTrueGuard(s2), new SDT(Map.of( + new SDTGuard.SDTTrueGuard(s3), SDTLeaf.REJECTING)))))); + + SDT u1ExtPriorSdt = new SDT(Map.of( + new SDTGuard.EqualityGuard(s1, d1), SDTLeaf.ACCEPTING, + new SDTGuard.DisequalityGuard(s1, d1), SDTLeaf.REJECTING)); + SDT u2ExtPriorSdt = new SDT(Map.of( + new SDTGuard.EqualityGuard(s1, d1), SDTLeaf.ACCEPTING, + new SDTGuard.DisequalityGuard(s1, d1), SDTLeaf.REJECTING)); + SDT u1Prior = new SDT(Map.of( + new SDTGuard.EqualityGuard(s1, d2), SDTLeaf.ACCEPTING, + new SDTGuard.DisequalityGuard(s1, d2), SDTLeaf.REJECTING)); + SDT u2Prior = new SDT(Map.of( + new SDTGuard.EqualityGuard(s1, d1), SDTLeaf.ACCEPTING, + new SDTGuard.DisequalityGuard(s1, d1), SDTLeaf.REJECTING)); + + Map restr1 = new LinkedHashMap<>(); + restr1.put(s1, DisjunctionRestriction.create(s1, new UnmappedEqualityRestriction(s1), new FreshSuffixValue(s1))); + restr1.put(s2, DisjunctionRestriction.create(s2, new UnmappedEqualityRestriction(s2), new FreshSuffixValue(s2))); + restr1.put(s3, SuffixValueRestriction.equalityRestriction(s3, d2)); + SymbolicSuffix suffix = new SymbolicSuffix(Word.fromSymbols(B, A), restr1); + SymbolicSuffix suffixPrior = new SymbolicSuffix(Word.fromSymbols(A), Map.of(s1, DisjunctionRestriction.create(s1, new UnmappedEqualityRestriction(s1), new FreshSuffixValue(s1)))); + + Bijection u1rp = new Bijection<>(); + u1rp.put(d2, d0); + Bijection u2rp = new Bijection<>(); + u2rp.put(d1, d0); + Bijection u1ExtRp = new Bijection<>(); + u1ExtRp.put(d1, d1); + u1ExtRp.put(d2, d2); + Bijection u2ExtRp = new Bijection<>(); + u2ExtRp.put(d1, d1); + Bijection u1ExtPriorRp = new Bijection<>(); + u1ExtPriorRp.put(d1, d2); + Bijection u2ExtPriorRp = new Bijection<>(); + u2ExtPriorRp.put(d1, d2); + + CTPath u1Path = new CTPath(false); + u1Path.putSDT(suffixPrior, u1Prior); + CTPath u2Path = new CTPath(false); + u2Path.putSDT(suffixPrior, u2Prior); + CTPath u1ExtPath = new CTPath(false); + u1ExtPath.putSDT(suffixPrior, u1ExtPriorSdt); + u1ExtPath.putSDT(suffix, u1ExtSdt); + CTPath u2ExtPath = new CTPath(false); + u2ExtPath.putSDT(suffixPrior, u2ExtPriorSdt); + u2ExtPath.putSDT(suffix, u2ExtSdt); + + Prefix u1 = new Prefix(Word.fromSymbols(b12), u1rp, u1Path); + Prefix u2 = new Prefix(Word.fromSymbols(a1, a2), u2rp, u2Path); + Prefix u1Ext = new Prefix(Word.fromSymbols(b12, b21), u1ExtRp, u1ExtPath); + u1Ext.putBijection(suffixPrior, u1ExtPriorRp); + Prefix u2Ext = new Prefix(Word.fromSymbols(a1, a2, b12), u2ExtRp, u2ExtPath); + u2Ext.putBijection(suffixPrior, u2ExtPriorRp); + + Map expRestrAlt1 = new LinkedHashMap<>(); + expRestrAlt1.put(s1, SuffixValueRestriction.equalityRestriction(s1, d0)); + expRestrAlt1.put(s2, DisjunctionRestriction.create(s2, new UnmappedEqualityRestriction(s2), new FreshSuffixValue(s2))); + expRestrAlt1.put(s3, SuffixValueRestriction.equalityRestriction(s3, d0)); + expRestrAlt1.put(s4, new FreshSuffixValue(s4)); + expRestrAlt1.put(s5, SuffixValueRestriction.equalityRestriction(s5, s1, s2)); + SymbolicSuffix expectedAlt1 = new SymbolicSuffix(Word.fromSymbols(B, B, A), expRestrAlt1); + + SymbolicSuffix actual = builder.extendSuffix(u1, u1Ext, u2, u2Ext, suffix, u1ExtSdt, u2ExtSdt); + Assert.assertEquals(actual, expectedAlt1); + } + + @Test + public void testMultipleParamsEquality() { + Constants consts = new Constants(); + RegisterAutomaton ra = buildAutomaton(); + DataWordOracle dwOracle = new SimulatorOracle(ra); + + final Map teachers = new LinkedHashMap<>(); + IntegerEqualityTheory theory = new IntegerEqualityTheory(TINT); + theory.setUseSuffixOpt(true); + teachers.put(TINT, theory); + + DataValue d0 = new DataValue(TINT, BigDecimal.ZERO); + + ConstraintSolver solver = new ConstraintSolver(); + + MultiTheoryTreeOracle mto = + new MultiTheoryTreeOracle(dwOracle, teachers, consts, solver); + + SLLambda learner = new SLLambda(mto, teachers, consts, false, solver, B, C); + + learner.learn(); + + Word ce = Word.fromSymbols( + new PSymbolInstance(B, d0, d0), + new PSymbolInstance(B, d0, d0), + new PSymbolInstance(C)); + learner.addCounterexample(new DefaultQuery<>(ce, true)); + + learner.learn(); + + RAEquivalenceTest ioEquiv = new RAEquivalenceTest( + ra, teachers, consts, true, B, C); + + Hypothesis hyp = learner.getHypothesis(); + DefaultQuery finalCe = ioEquiv.findCounterExample(hyp, null); + + Assert.assertNull(finalCe); + } + + private RegisterAutomaton buildAutomaton() { + MutableRegisterAutomaton ra = new MutableRegisterAutomaton(); + + RALocation l0 = ra.addInitialState(); + RALocation l1 = ra.addState(); + RALocation l2 = ra.addState(); + RALocation ls = ra.addState(false); + + Parameter p1 = new Parameter(TINT, 1); + Parameter p2 = new Parameter(TINT, 2); + Register r1 = new Register(TINT, 1); + Register r2 = new Register(TINT, 2); + + VarMapping storeMap = new VarMapping<>(); + storeMap.put(r1, p1); + storeMap.put(r2, p2); + + Assignment store = new Assignment(storeMap); + Assignment no = new Assignment(new VarMapping<>()); + + Expression gEq1 = new NumericBooleanExpression(r1, NumericComparator.EQ, p1); + Expression gEq2 = new NumericBooleanExpression(r2, NumericComparator.EQ, p2); + Expression gNe1 = new NumericBooleanExpression(r1, NumericComparator.NE, p1); + Expression gNe2 = new NumericBooleanExpression(r2, NumericComparator.NE, p2); + Expression gEq = ExpressionUtil.and(gEq1, gEq2); + Expression gNe = ExpressionUtil.or(gNe1, gNe2); + Expression gT = ExpressionUtil.TRUE; + + ra.addTransition(l0, B, new InputTransition(gT, B, l0, l1, store)); + ra.addTransition(l0, C, new InputTransition(gT, C, l0, ls, no)); + ra.addTransition(l1, B, new InputTransition(gEq, B, l1, l2, no)); + ra.addTransition(l1, B, new InputTransition(gNe, B, l1, ls, no)); + ra.addTransition(l1, C, new InputTransition(gT, C, l1, ls, no)); + ra.addTransition(l2, B, new InputTransition(gT, B, l2, ls, no)); + ra.addTransition(l2, C, new InputTransition(gT, C, l2, l0, no)); + ra.addTransition(ls, B, new InputTransition(gT, B, ls, ls, no)); + ra.addTransition(ls, C, new InputTransition(gT, C, ls, ls, no)); + + return ra; } } diff --git a/src/test/java/de/learnlib/ralib/learning/rastar/LoggingOracle.java b/src/test/java/de/learnlib/ralib/learning/rastar/LoggingOracle.java index e2177321c..09f86d914 100644 --- a/src/test/java/de/learnlib/ralib/learning/rastar/LoggingOracle.java +++ b/src/test/java/de/learnlib/ralib/learning/rastar/LoggingOracle.java @@ -16,7 +16,6 @@ */ package de.learnlib.ralib.learning.rastar; -import java.util.Map; import de.learnlib.ralib.data.Constants; import de.learnlib.ralib.learning.SymbolicSuffix; @@ -65,11 +64,11 @@ public Branching updateBranching(Word prefix, return b; } - @Override - public Map, Boolean> instantiate(Word prefix, - SymbolicSuffix suffix, SDT sdt) { - return treeoracle.instantiate(prefix, suffix, sdt); - } +// @Override +// public Map, Boolean> instantiate(Word prefix, +// SymbolicSuffix suffix, SDT sdt) { +// return treeoracle.instantiate(prefix, suffix, sdt); +// } @Override public SymbolicSuffixRestrictionBuilder getRestrictionBuilder() { diff --git a/src/test/java/de/learnlib/ralib/oracles/mto/InstantiateSymbolicWordTest.java b/src/test/java/de/learnlib/ralib/oracles/mto/InstantiateSymbolicWordTest.java deleted file mode 100644 index 66c166958..000000000 --- a/src/test/java/de/learnlib/ralib/oracles/mto/InstantiateSymbolicWordTest.java +++ /dev/null @@ -1,164 +0,0 @@ -package de.learnlib.ralib.oracles.mto; - -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.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.Register; -import de.learnlib.ralib.data.VarMapping; -import de.learnlib.ralib.data.util.SymbolicDataValueGenerator; -import de.learnlib.ralib.learning.SymbolicSuffix; -import de.learnlib.ralib.oracles.DataWordOracle; -import de.learnlib.ralib.oracles.SimulatorOracle; -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.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 InstantiateSymbolicWordTest { - - @Test - public void testInstantiateStack() { - 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 prefix = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ZERO))); - Word suffix = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ONE)), - new PSymbolInstance(I_POP, new DataValue(T_INT, BigDecimal.ONE)), - new PSymbolInstance(I_POP, new DataValue(T_INT, BigDecimal.ZERO))); - SymbolicSuffix symbSuffix = new SymbolicSuffix(prefix, suffix); - - SDT tqr = mto.treeQuery(prefix, symbSuffix); - - Map, Boolean> words = mto.instantiate(prefix, symbSuffix, tqr); - - Word p1 = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ZERO)), - new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ONE)), - new PSymbolInstance(I_POP, new DataValue(T_INT, BigDecimal.ONE)), - new PSymbolInstance(I_POP, new DataValue(T_INT, BigDecimal.ZERO))); - Word p2 = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ZERO)), - new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ONE)), - new PSymbolInstance(I_POP, new DataValue(T_INT, BigDecimal.ONE)), - new PSymbolInstance(I_POP, new DataValue(T_INT, new BigDecimal(2)))); - Word p3 = Word.fromSymbols( - new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ZERO)), - new PSymbolInstance(I_PUSH, new DataValue(T_INT, BigDecimal.ONE)), - new PSymbolInstance(I_POP, new DataValue(T_INT, new BigDecimal(2) )), - new PSymbolInstance(I_POP, new DataValue(T_INT, new BigDecimal(3) ))); - - Assert.assertEquals(words.size(), 3); - Assert.assertTrue(words.containsKey(p1) && - words.containsKey(p2) && - words.containsKey(p3)); - Assert.assertTrue(words.get(p1).booleanValue()); - } - - @Test - public void testInstantiateWithSuffixOpt() { - MutableRegisterAutomaton ra = new MutableRegisterAutomaton(); - - InputSymbol A = new InputSymbol("a", T_INT); - InputSymbol B = new InputSymbol("b", T_INT); - - RALocation l0 = ra.addInitialState(); - RALocation l1 = ra.addState(); - RALocation ls = ra.addState(false); - - // registers and parameters - SymbolicDataValueGenerator.RegisterGenerator rgen = new SymbolicDataValueGenerator.RegisterGenerator(); - SymbolicDataValue.Register r1 = rgen.next(T_INT); - SymbolicDataValueGenerator.ParameterGenerator pgen = new SymbolicDataValueGenerator.ParameterGenerator(); - SymbolicDataValue.Parameter p1 = pgen.next(T_INT); - - // guards - Expression equal = new NumericBooleanExpression(r1, NumericComparator.EQ, p1); - Expression notEqual = new NumericBooleanExpression(r1, NumericComparator.NE, p1); - - Expression equalGuard = equal; - Expression notEqualGuard = notEqual; - Expression trueGuard = ExpressionUtil.TRUE; - - // assignments - VarMapping store = new VarMapping(); - store.put(r1, p1); - VarMapping noMapping = new VarMapping(); - - Assignment storeAssign = new Assignment(store); - Assignment noAssign = new Assignment(noMapping); - - ra.addTransition(l0, A, new InputTransition(trueGuard, A, l0, l1, storeAssign)); - ra.addTransition(l0, B, new InputTransition(trueGuard, B, l0, ls, noAssign)); - - ra.addTransition(l1, B, new InputTransition(equalGuard, B, l1, l0, noAssign)); - ra.addTransition(l1, B, new InputTransition(notEqualGuard, B, l1, ls, noAssign)); - ra.addTransition(l1, A, new InputTransition(trueGuard, A, l1, ls, noAssign)); - - ra.addTransition(ls, A, new InputTransition(trueGuard, A, ls, ls, noAssign)); - ra.addTransition(ls, B, new InputTransition(trueGuard, B, ls, ls, noAssign)); - - final Map teachers = new LinkedHashMap<>(); - IntegerEqualityTheory intEq = new IntegerEqualityTheory(T_INT); - intEq.setUseSuffixOpt(true); - teachers.put(T_INT, intEq); - - DataWordOracle dwOracle = new SimulatorOracle(ra); - - MultiTheoryTreeOracle mto = new MultiTheoryTreeOracle( - dwOracle, teachers, new Constants(), new ConstraintSolver()); - - Word prefix = Word.fromSymbols( - new PSymbolInstance(A, new DataValue(T_INT, BigDecimal.ZERO)), - new PSymbolInstance(B, new DataValue(T_INT, BigDecimal.ZERO))); - Word suffix = Word.fromSymbols( - new PSymbolInstance(A, new DataValue(T_INT, BigDecimal.ONE)), - new PSymbolInstance(B, new DataValue(T_INT, BigDecimal.ONE))); - SymbolicSuffix symSuffix = new SymbolicSuffix(prefix, suffix); - - SDT tqr = mto.treeQuery(prefix, symSuffix); - Map, Boolean> words = mto.instantiate(prefix, symSuffix, tqr); - - Assert.assertEquals(words.size(), 1); - - Word word = words.keySet().iterator().next(); - DataValue suffixVal1 = word.getSymbol(2).getParameterValues()[0]; - DataValue suffixVal2 = word.getSymbol(3).getParameterValues()[0]; - - Assert.assertEquals(suffixVal1, suffixVal2); - } -} diff --git a/src/test/java/de/learnlib/ralib/oracles/mto/NonFreeSuffixValuesTest.java b/src/test/java/de/learnlib/ralib/oracles/mto/NonFreeSuffixValuesTest.java index 4f193a3e3..4d51287cc 100644 --- a/src/test/java/de/learnlib/ralib/oracles/mto/NonFreeSuffixValuesTest.java +++ b/src/test/java/de/learnlib/ralib/oracles/mto/NonFreeSuffixValuesTest.java @@ -128,18 +128,18 @@ public void testModelswithOutputFifo() { " []-TRUE: s2\n" + " []-(s3=0[int])\n" + " | []-(s4=1[int])\n" + - " | | []-(s5=s1)\n" + + " | | []-TRUE: s5\n" + " | | []-(s6=s2)\n" + " | | | [Leaf+]\n" + " | | +-(s6!=s2)\n" + " | | [Leaf-]\n" + " | +-(s4!=1[int])\n" + - " | []-(s5=s1)\n" + + " | []-TRUE: s5\n" + " | []-TRUE: s6\n" + " | [Leaf-]\n" + " +-(s3!=0[int])\n" + " []-TRUE: s4\n" + - " []-(s5=s1)\n" + + " []-TRUE: s5\n" + " []-TRUE: s6\n" + " [Leaf-]\n"; @@ -246,9 +246,9 @@ public void testNonFreeNonFresh() { String expectedTree = "[]-+\n" + " []-TRUE: s1\n" + -" []-(s2=s1)\n" + +" []-TRUE: s2\n" + " []-TRUE: s3\n" + -" []-(s4=s3)\n" + +" []-TRUE: s4\n" + " [Leaf-]\n"; checkTreeForSuffix(word.prefix(2), suffix, mto, expectedTree); diff --git a/src/test/java/de/learnlib/ralib/oracles/mto/OptimizedSymbolicSuffixBuilderTest.java b/src/test/java/de/learnlib/ralib/oracles/mto/OptimizedSymbolicSuffixBuilderTest.java index 0740bdebd..a67a9c302 100644 --- a/src/test/java/de/learnlib/ralib/oracles/mto/OptimizedSymbolicSuffixBuilderTest.java +++ b/src/test/java/de/learnlib/ralib/oracles/mto/OptimizedSymbolicSuffixBuilderTest.java @@ -188,7 +188,7 @@ public void testExtendDistinguishingSuffix() { Assert.assertEquals(actual1, expected1); SymbolicSuffix actual2 = builder.extendSuffix(word2.prefix(2), sdt2, suffix2); - Map expectedRestr2 = new LinkedHashMap<>(); + Map expectedRestr2 = new LinkedHashMap<>(); expectedRestr2.put(s1, new FreshSuffixValue(s1)); expectedRestr2.put(s2, new FreshSuffixValue(s2)); expectedRestr2.put(s3, new UnrestrictedSuffixValue(s3)); @@ -326,18 +326,18 @@ public void testExtendSuffix() { OptimizedSymbolicSuffixBuilder builder2 = new OptimizedSymbolicSuffixBuilder(consts2, restrictionBuilder2); SymbolicSuffix expected1 = new SymbolicSuffix(word1.prefix(1), word1.suffix(4), restrictionBuilder1); - SymbolicSuffix actual1 = builder1.extendSuffix(word1.prefix(2), sdtPath1, suffix1.getActions()); + SymbolicSuffix actual1 = builder1.extendSuffix(word1.prefix(2), sdtPath1, suffix1); Assert.assertEquals(actual1, expected1); SymbolicSuffix expected2 = new SymbolicSuffix(word2.prefix(1), word2.suffix(4), restrictionBuilder1); - SymbolicSuffix actual2 = builder1.extendSuffix(word2.prefix(2), sdtPath2, suffix2.getActions()); + SymbolicSuffix actual2 = builder1.extendSuffix(word2.prefix(2), sdtPath2, suffix2); Assert.assertEquals(actual2, expected2); SymbolicSuffix expected3 = new SymbolicSuffix(word3.prefix(1), word3.suffix(3), restrictionBuilder2); - SymbolicSuffix actual3 = builder1.extendSuffix(word3.prefix(2), sdtPath3, suffix3.getActions()); + SymbolicSuffix actual3 = builder1.extendSuffix(word3.prefix(2), sdtPath3, suffix3); Assert.assertEquals(actual3, expected3); - SymbolicSuffix actual4 = builder2.extendSuffix(word4.prefix(2), sdtPath4, suffix4.getActions()); + SymbolicSuffix actual4 = builder2.extendSuffix(word4.prefix(2), sdtPath4, suffix4); Assert.assertEquals(actual4.getFreeValues().size(), 1); } @@ -381,8 +381,8 @@ public void testBuildOptimizedSuffix() { ConstraintSolver solver = new ConstraintSolver(); OptimizedSymbolicSuffixBuilder builder = new OptimizedSymbolicSuffixBuilder(consts); - SymbolicSuffix suffix12 = builder.distinguishingSuffixFromSDTs(prefix1, sdt1, prefix2, sdt2, Word.fromSymbols(a, a, a), solver); - Map expectedRestr12 = new LinkedHashMap<>(); + SymbolicSuffix suffix12 = builder.distinguishingSuffixFromSDTs(prefix1, sdt1, prefix2, sdt2, builder.unrestrictedSuffix(Word.fromSymbols(a, a, a)), solver); + Map expectedRestr12 = new LinkedHashMap<>(); expectedRestr12.put(s1, new FreshSuffixValue(s1)); expectedRestr12.put(s2, new EqualRestriction(s2, s1)); expectedRestr12.put(s3, new FreshSuffixValue(s3)); @@ -411,8 +411,8 @@ public void testBuildOptimizedSuffix() { new SDTGuard.SDTTrueGuard(s2), new SDT(Map.of( new SDTGuard.SDTTrueGuard(s3), SDTLeaf.REJECTING)))))); - SymbolicSuffix suffix34 = builder.distinguishingSuffixFromSDTs(prefix3, sdt3, prefix4, sdt4, Word.fromSymbols(a, a, a), solver); - Map expectedRestr34 = new LinkedHashMap<>(); + SymbolicSuffix suffix34 = builder.distinguishingSuffixFromSDTs(prefix3, sdt3, prefix4, sdt4, builder.unrestrictedSuffix(Word.fromSymbols(a, a, a)), solver); + Map expectedRestr34 = new LinkedHashMap<>(); expectedRestr34.put(s1, new FreshSuffixValue(s1)); expectedRestr34.put(s2, new UnrestrictedSuffixValue(s2)); expectedRestr34.put(s3, new FreshSuffixValue(s3)); diff --git a/src/test/java/de/learnlib/ralib/theory/TestSuffixValueRestriction.java b/src/test/java/de/learnlib/ralib/theory/TestSuffixValueRestriction.java new file mode 100644 index 000000000..f7d390782 --- /dev/null +++ b/src/test/java/de/learnlib/ralib/theory/TestSuffixValueRestriction.java @@ -0,0 +1,215 @@ +package de.learnlib.ralib.theory; + +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.data.Constants; +import de.learnlib.ralib.data.DataType; +import de.learnlib.ralib.data.DataValue; +import de.learnlib.ralib.data.ParameterValuation; +import de.learnlib.ralib.data.RegisterValuation; +import de.learnlib.ralib.data.SymbolicDataValue.Constant; +import de.learnlib.ralib.data.SymbolicDataValue.Register; +import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; +import de.learnlib.ralib.learning.SymbolicSuffix; +import de.learnlib.ralib.oracles.mto.SLLambdaEqRestrictionBuilder; +import de.learnlib.ralib.smt.ConstraintSolver; +import de.learnlib.ralib.theory.equality.UnmappedEqualityRestriction; +import de.learnlib.ralib.tools.theories.IntegerEqualityTheory; +import de.learnlib.ralib.words.DataWords; +import de.learnlib.ralib.words.InputSymbol; +import de.learnlib.ralib.words.PSymbolInstance; +import net.automatalib.word.Word; + +public class TestSuffixValueRestriction extends RaLibTestSuite { + + private static final DataType T = new DataType("t"); + private static final InputSymbol A = new InputSymbol("α", T); + + @Test + public void testCEAnalysisRestrictions() { + Theory theory = new IntegerEqualityTheory(T); + Map teachers = Map.of(T, theory); + ConstraintSolver solver = new ConstraintSolver(); + + final DataValue dv1 = new DataValue(T, BigDecimal.ONE); + final DataValue dv2 = new DataValue(T, BigDecimal.valueOf(2)); + + Register r1 = new Register(T, 1); + Constant c1 = new Constant(T, 1); + SuffixValue s1 = new SuffixValue(T, 1); + SuffixValue s2 = new SuffixValue(T, 2); + SuffixValue s3 = new SuffixValue(T, 3); + + // fresh + Word prefix1 = Word.fromSymbols( + new PSymbolInstance(A, dv1)); + Word suffix1 = Word.fromSymbols( + new PSymbolInstance(A, dv2)); + Word u1 = Word.fromSymbols( + new PSymbolInstance(A, dv1)); + RegisterValuation val1 = new RegisterValuation(); + RegisterValuation uval1 = new RegisterValuation(); + Constants consts1 = new Constants(); + SLLambdaEqRestrictionBuilder builder1 = new SLLambdaEqRestrictionBuilder(consts1, teachers, solver); + AbstractSuffixValueRestriction restr1 = builder1.constructRestrictedSuffix(prefix1, suffix1, u1, val1, uval1).getRestriction(s1); + Assert.assertEquals(restr1.toString(), "Fresh(s1)"); + + // equal register, constant + Word prefix2 = Word.fromSymbols( + new PSymbolInstance(A, dv1), + new PSymbolInstance(A, dv2)); + Word suffix2 = Word.fromSymbols( + new PSymbolInstance(A, dv1), + new PSymbolInstance(A, dv2)); + Word u2 = prefix2; + RegisterValuation val2 = new RegisterValuation(); + val2.put(r1, dv1); + RegisterValuation uval2 = val2; + Constants consts2 = new Constants(); + consts2.put(c1, dv2); + SLLambdaEqRestrictionBuilder builder2 = new SLLambdaEqRestrictionBuilder(consts2, teachers, solver); + AbstractSuffixValueRestriction restr2Reg = builder2.constructRestrictedSuffix(prefix2, suffix2, u2, val2, uval2).getRestriction(s1); + AbstractSuffixValueRestriction restr2Con = builder2.constructRestrictedSuffix(prefix2, suffix2, u2, val2, uval2).getRestriction(s2); + Assert.assertEquals(restr2Reg.toString(), "(s1 == r1)"); + Assert.assertEquals(restr2Con.toString(), "(s2 == c1)"); + + // equal suffix + Word prefix3 = Word.fromSymbols( + new PSymbolInstance(A, dv1)); + Word suffix3 = Word.fromSymbols( + new PSymbolInstance(A, dv2), + new PSymbolInstance(A, dv2)); + Word u3 = prefix3; + RegisterValuation val3 = new RegisterValuation(); + RegisterValuation uval3 = val3; + Constants consts3 = new Constants(); + SLLambdaEqRestrictionBuilder builder3 = new SLLambdaEqRestrictionBuilder(consts3, teachers, solver); + AbstractSuffixValueRestriction restr3 = builder3.constructRestrictedSuffix(prefix3, suffix3, u3, val3, uval3).getRestriction(s2); + Assert.assertEquals(restr3.toString(), "(s2 == s1)"); + + // equal mapped + Word prefix4 = Word.fromSymbols( + new PSymbolInstance(A, dv1), + new PSymbolInstance(A, dv2)); + Word suffix4 = Word.fromSymbols( + new PSymbolInstance(A, dv1), + new PSymbolInstance(A, dv1)); + Word u4 = prefix4; + RegisterValuation val4 = new RegisterValuation(); + val4.put(r1, dv1); + RegisterValuation uval4 = val4; + Constants consts4 = new Constants(); + consts4.put(c1, dv1); + SLLambdaEqRestrictionBuilder builder4 = new SLLambdaEqRestrictionBuilder(consts4, teachers, solver); + AbstractSuffixValueRestriction restr4_1 = builder4.constructRestrictedSuffix(prefix4, suffix4, u4, val4, uval4).getRestriction(s1); + AbstractSuffixValueRestriction restr4_2 = builder4.constructRestrictedSuffix(prefix4, suffix4, u4, val4, uval4).getRestriction(s2); + Assert.assertEquals(restr4_1.toString(), "(Fresh(s1) OR (s1 == r1) OR (s1 == c1))"); + Assert.assertEquals(restr4_2.toString(), "(Fresh(s2) OR (s2 == s1) OR (s2 == r1) OR (s2 == c1))"); + + // equal unmapped + Word prefix5 = Word.fromSymbols( + new PSymbolInstance(A, dv1), + new PSymbolInstance(A, dv2), + new PSymbolInstance(A, dv2)); + Word suffix5 = Word.fromSymbols( + new PSymbolInstance(A, dv2)); + Word u5 = prefix5; + RegisterValuation val5 = new RegisterValuation(); + val5.put(r1, dv1); + RegisterValuation uval5 = val5; + Constants consts5 = new Constants(); + SLLambdaEqRestrictionBuilder builder5 = new SLLambdaEqRestrictionBuilder(consts5, teachers, solver); + AbstractSuffixValueRestriction restr5 = builder5.constructRestrictedSuffix(prefix5, suffix5, u5, val5, uval5).getRestriction(s1); + Assert.assertEquals(restr5.toString(), "(Unmapped(s1) OR Fresh(s1))"); + + // equal multiple suffix values + Word prefix6 = Word.fromSymbols( + new PSymbolInstance(A, dv1)); + Word suffix6 = Word.fromSymbols( + new PSymbolInstance(A, dv2), + new PSymbolInstance(A, dv2), + new PSymbolInstance(A, dv2)); + Word u6 = prefix6; + RegisterValuation val6 = new RegisterValuation(); + val6.put(r1, dv1); + RegisterValuation uval6 = val6; + Constants consts6 = new Constants(); + SLLambdaEqRestrictionBuilder builder6 = new SLLambdaEqRestrictionBuilder(consts6, teachers, solver); + AbstractSuffixValueRestriction restr6 = builder6.constructRestrictedSuffix(prefix6, suffix6, u6, val6, uval6).getRestriction(s3); + Assert.assertEquals(restr6.toString(), "(s3 == s1)"); + + // repeat occurrence of mapped + Word prefix7 = Word.fromSymbols( + new PSymbolInstance(A, dv1), + new PSymbolInstance(A, dv1)); + Word suffix7 = Word.fromSymbols( + new PSymbolInstance(A, dv1)); + Word u7 = Word.fromSymbols( + new PSymbolInstance(A, dv1), + new PSymbolInstance(A, dv2)); + RegisterValuation val7 = new RegisterValuation(); + val7.put(r1, dv1); + RegisterValuation uval7 = val7; + Constants consts7 = new Constants(); + SLLambdaEqRestrictionBuilder builder7 = new SLLambdaEqRestrictionBuilder(consts7, teachers, solver); + AbstractSuffixValueRestriction restr7 = builder7.constructRestrictedSuffix(prefix7, suffix7, u7, val7, uval7).getRestriction(s1); + Assert.assertEquals(restr7.toString(), "true"); + } + + @Test + public void testConcretize() { + Theory theory = new IntegerEqualityTheory(T); + Map teachers = new LinkedHashMap<>(); + teachers.put(T, theory); + + final DataValue dv1 = new DataValue(T, BigDecimal.ONE); + final DataValue dv2 = new DataValue(T, BigDecimal.valueOf(2)); + final DataValue dv3 = new DataValue(T, BigDecimal.valueOf(3)); + final DataValue dv4 = new DataValue(T, BigDecimal.valueOf(4)); + final DataValue dv5 = new DataValue(T, BigDecimal.valueOf(5)); + + Register r1 = new Register(T, 1); + Constant c1 = new Constant(T, 1); + SuffixValue s1 = new SuffixValue(T, 1); + SuffixValue s2 = new SuffixValue(T, 2); + SuffixValue s3 = new SuffixValue(T, 3); + SuffixValue s4 = new SuffixValue(T, 4); + SuffixValue s5 = new SuffixValue(T, 5); + + Word prefix = Word.fromSymbols( + new PSymbolInstance(A, dv1), + new PSymbolInstance(A, dv2), + new PSymbolInstance(A, dv3), + new PSymbolInstance(A, dv4)); + Word suffix = Word.fromSymbols( + new PSymbolInstance(A, dv1), + new PSymbolInstance(A, dv2), + new PSymbolInstance(A, dv3), + new PSymbolInstance(A, dv5), + new PSymbolInstance(A, dv5)); + RegisterValuation val = new RegisterValuation(); + val.put(r1, dv1); + Constants consts = new Constants(); + consts.put(c1, dv2); + Map restrs = new LinkedHashMap<>(); + restrs.put(s1, SuffixValueRestriction.equalityRestriction(s1, r1)); + restrs.put(s2, SuffixValueRestriction.equalityRestriction(s2, c1)); + restrs.put(s3, new UnmappedEqualityRestriction(s3)); + restrs.put(s4, new FreshSuffixValue(s4)); + restrs.put(s5, SuffixValueRestriction.equalityRestriction(s5, s4)); + SymbolicSuffix symSuff = new SymbolicSuffix(DataWords.actsOf(suffix), restrs); + + SymbolicSuffix symSuff1Conc = SLLambdaEqRestrictionBuilder.concretize(symSuff, + val, + consts, + ParameterValuation.fromPSymbolWord(prefix)); + + Assert.assertEquals(symSuff1Conc.toString(), "((?α[t] ?α[t] ?α[t] ?α[t] ?α[t]))[(s1 == 1[t]), (s2 == 2[t]), (s3 == 3[t]) OR (s3 == 4[t]), Fresh(s4), (s5 == s4)]"); + } +} diff --git a/src/test/java/de/learnlib/ralib/theory/inequality/IneqTheoryRestrictionsTest.java b/src/test/java/de/learnlib/ralib/theory/inequality/IneqTheoryRestrictionsTest.java index 5c884ec06..6b4dc79eb 100644 --- a/src/test/java/de/learnlib/ralib/theory/inequality/IneqTheoryRestrictionsTest.java +++ b/src/test/java/de/learnlib/ralib/theory/inequality/IneqTheoryRestrictionsTest.java @@ -15,7 +15,7 @@ import de.learnlib.ralib.data.util.SymbolicDataValueGenerator.SuffixValueGenerator; import de.learnlib.ralib.learning.SymbolicSuffix; import de.learnlib.ralib.oracles.mto.SymbolicSuffixRestrictionBuilder; -import de.learnlib.ralib.theory.SuffixValueRestriction; +import de.learnlib.ralib.theory.AbstractSuffixValueRestriction; import de.learnlib.ralib.theory.Theory; import de.learnlib.ralib.theory.UnrestrictedSuffixValue; import de.learnlib.ralib.tools.theories.DoubleInequalityTheory; @@ -58,7 +58,7 @@ public void testOptimizationFromConcreteValues() { new PSymbolInstance(A, dv1), new PSymbolInstance(A, dv3)); - Map restr1 = new LinkedHashMap<>(); + Map restr1 = new LinkedHashMap<>(); restr1.put(s1, new UnrestrictedSuffixValue(s1)); restr1.put(s2, new GreaterSuffixValue(s2)); restr1.put(s3, new UnrestrictedSuffixValue(s3)); @@ -76,7 +76,7 @@ public void testOptimizationFromConcreteValues() { new PSymbolInstance(A, dv1), new PSymbolInstance(A, dv0)); - Map restr2 = new LinkedHashMap<>(); + Map restr2 = new LinkedHashMap<>(); restr2.put(s1, new UnrestrictedSuffixValue(s1)); restr2.put(s2, new LesserSuffixValue(s2)); restr2.put(s3, new UnrestrictedSuffixValue(s3)); diff --git a/src/test/java/de/learnlib/ralib/words/TestWords.java b/src/test/java/de/learnlib/ralib/words/TestWords.java index 343c2e00f..bf7b32335 100644 --- a/src/test/java/de/learnlib/ralib/words/TestWords.java +++ b/src/test/java/de/learnlib/ralib/words/TestWords.java @@ -36,8 +36,8 @@ import de.learnlib.ralib.data.DataValue; import de.learnlib.ralib.data.SymbolicDataValue.SuffixValue; import de.learnlib.ralib.learning.SymbolicSuffix; +import de.learnlib.ralib.theory.AbstractSuffixValueRestriction; import de.learnlib.ralib.theory.FreshSuffixValue; -import de.learnlib.ralib.theory.SuffixValueRestriction; import de.learnlib.ralib.theory.UnrestrictedSuffixValue; import de.learnlib.ralib.theory.equality.EqualRestriction; import net.automatalib.word.Word; @@ -80,7 +80,7 @@ public void testSymbolicSuffix1() { logger.log(Level.FINE, "Symbolic Suffix: {0}", sym); Collection symSVs = sym.getDataValues(); SuffixValue[] symSVArr = symSVs.toArray(new SuffixValue[symSVs.size()]); - Map expRestr = new LinkedHashMap<>(); + Map expRestr = new LinkedHashMap<>(); expRestr.put(symSVArr[0], new UnrestrictedSuffixValue(symSVArr[0])); expRestr.put(symSVArr[1], new FreshSuffixValue(symSVArr[1])); expRestr.put(symSVArr[2], new EqualRestriction(symSVArr[2], symSVArr[1]));