diff --git a/.classpath b/.classpath index c040ff969f..20db71ea7d 100644 --- a/.classpath +++ b/.classpath @@ -14,6 +14,15 @@ SPDX-License-Identifier: Apache-2.0 + + + + + + + + + diff --git a/.idea/JavaSMT.iml b/.idea/JavaSMT.iml index fd67bcca0f..74b7673b48 100644 --- a/.idea/JavaSMT.iml +++ b/.idea/JavaSMT.iml @@ -5,7 +5,7 @@ This file is part of JavaSMT, an API wrapper for a collection of SMT solvers: https://github.com/sosy-lab/java-smt -SPDX-FileCopyrightText: 2020 Dirk Beyer +SPDX-FileCopyrightText: 2025 Dirk Beyer SPDX-License-Identifier: Apache-2.0 --> @@ -174,6 +174,17 @@ SPDX-License-Identifier: Apache-2.0 + + + + + + + + + + + @@ -340,6 +351,70 @@ SPDX-License-Identifier: Apache-2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/ant.xml b/.idea/ant.xml index 3e3b7e7277..136ff00e44 100644 --- a/.idea/ant.xml +++ b/.idea/ant.xml @@ -5,7 +5,7 @@ This file is part of JavaSMT, an API wrapper for a collection of SMT solvers: https://github.com/sosy-lab/java-smt -SPDX-FileCopyrightText: 2020 Dirk Beyer +SPDX-FileCopyrightText: 2025 Dirk Beyer SPDX-License-Identifier: Apache-2.0 --> @@ -13,7 +13,10 @@ SPDX-License-Identifier: Apache-2.0 + + + - + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 1214f58eab..f4710dc490 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -5,15 +5,13 @@ This file is part of JavaSMT, an API wrapper for a collection of SMT solvers: https://github.com/sosy-lab/java-smt -SPDX-FileCopyrightText: 2020 Dirk Beyer +SPDX-FileCopyrightText: 2025 Dirk Beyer SPDX-License-Identifier: Apache-2.0 --> - - + \ No newline at end of file diff --git a/lib/ivy.xml b/lib/ivy.xml index 38d7d17d16..ca483f227a 100644 --- a/lib/ivy.xml +++ b/lib/ivy.xml @@ -167,6 +167,28 @@ SPDX-License-Identifier: Apache-2.0 + + + + + + + + + + + + + + diff --git a/src/org/sosy_lab/java_smt/SolverContextFactory.java b/src/org/sosy_lab/java_smt/SolverContextFactory.java index 576285424d..792050b3a6 100644 --- a/src/org/sosy_lab/java_smt/SolverContextFactory.java +++ b/src/org/sosy_lab/java_smt/SolverContextFactory.java @@ -292,17 +292,23 @@ private SolverContext generateContext0(Solvers solverToCreate) case PRINCESS: return PrincessSolverContext.create( - config, shutdownNotifier, logfile, (int) randomSeed, nonLinearArithmetic); + config, shutdownNotifier, logfile, (int) randomSeed, nonLinearArithmetic, logger); case YICES2: - return Yices2SolverContext.create(nonLinearArithmetic, shutdownNotifier, loader); + return Yices2SolverContext.create(nonLinearArithmetic, shutdownNotifier, loader, logger); case BOOLECTOR: return BoolectorSolverContext.create(config, shutdownNotifier, logfile, randomSeed, loader); case BITWUZLA: return BitwuzlaSolverContext.create( - config, shutdownNotifier, logfile, randomSeed, floatingPointRoundingMode, loader); + config, + shutdownNotifier, + logfile, + randomSeed, + floatingPointRoundingMode, + loader, + logger); default: throw new AssertionError("no solver selected"); diff --git a/src/org/sosy_lab/java_smt/api/QuantifiedFormulaManager.java b/src/org/sosy_lab/java_smt/api/QuantifiedFormulaManager.java index eed6b6dfcf..a07b92fbf7 100644 --- a/src/org/sosy_lab/java_smt/api/QuantifiedFormulaManager.java +++ b/src/org/sosy_lab/java_smt/api/QuantifiedFormulaManager.java @@ -23,10 +23,60 @@ enum Quantifier { EXISTS } + enum QuantifierEliminationMethod { + /** Use the solver's built-in quantifier elimination method. */ + NATIVE, + + /** Use the solver's built-in quantifier elimination method. */ + NATIVE_FALLBACK_ON_FAILURE, + + /** Use the solver's built-in quantifier elimination method. */ + NATIVE_FALLBACK_WITH_WARNING_ON_FAILURE, + + /** Whether the solver should enable quantifier eliminiation via UltimateEliminator. */ + ULTIMATE_ELIMINATOR, + + /** + * Whether the solver should enable quantifier eliminiation via UltimateEliminator and fall back + * to the native quantifier elimination if UltimateEliminator's elimination method fails. The + * solver will not log a warning in this case. + */ + ULTIMATE_ELIMINATOR_FALLBACK_ON_FAILURE, + + /** + * Whether the solver should enable quantifier eliminiation via UltimateEliminator and back to + * the native quantifier elimination if UltimateEliminator's elimination method fails. The + * solver will log a warning in this case. + */ + ULTIMATE_ELIMINATOR_FALLBACK_WITH_WARNING_ON_FAILURE, + } + + enum QuantifierCreationMethod { + /** + * Whether the solver should eliminate the quantifier via UltimateEliminator before inserting it + * to the native quantified formula. + */ + ULTIMATE_ELIMINATOR_BEFORE_FORMULA_CREATION, + + /** + * Whether the solver should eliminate the quantifier via UltimateEliminator before inserting it + * to the native quantified formula and fall back to the native quantifier creation if + * UltimateEliminator fails. The solver will not log a warning in this case. + */ + ULTIMATE_ELIMINATOR_BEFORE_FORMULA_CREATION_FALLBACK, + + /** + * Whether the solver should eliminate the quantifier via UltimateEliminator before inserting it + * to the native quantified formula and fall back to the native quantifier creation if + * UltimateEliminator fails. The solver will log a warning in this case. + */ + ULTIMATE_ELIMINATOR_BEFORE_FORMULA_CREATION_FALLBACK_WARN_ON_FAILURE + } + /** - * @return An existentially quantified formula. * @param pVariables The variables that will get bound (variables) by the quantification. * @param pBody The {@link BooleanFormula}} within that the binding will be performed. + * @return An existentially quantified formula. * @throws IllegalArgumentException If the list {@code pVariables} is empty. */ default BooleanFormula exists(List pVariables, BooleanFormula pBody) { @@ -34,9 +84,9 @@ default BooleanFormula exists(List pVariables, BooleanFormula } /** - * @return A universally quantified formula. * @param pVariables The variables that will get bound (variables) by the quantification. * @param pBody The {@link BooleanFormula}} within that the binding will be performed. + * @return A universally quantified formula. * @throws IllegalArgumentException If the list {@code pVariables} is empty. */ default BooleanFormula forall(List pVariables, BooleanFormula pBody) { @@ -54,15 +104,67 @@ default BooleanFormula exists(Formula quantifiedArg, BooleanFormula pBody) { } /** - * @return A quantified formula + * @param pVariables The variables that will get bound (variables) by the quantification. + * @param pBody The {@link BooleanFormula}} within that the binding will be performed. + * @param pMethod The {@link QuantifierCreationMethod}} to decide on the creation method. + * @return An existentially quantified formula. + * @throws IllegalArgumentException If the list {@code pVariables} is empty. + */ + default BooleanFormula exists( + List pVariables, BooleanFormula pBody, QuantifierCreationMethod pMethod) { + return mkQuantifier(Quantifier.EXISTS, pVariables, pBody, pMethod); + } + + /** + * @param pVariables The variables that will get bound (variables) by the quantification. + * @param pBody The {@link BooleanFormula}} within that the binding will be performed. + * @param pMethod The {@link QuantifierCreationMethod}} to decide on the creation method. + * @return A universally quantified formula. + * @throws IllegalArgumentException If the list {@code pVariables} is empty. + */ + default BooleanFormula forall( + List pVariables, BooleanFormula pBody, QuantifierCreationMethod pMethod) { + return mkQuantifier(Quantifier.FORALL, pVariables, pBody, pMethod); + } + + /** Syntax sugar, see {@link #forall(List, BooleanFormula)}. */ + default BooleanFormula forall( + Formula quantifiedArg, BooleanFormula pBody, QuantifierCreationMethod pMethod) { + return forall(ImmutableList.of(quantifiedArg), pBody, pMethod); + } + + /** Syntax sugar, see {@link #exists(List, BooleanFormula)}. */ + default BooleanFormula exists( + Formula quantifiedArg, BooleanFormula pBody, QuantifierCreationMethod pMethod) { + return exists(ImmutableList.of(quantifiedArg), pBody, pMethod); + } + + /** * @param q Quantifier type * @param pVariables The variables that will get bound (variables) by the quantification. * @param pBody The {@link BooleanFormula}} within that the binding will be performed. + * @return A quantified formula * @throws IllegalArgumentException If the list {@code pVariables} is empty. */ BooleanFormula mkQuantifier( Quantifier q, List pVariables, BooleanFormula pBody); + /** + * Create a formula with a specific quantifier elimination method. + * + * @param q Quantifier type + * @param pVariables The variables that will get bound (variables) by the quantification. + * @param pBody The {@link BooleanFormula}} within that the binding will be performed. + * @param pMethod The {@link QuantifierCreationMethod}} to decide on the creation method. + * @return A boolean formula where the quantifier may already have been eliminated. + * @throws IllegalArgumentException If the list {@code pVariables} is empty. + */ + BooleanFormula mkQuantifier( + Quantifier q, + List pVariables, + BooleanFormula pBody, + QuantifierCreationMethod pMethod); + /** * Eliminate the quantifiers for a given formula. If this is not possible, it will return the * input formula. Note that CVC4 only supports this for LIA and LRA. @@ -72,4 +174,17 @@ BooleanFormula mkQuantifier( */ BooleanFormula eliminateQuantifiers(BooleanFormula pF) throws InterruptedException, SolverException; + + /** + * Eliminate the quantifiers for a given formula using either UltimateEliminator or the native + * method. If this is not possible, depending on the given method it will fallback to the + * alternative method or throw an Exception. Note that CVC4 only supports this for LIA and LRA in + * the native elimination. + * + * @param pF Formula with quantifiers. + * @param pMethod enum value for method to be used for eliminating quantifiers. + * @return New formula without quantifiers. + */ + BooleanFormula eliminateQuantifiers(BooleanFormula pF, QuantifierEliminationMethod pMethod) + throws InterruptedException, SolverException; } diff --git a/src/org/sosy_lab/java_smt/api/visitors/BooleanFormulaTransformationVisitor.java b/src/org/sosy_lab/java_smt/api/visitors/BooleanFormulaTransformationVisitor.java index c6a30984a0..b61f377cdb 100644 --- a/src/org/sosy_lab/java_smt/api/visitors/BooleanFormulaTransformationVisitor.java +++ b/src/org/sosy_lab/java_smt/api/visitors/BooleanFormulaTransformationVisitor.java @@ -8,6 +8,7 @@ package org.sosy_lab.java_smt.api.visitors; +import java.io.IOException; import java.util.List; import org.sosy_lab.java_smt.api.BooleanFormula; import org.sosy_lab.java_smt.api.BooleanFormulaManager; @@ -93,7 +94,8 @@ public BooleanFormula visitQuantifier( Quantifier quantifier, BooleanFormula quantifiedAST, List boundVars, - BooleanFormula processedBody) { + BooleanFormula processedBody) + throws IOException { return mgr.getQuantifiedFormulaManager().mkQuantifier(quantifier, boundVars, processedBody); } } diff --git a/src/org/sosy_lab/java_smt/api/visitors/BooleanFormulaVisitor.java b/src/org/sosy_lab/java_smt/api/visitors/BooleanFormulaVisitor.java index 42ef713934..178d8bc059 100644 --- a/src/org/sosy_lab/java_smt/api/visitors/BooleanFormulaVisitor.java +++ b/src/org/sosy_lab/java_smt/api/visitors/BooleanFormulaVisitor.java @@ -8,6 +8,7 @@ package org.sosy_lab.java_smt.api.visitors; +import java.io.IOException; import java.util.List; import org.sosy_lab.java_smt.api.BooleanFormula; import org.sosy_lab.java_smt.api.BooleanFormulaManager; @@ -108,7 +109,8 @@ R visitQuantifier( Quantifier quantifier, BooleanFormula quantifiedAST, List boundVars, - BooleanFormula body); + BooleanFormula body) + throws IOException; /** * Visit an SMT atom. An atom can be a theory expression, constant, or a variable. diff --git a/src/org/sosy_lab/java_smt/api/visitors/FormulaTransformationVisitor.java b/src/org/sosy_lab/java_smt/api/visitors/FormulaTransformationVisitor.java index 81e24a0cef..b99dfb22f6 100644 --- a/src/org/sosy_lab/java_smt/api/visitors/FormulaTransformationVisitor.java +++ b/src/org/sosy_lab/java_smt/api/visitors/FormulaTransformationVisitor.java @@ -8,6 +8,7 @@ package org.sosy_lab.java_smt.api.visitors; +import java.io.IOException; import java.util.List; import org.sosy_lab.java_smt.api.BooleanFormula; import org.sosy_lab.java_smt.api.Formula; @@ -69,7 +70,8 @@ public BooleanFormula visitQuantifier( BooleanFormula f, Quantifier quantifier, List boundVariables, - BooleanFormula transformedBody) { + BooleanFormula transformedBody) + throws IOException { return fmgr.getQuantifiedFormulaManager() .mkQuantifier(quantifier, boundVariables, transformedBody); } diff --git a/src/org/sosy_lab/java_smt/api/visitors/FormulaVisitor.java b/src/org/sosy_lab/java_smt/api/visitors/FormulaVisitor.java index 665cc40249..8777906682 100644 --- a/src/org/sosy_lab/java_smt/api/visitors/FormulaVisitor.java +++ b/src/org/sosy_lab/java_smt/api/visitors/FormulaVisitor.java @@ -8,6 +8,7 @@ package org.sosy_lab.java_smt.api.visitors; +import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.List; @@ -78,5 +79,6 @@ public interface FormulaVisitor { * @param body Body of the quantifier. */ R visitQuantifier( - BooleanFormula f, Quantifier quantifier, List boundVariables, BooleanFormula body); + BooleanFormula f, Quantifier quantifier, List boundVariables, BooleanFormula body) + throws IOException; } diff --git a/src/org/sosy_lab/java_smt/basicimpl/AbstractBooleanFormulaManager.java b/src/org/sosy_lab/java_smt/basicimpl/AbstractBooleanFormulaManager.java index 2a6f552249..3bc325ab16 100644 --- a/src/org/sosy_lab/java_smt/basicimpl/AbstractBooleanFormulaManager.java +++ b/src/org/sosy_lab/java_smt/basicimpl/AbstractBooleanFormulaManager.java @@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; +import java.io.IOException; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collection; @@ -279,7 +280,11 @@ public final T ifThenElse(BooleanFormula pBits, T f1, T f2) @Override public R visit(BooleanFormula pFormula, BooleanFormulaVisitor visitor) { - return formulaCreator.visit(pFormula, new DelegatingFormulaVisitor<>(visitor)); + try { + return formulaCreator.visit(pFormula, new DelegatingFormulaVisitor<>(visitor)); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Override @@ -403,10 +408,8 @@ public R visitFunction( @Override public R visitQuantifier( - BooleanFormula f, - Quantifier quantifier, - List boundVariables, - BooleanFormula body) { + BooleanFormula f, Quantifier quantifier, List boundVariables, BooleanFormula body) + throws IOException { return delegate.visitQuantifier(quantifier, f, boundVariables, body); } @@ -421,7 +424,11 @@ public Set toConjunctionArgs(BooleanFormula f, boolean flatten) if (flatten) { return asFuncRecursive(f, conjunctionFinder); } - return formulaCreator.visit(f, conjunctionFinder); + try { + return formulaCreator.visit(f, conjunctionFinder); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Override @@ -429,7 +436,11 @@ public Set toDisjunctionArgs(BooleanFormula f, boolean flatten) if (flatten) { return asFuncRecursive(f, disjunctionFinder); } - return formulaCreator.visit(f, disjunctionFinder); + try { + return formulaCreator.visit(f, disjunctionFinder); + } catch (IOException e) { + throw new RuntimeException(e); + } } /** Optimized non-recursive flattening implementation. */ @@ -442,7 +453,16 @@ private Set asFuncRecursive( while (!toProcess.isEmpty()) { BooleanFormula s = toProcess.pop(); - Set out = cache.computeIfAbsent(s, ss -> formulaCreator.visit(ss, visitor)); + Set out = + cache.computeIfAbsent( + s, + ss -> { + try { + return formulaCreator.visit(ss, visitor); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); if (out.size() == 1 && s.equals(out.iterator().next())) { output.add(s); } diff --git a/src/org/sosy_lab/java_smt/basicimpl/AbstractFormulaManager.java b/src/org/sosy_lab/java_smt/basicimpl/AbstractFormulaManager.java index f497fd7ebc..232678262c 100644 --- a/src/org/sosy_lab/java_smt/basicimpl/AbstractFormulaManager.java +++ b/src/org/sosy_lab/java_smt/basicimpl/AbstractFormulaManager.java @@ -440,7 +440,11 @@ protected TFormulaInfo simplify(TFormulaInfo f) throws InterruptedException { @Override public R visit(Formula input, FormulaVisitor visitor) { - return formulaCreator.visit(input, visitor); + try { + return formulaCreator.visit(input, visitor); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Override diff --git a/src/org/sosy_lab/java_smt/basicimpl/AbstractQuantifiedFormulaManager.java b/src/org/sosy_lab/java_smt/basicimpl/AbstractQuantifiedFormulaManager.java index ede1863996..1ac3ac2f5a 100644 --- a/src/org/sosy_lab/java_smt/basicimpl/AbstractQuantifiedFormulaManager.java +++ b/src/org/sosy_lab/java_smt/basicimpl/AbstractQuantifiedFormulaManager.java @@ -9,20 +9,44 @@ package org.sosy_lab.java_smt.basicimpl; import com.google.common.collect.Lists; +import de.uni_freiburg.informatik.ultimate.logic.Term; +import java.io.IOException; +import java.util.ArrayList; import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.logging.Level; +import org.sosy_lab.common.log.LogManager; import org.sosy_lab.java_smt.api.BooleanFormula; import org.sosy_lab.java_smt.api.Formula; +import org.sosy_lab.java_smt.api.FormulaManager; +import org.sosy_lab.java_smt.api.FormulaType; import org.sosy_lab.java_smt.api.QuantifiedFormulaManager; import org.sosy_lab.java_smt.api.SolverException; +import org.sosy_lab.java_smt.api.visitors.DefaultFormulaVisitor; +import org.sosy_lab.java_smt.api.visitors.TraversalProcess; +import org.sosy_lab.java_smt.test.ultimate.UltimateEliminatorWrapper; @SuppressWarnings("ClassTypeParameterName") public abstract class AbstractQuantifiedFormulaManager extends AbstractBaseFormulaManager implements QuantifiedFormulaManager { + /* + For parsing and dumping formula between UltimateEliminator and the native solver. + */ + private Optional> fmgr; + /* + for logging warnings. + */ + private final LogManager logger; + + private final UltimateEliminatorWrapper ultimateEliminatorWrapper; protected AbstractQuantifiedFormulaManager( - FormulaCreator pCreator) { + FormulaCreator pCreator, LogManager pLogger) { super(pCreator); + ultimateEliminatorWrapper = new UltimateEliminatorWrapper(pLogger); + logger = pLogger; } private BooleanFormula wrap(TFormulaInfo formulaInfo) { @@ -35,6 +59,85 @@ public BooleanFormula eliminateQuantifiers(BooleanFormula pF) return wrap(eliminateQuantifiers(extractInfo(pF))); } + @Override + public BooleanFormula eliminateQuantifiers(BooleanFormula pF, QuantifierEliminationMethod pMethod) + throws InterruptedException, SolverException { + assert pMethod != null : "Quantifier elimination method cannot be null"; + switch (pMethod) { + case ULTIMATE_ELIMINATOR: + try { + return wrap(eliminateQuantifiersUltimateEliminator(pF)); + } catch (UnsupportedOperationException | IllegalArgumentException e) { + logger.logException( + Level.SEVERE, + e, + "UltimateEliminator failed. Please adjust the " + + "option if you want to use the native quantifier elimination"); + + throw e; + } + + case ULTIMATE_ELIMINATOR_FALLBACK_ON_FAILURE: + try { + return wrap(eliminateQuantifiersUltimateEliminator(pF)); + } catch (UnsupportedOperationException | IllegalArgumentException e) { + return wrap(eliminateQuantifiers(extractInfo(pF))); + } + + case ULTIMATE_ELIMINATOR_FALLBACK_WITH_WARNING_ON_FAILURE: + try { + return wrap(eliminateQuantifiersUltimateEliminator(pF)); + } catch (UnsupportedOperationException | IllegalArgumentException e) { + logger.logException( + Level.WARNING, + e, + "UltimateEliminator failed. " + "Reverting to native " + "quantifier elimination"); + return wrap(eliminateQuantifiers(extractInfo(pF))); + } + + case NATIVE: + try { + return wrap(eliminateQuantifiers(extractInfo(pF))); + } catch (Exception e1) { + logger.logException( + Level.SEVERE, + e1, + "Native quantifier elimination failed. Please adjust the " + + "option if you want to use the UltimateEliminator quantifier elimination"); + throw e1; + } + + case NATIVE_FALLBACK_ON_FAILURE: + try { + return wrap(eliminateQuantifiers(extractInfo(pF))); + } catch (UnsupportedOperationException | IllegalArgumentException e) { + return wrap(eliminateQuantifiersUltimateEliminator(pF)); + } + + case NATIVE_FALLBACK_WITH_WARNING_ON_FAILURE: + try { + return wrap(eliminateQuantifiers(extractInfo(pF))); + } catch (UnsupportedOperationException | IllegalArgumentException e) { + logger.logException( + Level.WARNING, + e, + "Default quantifier elimination failed. Switching to UltimateEliminator"); + return wrap(eliminateQuantifiersUltimateEliminator(pF)); + } + + default: + break; + } + return pF; + } + + protected TFormulaInfo eliminateQuantifiersUltimateEliminator(BooleanFormula pExtractInfo) { + String form = dumpFormula(pExtractInfo); + Term formula = ultimateEliminatorWrapper.parse(form); + formula = ultimateEliminatorWrapper.simplify(formula); + return extractInfo(parseFormula(ultimateEliminatorWrapper.dumpFormula(formula).toString())); + } + protected abstract TFormulaInfo eliminateQuantifiers(TFormulaInfo pExtractInfo) throws SolverException, InterruptedException; @@ -45,6 +148,155 @@ public BooleanFormula mkQuantifier( mkQuantifier(q, Lists.transform(pVariables, this::extractInfo), extractInfo(pBody))); } + @Override + public BooleanFormula mkQuantifier( + Quantifier q, + List pVariables, + BooleanFormula pBody, + QuantifierCreationMethod pMethod) { + switch (pMethod) { + case ULTIMATE_ELIMINATOR_BEFORE_FORMULA_CREATION: + try { + return eliminateQuantifierBeforeMakingFormula(q, pVariables, pBody); + } catch (UnsupportedOperationException e) { + logger.logException(Level.WARNING, e, "External quantifier creation failed."); + throw e; + } + + case ULTIMATE_ELIMINATOR_BEFORE_FORMULA_CREATION_FALLBACK: + try { + return eliminateQuantifierBeforeMakingFormula(q, pVariables, pBody); + } catch (UnsupportedOperationException e) { + return wrap( + mkQuantifier(q, Lists.transform(pVariables, this::extractInfo), extractInfo(pBody))); + } + + case ULTIMATE_ELIMINATOR_BEFORE_FORMULA_CREATION_FALLBACK_WARN_ON_FAILURE: + try { + return eliminateQuantifierBeforeMakingFormula(q, pVariables, pBody); + } catch (UnsupportedOperationException e) { + logger.logException( + Level.WARNING, e, "External quantifier creation failed. Falling back to native"); + return wrap( + mkQuantifier(q, Lists.transform(pVariables, this::extractInfo), extractInfo(pBody))); + } + + default: + try { + return wrap( + mkQuantifier(q, Lists.transform(pVariables, this::extractInfo), extractInfo(pBody))); + } catch (Exception e) { + logger.logException( + Level.SEVERE, + e, + "Native quantifier creation failed. Please adjust the " + + "option if you want to use the UltimateEliminator quantifier creation"); + throw e; + } + } + } + public abstract TFormulaInfo mkQuantifier( Quantifier q, List vars, TFormulaInfo body); + + protected FormulaManager getFmgr() { + return fmgr.orElseThrow(); + } + + public void setFmgr(AbstractFormulaManager pFmgr) { + fmgr = Optional.of(pFmgr); + } + + private BooleanFormula eliminateQuantifierBeforeMakingFormula( + Quantifier q, List pVariables, BooleanFormula pBody) { + List boundVariablesNameList = new ArrayList<>(); + List boundVariablesSortList = new ArrayList<>(); + + String form = dumpFormula(pBody); + Term ultimateBody = ultimateEliminatorWrapper.parse(form); + for (Formula var : pVariables) { + enrichBoundVariablesNameAndSortList(var, boundVariablesNameList, boundVariablesSortList); + } + String ultimateFormula = + buildSmtlib2Formula(q, boundVariablesNameList, boundVariablesSortList, ultimateBody); + + Term parsedResult = ultimateEliminatorWrapper.parse(ultimateFormula); + Term resultFormula = ultimateEliminatorWrapper.simplify(parsedResult); + + BooleanFormula result = + parseFormula(ultimateEliminatorWrapper.dumpFormula(resultFormula).toString()); + return result; + } + + private String mapTypeToUltimateSort(String pSort) { + return pSort + .replace("<", " ") + .replace(">", "") + .replace(",", " ") + .replace("Integer", "Int") + .replace("Boolean", "Bool"); + } + + private String buildSmtlib2Formula( + Quantifier pQ, + List pBoundVariablesNameList, + List pBoundVariablesSortList, + Term pUltimateBody) { + StringBuilder sb = new StringBuilder(); + sb.append("(assert (").append(pQ.toString().toLowerCase(Locale.getDefault())).append(" ("); + if (!pBoundVariablesNameList.isEmpty()) { + for (int i = 0; i < pBoundVariablesNameList.size(); i++) { + sb.append("(") + .append(pBoundVariablesNameList.get(i)) + .append(" ") + .append(pBoundVariablesSortList.get(i)) + .append(")"); + } + } + sb.append(") "); + sb.append(pUltimateBody); + sb.append(" ))"); + return sb.toString(); + } + + private String getSortAsString(Formula pF) { + if (fmgr.orElseThrow().getFormulaType(pF) instanceof FormulaType.ArrayFormulaType) { + return "(" + fmgr.orElseThrow().getFormulaType(pF) + ")"; + } else { + return fmgr.orElseThrow().getFormulaType(pF).toString(); + } + } + + private void enrichBoundVariablesNameAndSortList( + Formula pF, List nameList, List sortList) { + try { + formulaCreator.visit( + pF, + new DefaultFormulaVisitor() { + + @Override + protected TraversalProcess visitDefault(Formula f) { + return TraversalProcess.CONTINUE; + } + + @Override + public TraversalProcess visitFreeVariable(Formula f, String name) { + nameList.add(name); + String sort = getSortAsString(f); + sortList.add(mapTypeToUltimateSort(sort)); + return TraversalProcess.CONTINUE; + } + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + protected String dumpFormula(BooleanFormula bf) { + return fmgr.orElseThrow().dumpFormula(bf).toString(); + } + + protected BooleanFormula parseFormula(String pFormula) { + return fmgr.orElseThrow().parse(pFormula); + } } diff --git a/src/org/sosy_lab/java_smt/basicimpl/FormulaCreator.java b/src/org/sosy_lab/java_smt/basicimpl/FormulaCreator.java index e5dd09e4ad..3ad55e62a6 100644 --- a/src/org/sosy_lab/java_smt/basicimpl/FormulaCreator.java +++ b/src/org/sosy_lab/java_smt/basicimpl/FormulaCreator.java @@ -15,6 +15,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.io.IOException; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; @@ -292,14 +293,15 @@ protected FormulaType getFormulaType(T formula) { * @see org.sosy_lab.java_smt.api.FormulaManager#visit */ @CanIgnoreReturnValue - public R visit(Formula input, FormulaVisitor visitor) { + public R visit(Formula input, FormulaVisitor visitor) throws IOException { return visit(visitor, input, extractInfo(input)); } /** * @see org.sosy_lab.java_smt.api.FormulaManager#visit */ - public abstract R visit(FormulaVisitor visitor, Formula formula, TFormulaInfo f); + public abstract R visit(FormulaVisitor visitor, Formula formula, TFormulaInfo f) + throws IOException; protected List extractInfo(List input) { return Lists.transform(input, this::extractInfo); @@ -324,7 +326,12 @@ public void visitRecursively( while (!recVisitor.isQueueEmpty()) { Formula tt = recVisitor.pop(); if (shouldProcess.test(tt)) { - TraversalProcess process = visit(tt, recVisitor); + TraversalProcess process = null; + try { + process = visit(tt, recVisitor); + } catch (IOException e) { + throw new RuntimeException(e); + } if (process == TraversalProcess.ABORT) { return; } @@ -356,7 +363,11 @@ public T transformRecursively( } if (shouldProcess.test(tt)) { - visit(tt, recVisitor); + try { + visit(tt, recVisitor); + } catch (IOException e) { + throw new RuntimeException(e); + } } else { pCache.put(tt, tt); } diff --git a/src/org/sosy_lab/java_smt/basicimpl/FormulaTransformationVisitorImpl.java b/src/org/sosy_lab/java_smt/basicimpl/FormulaTransformationVisitorImpl.java index d1d176f2e0..5d39f6ab12 100644 --- a/src/org/sosy_lab/java_smt/basicimpl/FormulaTransformationVisitorImpl.java +++ b/src/org/sosy_lab/java_smt/basicimpl/FormulaTransformationVisitorImpl.java @@ -9,6 +9,7 @@ package org.sosy_lab.java_smt.basicimpl; import com.google.common.base.Preconditions; +import java.io.IOException; import java.util.ArrayList; import java.util.Deque; import java.util.List; @@ -95,7 +96,8 @@ public Void visitFunction( @Override public Void visitQuantifier( - BooleanFormula f, Quantifier quantifier, List boundVariables, BooleanFormula body) { + BooleanFormula f, Quantifier quantifier, List boundVariables, BooleanFormula body) + throws IOException { Preconditions.checkNotNull(f); Preconditions.checkNotNull(quantifier); Preconditions.checkNotNull(boundVariables); diff --git a/src/org/sosy_lab/java_smt/basicimpl/RecursiveFormulaVisitorImpl.java b/src/org/sosy_lab/java_smt/basicimpl/RecursiveFormulaVisitorImpl.java index b08a296149..2624ad6e76 100644 --- a/src/org/sosy_lab/java_smt/basicimpl/RecursiveFormulaVisitorImpl.java +++ b/src/org/sosy_lab/java_smt/basicimpl/RecursiveFormulaVisitorImpl.java @@ -11,6 +11,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.ImmutableList; +import java.io.IOException; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; @@ -84,7 +85,8 @@ public TraversalProcess visitFunction( @Override public TraversalProcess visitQuantifier( - BooleanFormula pF, Quantifier pQuantifier, List boundVars, BooleanFormula pBody) { + BooleanFormula pF, Quantifier pQuantifier, List boundVars, BooleanFormula pBody) + throws IOException { TraversalProcess result = delegate.visitQuantifier(pF, pQuantifier, boundVars, pBody); addToQueueIfNecessary(result, ImmutableList.of(pBody)); return result; diff --git a/src/org/sosy_lab/java_smt/delegate/debugging/DebuggingQuantifiedFormulaManager.java b/src/org/sosy_lab/java_smt/delegate/debugging/DebuggingQuantifiedFormulaManager.java index 134eafd124..7ca4a950d3 100644 --- a/src/org/sosy_lab/java_smt/delegate/debugging/DebuggingQuantifiedFormulaManager.java +++ b/src/org/sosy_lab/java_smt/delegate/debugging/DebuggingQuantifiedFormulaManager.java @@ -39,6 +39,22 @@ public BooleanFormula mkQuantifier( return result; } + @Override + public BooleanFormula mkQuantifier( + Quantifier q, + List pVariables, + BooleanFormula pBody, + QuantifierCreationMethod pMethod) { + debugging.assertThreadLocal(); + for (Formula t : pVariables) { + debugging.assertFormulaInContext(t); + } + debugging.assertFormulaInContext(pBody); + BooleanFormula result = delegate.mkQuantifier(q, pVariables, pBody, pMethod); + debugging.addFormulaTerm(result); + return result; + } + @Override public BooleanFormula eliminateQuantifiers(BooleanFormula pF) throws InterruptedException, SolverException { @@ -48,4 +64,14 @@ public BooleanFormula eliminateQuantifiers(BooleanFormula pF) debugging.addFormulaTerm(result); return result; } + + @Override + public BooleanFormula eliminateQuantifiers(BooleanFormula pF, QuantifierEliminationMethod pMethod) + throws InterruptedException, SolverException { + debugging.assertThreadLocal(); + debugging.assertFormulaInContext(pF); + BooleanFormula result = delegate.eliminateQuantifiers(pF, pMethod); + debugging.addFormulaTerm(result); + return result; + } } diff --git a/src/org/sosy_lab/java_smt/delegate/statistics/StatisticsQuantifiedFormulaManager.java b/src/org/sosy_lab/java_smt/delegate/statistics/StatisticsQuantifiedFormulaManager.java index fa02bf8e49..9b3632eced 100644 --- a/src/org/sosy_lab/java_smt/delegate/statistics/StatisticsQuantifiedFormulaManager.java +++ b/src/org/sosy_lab/java_smt/delegate/statistics/StatisticsQuantifiedFormulaManager.java @@ -33,10 +33,25 @@ public BooleanFormula mkQuantifier( return delegate.mkQuantifier(pQ, pVariables, pBody); } + @Override + public BooleanFormula mkQuantifier( + Quantifier pQ, + List pVariables, + BooleanFormula pBody, + QuantifierCreationMethod pMethod) { + return delegate.mkQuantifier(pQ, pVariables, pBody, pMethod); + } + @Override public BooleanFormula eliminateQuantifiers(BooleanFormula pF) throws InterruptedException, SolverException { stats.quantifierOperations.getAndIncrement(); return delegate.eliminateQuantifiers(pF); } + + @Override + public BooleanFormula eliminateQuantifiers(BooleanFormula pF, QuantifierEliminationMethod pMethod) + throws InterruptedException, SolverException { + return delegate.eliminateQuantifiers(pF, pMethod); + } } diff --git a/src/org/sosy_lab/java_smt/delegate/synchronize/SynchronizedQuantifiedFormulaManager.java b/src/org/sosy_lab/java_smt/delegate/synchronize/SynchronizedQuantifiedFormulaManager.java index 144040537f..8df35124f8 100644 --- a/src/org/sosy_lab/java_smt/delegate/synchronize/SynchronizedQuantifiedFormulaManager.java +++ b/src/org/sosy_lab/java_smt/delegate/synchronize/SynchronizedQuantifiedFormulaManager.java @@ -35,6 +35,17 @@ public BooleanFormula mkQuantifier( } } + @Override + public BooleanFormula mkQuantifier( + Quantifier pQ, + List pVariables, + BooleanFormula pBody, + QuantifierCreationMethod pMethod) { + synchronized (sync) { + return delegate.mkQuantifier(pQ, pVariables, pBody, pMethod); + } + } + @Override public BooleanFormula eliminateQuantifiers(BooleanFormula pF) throws InterruptedException, SolverException { @@ -42,4 +53,12 @@ public BooleanFormula eliminateQuantifiers(BooleanFormula pF) return delegate.eliminateQuantifiers(pF); } } + + @Override + public BooleanFormula eliminateQuantifiers(BooleanFormula pF, QuantifierEliminationMethod pMethod) + throws InterruptedException, SolverException { + synchronized (sync) { + return delegate.eliminateQuantifiers(pF, pMethod); + } + } } diff --git a/src/org/sosy_lab/java_smt/solvers/bitwuzla/BitwuzlaFormulaCreator.java b/src/org/sosy_lab/java_smt/solvers/bitwuzla/BitwuzlaFormulaCreator.java index d231b49305..ac529839a4 100644 --- a/src/org/sosy_lab/java_smt/solvers/bitwuzla/BitwuzlaFormulaCreator.java +++ b/src/org/sosy_lab/java_smt/solvers/bitwuzla/BitwuzlaFormulaCreator.java @@ -15,6 +15,7 @@ import com.google.common.collect.HashBasedTable; import com.google.common.collect.ImmutableList; import com.google.common.collect.Table; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayDeque; import java.util.ArrayList; @@ -395,7 +396,7 @@ public FormulaType getFormulaType(Term formula) { @Override public R visit(FormulaVisitor visitor, Formula formula, Term f) - throws UnsupportedOperationException { + throws UnsupportedOperationException, IOException { Kind kind = f.kind(); if (f.is_value()) { return visitor.visitConstant(formula, convertValue(f)); diff --git a/src/org/sosy_lab/java_smt/solvers/bitwuzla/BitwuzlaQuantifiedFormulaManager.java b/src/org/sosy_lab/java_smt/solvers/bitwuzla/BitwuzlaQuantifiedFormulaManager.java index 21831de58b..41e0002557 100644 --- a/src/org/sosy_lab/java_smt/solvers/bitwuzla/BitwuzlaQuantifiedFormulaManager.java +++ b/src/org/sosy_lab/java_smt/solvers/bitwuzla/BitwuzlaQuantifiedFormulaManager.java @@ -11,6 +11,7 @@ import static com.google.common.base.Preconditions.checkArgument; import java.util.List; +import org.sosy_lab.common.log.LogManager; import org.sosy_lab.java_smt.api.SolverException; import org.sosy_lab.java_smt.basicimpl.AbstractQuantifiedFormulaManager; import org.sosy_lab.java_smt.solvers.bitwuzla.api.Kind; @@ -25,8 +26,8 @@ public class BitwuzlaQuantifiedFormulaManager extends AbstractQuantifiedFormulaManager { private final TermManager termManager; - protected BitwuzlaQuantifiedFormulaManager(BitwuzlaFormulaCreator pCreator) { - super(pCreator); + protected BitwuzlaQuantifiedFormulaManager(BitwuzlaFormulaCreator pCreator, LogManager pLogger) { + super(pCreator, pLogger); termManager = pCreator.getTermManager(); } diff --git a/src/org/sosy_lab/java_smt/solvers/bitwuzla/BitwuzlaSolverContext.java b/src/org/sosy_lab/java_smt/solvers/bitwuzla/BitwuzlaSolverContext.java index 20c0a2a389..4790627def 100644 --- a/src/org/sosy_lab/java_smt/solvers/bitwuzla/BitwuzlaSolverContext.java +++ b/src/org/sosy_lab/java_smt/solvers/bitwuzla/BitwuzlaSolverContext.java @@ -79,6 +79,7 @@ import org.sosy_lab.common.configuration.Configuration; import org.sosy_lab.common.configuration.InvalidConfigurationException; import org.sosy_lab.common.io.PathCounterTemplate; +import org.sosy_lab.common.log.LogManager; import org.sosy_lab.java_smt.SolverContextFactory.Solvers; import org.sosy_lab.java_smt.api.FloatingPointRoundingMode; import org.sosy_lab.java_smt.api.InterpolatingProverEnvironment; @@ -157,7 +158,8 @@ public static BitwuzlaSolverContext create( @Nullable PathCounterTemplate solverLogfile, long randomSeed, FloatingPointRoundingMode pFloatingPointRoundingMode, - Consumer pLoader) + Consumer pLoader, + LogManager pLogger) throws InvalidConfigurationException { loadLibrary(pLoader); @@ -170,7 +172,7 @@ public static BitwuzlaSolverContext create( BitwuzlaBitvectorFormulaManager bitvectorTheory = new BitwuzlaBitvectorFormulaManager(creator, booleanTheory); BitwuzlaQuantifiedFormulaManager quantifierTheory = - new BitwuzlaQuantifiedFormulaManager(creator); + new BitwuzlaQuantifiedFormulaManager(creator, pLogger); BitwuzlaFloatingPointManager floatingPointTheory = new BitwuzlaFloatingPointManager(creator, pFloatingPointRoundingMode); BitwuzlaArrayFormulaManager arrayTheory = new BitwuzlaArrayFormulaManager(creator); @@ -185,6 +187,8 @@ public static BitwuzlaSolverContext create( arrayTheory, solverOptions); + quantifierTheory.setFmgr(manager); + return new BitwuzlaSolverContext(manager, creator, pShutdownNotifier, solverOptions); } diff --git a/src/org/sosy_lab/java_smt/solvers/boolector/BoolectorQuantifiedFormulaManager.java b/src/org/sosy_lab/java_smt/solvers/boolector/BoolectorQuantifiedFormulaManager.java new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/org/sosy_lab/java_smt/solvers/cvc4/CVC4FormulaCreator.java b/src/org/sosy_lab/java_smt/solvers/cvc4/CVC4FormulaCreator.java index 5e983d6c3e..c5985d235e 100644 --- a/src/org/sosy_lab/java_smt/solvers/cvc4/CVC4FormulaCreator.java +++ b/src/org/sosy_lab/java_smt/solvers/cvc4/CVC4FormulaCreator.java @@ -29,6 +29,7 @@ import edu.stanford.CVC4.Type; import edu.stanford.CVC4.vectorExpr; import edu.stanford.CVC4.vectorType; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; @@ -297,7 +298,7 @@ private static String getName(Expr e) { } @Override - public R visit(FormulaVisitor visitor, Formula formula, final Expr f) { + public R visit(FormulaVisitor visitor, Formula formula, final Expr f) throws IOException { checkState(!f.isNull()); Type type = f.getType(); diff --git a/src/org/sosy_lab/java_smt/solvers/cvc4/CVC4QuantifiedFormulaManager.java b/src/org/sosy_lab/java_smt/solvers/cvc4/CVC4QuantifiedFormulaManager.java index 0387af5f17..d80f9bea02 100644 --- a/src/org/sosy_lab/java_smt/solvers/cvc4/CVC4QuantifiedFormulaManager.java +++ b/src/org/sosy_lab/java_smt/solvers/cvc4/CVC4QuantifiedFormulaManager.java @@ -15,6 +15,7 @@ import edu.stanford.CVC4.Type; import edu.stanford.CVC4.vectorExpr; import java.util.List; +import org.sosy_lab.common.log.LogManager; import org.sosy_lab.java_smt.api.SolverException; import org.sosy_lab.java_smt.basicimpl.AbstractQuantifiedFormulaManager; import org.sosy_lab.java_smt.basicimpl.FormulaCreator; @@ -25,8 +26,8 @@ public class CVC4QuantifiedFormulaManager private final ExprManager exprManager; protected CVC4QuantifiedFormulaManager( - FormulaCreator pFormulaCreator) { - super(pFormulaCreator); + FormulaCreator pFormulaCreator, LogManager pLogger) { + super(pFormulaCreator, pLogger); exprManager = pFormulaCreator.getEnv(); } diff --git a/src/org/sosy_lab/java_smt/solvers/cvc4/CVC4SolverContext.java b/src/org/sosy_lab/java_smt/solvers/cvc4/CVC4SolverContext.java index b5bd086812..389c75572b 100644 --- a/src/org/sosy_lab/java_smt/solvers/cvc4/CVC4SolverContext.java +++ b/src/org/sosy_lab/java_smt/solvers/cvc4/CVC4SolverContext.java @@ -87,7 +87,7 @@ public static SolverContext create( // throw new AssertionError("CVC4 was built without support for FloatingPoint theory"); } - CVC4QuantifiedFormulaManager qfTheory = new CVC4QuantifiedFormulaManager(creator); + CVC4QuantifiedFormulaManager qfTheory = new CVC4QuantifiedFormulaManager(creator, pLogger); CVC4ArrayFormulaManager arrayTheory = new CVC4ArrayFormulaManager(creator); CVC4SLFormulaManager slTheory = new CVC4SLFormulaManager(creator); @@ -106,6 +106,8 @@ public static SolverContext create( slTheory, strTheory); + qfTheory.setFmgr(manager); + return new CVC4SolverContext(creator, manager, pShutdownNotifier, randomSeed); } diff --git a/src/org/sosy_lab/java_smt/solvers/cvc5/CVC5FormulaCreator.java b/src/org/sosy_lab/java_smt/solvers/cvc5/CVC5FormulaCreator.java index 44a75476de..0dab87d9cb 100644 --- a/src/org/sosy_lab/java_smt/solvers/cvc5/CVC5FormulaCreator.java +++ b/src/org/sosy_lab/java_smt/solvers/cvc5/CVC5FormulaCreator.java @@ -30,6 +30,7 @@ import io.github.cvc5.Solver; import io.github.cvc5.Sort; import io.github.cvc5.Term; +import java.io.IOException; import java.lang.reflect.Array; import java.math.BigInteger; import java.util.ArrayList; @@ -377,7 +378,7 @@ private String getName(Term e) { } @Override - public R visit(FormulaVisitor visitor, Formula formula, final Term f) { + public R visit(FormulaVisitor visitor, Formula formula, final Term f) throws IOException { checkState(!f.isNull()); Sort sort = f.getSort(); try { @@ -505,6 +506,8 @@ public R visit(FormulaVisitor visitor, Formula formula, final Term f) { } } catch (CVC5ApiException e) { throw new IllegalArgumentException("Failure visiting the Term '" + f + "'.", e); + } catch (IOException e) { + throw new IOException(e); } } diff --git a/src/org/sosy_lab/java_smt/solvers/cvc5/CVC5QuantifiedFormulaManager.java b/src/org/sosy_lab/java_smt/solvers/cvc5/CVC5QuantifiedFormulaManager.java index 8230abf702..6705751a6f 100644 --- a/src/org/sosy_lab/java_smt/solvers/cvc5/CVC5QuantifiedFormulaManager.java +++ b/src/org/sosy_lab/java_smt/solvers/cvc5/CVC5QuantifiedFormulaManager.java @@ -14,6 +14,7 @@ import io.github.cvc5.Term; import java.util.ArrayList; import java.util.List; +import org.sosy_lab.common.log.LogManager; import org.sosy_lab.java_smt.api.SolverException; import org.sosy_lab.java_smt.basicimpl.AbstractQuantifiedFormulaManager; import org.sosy_lab.java_smt.basicimpl.FormulaCreator; @@ -23,9 +24,9 @@ public class CVC5QuantifiedFormulaManager private final Solver solver; - protected CVC5QuantifiedFormulaManager(FormulaCreator pFormulaCreator) { - super(pFormulaCreator); - + protected CVC5QuantifiedFormulaManager( + FormulaCreator pFormulaCreator, LogManager pLogger) { + super(pFormulaCreator, pLogger); solver = pFormulaCreator.getEnv(); } diff --git a/src/org/sosy_lab/java_smt/solvers/cvc5/CVC5SolverContext.java b/src/org/sosy_lab/java_smt/solvers/cvc5/CVC5SolverContext.java index 7e31515969..8981ea8cbb 100644 --- a/src/org/sosy_lab/java_smt/solvers/cvc5/CVC5SolverContext.java +++ b/src/org/sosy_lab/java_smt/solvers/cvc5/CVC5SolverContext.java @@ -140,7 +140,7 @@ public static SolverContext create( new CVC5BitvectorFormulaManager(pCreator, booleanTheory); CVC5FloatingPointFormulaManager fpTheory = new CVC5FloatingPointFormulaManager(pCreator, pFloatingPointRoundingMode); - CVC5QuantifiedFormulaManager qfTheory = new CVC5QuantifiedFormulaManager(pCreator); + CVC5QuantifiedFormulaManager qfTheory = new CVC5QuantifiedFormulaManager(pCreator, pLogger); CVC5ArrayFormulaManager arrayTheory = new CVC5ArrayFormulaManager(pCreator); CVC5SLFormulaManager slTheory = new CVC5SLFormulaManager(pCreator); CVC5StringFormulaManager strTheory = new CVC5StringFormulaManager(pCreator); @@ -159,6 +159,7 @@ public static SolverContext create( slTheory, strTheory, enumTheory); + qfTheory.setFmgr(manager); return new CVC5SolverContext( pCreator, manager, pShutdownNotifier, newSolver, randomSeed, settings); diff --git a/src/org/sosy_lab/java_smt/solvers/mathsat5/Mathsat5FormulaCreator.java b/src/org/sosy_lab/java_smt/solvers/mathsat5/Mathsat5FormulaCreator.java index d63f8b403e..fc5deeea02 100644 --- a/src/org/sosy_lab/java_smt/solvers/mathsat5/Mathsat5FormulaCreator.java +++ b/src/org/sosy_lab/java_smt/solvers/mathsat5/Mathsat5FormulaCreator.java @@ -99,6 +99,7 @@ import static org.sosy_lab.java_smt.solvers.mathsat5.Mathsat5NativeApi.msat_is_rational_type; import static org.sosy_lab.java_smt.solvers.mathsat5.Mathsat5NativeApi.msat_make_constant; import static org.sosy_lab.java_smt.solvers.mathsat5.Mathsat5NativeApi.msat_make_term; +import static org.sosy_lab.java_smt.solvers.mathsat5.Mathsat5NativeApi.msat_make_variable; import static org.sosy_lab.java_smt.solvers.mathsat5.Mathsat5NativeApi.msat_term_arity; import static org.sosy_lab.java_smt.solvers.mathsat5.Mathsat5NativeApi.msat_term_get_arg; import static org.sosy_lab.java_smt.solvers.mathsat5.Mathsat5NativeApi.msat_term_get_decl; @@ -633,4 +634,19 @@ public Long callFunctionImpl(Long declaration, List args) { protected Long getBooleanVarDeclarationImpl(Long pLong) { return msat_term_get_decl(pLong); } + + /** + * Makes a bound copy of a variable for use in quantifier. Note that all occurrences of the free + * var have to be substituted by the bound once it exists. + * + * @param pSolver Solver in use. + * @param pVar Variable you want a bound copy of. + * @return Bound Variable + */ + public long makeBoundCopy(Long pSolver, Long pVar) { + Long sort = msat_term_get_type(pVar); + String name = getName(pVar); + long boundCopy = msat_make_variable(pSolver, name, sort); + return boundCopy; + } } diff --git a/src/org/sosy_lab/java_smt/solvers/mathsat5/Mathsat5FormulaManager.java b/src/org/sosy_lab/java_smt/solvers/mathsat5/Mathsat5FormulaManager.java index 7273c044e7..4a382d5636 100644 --- a/src/org/sosy_lab/java_smt/solvers/mathsat5/Mathsat5FormulaManager.java +++ b/src/org/sosy_lab/java_smt/solvers/mathsat5/Mathsat5FormulaManager.java @@ -13,6 +13,7 @@ import static org.sosy_lab.java_smt.solvers.mathsat5.Mathsat5NativeApi.msat_make_copy_from; import static org.sosy_lab.java_smt.solvers.mathsat5.Mathsat5NativeApi.msat_simplify; import static org.sosy_lab.java_smt.solvers.mathsat5.Mathsat5NativeApi.msat_to_smtlib2; +import static org.sosy_lab.java_smt.solvers.mathsat5.Mathsat5NativeApi.msat_to_smtlib2_ext; import com.google.common.collect.Collections2; import com.google.common.primitives.Longs; @@ -35,6 +36,7 @@ final class Mathsat5FormulaManager extends AbstractFormulaManager T substitute( final T f, final Map fromToMapping) { diff --git a/src/org/sosy_lab/java_smt/solvers/mathsat5/Mathsat5QuantifiedFormulaManager.java b/src/org/sosy_lab/java_smt/solvers/mathsat5/Mathsat5QuantifiedFormulaManager.java new file mode 100644 index 0000000000..10edb763c4 --- /dev/null +++ b/src/org/sosy_lab/java_smt/solvers/mathsat5/Mathsat5QuantifiedFormulaManager.java @@ -0,0 +1,78 @@ +/* + * This file is part of JavaSMT, + * an API wrapper for a collection of SMT solvers: + * https://github.com/sosy-lab/java-smt + * + * SPDX-FileCopyrightText: 2024 Dirk Beyer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.sosy_lab.java_smt.solvers.mathsat5; + +import static com.google.common.base.Preconditions.checkArgument; +import static org.sosy_lab.java_smt.solvers.mathsat5.Mathsat5NativeApi.msat_apply_substitution; +import static org.sosy_lab.java_smt.solvers.mathsat5.Mathsat5NativeApi.msat_make_exists; +import static org.sosy_lab.java_smt.solvers.mathsat5.Mathsat5NativeApi.msat_make_forall; + +import java.util.List; +import org.sosy_lab.common.log.LogManager; +import org.sosy_lab.java_smt.api.BooleanFormula; +import org.sosy_lab.java_smt.api.SolverException; +import org.sosy_lab.java_smt.basicimpl.AbstractQuantifiedFormulaManager; +import org.sosy_lab.java_smt.basicimpl.FormulaCreator; + +public class Mathsat5QuantifiedFormulaManager + extends AbstractQuantifiedFormulaManager { + private final Long solver; + + protected Mathsat5QuantifiedFormulaManager( + FormulaCreator pFormulaCreator, LogManager pLogger) { + super(pFormulaCreator, pLogger); + + solver = pFormulaCreator.getEnv(); + } + + @Override + protected Long eliminateQuantifiers(Long input) throws SolverException, InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public Long mkQuantifier(Quantifier pQ, List pVars, Long pBody) { + // Note: Mathsat supports this only for printing SMTLib2, not solving! + checkArgument(!pVars.isEmpty(), "List of quantified variables can not be empty"); + + long quantifiedFormula; + + long[] changeFrom = new long[pVars.size()]; + long[] changeTo = new long[pVars.size()]; + int idx = 0; + for (Long var : pVars) { + changeFrom[idx] = var; + changeTo[idx] = ((Mathsat5FormulaCreator) formulaCreator).makeBoundCopy(solver, var); + idx++; + } + + long substBody = msat_apply_substitution(solver, pBody, 1, changeFrom, changeTo); + + if (pQ == Quantifier.EXISTS) { + quantifiedFormula = msat_make_exists(solver, changeTo[0], substBody); + for (int i = 1; i < changeTo.length; i++) { + quantifiedFormula = msat_make_exists(solver, changeTo[i], substBody); + } + } else { + quantifiedFormula = msat_make_forall(solver, changeTo[0], substBody); + for (int i = 1; i < changeTo.length; i++) { + quantifiedFormula = msat_make_forall(solver, changeTo[i], substBody); + } + } + return quantifiedFormula; + } + + @Override + protected String dumpFormula(BooleanFormula bf) { + return ((Mathsat5FormulaManager) getFmgr()) + .dumpFormulaImplExt(extractInfo(bf), "qFormulaNameMathsat5"); + } +} diff --git a/src/org/sosy_lab/java_smt/solvers/mathsat5/Mathsat5SolverContext.java b/src/org/sosy_lab/java_smt/solvers/mathsat5/Mathsat5SolverContext.java index 8ca17b8a2a..7f948a2077 100644 --- a/src/org/sosy_lab/java_smt/solvers/mathsat5/Mathsat5SolverContext.java +++ b/src/org/sosy_lab/java_smt/solvers/mathsat5/Mathsat5SolverContext.java @@ -196,6 +196,8 @@ public static Mathsat5SolverContext create( Mathsat5ArrayFormulaManager arrayTheory = new Mathsat5ArrayFormulaManager(creator); Mathsat5EnumerationFormulaManager enumerationTheory = new Mathsat5EnumerationFormulaManager(creator); + Mathsat5QuantifiedFormulaManager quantifiedTheory = + new Mathsat5QuantifiedFormulaManager(creator, logger); Mathsat5FormulaManager manager = new Mathsat5FormulaManager( creator, @@ -205,8 +207,10 @@ public static Mathsat5SolverContext create( rationalTheory, bitvectorTheory, floatingPointTheory, + quantifiedTheory, arrayTheory, enumerationTheory); + quantifiedTheory.setFmgr(manager); return new Mathsat5SolverContext( logger, msatConf, settings, randomSeed, pShutdownNotifier, manager, creator); } diff --git a/src/org/sosy_lab/java_smt/solvers/princess/PrincessFormulaCreator.java b/src/org/sosy_lab/java_smt/solvers/princess/PrincessFormulaCreator.java index dfcfd77a52..869756c3ab 100644 --- a/src/org/sosy_lab/java_smt/solvers/princess/PrincessFormulaCreator.java +++ b/src/org/sosy_lab/java_smt/solvers/princess/PrincessFormulaCreator.java @@ -52,6 +52,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Table; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; @@ -383,7 +384,8 @@ private static boolean isRatFrac(IFunApp pExpr) { } @Override - public R visit(FormulaVisitor visitor, final Formula f, final IExpression input) { + public R visit(FormulaVisitor visitor, final Formula f, final IExpression input) + throws IOException { if (input instanceof IIntLit) { IdealInt value = ((IIntLit) input).value(); return visitor.visitConstant(f, value.bigIntValue()); diff --git a/src/org/sosy_lab/java_smt/solvers/princess/PrincessQuantifiedFormulaManager.java b/src/org/sosy_lab/java_smt/solvers/princess/PrincessQuantifiedFormulaManager.java index 813d30d273..bcb1591ac9 100644 --- a/src/org/sosy_lab/java_smt/solvers/princess/PrincessQuantifiedFormulaManager.java +++ b/src/org/sosy_lab/java_smt/solvers/princess/PrincessQuantifiedFormulaManager.java @@ -21,6 +21,7 @@ import ap.types.Sort; import java.util.ArrayList; import java.util.List; +import org.sosy_lab.common.log.LogManager; import org.sosy_lab.java_smt.api.SolverException; import org.sosy_lab.java_smt.basicimpl.AbstractQuantifiedFormulaManager; import org.sosy_lab.java_smt.basicimpl.FormulaCreator; @@ -32,9 +33,9 @@ class PrincessQuantifiedFormulaManager private final PrincessEnvironment env; PrincessQuantifiedFormulaManager( - FormulaCreator - pCreator) { - super(pCreator); + FormulaCreator pCreator, + LogManager pLogger) { + super(pCreator, pLogger); env = getFormulaCreator().getEnv(); } diff --git a/src/org/sosy_lab/java_smt/solvers/princess/PrincessSolverContext.java b/src/org/sosy_lab/java_smt/solvers/princess/PrincessSolverContext.java index bb7b14d6f8..d74fc66283 100644 --- a/src/org/sosy_lab/java_smt/solvers/princess/PrincessSolverContext.java +++ b/src/org/sosy_lab/java_smt/solvers/princess/PrincessSolverContext.java @@ -15,6 +15,7 @@ import org.sosy_lab.common.configuration.Configuration; import org.sosy_lab.common.configuration.InvalidConfigurationException; import org.sosy_lab.common.io.PathCounterTemplate; +import org.sosy_lab.common.log.LogManager; import org.sosy_lab.java_smt.SolverContextFactory.Solvers; import org.sosy_lab.java_smt.api.InterpolatingProverEnvironment; import org.sosy_lab.java_smt.api.OptimizationProverEnvironment; @@ -39,7 +40,8 @@ public static SolverContext create( ShutdownNotifier pShutdownNotifier, @Nullable PathCounterTemplate pLogfileTemplate, int pRandomSeed, - NonLinearArithmetic pNonLinearArithmetic) + NonLinearArithmetic pNonLinearArithmetic, + LogManager pLogger) throws InvalidConfigurationException { PrincessEnvironment env = new PrincessEnvironment(config, pLogfileTemplate, pShutdownNotifier, pRandomSeed); @@ -56,7 +58,7 @@ public static SolverContext create( new PrincessBitvectorFormulaManager(creator, booleanTheory); PrincessArrayFormulaManager arrayTheory = new PrincessArrayFormulaManager(creator); PrincessQuantifiedFormulaManager quantifierTheory = - new PrincessQuantifiedFormulaManager(creator); + new PrincessQuantifiedFormulaManager(creator, pLogger); PrincessStringFormulaManager stringTheory = new PrincessStringFormulaManager(creator); PrincessFormulaManager manager = new PrincessFormulaManager( @@ -69,6 +71,7 @@ public static SolverContext create( arrayTheory, quantifierTheory, stringTheory); + quantifierTheory.setFmgr(manager); return new PrincessSolverContext(manager, creator); } diff --git a/src/org/sosy_lab/java_smt/solvers/smtinterpol/FormulaCollectionScript.java b/src/org/sosy_lab/java_smt/solvers/smtinterpol/FormulaCollectionScript.java index a0a9a9c0ef..6ec6b81ace 100644 --- a/src/org/sosy_lab/java_smt/solvers/smtinterpol/FormulaCollectionScript.java +++ b/src/org/sosy_lab/java_smt/solvers/smtinterpol/FormulaCollectionScript.java @@ -44,7 +44,7 @@ * changed only by declaring and defining terms, sorts etc., so these terms can be used in that * environment afterwards. */ -class FormulaCollectionScript implements Script { +public class FormulaCollectionScript implements Script { private final Theory theory; private final Script script; diff --git a/src/org/sosy_lab/java_smt/solvers/smtinterpol/LogProxyForwarder.java b/src/org/sosy_lab/java_smt/solvers/smtinterpol/LogProxyForwarder.java index bc1572fd23..a8a9837ab0 100644 --- a/src/org/sosy_lab/java_smt/solvers/smtinterpol/LogProxyForwarder.java +++ b/src/org/sosy_lab/java_smt/solvers/smtinterpol/LogProxyForwarder.java @@ -16,7 +16,7 @@ /** Implementation of {@link LogProxy} that forwards to {@link LogManager}. */ @SuppressWarnings("FormatStringAnnotation") -final class LogProxyForwarder implements LogProxy { +public final class LogProxyForwarder implements LogProxy { private static final Level LEVEL_FATAL = Level.SEVERE; diff --git a/src/org/sosy_lab/java_smt/solvers/smtinterpol/UltimateEliminatorParser.java b/src/org/sosy_lab/java_smt/solvers/smtinterpol/UltimateEliminatorParser.java new file mode 100644 index 0000000000..a19ce39eb6 --- /dev/null +++ b/src/org/sosy_lab/java_smt/solvers/smtinterpol/UltimateEliminatorParser.java @@ -0,0 +1,136 @@ +/* + * This file is part of JavaSMT, + * an API wrapper for a collection of SMT solvers: + * https://github.com/sosy-lab/java-smt + * + * SPDX-FileCopyrightText: 2025 Dirk Beyer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.sosy_lab.java_smt.solvers.smtinterpol; + +import static com.google.common.collect.Iterables.getOnlyElement; + +import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.smt.UltimateEliminator; +import de.uni_freiburg.informatik.ultimate.logic.AnnotatedTerm; +import de.uni_freiburg.informatik.ultimate.logic.ApplicationTerm; +import de.uni_freiburg.informatik.ultimate.logic.FormulaLet; +import de.uni_freiburg.informatik.ultimate.logic.FormulaUnLet; +import de.uni_freiburg.informatik.ultimate.logic.FunctionSymbol; +import de.uni_freiburg.informatik.ultimate.logic.PrintTerm; +import de.uni_freiburg.informatik.ultimate.logic.SMTLIBException; +import de.uni_freiburg.informatik.ultimate.logic.Sort; +import de.uni_freiburg.informatik.ultimate.logic.Term; +import de.uni_freiburg.informatik.ultimate.smtinterpol.LogProxy; +import de.uni_freiburg.informatik.ultimate.smtinterpol.option.OptionMap; +import de.uni_freiburg.informatik.ultimate.smtinterpol.smtlib2.ParseEnvironment; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Deque; +import java.util.HashSet; +import java.util.Set; +import org.sosy_lab.common.Appender; +import org.sosy_lab.common.Appenders; +import org.sosy_lab.common.log.LogManager; + +public final class UltimateEliminatorParser { + + private UltimateEliminatorParser() { + // Utility class + } + + public static Appender dumpFormula(final Term formula) { + return new Appenders.AbstractAppender() { + + @Override + public void appendTo(Appendable out) throws IOException { + Set seen = new HashSet<>(); + Set declaredFunctions = new HashSet<>(); + Deque todo = new ArrayDeque<>(); + PrintTerm termPrinter = new PrintTerm(); + + todo.addLast(formula); + + while (!todo.isEmpty()) { + Term t = todo.removeLast(); + while (t instanceof AnnotatedTerm) { + t = ((AnnotatedTerm) t).getSubterm(); + } + if (!(t instanceof ApplicationTerm) || !seen.add(t)) { + continue; + } + + ApplicationTerm term = (ApplicationTerm) t; + Collections.addAll(todo, term.getParameters()); + + FunctionSymbol func = term.getFunction(); + if (func.isIntern()) { + continue; + } + + if (func.getDefinition() == null) { + if (declaredFunctions.add(func)) { + out.append("(declare-fun "); + out.append(PrintTerm.quoteIdentifier(func.getName())); + out.append(" ("); + int counter = 0; + for (Sort paramSort : func.getParameterSorts()) { + termPrinter.append(out, paramSort); + + if (++counter < func.getParameterSorts().length) { + out.append(' '); + } + } + out.append(") "); + termPrinter.append(out, func.getReturnSort()); + out.append(")\n"); + } + } else { + // We would have to print a (define-fun) command and + // recursively traverse into func.getDefinition() (in post-order!). + // However, such terms should actually not occur. + throw new IllegalArgumentException("Terms with definition are unsupported."); + } + } + out.append("(assert "); + + // This is the same as t.toString() does, + // but directly uses the Appendable for better performance + // and less memory consumption. + Term letted = new FormulaLet().let(formula); + termPrinter.append(out, letted); + + out.append(")"); + } + }; + } + + public static Term parseImpl(String pS, LogManager pLogger, UltimateEliminator ue) + throws IllegalArgumentException { + FormulaCollectionScript parseScript = + new FormulaCollectionScript(ue.getScriptIterator().next(), ue.getTheory()); + LogProxy logProxy = new LogProxyForwarder(pLogger.withComponentName("UltimateEliminator")); + final ParseEnvironment parseEnv = + new ParseEnvironment(parseScript, new OptionMap(logProxy, true)) { + @Override + public void printError(String pMessage) { + throw new SMTLIBException(pMessage); + } + + @Override + public void printSuccess() {} + }; + + try { + parseEnv.parseStream(new StringReader(pS), ""); + } catch (SMTLIBException nested) { + throw new IllegalArgumentException(nested); + } + + Term term = getOnlyElement(parseScript.getAssertedTerms()); + return new FormulaUnLet().unlet(term); + } +} diff --git a/src/org/sosy_lab/java_smt/solvers/yices2/Yices2FormulaCreator.java b/src/org/sosy_lab/java_smt/solvers/yices2/Yices2FormulaCreator.java index 3d336c0d80..c7261e1692 100644 --- a/src/org/sosy_lab/java_smt/solvers/yices2/Yices2FormulaCreator.java +++ b/src/org/sosy_lab/java_smt/solvers/yices2/Yices2FormulaCreator.java @@ -35,6 +35,7 @@ import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.YICES_DIVIDES_ATOM; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.YICES_EQ_TERM; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.YICES_FLOOR; +import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.YICES_FORALL_TERM; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.YICES_IDIV; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.YICES_IMOD; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.YICES_IS_INT_ATOM; @@ -81,6 +82,7 @@ import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_eq; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_floor; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_function_type; +import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_get_term_by_name; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_get_term_name; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_idiv; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_imod; @@ -89,7 +91,8 @@ import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_is_int_atom; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_ite; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_mul; -import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_named_variable; +import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_new_uninterpreted_term; +import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_new_variable; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_not; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_or; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_parse_rational; @@ -100,6 +103,7 @@ import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_proj_index; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_rational_const_value; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_real_type; +import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_set_term_name; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_sum; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_sum_component; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_term_bitsize; @@ -120,10 +124,13 @@ import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.Collections2; +import com.google.common.collect.HashBasedTable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import com.google.common.collect.Table; import com.google.common.primitives.Ints; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -135,6 +142,7 @@ import org.sosy_lab.java_smt.api.FormulaType; import org.sosy_lab.java_smt.api.FormulaType.FloatingPointType; import org.sosy_lab.java_smt.api.FunctionDeclarationKind; +import org.sosy_lab.java_smt.api.QuantifiedFormulaManager.Quantifier; import org.sosy_lab.java_smt.api.visitors.FormulaVisitor; import org.sosy_lab.java_smt.basicimpl.FormulaCreator; import org.sosy_lab.java_smt.basicimpl.FunctionDeclarationImpl; @@ -153,6 +161,12 @@ public class Yices2FormulaCreator extends FormulaCreator formulaCache = HashBasedTable.create(); + protected Yices2FormulaCreator() { super(null, yices_bool_type(), yices_int_type(), yices_real_type(), null, null); } @@ -174,7 +188,11 @@ public Integer getArrayType(Integer pIndexType, Integer pElementType) { @Override public Integer makeVariable(Integer pType, String pVarName) { - return yices_named_variable(pType, pVarName); + return createNamedVariable(pType, pVarName); + } + + public Integer makeVariable(Integer pTerm) { + return makeVariable(yices_type_of_term(pTerm), yices_get_term_name(pTerm)); } @Override @@ -246,8 +264,62 @@ public FormulaType getFormulaType(Integer pFormula) { yices_type_to_string(yices_type_of_term(pFormula)), yices_term_to_string(pFormula))); } + /** Creates a named, free variable. Might retrieve it from the cache if created prior. */ + protected int createNamedVariable(int type, String name) { + Integer maybeFormula = formulaCache.get(name, type); + if (maybeFormula != null) { + return maybeFormula; + } + if (formulaCache.containsRow(name)) { + throw new IllegalArgumentException( + "Symbol " + name + " already used for a variable of type " + formulaCache.row(name)); + } + int var = yices_new_uninterpreted_term(type); + // Names in Yices2 behave like a stack. The last variable named is retrieved when asking for + // a term with a specific name. Since we substitute free vars with bound for quantifiers, + // this sometimes mixes them up, hence we track them ourselves. + yices_set_term_name(var, name); + formulaCache.put(name, type, var); + return var; + } + + protected int createBoundVariableFromFreeVariable(int unboundVar) { + int type = yices_type_of_term(unboundVar); + String name = yices_get_term_name(unboundVar); + + int termFromName = yices_get_term_by_name(name); + if (termFromName != -1) { + int termFromNameType = yices_type_of_term(termFromName); + if (type == termFromNameType) { + int constructor = yices_term_constructor(termFromName); + if (constructor == YICES_VARIABLE) { + // Already a bound var + return termFromName; + } + } else { + throw new IllegalArgumentException( + String.format( + "Can't create variable with name '%s' and type '%s' " + + "as it would omit a variable with type '%s'", + name, yices_type_to_string(type), yices_type_to_string(termFromNameType))); + } + } + // reset term name binding + // TODO: add yices_remove_term_name(); + int bound = yices_new_variable(type); + // Names in Yices2 behave like a stack. The last variable named is retrieved when asking for + // a term with a specific name. Since we substitute free vars with bound for quantifiers, + // this sometimes mixes them up, hence we track them ourselves. + // This overrides the naming, but the old is cached. + // Meaning that if we remove the new name, the old term gets its name back. + // Since we just want to retrieve the same var for the quantifier we are currently building, + // this is fine. + yices_set_term_name(bound, name); + return bound; + } + @Override - public R visit(FormulaVisitor pVisitor, Formula pFormula, Integer pF) { + public R visit(FormulaVisitor pVisitor, Formula pFormula, Integer pF) throws IOException { int constructor = yices_term_constructor(pF); switch (constructor) { case YICES_BOOL_CONST: @@ -258,6 +330,22 @@ public R visit(FormulaVisitor pVisitor, Formula pFormula, Integer pF) { return pVisitor.visitConstant(pFormula, convertValue(pF, pF)); case YICES_UNINTERPRETED_TERM: return pVisitor.visitFreeVariable(pFormula, yices_get_term_name(pF)); + case YICES_VARIABLE: + return pVisitor.visitBoundVariable(pFormula, 0); + case YICES_FORALL_TERM: + int children = yices_term_num_children(pF); + ImmutableList.Builder boundVars = ImmutableList.builder(); + for (int i = 0; i < children - 1; i++) { + int boundVar = yices_term_child(pF, i); + boundVars.add(encapsulateWithTypeOf(boundVar)); + } + BooleanFormula body = encapsulateBoolean(yices_term_child(pF, children - 1)); + Quantifier quant = Quantifier.EXISTS; + if (yices_term_to_string(pF).startsWith("(forall")) { + // TODO: this is stupid, but i could not find a way of discerning between the quantifiers + quant = Quantifier.FORALL; + } + return pVisitor.visitQuantifier((BooleanFormula) pFormula, quant, boundVars.build(), body); default: return visitFunctionApplication(pVisitor, pFormula, pF, constructor); } @@ -696,7 +784,7 @@ public Integer declareUFImpl(String pName, Integer pReturnType, List pA } else { yicesFuncType = yices_function_type(size, argTypeArray, pReturnType); } - int uf = yices_named_variable(yicesFuncType, pName); + int uf = createNamedVariable(yicesFuncType, pName); return uf; } diff --git a/src/org/sosy_lab/java_smt/solvers/yices2/Yices2FormulaManager.java b/src/org/sosy_lab/java_smt/solvers/yices2/Yices2FormulaManager.java index 8408917411..20a3971f03 100644 --- a/src/org/sosy_lab/java_smt/solvers/yices2/Yices2FormulaManager.java +++ b/src/org/sosy_lab/java_smt/solvers/yices2/Yices2FormulaManager.java @@ -45,7 +45,8 @@ protected Yices2FormulaManager( Yices2BooleanFormulaManager pBooleanManager, Yices2IntegerFormulaManager pIntegerManager, Yices2RationalFormulaManager pRationalManager, - Yices2BitvectorFormulaManager pBitvectorManager) { + Yices2BitvectorFormulaManager pBitvectorManager, + Yices2QuantifiedFormulaManager pQuantifiedFormulaManager) { super( pFormulaCreator, pFunctionManager, @@ -54,7 +55,7 @@ protected Yices2FormulaManager( pRationalManager, pBitvectorManager, null, - null, + pQuantifiedFormulaManager, null, null, null, diff --git a/src/org/sosy_lab/java_smt/solvers/yices2/Yices2NativeApi.java b/src/org/sosy_lab/java_smt/solvers/yices2/Yices2NativeApi.java index bf0f33e870..f30ee156b7 100644 --- a/src/org/sosy_lab/java_smt/solvers/yices2/Yices2NativeApi.java +++ b/src/org/sosy_lab/java_smt/solvers/yices2/Yices2NativeApi.java @@ -783,25 +783,6 @@ public static String yices_model_to_string(long m) { public static native int yices_subst_term(int size, int[] from, int[] to, int t); - public static int yices_named_variable(int type, String name) { - int termFromName = yices_get_term_by_name(name); - if (termFromName != -1) { - int termFromNameType = yices_type_of_term(termFromName); - if (type == termFromNameType) { - return termFromName; - } else { - throw new IllegalArgumentException( - String.format( - "Can't create variable with name '%s' and type '%s' " - + "as it would omit a variable with type '%s'", - name, yices_type_to_string(type), yices_type_to_string(termFromNameType))); - } - } - int var = yices_new_uninterpreted_term(type); - yices_set_term_name(var, name); - return var; - } - /** * @return int 1 if the Yices2-lib is compiled thread-safe and 0 otherwise */ diff --git a/src/org/sosy_lab/java_smt/solvers/yices2/Yices2NativeApiTest.java b/src/org/sosy_lab/java_smt/solvers/yices2/Yices2NativeApiTest.java index 868698f9a3..535468faa6 100644 --- a/src/org/sosy_lab/java_smt/solvers/yices2/Yices2NativeApiTest.java +++ b/src/org/sosy_lab/java_smt/solvers/yices2/Yices2NativeApiTest.java @@ -47,6 +47,7 @@ import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_free_config; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_free_context; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_function_type; +import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_get_term_by_name; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_get_term_name; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_idiv; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_iff; @@ -55,7 +56,6 @@ import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_int64; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_int_type; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_mul; -import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_named_variable; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_new_config; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_new_context; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_new_uninterpreted_term; @@ -82,6 +82,8 @@ import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_term_num_children; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_term_to_string; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_true; +import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_type_of_term; +import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_type_to_string; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_zero_extend; import com.google.common.base.Joiner; @@ -96,6 +98,7 @@ import org.junit.AssumptionViolatedException; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.sosy_lab.common.NativeLibraries; import org.sosy_lab.common.rationals.Rational; @@ -201,6 +204,8 @@ public void rationalError() { assertThrows(IllegalArgumentException.class, () -> yices_rational32(1, 0)); } + // TODO: what is this test supposed to be doing? And remove print. + @Ignore @Test public void negativeRationalError() { // TODO negative unsigned integer causes no error. Need to ensure positive value before @@ -465,6 +470,8 @@ public void arithSimplification() { assertThat(yices_term_constructor(mul)).isEqualTo(YICES_ARITH_CONST); } + // TODO: what is this test supposed to be doing? And remove print. + @Ignore @Test public void sumComponents() { int three = yices_int32(3); @@ -497,6 +504,8 @@ public void sumComponents() { } } + // TODO: what is this test supposed to be doing? And remove print. + @Ignore @Test public void bvSumComponents() { int bv1 = yices_parse_bvbin("00101"); @@ -519,6 +528,8 @@ public void bvSumComponents() { } } + // TODO: what is this test supposed to be doing? And remove print. + @Ignore @Test public void bvExtensionStructureTest() { int extendBy = 5; @@ -567,6 +578,8 @@ public void bvSum() { assertThat(constructor).isEqualTo(YICES_BV_SUM); } + // TODO: what is this test supposed to be doing? And remove print. + @Ignore @Test public void bvMul() { int type = yices_bv_type(5); @@ -578,4 +591,27 @@ public void bvMul() { System.out.println(component[1]); System.out.println(yices_term_constructor(yices_bvpower(component[0], component[1]))); } + + /** + * Only to be used for tests in this class. Old implementation used for creating/retrieving named + * variables. Superseded by Yices2FormulaCreator.createNamedVariable() for reasons outlined there. + */ + private static int yices_named_variable(int type, String name) { + int termFromName = yices_get_term_by_name(name); + if (termFromName != -1) { + int termFromNameType = yices_type_of_term(termFromName); + if (type == termFromNameType) { + return termFromName; + } else { + throw new IllegalArgumentException( + String.format( + "Can't create variable with name '%s' and type '%s' " + + "as it would omit a variable with type '%s'", + name, yices_type_to_string(type), yices_type_to_string(termFromNameType))); + } + } + int var = yices_new_uninterpreted_term(type); + yices_set_term_name(var, name); + return var; + } } diff --git a/src/org/sosy_lab/java_smt/solvers/yices2/Yices2QuantifiedFormulaManager.java b/src/org/sosy_lab/java_smt/solvers/yices2/Yices2QuantifiedFormulaManager.java index 957d078f9b..ebbcd93919 100644 --- a/src/org/sosy_lab/java_smt/solvers/yices2/Yices2QuantifiedFormulaManager.java +++ b/src/org/sosy_lab/java_smt/solvers/yices2/Yices2QuantifiedFormulaManager.java @@ -10,9 +10,12 @@ import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_exists; import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_forall; +import static org.sosy_lab.java_smt.solvers.yices2.Yices2NativeApi.yices_subst_term; import com.google.common.primitives.Ints; +import java.util.ArrayList; import java.util.List; +import org.sosy_lab.common.log.LogManager; import org.sosy_lab.java_smt.api.SolverException; import org.sosy_lab.java_smt.basicimpl.AbstractQuantifiedFormulaManager; import org.sosy_lab.java_smt.basicimpl.FormulaCreator; @@ -21,34 +24,38 @@ public class Yices2QuantifiedFormulaManager extends AbstractQuantifiedFormulaManager { protected Yices2QuantifiedFormulaManager( - FormulaCreator pCreator) { - super(pCreator); + FormulaCreator pCreator, LogManager pLogger) { + super(pCreator, pLogger); } @Override protected Integer eliminateQuantifiers(Integer pExtractInfo) throws SolverException, InterruptedException { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Yices does not support eliminating Quantifiers."); + throw new UnsupportedOperationException("Yices2 does not support quantifier elimination."); } @Override public Integer mkQuantifier(Quantifier pQ, List pVars, Integer pBody) { - /* - * TODO Yices needs variables constructed using yices_new_variable(), but variables passed in - * pVars are constructed with yices_new uninterpreted_term(). Need to construct the correct - * variable type from the variables in pVars and map between them. - */ + // Quantifier support is very limited in Yices2 if (pVars.isEmpty()) { throw new IllegalArgumentException("Empty variable list for Quantifier."); } else { - int[] terms = Ints.toArray(pVars); + List yicesVars = new ArrayList<>(); + for (int var : pVars) { + yicesVars.add( + ((Yices2FormulaCreator) formulaCreator).createBoundVariableFromFreeVariable(var)); + } + int substBody = pBody; + substBody = + yices_subst_term( + yicesVars.size(), Ints.toArray(pVars), Ints.toArray(yicesVars), substBody); + + int[] terms = Ints.toArray(yicesVars); if (pQ == Quantifier.FORALL) { - return yices_forall(terms.length, terms, pBody); - } else if (pQ == Quantifier.EXISTS) { - return yices_exists(terms.length, terms, pBody); + return yices_forall(terms.length, terms, substBody); + } else { + return yices_exists(terms.length, terms, substBody); } } - return null; } } diff --git a/src/org/sosy_lab/java_smt/solvers/yices2/Yices2SolverContext.java b/src/org/sosy_lab/java_smt/solvers/yices2/Yices2SolverContext.java index c68e90aa3c..1837874de4 100644 --- a/src/org/sosy_lab/java_smt/solvers/yices2/Yices2SolverContext.java +++ b/src/org/sosy_lab/java_smt/solvers/yices2/Yices2SolverContext.java @@ -17,6 +17,7 @@ import java.util.Set; import java.util.function.Consumer; import org.sosy_lab.common.ShutdownNotifier; +import org.sosy_lab.common.log.LogManager; import org.sosy_lab.java_smt.SolverContextFactory.Solvers; import org.sosy_lab.java_smt.api.BooleanFormulaManager; import org.sosy_lab.java_smt.api.FormulaManager; @@ -49,7 +50,8 @@ public Yices2SolverContext( public static Yices2SolverContext create( NonLinearArithmetic pNonLinearArithmetic, ShutdownNotifier pShutdownManager, - Consumer pLoader) { + Consumer pLoader, + LogManager pLogger) { pLoader.accept("yices2j"); @@ -72,9 +74,18 @@ public static Yices2SolverContext create( new Yices2IntegerFormulaManager(creator, pNonLinearArithmetic); Yices2RationalFormulaManager rationalTheory = new Yices2RationalFormulaManager(creator, pNonLinearArithmetic); + Yices2QuantifiedFormulaManager quantifierManager = + new Yices2QuantifiedFormulaManager(creator, pLogger); Yices2FormulaManager manager = new Yices2FormulaManager( - creator, functionTheory, booleanTheory, integerTheory, rationalTheory, bitvectorTheory); + creator, + functionTheory, + booleanTheory, + integerTheory, + rationalTheory, + bitvectorTheory, + quantifierManager); + quantifierManager.setFmgr(manager); return new Yices2SolverContext(manager, creator, booleanTheory, pShutdownManager); } diff --git a/src/org/sosy_lab/java_smt/solvers/z3/Z3FormulaCreator.java b/src/org/sosy_lab/java_smt/solvers/z3/Z3FormulaCreator.java index df73ffdecf..aad94f2bc2 100644 --- a/src/org/sosy_lab/java_smt/solvers/z3/Z3FormulaCreator.java +++ b/src/org/sosy_lab/java_smt/solvers/z3/Z3FormulaCreator.java @@ -25,6 +25,7 @@ import com.microsoft.z3.enumerations.Z3_decl_kind; import com.microsoft.z3.enumerations.Z3_sort_kind; import com.microsoft.z3.enumerations.Z3_symbol_kind; +import java.io.IOException; import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; import java.math.BigInteger; @@ -485,7 +486,8 @@ private String getAppName(long f) { } @Override - public R visit(FormulaVisitor visitor, final Formula formula, final Long f) { + public R visit(FormulaVisitor visitor, final Formula formula, final Long f) + throws IOException { switch (Z3_ast_kind.fromInt(Native.getAstKind(environment, f))) { case Z3_NUMERAL_AST: return visitor.visitConstant(formula, convertValue(f)); diff --git a/src/org/sosy_lab/java_smt/solvers/z3/Z3QuantifiedFormulaManager.java b/src/org/sosy_lab/java_smt/solvers/z3/Z3QuantifiedFormulaManager.java index 8f0389cab9..1f28494883 100644 --- a/src/org/sosy_lab/java_smt/solvers/z3/Z3QuantifiedFormulaManager.java +++ b/src/org/sosy_lab/java_smt/solvers/z3/Z3QuantifiedFormulaManager.java @@ -13,6 +13,7 @@ import com.google.common.primitives.Longs; import com.microsoft.z3.Native; import java.util.List; +import org.sosy_lab.common.log.LogManager; import org.sosy_lab.java_smt.api.SolverException; import org.sosy_lab.java_smt.basicimpl.AbstractQuantifiedFormulaManager; @@ -21,8 +22,8 @@ class Z3QuantifiedFormulaManager extends AbstractQuantifiedFormulaManager + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.sosy_lab.java_smt.test; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.TruthJUnit.assume; +import static org.junit.Assert.assertThrows; +import static org.sosy_lab.java_smt.api.QuantifiedFormulaManager.QuantifierCreationMethod.ULTIMATE_ELIMINATOR_BEFORE_FORMULA_CREATION; +import static org.sosy_lab.java_smt.api.QuantifiedFormulaManager.QuantifierCreationMethod.ULTIMATE_ELIMINATOR_BEFORE_FORMULA_CREATION_FALLBACK; +import static org.sosy_lab.java_smt.api.QuantifiedFormulaManager.QuantifierEliminationMethod.ULTIMATE_ELIMINATOR; +import static org.sosy_lab.java_smt.api.QuantifiedFormulaManager.QuantifierEliminationMethod.ULTIMATE_ELIMINATOR_FALLBACK_ON_FAILURE; +import static org.sosy_lab.java_smt.api.QuantifiedFormulaManager.QuantifierEliminationMethod.ULTIMATE_ELIMINATOR_FALLBACK_WITH_WARNING_ON_FAILURE; + +import org.junit.Test; +import org.sosy_lab.java_smt.SolverContextFactory.Solvers; +import org.sosy_lab.java_smt.api.ArrayFormula; +import org.sosy_lab.java_smt.api.BooleanFormula; +import org.sosy_lab.java_smt.api.FormulaType; +import org.sosy_lab.java_smt.api.NumeralFormula.IntegerFormula; +import org.sosy_lab.java_smt.api.SolverException; + +public class QuantifierEliminationTest extends SolverBasedTest0.ParameterizedSolverBasedTest0 { + + @Test + public void testEliminationWithUltimateEliminator() throws SolverException, InterruptedException { + requireIntegers(); + requireQuantifiers(); + + assume() + .withMessage("Solver %s does not support quantifiers via JavaSMT", solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.BOOLECTOR); + + assume() + .withMessage("Solver %s does not support parsing yet", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.CVC4, Solvers.CVC5, Solvers.YICES2, Solvers.MATHSAT5); + + IntegerFormula x = imgr.makeVariable("x"); + IntegerFormula y = imgr.makeVariable("y"); + IntegerFormula z = imgr.makeVariable("z"); + + // Formula: forall z, (z = x => z > y) + BooleanFormula query = + qmgr.forall(z, bmgr.implication(imgr.equal(z, x), imgr.greaterThan(z, y))); + query = qmgr.eliminateQuantifiers(query, ULTIMATE_ELIMINATOR); + + assertThatFormula(query).isEquivalentTo(imgr.greaterThan(x, y)); + } + + @Test + public void testQuantifierEliminationWithoutUltimateEliminatorNoFallbackThrowsException() { + requireIntegers(); + requireQuantifiers(); + + assume() + .withMessage("Solver %s does not abort with given conditions", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.CVC4, Solvers.CVC5, Solvers.Z3, Solvers.PRINCESS); + + IntegerFormula x = imgr.makeVariable("x"); + IntegerFormula y = imgr.makeVariable("y"); + IntegerFormula z = imgr.makeVariable("z"); + + // Formula: forall z, (z = x => z > y) + BooleanFormula query = + qmgr.forall(z, bmgr.implication(imgr.equal(z, x), imgr.greaterThan(z, y))); + + Exception exception = assertThrows(Exception.class, () -> qmgr.eliminateQuantifiers(query)); + + assertThat((exception instanceof UnsupportedOperationException)).isTrue(); + } + + @Test + public void testQuantifierEliminationWithoutUltimateEliminatorFallbackThrowsException() { + requireIntegers(); + requireQuantifiers(); + + assume() + .withMessage("Solver %s does not abort with given conditions", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.CVC4, Solvers.CVC5, Solvers.Z3, Solvers.PRINCESS, Solvers.YICES2); + + IntegerFormula x = imgr.makeVariable("x"); + IntegerFormula y = imgr.makeVariable("y"); + IntegerFormula z = imgr.makeVariable("z"); + + // Formula: forall z, (z = x => z > y) + BooleanFormula query = + qmgr.forall(z, bmgr.implication(imgr.equal(z, x), imgr.greaterThan(z, y))); + + Exception exception = + assertThrows( + Exception.class, + () -> + qmgr.eliminateQuantifiers( + query, ULTIMATE_ELIMINATOR_FALLBACK_WITH_WARNING_ON_FAILURE)); + + assertThat((exception instanceof UnsupportedOperationException)).isTrue(); + } + + @Test + public void + testQuantifierEliminationWithoutUltimateEliminatorFallbackWithoutWarningThrowsException() { + requireIntegers(); + requireQuantifiers(); + + assume() + .withMessage("Solver %s does not abort with given conditions", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.CVC4, Solvers.CVC5, Solvers.Z3, Solvers.PRINCESS, Solvers.YICES2); + + IntegerFormula x = imgr.makeVariable("x"); + IntegerFormula y = imgr.makeVariable("y"); + IntegerFormula z = imgr.makeVariable("z"); + + // Formula: forall z, (z = x => z > y) + BooleanFormula query = + qmgr.forall( + z, + bmgr.implication(imgr.equal(z, x), imgr.greaterThan(z, y)), + ULTIMATE_ELIMINATOR_BEFORE_FORMULA_CREATION_FALLBACK); + + Exception exception = assertThrows(Exception.class, () -> qmgr.eliminateQuantifiers(query)); + + assertThat((exception instanceof UnsupportedOperationException)).isTrue(); + } + + @Test + public void testEliminationWithUltimateEliminatorWithArray() + throws SolverException, InterruptedException { + requireIntegers(); + requireArrays(); + requireQuantifiers(); + + assume() + .withMessage("Solver %s does not support quantifiers via JavaSMT", solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.BOOLECTOR); + + assume() + .withMessage("Solver %s does not support parsing yet", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.CVC4, Solvers.CVC5, Solvers.MATHSAT5); + + IntegerFormula k = imgr.makeVariable("k"); + IntegerFormula i = imgr.makeVariable("i"); + + ArrayFormula var = + amgr.makeArray("arr", FormulaType.IntegerType, FormulaType.IntegerType); + BooleanFormula query = qmgr.forall(var, imgr.equal(amgr.select(var, k), amgr.select(var, i))); + + query = qmgr.eliminateQuantifiers(query, ULTIMATE_ELIMINATOR); + + assertThatFormula(query).isEquivalentTo(imgr.equal(k, i)); + } + + @Test + public void testEliminationWithUltimateEliminatormkWithoutQuantifier() + throws SolverException, InterruptedException { + requireIntegers(); + requireArrays(); + requireQuantifiers(); + + assume() + .withMessage("Solver %s does not support quantifiers via JavaSMT", solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.BOOLECTOR); + + assume() + .withMessage("Solver %s does not support parsing yet", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.CVC4, Solvers.CVC5); + + IntegerFormula k = imgr.makeVariable("k"); + IntegerFormula i = imgr.makeVariable("i"); + + ArrayFormula var = + amgr.makeArray("arr", FormulaType.IntegerType, FormulaType.IntegerType); + + BooleanFormula query = + qmgr.forall( + var, + imgr.equal(amgr.select(var, k), amgr.select(var, i)), + ULTIMATE_ELIMINATOR_BEFORE_FORMULA_CREATION); + + assertThatFormula(query).isEquivalentTo(imgr.equal(k, i)); + } + + @Test + public void testEliminationWithUltimateEliminatormkWithoutQuantifierThrowsException() { + requireIntegers(); + requireArrays(); + requireQuantifiers(); + + assume() + .withMessage("Solver %s does not support quantifiers via JavaSMT", solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.BOOLECTOR); + + assume() + .withMessage("Solver %s does not abort with given conditions", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.MATHSAT5, Solvers.Z3, Solvers.PRINCESS); + + IntegerFormula k = imgr.makeVariable("k"); + IntegerFormula i = imgr.makeVariable("i"); + + ArrayFormula var = + amgr.makeArray("arr", FormulaType.IntegerType, FormulaType.IntegerType); + + assertThrows( + UnsupportedOperationException.class, + () -> + qmgr.forall( + var, + imgr.equal(amgr.select(var, k), amgr.select(var, i)), + ULTIMATE_ELIMINATOR_BEFORE_FORMULA_CREATION)); + } + + @Test + public void testEliminationWithoutArraysBefore() throws SolverException, InterruptedException { + requireIntegers(); + requireQuantifiers(); + + assume() + .withMessage("Solver %s does not support quantifiers via JavaSMT", solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.BOOLECTOR); + + assume() + .withMessage("Solver %s does not support parsing yet", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.CVC4, Solvers.CVC5, Solvers.YICES2); + + IntegerFormula k = imgr.makeVariable("k"); + IntegerFormula two = imgr.makeNumber(2); + IntegerFormula five = imgr.makeNumber(5); + + BooleanFormula query = + qmgr.forall( + k, + bmgr.or(imgr.lessOrEquals(k, five), imgr.greaterOrEquals(k, two)), + ULTIMATE_ELIMINATOR_BEFORE_FORMULA_CREATION); + + assertThatFormula(query).isSatisfiable(); + } + + @Test + public void testQuantElimBefore() throws SolverException, InterruptedException { + requireIntegers(); + requireQuantifiers(); + + assume() + .withMessage("Solver %s does not support quantifiers via JavaSMT", solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.BOOLECTOR); + + assume() + .withMessage("Solver %s does not support parsing yet", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.CVC4, Solvers.CVC5, Solvers.YICES2); + + IntegerFormula x = imgr.makeVariable("x"); + IntegerFormula y = imgr.makeVariable("y"); + IntegerFormula zero = imgr.makeNumber(0); + + BooleanFormula query = + qmgr.forall( + x, + bmgr.or( + imgr.greaterOrEquals(x, zero), + qmgr.forall( + y, imgr.greaterOrEquals(y, zero), ULTIMATE_ELIMINATOR_BEFORE_FORMULA_CREATION)), + ULTIMATE_ELIMINATOR_BEFORE_FORMULA_CREATION); + + assertThatFormula(query).isUnsatisfiable(); + } + + @Test + public void testQuantElimNoFallback() throws SolverException, InterruptedException { + requireIntegers(); + requireQuantifiers(); + + assume() + .withMessage("Solver %s does not support quantifiers via JavaSMT", solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.BOOLECTOR); + + assume() + .withMessage("Solver %s does not support parsing yet", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.CVC4, Solvers.CVC5, Solvers.MATHSAT5, Solvers.YICES2); + + IntegerFormula k = imgr.makeVariable("k"); + IntegerFormula i = imgr.makeVariable("i"); + + // Case: Unsupported quantifier elimination + BooleanFormula unsupportedQuery = qmgr.forall(k, imgr.equal(k, i)); + assertThatFormula(qmgr.eliminateQuantifiers(unsupportedQuery, ULTIMATE_ELIMINATOR)) + .isUnsatisfiable(); + } + + @Test + public void testQuantElimAbort() { + requireIntegers(); + requireQuantifiers(); + + assume() + .withMessage("Solver %s does not support quantifiers via JavaSMT", solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.BOOLECTOR); + + assume() + .withMessage("Solver %s does not abort with given conditions", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.PRINCESS, Solvers.Z3); + + assume() + .withMessage( + "Solver %s does not support parseable dumping format for UltimateEliminator " + "yet", + solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.YICES2); + + IntegerFormula k = imgr.makeVariable("k"); + IntegerFormula i = imgr.makeVariable("i"); + + // Case: Unsupported quantifier elimination + BooleanFormula unsupportedQuery = qmgr.forall(k, imgr.equal(k, i)); + Exception exception = + assertThrows( + Exception.class, + () -> qmgr.eliminateQuantifiers(unsupportedQuery, ULTIMATE_ELIMINATOR)); + assertThat( + (exception instanceof UnsupportedOperationException) + || exception instanceof IllegalArgumentException) + .isTrue(); + } + + @Test + public void testQuantElimFallbackException() { + requireIntegers(); + requireQuantifiers(); + + assume() + .withMessage("Solver %s does not support quantifiers via JavaSMT", solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.BOOLECTOR); + + assume() + .withMessage("Solver %s does not abort with given conditions", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.PRINCESS, Solvers.Z3, Solvers.CVC5, Solvers.CVC4); + + IntegerFormula k = imgr.makeVariable("k"); + IntegerFormula i = imgr.makeVariable("i"); + + // Case: Unsupported quantifier elimination + BooleanFormula query = qmgr.forall(k, imgr.equal(k, i)); + Exception exception = + assertThrows( + Exception.class, + () -> + qmgr.eliminateQuantifiers( + query, ULTIMATE_ELIMINATOR_FALLBACK_WITH_WARNING_ON_FAILURE)); + + String expectedMessage1 = + "UltimateEliminator failed. " + "Reverting to native " + "quantifier elimination"; + + String expectedMessage2 = + "printing without use-defines is not supported for quantified formulas"; + + String expectedMessage = expectedMessage1 + expectedMessage2; + + assertThat( + (exception instanceof UnsupportedOperationException) + || expectedMessage.contains(exception.getMessage())) + .isTrue(); + } + + @Test + public void testQuantElimFallback() throws SolverException, InterruptedException { + requireIntegers(); + requireQuantifiers(); + requireArrays(); + + assume() + .withMessage("Solver %s does not support quantifiers via JavaSMT", solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.BOOLECTOR); + + assume() + .withMessage("Solver %s does not abort with given conditions", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.PRINCESS, Solvers.Z3); + + assume() + .withMessage("Solver %s does not support parsing yet", solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); + + IntegerFormula xx = imgr.makeVariable("x"); + IntegerFormula yy = imgr.makeVariable("y"); + BooleanFormula f = + qmgr.forall( + xx, + bmgr.or( + imgr.lessThan(xx, imgr.makeNumber(5)), + imgr.lessThan(imgr.makeNumber(7), imgr.add(xx, yy)))); + BooleanFormula qFreeF = + qmgr.eliminateQuantifiers(f, ULTIMATE_ELIMINATOR_FALLBACK_WITH_WARNING_ON_FAILURE); + assertThatFormula(qFreeF).isEquivalentTo(imgr.lessThan(imgr.makeNumber(2), yy)); + } + + @Test + public void testQuantElimFallbackWithoutWarning() throws SolverException, InterruptedException { + requireIntegers(); + requireQuantifiers(); + + assume() + .withMessage("Solver %s does not support quantifiers via JavaSMT", solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.BOOLECTOR); + + assume() + .withMessage("Solver %s does not support parsing yet", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.MATHSAT5, Solvers.YICES2); + + IntegerFormula xx = imgr.makeVariable("x"); + IntegerFormula yy = imgr.makeVariable("y"); + BooleanFormula f = + qmgr.forall( + xx, + bmgr.or( + imgr.lessThan(xx, imgr.makeNumber(5)), + imgr.lessThan(imgr.makeNumber(7), imgr.add(xx, yy)))); + BooleanFormula qFreeF = qmgr.eliminateQuantifiers(f, ULTIMATE_ELIMINATOR_FALLBACK_ON_FAILURE); + assertThatFormula(qFreeF).isEquivalentTo(imgr.lessThan(imgr.makeNumber(2), yy)); + } + + @Test + public void testQuantElimFallbackNoWarnException() { + requireIntegers(); + requireQuantifiers(); + + assume() + .withMessage("Solver %s does not support quantifiers via JavaSMT", solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.BOOLECTOR); + + assume() + .withMessage("Solver %s does not abort with given conditions", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.PRINCESS, Solvers.Z3, Solvers.CVC5, Solvers.CVC4); + + IntegerFormula xx = imgr.makeVariable("x"); + IntegerFormula yy = imgr.makeVariable("y"); + BooleanFormula f = + qmgr.forall( + xx, + bmgr.or( + imgr.lessThan(xx, imgr.makeNumber(5)), + imgr.lessThan(imgr.makeNumber(7), imgr.add(xx, yy)))); + + Exception exception = + assertThrows( + Exception.class, + () -> qmgr.eliminateQuantifiers(f, ULTIMATE_ELIMINATOR_FALLBACK_ON_FAILURE)); + + String expectedMessage1 = + "UltimateEliminator failed. " + "Reverting to native " + "quantifier elimination"; + + String expectedMessage2 = + "printing without use-defines is not supported for quantified formulas"; + + String expectedMessage = expectedMessage1 + expectedMessage2; + + assertThat( + (exception instanceof UnsupportedOperationException) + || expectedMessage.contains(exception.getMessage())) + .isTrue(); + } +} diff --git a/src/org/sosy_lab/java_smt/test/QuantifierManagerTest.java b/src/org/sosy_lab/java_smt/test/QuantifierManagerTest.java index 1d5d9bc90c..85619d267a 100644 --- a/src/org/sosy_lab/java_smt/test/QuantifierManagerTest.java +++ b/src/org/sosy_lab/java_smt/test/QuantifierManagerTest.java @@ -65,6 +65,15 @@ public class QuantifierManagerTest extends SolverBasedTest0.ParameterizedSolverB @SuppressWarnings("checkstyle:membername") private BooleanFormula bv_forall_x_a_at_x_eq_0; + @Before + public void setUp() { + requireQuantifiers(); + assume() + .withMessage("Mathsat5 does not support quantifiers without UltimateEliminator") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); + } + @Before public void setUpLIA() { requireIntegers(); @@ -146,6 +155,7 @@ public void testBVForallArrayConjunctUnsat() throws SolverException, Interrupted bvmgr.equal( amgr.select(bvArray, bvmgr.makeBitvector(bvWidth, 123)), bvmgr.makeBitvector(bvWidth, 1))); + assertThatFormula(f).isUnsatisfiable(); } @@ -163,6 +173,7 @@ public void testLIAForallArrayConjunctSat() throws SolverException, InterruptedE bmgr.and( qmgr.forall(ImmutableList.of(x), a_at_x_eq_0), imgr.equal(amgr.select(a, imgr.makeNumber(123)), imgr.makeNumber(0))); + try { // CVC4 and Princess fail this assertThatFormula(f).isSatisfiable(); diff --git a/src/org/sosy_lab/java_smt/test/RationalFormulaManagerTest.java b/src/org/sosy_lab/java_smt/test/RationalFormulaManagerTest.java index 2f38374017..0f751e56d7 100644 --- a/src/org/sosy_lab/java_smt/test/RationalFormulaManagerTest.java +++ b/src/org/sosy_lab/java_smt/test/RationalFormulaManagerTest.java @@ -9,6 +9,7 @@ package org.sosy_lab.java_smt.test; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.TruthJUnit.assume; import static org.junit.Assert.assertThrows; import com.google.common.collect.Iterables; @@ -16,6 +17,7 @@ import java.util.List; import java.util.Set; import org.junit.Test; +import org.sosy_lab.java_smt.SolverContextFactory.Solvers; import org.sosy_lab.java_smt.api.Formula; import org.sosy_lab.java_smt.api.FormulaType; import org.sosy_lab.java_smt.api.FunctionDeclaration; @@ -92,6 +94,15 @@ public void forallFloorIsLessOrEqualsValueTest() throws SolverException, Interru requireRationals(); requireQuantifiers(); requireRationalFloor(); + assume() + .withMessage("Yices2 quantifier support is very limited at the moment") + .that(solverToUse()) + .isNotEqualTo(Solvers.YICES2); + assume() + .withMessage("Mathsat5 quantifier support is very limited at the moment") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); + RationalFormula v = rmgr.makeVariable("v"); assertThatFormula(qmgr.forall(v, rmgr.lessOrEquals(rmgr.floor(v), v))).isTautological(); } @@ -101,6 +112,15 @@ public void forallFloorIsLessThanValueTest() throws SolverException, Interrupted requireRationals(); requireQuantifiers(); requireRationalFloor(); + assume() + .withMessage("Yices2 quantifier support is very limited at the moment") + .that(solverToUse()) + .isNotEqualTo(Solvers.YICES2); + assume() + .withMessage("Mathsat5 does not support quantifiers without UltimateEliminator") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); + RationalFormula v = rmgr.makeVariable("v"); // counterexample: all integers assertThatFormula(qmgr.forall(v, rmgr.lessThan(rmgr.floor(v), v))).isUnsatisfiable(); @@ -110,6 +130,14 @@ public void forallFloorIsLessThanValueTest() throws SolverException, Interrupted public void visitFloorTest() { requireRationals(); requireRationalFloor(); + assume() + .withMessage("Yices2 quantifier support is very limited at the moment") + .that(solverToUse()) + .isNotEqualTo(Solvers.YICES2); + assume() + .withMessage("Mathsat5 does not support quantifiers without UltimateEliminator") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); IntegerFormula f = rmgr.floor(rmgr.makeVariable("v")); assertThat(mgr.extractVariables(f)).hasSize(1); diff --git a/src/org/sosy_lab/java_smt/test/SolverAllSatTest.java b/src/org/sosy_lab/java_smt/test/SolverAllSatTest.java index 07752392c6..88b87acb75 100644 --- a/src/org/sosy_lab/java_smt/test/SolverAllSatTest.java +++ b/src/org/sosy_lab/java_smt/test/SolverAllSatTest.java @@ -207,6 +207,11 @@ public void allSatTest_withQuantifier() throws SolverException, InterruptedExcep .that(solverToUse()) .isNotEqualTo(Solvers.BOOLECTOR); + assume() + .withMessage("solver does not support quantifiers yet") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); + if ("opt".equals(proverEnv)) { assume() .withMessage("solver reports a partial model when using optimization") @@ -258,6 +263,11 @@ public void allSatTest_withQuantifier() throws SolverException, InterruptedExcep assertThat(env.isUnsat()).isFalse(); + assume() + .withMessage("Yices2 quantifier support is very limited at the moment") + .that(solverToUse()) + .isNotEqualTo(Solvers.YICES2); + TestAllSatCallback callback = new TestAllSatCallback(); assertThat(env.allSat(callback, ImmutableList.of(pred1, pred3))).isEqualTo(EXPECTED_RESULT); diff --git a/src/org/sosy_lab/java_smt/test/SolverBasedTest0.java b/src/org/sosy_lab/java_smt/test/SolverBasedTest0.java index de2715e3f2..e96c7b39f1 100644 --- a/src/org/sosy_lab/java_smt/test/SolverBasedTest0.java +++ b/src/org/sosy_lab/java_smt/test/SolverBasedTest0.java @@ -262,6 +262,15 @@ protected final void requireQuantifiers() { .isNotNull(); } + @SuppressWarnings("unused") + protected final void requireQuantifierElimination() { + requireQuantifiers(); + assume() + .withMessage("Solver %s does not support quantifier elimination", solverToUse()) + .that(solverToUse()) + .isNoneOf(Solvers.BOOLECTOR, Solvers.MATHSAT5, Solvers.YICES2, Solvers.BITWUZLA); + } + /** Skip test if the solver does not support arrays. */ protected /*final*/ void requireArrays() { assume() diff --git a/src/org/sosy_lab/java_smt/test/SolverTacticsTest.java b/src/org/sosy_lab/java_smt/test/SolverTacticsTest.java index 118e089e34..c5fb5be02a 100644 --- a/src/org/sosy_lab/java_smt/test/SolverTacticsTest.java +++ b/src/org/sosy_lab/java_smt/test/SolverTacticsTest.java @@ -9,6 +9,7 @@ package org.sosy_lab.java_smt.test; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.TruthJUnit.assume; import static org.junit.Assert.assertThrows; import static org.sosy_lab.java_smt.api.FormulaType.BooleanType; import static org.sosy_lab.java_smt.api.FormulaType.IntegerType; @@ -202,6 +203,11 @@ public void ufEliminationNestedUfsTest() throws SolverException, InterruptedExce public void ufEliminationNestedQuantifierTest() { requireIntegers(); requireQuantifiers(); + assume() + .withMessage("Mathsat5 does not support quantifiers without UltimateEliminator") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); + // f := exists v1,v2v,v3,v4 : uf(v1, v3) == uf(v2, v4) IntegerFormula variable1 = imgr.makeVariable("variable1"); IntegerFormula variable2 = imgr.makeVariable("variable2"); diff --git a/src/org/sosy_lab/java_smt/test/SolverTheoriesTest.java b/src/org/sosy_lab/java_smt/test/SolverTheoriesTest.java index e75e8b7ff0..9d8373f2ab 100644 --- a/src/org/sosy_lab/java_smt/test/SolverTheoriesTest.java +++ b/src/org/sosy_lab/java_smt/test/SolverTheoriesTest.java @@ -635,6 +635,7 @@ public void testUfWithBoolArg() throws SolverException, InterruptedException { public void quantifierEliminationTest1() throws SolverException, InterruptedException { requireQuantifiers(); requireIntegers(); + requireQuantifierElimination(); IntegerFormula var_B = imgr.makeVariable("b"); IntegerFormula var_C = imgr.makeVariable("c"); @@ -659,6 +660,7 @@ public void quantifierEliminationTest1() throws SolverException, InterruptedExce public void quantifierEliminationTest2() throws SolverException, InterruptedException { requireQuantifiers(); requireIntegers(); + requireQuantifierElimination(); IntegerFormula i1 = imgr.makeVariable("i@1"); IntegerFormula j1 = imgr.makeVariable("j@1"); diff --git a/src/org/sosy_lab/java_smt/test/SolverVisitorTest.java b/src/org/sosy_lab/java_smt/test/SolverVisitorTest.java index d5242db776..08c984fc9a 100644 --- a/src/org/sosy_lab/java_smt/test/SolverVisitorTest.java +++ b/src/org/sosy_lab/java_smt/test/SolverVisitorTest.java @@ -880,6 +880,14 @@ public void testBooleanFormulaQuantifierHandling() throws Exception { .withMessage("Princess does not support quantifier over boolean variables") .that(solverToUse()) .isNotEqualTo(Solvers.PRINCESS); + assume() + .withMessage("Yices2 quantifier support is very limited at the moment") + .that(solverToUse()) + .isNotEqualTo(Solvers.YICES2); + assume() + .withMessage("Mathsat5 does not support quantifiers without UltimateEliminator") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); BooleanFormula x = bmgr.makeVariable("x"); BooleanFormula constraint = qmgr.forall(ImmutableList.of(x), x); @@ -896,6 +904,14 @@ public void testBooleanFormulaQuantifierRecursiveHandling() throws Exception { .withMessage("Princess does not support quantifier over boolean variables") .that(solverToUse()) .isNotEqualTo(Solvers.PRINCESS); + assume() + .withMessage("Yices2 quantifier support is very limited at the moment") + .that(solverToUse()) + .isNotEqualTo(Solvers.YICES2); + assume() + .withMessage("Mathsat5 does not support quantifiers without UltimateEliminator") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); BooleanFormula x = bmgr.makeVariable("x"); BooleanFormula constraint = qmgr.forall(ImmutableList.of(x), x); @@ -910,6 +926,14 @@ public void testBooleanFormulaQuantifierRecursiveHandling() throws Exception { public void testIntegerFormulaQuantifierHandlingUNSAT() throws Exception { requireQuantifiers(); requireIntegers(); + assume() + .withMessage("Yices2 quantifier support is very limited at the moment") + .that(solverToUse()) + .isNotEqualTo(Solvers.YICES2); + assume() + .withMessage("Mathsat5 does not support quantifiers without UltimateEliminator") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); IntegerFormula x = imgr.makeVariable("x"); BooleanFormula xEq1 = bmgr.not(imgr.equal(imgr.makeNumber(1), x)); @@ -924,6 +948,10 @@ public void testIntegerFormulaQuantifierHandlingUNSAT() throws Exception { public void testIntegerFormulaQuantifierHandlingTrivialSAT() throws Exception { requireQuantifiers(); requireIntegers(); + assume() + .withMessage("Mathsat5 does not support quantifiers without UltimateEliminator") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); IntegerFormula x = imgr.makeVariable("x"); BooleanFormula xEqx = imgr.equal(x, x); @@ -938,6 +966,10 @@ public void testIntegerFormulaQuantifierHandlingTrivialSAT() throws Exception { public void testIntegerFormulaQuantifierSymbolsExtraction() { requireQuantifiers(); requireIntegers(); + assume() + .withMessage("Mathsat5 does not support quantifiers without UltimateEliminator") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); IntegerFormula x = imgr.makeVariable("x"); IntegerFormula y = imgr.makeVariable("y"); @@ -962,6 +994,10 @@ public void testIntegerFormulaQuantifierSymbolsExtraction() { public void testIntegerFormulaQuantifierHandlingTrivialUNSAT() throws Exception { requireQuantifiers(); requireIntegers(); + assume() + .withMessage("Mathsat5 does not support quantifiers without UltimateEliminator") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); IntegerFormula x = imgr.makeVariable("x"); BooleanFormula notxEqx = bmgr.not(imgr.equal(x, x)); @@ -985,6 +1021,14 @@ public void testNestedIntegerFormulaQuantifierHandling() throws Exception { requireIntegers(); // Z3 returns UNKNOWN as its quantifiers can not handle this. assume().that(solverToUse()).isNotEqualTo(Solvers.Z3); + assume() + .withMessage("Yices2 quantifier support is very limited at the moment") + .that(solverToUse()) + .isNotEqualTo(Solvers.YICES2); + assume() + .withMessage("Mathsat5 does not support quantifiers without UltimateEliminator") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); IntegerFormula x = imgr.makeVariable("x"); BooleanFormula xEq1 = imgr.equal(x, imgr.makeNumber(1)); @@ -1003,6 +1047,14 @@ public void testNestedIntegerFormulaQuantifierRecursiveHandling() throws Excepti requireIntegers(); // Z3 returns UNKNOWN as its quantifiers can not handle this. assume().that(solverToUse()).isNotEqualTo(Solvers.Z3); + assume() + .withMessage("Yices2 quantifier support is very limited at the moment") + .that(solverToUse()) + .isNotEqualTo(Solvers.YICES2); + assume() + .withMessage("Mathsat5 does not support quantifiers without UltimateEliminator") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); IntegerFormula x = imgr.makeVariable("x"); BooleanFormula xEq1 = imgr.equal(x, imgr.makeNumber(1)); @@ -1158,6 +1210,10 @@ public void testTransformationInsideQuantifiers() { .withMessage("Princess does not support quantifier over boolean variables") .that(solverToUse()) .isNotEqualTo(Solvers.PRINCESS); + assume() + .withMessage("Mathsat5 does not support quantifiers without UltimateEliminator") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); BooleanFormula[] usedVars = Stream.of("a", "b", "c", "d", "e", "f") @@ -1193,6 +1249,8 @@ public void testTransformationInsideQuantifiersWithTrue() throws SolverException, InterruptedException { requireQuantifiers(); requireIntegers(); + requireQuantifierElimination(); + List quantifiedVars = ImmutableList.of(imgr.makeVariable("x")); BooleanFormula body = bmgr.makeTrue(); BooleanFormula f = qmgr.exists(quantifiedVars, body); @@ -1206,6 +1264,8 @@ public void testTransformationInsideQuantifiersWithFalse() throws SolverException, InterruptedException { requireQuantifiers(); requireIntegers(); + requireQuantifierElimination(); + List quantifiedVars = ImmutableList.of(imgr.makeVariable("x")); BooleanFormula body = bmgr.makeFalse(); BooleanFormula f = qmgr.exists(quantifiedVars, body); @@ -1219,6 +1279,8 @@ public void testTransformationInsideQuantifiersWithVariable() throws SolverException, InterruptedException { requireQuantifiers(); requireIntegers(); + requireQuantifierElimination(); + List quantifiedVars = ImmutableList.of(imgr.makeVariable("x")); BooleanFormula body = bmgr.makeVariable("b"); BooleanFormula f = qmgr.exists(quantifiedVars, body); diff --git a/src/org/sosy_lab/java_smt/test/UfEliminationTest.java b/src/org/sosy_lab/java_smt/test/UfEliminationTest.java index 5f3bbe2e01..5ef5141558 100644 --- a/src/org/sosy_lab/java_smt/test/UfEliminationTest.java +++ b/src/org/sosy_lab/java_smt/test/UfEliminationTest.java @@ -223,6 +223,10 @@ public void twoFormulasTest() throws SolverException, InterruptedException { public void quantifierTest() { requireQuantifiers(); requireIntegers(); + assume() + .withMessage("Mathsat5 does not support quantifiers without UltimateEliminator") + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); // f := exists v1,v2v,v3,v4 : uf(v1, v3) == uf(v2, v4) IntegerFormula variable1 = imgr.makeVariable("variable1"); diff --git a/src/org/sosy_lab/java_smt/test/VariableNamesTest.java b/src/org/sosy_lab/java_smt/test/VariableNamesTest.java index 2f61eb09cd..c2898be122 100644 --- a/src/org/sosy_lab/java_smt/test/VariableNamesTest.java +++ b/src/org/sosy_lab/java_smt/test/VariableNamesTest.java @@ -369,6 +369,10 @@ public void testNameUF2Int() throws SolverException, InterruptedException { public void testNameInQuantification() { requireQuantifiers(); requireIntegers(); + assume() + .withMessage("Solver %s does not support native quantification yet", solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); for (String name : getAllNames()) { @@ -425,6 +429,10 @@ protected Void visitDefault(Formula pF) { public void testNameInNestedQuantification() { requireQuantifiers(); requireIntegers(); + assume() + .withMessage("Solver %s does not support native quantification yet", solverToUse()) + .that(solverToUse()) + .isNotEqualTo(Solvers.MATHSAT5); for (String name : getAllNames()) { diff --git a/src/org/sosy_lab/java_smt/test/ultimate/BacktranslationServiceMock.java b/src/org/sosy_lab/java_smt/test/ultimate/BacktranslationServiceMock.java new file mode 100644 index 0000000000..30d8812571 --- /dev/null +++ b/src/org/sosy_lab/java_smt/test/ultimate/BacktranslationServiceMock.java @@ -0,0 +1,116 @@ +/* + * This file is part of JavaSMT, + * an API wrapper for a collection of SMT solvers: + * https://github.com/sosy-lab/java-smt + * + * SPDX-FileCopyrightText: 2025 Dirk Beyer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Copyright (C) 2016 Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + * Copyright (C) 2016 University of Freiburg + * + * This file is part of the ULTIMATE JUnit Helper Library. + * + * The ULTIMATE JUnit Helper Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ULTIMATE JUnit Helper Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ULTIMATE JUnit Helper Library. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * If you modify the ULTIMATE JUnit Helper Library, or any covered work, by linking + * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), + * containing parts covered by the terms of the Eclipse Public License, the + * licensors of the ULTIMATE JUnit Helper Library grant you additional permission + * to convey the resulting work. + */ + +package org.sosy_lab.java_smt.test.ultimate; + +import com.google.common.collect.ImmutableList; +import de.uni_freiburg.informatik.ultimate.core.model.services.IBacktranslationService; +import de.uni_freiburg.informatik.ultimate.core.model.translation.IBacktranslatedCFG; +import de.uni_freiburg.informatik.ultimate.core.model.translation.IProgramExecution; +import de.uni_freiburg.informatik.ultimate.core.model.translation.IProgramExecution.ProgramState; +import de.uni_freiburg.informatik.ultimate.core.model.translation.ITranslator; +import java.util.List; + +/** + * @author Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + */ +final class BacktranslationServiceMock implements IBacktranslationService { + + @SuppressWarnings("checkstyle:MethodTypeParameterName") + @Override + public void addTranslator( + final ITranslator translator) { + // does nothing + } + + @SuppressWarnings({"checkstyle:MethodTypeParameterName", "TypeParameterUnusedInFormals"}) + @Override + public TE translateExpression( + final SE expression, final Class sourceExpressionClass) { + return null; + } + + @SuppressWarnings("checkstyle:MethodTypeParameterName") + @Override + public String translateExpressionToString(final SE expression, final Class clazz) { + return ""; + } + + @SuppressWarnings("checkstyle:MethodTypeParameterName") + @Override + public List translateTrace(final List trace, final Class clazz) { + return ImmutableList.of(); + } + + @SuppressWarnings("checkstyle:MethodTypeParameterName") + @Override + public List translateTraceToHumanReadableString( + final List trace, final Class clazz) { + return ImmutableList.of(); + } + + @SuppressWarnings("checkstyle:MethodTypeParameterName") + @Override + public IProgramExecution translateProgramExecution( + final IProgramExecution programExecution) { + return new ProgramExecutionMock<>(null, null); + } + + @SuppressWarnings("checkstyle:MethodTypeParameterName") + @Override + public ProgramState translateProgramState(final ProgramState programState) { + return null; + } + + @SuppressWarnings("checkstyle:MethodTypeParameterName") + @Override + public String translateProgramStateToString(ProgramState programState) { + return null; + } + + @SuppressWarnings({"checkstyle:MethodTypeParameterName", "unused"}) + @Override + public IBacktranslatedCFG translateCFG(final IBacktranslatedCFG cfg) { + return null; + } + + @SuppressWarnings("checkstyle:MethodTypeParameterName") + @Override + public IBacktranslationService getTranslationServiceCopy() { + return this; + } +} diff --git a/src/org/sosy_lab/java_smt/test/ultimate/ConsoleLogger.java b/src/org/sosy_lab/java_smt/test/ultimate/ConsoleLogger.java new file mode 100644 index 0000000000..179677bee7 --- /dev/null +++ b/src/org/sosy_lab/java_smt/test/ultimate/ConsoleLogger.java @@ -0,0 +1,135 @@ +/* + * This file is part of JavaSMT, + * an API wrapper for a collection of SMT solvers: + * https://github.com/sosy-lab/java-smt + * + * SPDX-FileCopyrightText: 2025 Dirk Beyer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Copyright (C) 2014-2015 Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + * Copyright (C) 2019 Claus Schätzle (schaetzc@tf.uni-freiburg.de) + * Copyright (C) 2015,2019 University of Freiburg + * + * This file is part of the ULTIMATE UnitTest Library. + * + * The ULTIMATE UnitTest Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ULTIMATE UnitTest Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ULTIMATE UnitTest Library. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * If you modify the ULTIMATE UnitTest Library, or any covered work, by linking + * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), + * containing parts covered by the terms of the Eclipse Public License, the + * licensors of the ULTIMATE UnitTest Library grant you additional permission + * to convey the resulting work. + */ + +package org.sosy_lab.java_smt.test.ultimate; + +import de.uni_freiburg.informatik.ultimate.core.model.services.ILogger; + +/** + * A very simple implementation of ILogger. All levels are always enabled, it prints error and fatal + * on stderr and the rest on stdout. + * + * @author Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + */ +public final class ConsoleLogger implements ILogger { + + private LogLevel mLevel; + private final ILogFunction[] mLevelToFunction = new ILogFunction[LogLevel.values().length]; + + public ConsoleLogger() { + setLevel(LogLevel.DEBUG); + } + + public ConsoleLogger(final LogLevel level) { + setLevel(level); + } + + @SuppressWarnings("EnumOrdinal") + @Override + public void fatal(final Object msg, final Throwable t) { + mLevelToFunction[LogLevel.FATAL.ordinal()].log(msg, t); + } + + @SuppressWarnings("EnumOrdinal") + @Override + public void error(final Object msg, final Throwable t) { + mLevelToFunction[LogLevel.ERROR.ordinal()].log(msg, t); + } + + @SuppressWarnings("EnumOrdinal") + @Override + public void log(final LogLevel level, final String msg) { + mLevelToFunction[level.ordinal()].log(msg); + } + + @SuppressWarnings("EnumOrdinal") + @Override + public void setLevel(final LogLevel level) { + mLevel = level; + final ILogFunction noLog = new NoLog(); + for (LogLevel levelIter : LogLevel.values()) { + mLevelToFunction[levelIter.ordinal()] = + isLogLevelEnabled(levelIter) ? new Log("[" + levelIter + "]: ") : noLog; + } + } + + @Override + public boolean isLogLevelEnabled(final LogLevel queryLevel) { + // lower levels log more, see documentation of ILogger.LogLevel + return queryLevel.compareTo(mLevel) >= 0; + } + + private interface ILogFunction { + + void log(Object msg); + + void log(Object msg, Throwable t); + } + + private static final class Log implements ILogFunction { + + private final String mPrefix; + + private Log(final String prefix) { + mPrefix = prefix; + } + + @Override + public void log(final Object msg) { + System.out.println(mPrefix + msg); + } + + @Override + public void log(final Object msg, final Throwable t) { + System.out.println(mPrefix + msg + " " + t); + } + } + + private static final class NoLog implements ILogFunction { + + @Override + public void log(final Object msg) { + // do nothing + } + + @Override + public void log(final Object msg, final Throwable t) { + // do nothing + } + } +} diff --git a/src/org/sosy_lab/java_smt/test/ultimate/IBacktranslationService.java b/src/org/sosy_lab/java_smt/test/ultimate/IBacktranslationService.java new file mode 100644 index 0000000000..194121017d --- /dev/null +++ b/src/org/sosy_lab/java_smt/test/ultimate/IBacktranslationService.java @@ -0,0 +1,92 @@ +/* + * This file is part of JavaSMT, + * an API wrapper for a collection of SMT solvers: + * https://github.com/sosy-lab/java-smt + * + * SPDX-FileCopyrightText: 2025 Dirk Beyer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Copyright (C) 2014-2015 Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + * Copyright (C) 2015 University of Freiburg + * + * This file is part of the ULTIMAT2 Core. + * + * The ULTIMAT2 Core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ULTIMAT2 Core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ULTIMAT2 Core. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * If you modify the ULTIMAT2 Core, or any covered work, by linking + * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), + * containing parts covered by the terms of the Eclipse Public License, the + * licensors of the ULTIMAT2 Core grant you additional permission + * to convey the resulting work. + */ + +package org.sosy_lab.java_smt.test.ultimate; + +import de.uni_freiburg.informatik.ultimate.core.model.translation.IBacktranslatedCFG; +import de.uni_freiburg.informatik.ultimate.core.model.translation.IProgramExecution; +import de.uni_freiburg.informatik.ultimate.core.model.translation.IProgramExecution.ProgramState; +import de.uni_freiburg.informatik.ultimate.core.model.translation.ITranslator; +import java.util.List; + +/** + * {@link IBacktranslationService} contains all {@link ITranslator} instances for the currently + * running toolchain. + * + * @author Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + */ +interface IBacktranslationService { + + /** + * Add a new translator to the backtranslation service. It has to be type-compatible with the + * existing ones! + * + * @param translator translator + */ + void addTranslator(ITranslator translator); + + @SuppressWarnings("TypeParameterUnusedInFormals") + T2 translateExpression(S2 expression, Class sourceExpressionClass); + + /** + * Translate an expression from the output type to a String. + * + * @param expression the expression + * @param clazz the class + * @return translated expression + */ + String translateExpressionToString(S2 expression, Class clazz); + + List translateTrace(List trace, Class clazz); + + List translateTraceToHumanReadableString(List trace, Class clazz); + + IProgramExecution translateProgramExecution( + IProgramExecution programExecution); + + ProgramState translateProgramState(ProgramState programState); + + String translateProgramStateToString(ProgramState programState); + + IBacktranslatedCFG translateCFG(IBacktranslatedCFG cfg); + + /** + * Use this if you want to keep a certain state of the backtranslation chain during toolchain + * execution. + */ + IBacktranslationService getTranslationServiceCopy(); +} diff --git a/src/org/sosy_lab/java_smt/test/ultimate/LoggingServiceMock.java b/src/org/sosy_lab/java_smt/test/ultimate/LoggingServiceMock.java new file mode 100644 index 0000000000..fa63e71b37 --- /dev/null +++ b/src/org/sosy_lab/java_smt/test/ultimate/LoggingServiceMock.java @@ -0,0 +1,116 @@ +/* + * This file is part of JavaSMT, + * an API wrapper for a collection of SMT solvers: + * https://github.com/sosy-lab/java-smt + * + * SPDX-FileCopyrightText: 2025 Dirk Beyer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Copyright (C) 2016 Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + * Copyright (C) 2016 University of Freiburg + * + * This file is part of the ULTIMATE JUnit Helper Library. + * + * The ULTIMATE JUnit Helper Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ULTIMATE JUnit Helper Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ULTIMATE JUnit Helper Library. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * If you modify the ULTIMATE JUnit Helper Library, or any covered work, by linking + * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), + * containing parts covered by the terms of the Eclipse Public License, the + * licensors of the ULTIMATE JUnit Helper Library grant you additional permission + * to convey the resulting work. + */ + +package org.sosy_lab.java_smt.test.ultimate; + +import de.uni_freiburg.informatik.ultimate.core.model.services.ILogger; +import de.uni_freiburg.informatik.ultimate.core.model.services.ILogger.LogLevel; +import de.uni_freiburg.informatik.ultimate.core.model.services.ILoggingService; +import de.uni_freiburg.informatik.ultimate.core.model.services.IToolchainStorage; +import java.io.Writer; + +/** + * @author Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + */ +final class LoggingServiceMock implements ILoggingService { + + private final LogLevel mDefaultLevel; + + LoggingServiceMock(final LogLevel defaultLevel) { + mDefaultLevel = defaultLevel; + } + + @Override + public ILogger getLogger(final String pluginId) { + return new ConsoleLogger(mDefaultLevel); + } + + @Override + public ILogger getLogger(final Class clazz) { + return new ConsoleLogger(mDefaultLevel); + } + + @Override + public ILogger getLoggerForExternalTool(final String id) { + return new ConsoleLogger(mDefaultLevel); + } + + @Override + public ILogger getControllerLogger() { + return new ConsoleLogger(mDefaultLevel); + } + + @Override + public Object getBacking(final ILogger logger, final Class backingType) { + throw new UnsupportedOperationException(); + } + + @Override + public void addWriter(final Writer writer, final String logPattern) { + // do nothing + } + + @Override + public void removeWriter(final Writer writer) { + // do nothing + } + + @Override + public void reloadLoggers() { + // do nothing + } + + @Override + public void setCurrentControllerID(final String name) { + // do nothing + } + + @Override + public void store(final IToolchainStorage storage) { + // do nothing + } + + @Override + public void setLogLevel(final Class clazz, final LogLevel level) { + // do nothing + } + + @Override + public void setLogLevel(final String id, final LogLevel level) { + // do nothing + } +} diff --git a/src/org/sosy_lab/java_smt/test/ultimate/ProcedureContract.java b/src/org/sosy_lab/java_smt/test/ultimate/ProcedureContract.java new file mode 100644 index 0000000000..bf8799fd08 --- /dev/null +++ b/src/org/sosy_lab/java_smt/test/ultimate/ProcedureContract.java @@ -0,0 +1,146 @@ +/* + * This file is part of JavaSMT, + * an API wrapper for a collection of SMT solvers: + * https://github.com/sosy-lab/java-smt + * + * SPDX-FileCopyrightText: 2025 Dirk Beyer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Copyright (C) 2024 Dominik Klumpp (klumpp@informatik.uni-freiburg.de) + * Copyright (C) 2024 University of Freiburg + * + * This file is part of the ULTIMATE Core. + * + * The ULTIMATE Core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ULTIMATE Core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ULTIMATE Core. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * If you modify the ULTIMATE Core, or any covered work, by linking + * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), + * containing parts covered by the terms of the Eclipse Public License, the + * licensors of the ULTIMATE Core grant you additional permission + * to convey the resulting work. + */ + +package org.sosy_lab.java_smt.test.ultimate; + +import de.uni_freiburg.informatik.ultimate.core.model.models.annotation.Visualizable; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +/** + * A contract specifying the behaviour of a procedure (in Boogie or the ICFG) or function (in C). + * + *

The behaviour is specified using up to three kinds of clauses: + * + *

    + *
  1. A "requires" clause specifies a precondition that must hold before a call. + *
  2. An "ensures" clause specifies a relation guaranteed to hold between the state before the + * call and the state at the procedure exit. + *
  3. Optionally, a "modifies" clause specifies a subset of global variables that the procedure + * may write to. Writes to any other global state is forbidden. + *
+ * + * @author Dominik Klumpp (klumpp@informatik.uni-freiburg.de) + * @param The type of expressions + * @param The type of variables + */ +public class ProcedureContract { + @Visualizable private final String mProcedure; + + @Visualizable private final E mRequires; + + @Visualizable private final E mEnsures; + + @Visualizable private final Set mModifies; + + /** + * Creates a new contract that does not have a "modifies" clause, i.e., the contract specifies + * that the procedure may arbitrarily modify global state as long as it satisfies the "ensures" + * clause. + * + * @param procedure The procedure for which a contract is being created + * @param requires The "requires" clause. A value of {@code null} represents a trivial clause, + * i.e., the logical formula {@code true}. + * @param ensures The "ensures" clause. A value of {@code null} represents a trivial clause, i.e., + * the logical formula {@code true}. + */ + public ProcedureContract(final String procedure, final E requires, final E ensures) { + mProcedure = Objects.requireNonNull(procedure); + mRequires = requires; + mEnsures = ensures; + mModifies = null; + } + + /** + * Creates a new contract with a "modifies" clause, i.e., the contract specifies that the + * procedure may only modify the given global variables. + * + * @param procedure The procedure for which a contract is being created + * @param requires The "requires" clause. A value of {@code null} represents a trivial clause, + * i.e., the logical formula {@code true}. + * @param ensures The "ensures" clause. A value of {@code null} represents a trivial clause, i.e., + * the logical formula {@code true}. + * @param modifies The set of global variables modifiable by the procedure. + */ + public ProcedureContract( + final String procedure, final E requires, final E ensures, final Set modifies) { + mProcedure = Objects.requireNonNull(procedure); + mRequires = requires; + mEnsures = ensures; + mModifies = Objects.requireNonNull(modifies); + } + + public boolean hasModifies() { + return mModifies != null; + } + + public String getProcedure() { + return mProcedure; + } + + public E getRequires() { + return mRequires; + } + + public E getEnsures() { + return mEnsures; + } + + public Set getModifies() { + return Collections.unmodifiableSet(mModifies); + } + + /** + * Determines whether both the "requires" and "ensures" clauses of the contract are {@code null}, + * i.e., represent the logical formula {@code true}, and the contract does not have a "modifies" + * clause. + * + *

Note that this does not mean that the entire contract is trivial in the sense that any + * procedure would satisfy it, as a "requires" clause of {@code true} indicates that there can be + * no assertion failure during execution of the procedure for any input or initial global state. + * An entirely trivial contract would thus instead need to have a "requires" clause {@code false}. + * + *

For contracts with "modifies" clauses this method always return {@code false} as there is no + * way to determine if the "modifies" clause is trivial without knowing the entire set of global + * variables in the program: only when every variable can be modified, the clause would be + * trivial. + */ + public boolean hasOnlyTrivialClauses() { + return mRequires == null && mEnsures == null && !hasModifies(); + } +} diff --git a/src/org/sosy_lab/java_smt/test/ultimate/ProgramExecutionMock.java b/src/org/sosy_lab/java_smt/test/ultimate/ProgramExecutionMock.java new file mode 100644 index 0000000000..52c7d84f5d --- /dev/null +++ b/src/org/sosy_lab/java_smt/test/ultimate/ProgramExecutionMock.java @@ -0,0 +1,102 @@ +/* + * This file is part of JavaSMT, + * an API wrapper for a collection of SMT solvers: + * https://github.com/sosy-lab/java-smt + * + * SPDX-FileCopyrightText: 2025 Dirk Beyer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Copyright (C) 2016 Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + * Copyright (C) 2016 University of Freiburg + * + * This file is part of the ULTIMATE JUnit Helper Library. + * + * The ULTIMATE JUnit Helper Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ULTIMATE JUnit Helper Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ULTIMATE JUnit Helper Library. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * If you modify the ULTIMATE JUnit Helper Library, or any covered work, by linking + * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), + * containing parts covered by the terms of the Eclipse Public License, the + * licensors of the ULTIMATE JUnit Helper Library grant you additional permission + * to convey the resulting work. + */ + +package org.sosy_lab.java_smt.test.ultimate; + +import com.google.common.collect.ImmutableMap; +import de.uni_freiburg.informatik.ultimate.core.lib.results.NoBacktranslationValueProvider; +import de.uni_freiburg.informatik.ultimate.core.model.translation.AtomicTraceElement; +import de.uni_freiburg.informatik.ultimate.core.model.translation.IBacktranslationValueProvider; +import de.uni_freiburg.informatik.ultimate.core.model.translation.IProgramExecution; + +/** + * This class mocks {@link IProgramExecution}. It can be used in JUnit tests. + * + * @author Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + * @param TraceElementClazz + * @param ExprClazz + */ +final class ProgramExecutionMock implements IProgramExecution { + + private final Class mExprClazz; + private final Class mTraceElementClazz; + + ProgramExecutionMock(final Class exprClazz, final Class traceElementClazz) { + mExprClazz = exprClazz; + mTraceElementClazz = traceElementClazz; + } + + @Override + public int getLength() { + return 0; + } + + @Override + public AtomicTraceElement getTraceElement(final int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ProgramState getProgramState(final int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ProgramState getInitialProgramState() { + return new ProgramState<>(ImmutableMap.of(), null); + } + + @Override + public Class getExpressionClass() { + return mExprClazz; + } + + @Override + public Class getTraceElementClass() { + return mTraceElementClazz; + } + + @Override + public boolean isConcurrent() { + return false; + } + + @Override + public IBacktranslationValueProvider getBacktranslationValueProvider() { + return new NoBacktranslationValueProvider<>(); + } +} diff --git a/src/org/sosy_lab/java_smt/test/ultimate/ProgressMonitorServiceMock.java b/src/org/sosy_lab/java_smt/test/ultimate/ProgressMonitorServiceMock.java new file mode 100644 index 0000000000..75876c6c85 --- /dev/null +++ b/src/org/sosy_lab/java_smt/test/ultimate/ProgressMonitorServiceMock.java @@ -0,0 +1,114 @@ +/* + * This file is part of JavaSMT, + * an API wrapper for a collection of SMT solvers: + * https://github.com/sosy-lab/java-smt + * + * SPDX-FileCopyrightText: 2025 Dirk Beyer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Copyright (C) 2016 Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + * Copyright (C) 2016 University of Freiburg + * + * This file is part of the ULTIMATE JUnit Helper Library. + * + * The ULTIMATE JUnit Helper Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ULTIMATE JUnit Helper Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ULTIMATE JUnit Helper Library. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * If you modify the ULTIMATE JUnit Helper Library, or any covered work, by linking + * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), + * containing parts covered by the terms of the Eclipse Public License, the + * licensors of the ULTIMATE JUnit Helper Library grant you additional permission + * to convey the resulting work. + */ + +package org.sosy_lab.java_smt.test.ultimate; + +import de.uni_freiburg.informatik.ultimate.core.model.services.IProgressAwareTimer; +import de.uni_freiburg.informatik.ultimate.core.model.services.IProgressMonitorService; +import de.uni_freiburg.informatik.ultimate.core.model.services.IUltimateServiceProvider; +import java.util.concurrent.CountDownLatch; + +/** + * @author Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + */ +final class ProgressMonitorServiceMock implements IProgressMonitorService { + + private long mDeadline = -1; + + @Override + public boolean continueProcessing() { + if (mDeadline == -1) { + return true; + } + return System.currentTimeMillis() < mDeadline; + } + + @Override + public IProgressAwareTimer getChildTimer(final long timeout) { + throw new UnsupportedOperationException(); + } + + @Override + public IProgressAwareTimer getChildTimer(final double percentage) { + throw new UnsupportedOperationException(); + } + + @Override + public void setSubtask(final String task) { + // mock + } + + @Override + public void setDeadline(final long date) { + mDeadline = date; + } + + @Override + public CountDownLatch cancelToolchain() { + return new CountDownLatch(0); + } + + @Override + public IProgressAwareTimer getParent() { + return null; + } + + @Override + public long getDeadline() { + return mDeadline; + } + + @Override + public IProgressAwareTimer getTimer(final long timeout) { + throw new UnsupportedOperationException(); + } + + @Override + public long remainingTime() { + if (mDeadline == -1) { + return -1; + } + return System.currentTimeMillis() - mDeadline; + } + + @Override + public IUltimateServiceProvider registerChildTimer( + final IUltimateServiceProvider services, final IProgressAwareTimer timer) { + // mock, does not set timer + return services; + } +} diff --git a/src/org/sosy_lab/java_smt/test/ultimate/ResultServiceMock.java b/src/org/sosy_lab/java_smt/test/ultimate/ResultServiceMock.java new file mode 100644 index 0000000000..46ddae810c --- /dev/null +++ b/src/org/sosy_lab/java_smt/test/ultimate/ResultServiceMock.java @@ -0,0 +1,64 @@ +/* + * This file is part of JavaSMT, + * an API wrapper for a collection of SMT solvers: + * https://github.com/sosy-lab/java-smt + * + * SPDX-FileCopyrightText: 2025 Dirk Beyer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Copyright (C) 2016 Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + * Copyright (C) 2016 University of Freiburg + * + * This file is part of the ULTIMATE JUnit Helper Library. + * + * The ULTIMATE JUnit Helper Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ULTIMATE JUnit Helper Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ULTIMATE JUnit Helper Library. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * If you modify the ULTIMATE JUnit Helper Library, or any covered work, by linking + * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), + * containing parts covered by the terms of the Eclipse Public License, the + * licensors of the ULTIMATE JUnit Helper Library grant you additional permission + * to convey the resulting work. + */ + +package org.sosy_lab.java_smt.test.ultimate; + +import com.google.common.collect.ImmutableMap; +import de.uni_freiburg.informatik.ultimate.core.model.results.IResult; +import de.uni_freiburg.informatik.ultimate.core.model.services.IResultService; +import java.util.List; +import java.util.Map; +import java.util.function.UnaryOperator; + +final class ResultServiceMock implements IResultService { + + @Override + public Map> getResults() { + return ImmutableMap.of(); + } + + @Override + public void reportResult(final String pluginId, final IResult result) { + // do nothing + } + + @Override + public void registerTransformer( + final String name, final UnaryOperator resultTransformer) { + // do nothing + } +} diff --git a/src/org/sosy_lab/java_smt/test/ultimate/UltimateEliminatorWrapper.java b/src/org/sosy_lab/java_smt/test/ultimate/UltimateEliminatorWrapper.java new file mode 100644 index 0000000000..c8de5591b7 --- /dev/null +++ b/src/org/sosy_lab/java_smt/test/ultimate/UltimateEliminatorWrapper.java @@ -0,0 +1,68 @@ +/* + * This file is part of JavaSMT, + * an API wrapper for a collection of SMT solvers: + * https://github.com/sosy-lab/java-smt + * + * SPDX-FileCopyrightText: 2024 Dirk Beyer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.sosy_lab.java_smt.test.ultimate; + +import de.uni_freiburg.informatik.ultimate.core.model.services.ILogger; +import de.uni_freiburg.informatik.ultimate.core.model.services.IUltimateServiceProvider; +import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.smt.UltimateEliminator; +import de.uni_freiburg.informatik.ultimate.logic.Logics; +import de.uni_freiburg.informatik.ultimate.logic.Script; +import de.uni_freiburg.informatik.ultimate.logic.Term; +import de.uni_freiburg.informatik.ultimate.smtinterpol.smtlib2.SMTInterpol; +import org.sosy_lab.common.Appender; +import org.sosy_lab.common.log.LogManager; +import org.sosy_lab.java_smt.solvers.smtinterpol.UltimateEliminatorParser; + +/* + * UltimateEliminatorWrapper is a wrapper for the UltimateEliminator, which is used to + * eliminate quantifiers from formulas. + * + * This class provides methods to simplify formulas, parse strings into terms, and dump formulas. + */ +public class UltimateEliminatorWrapper { + private final IUltimateServiceProvider provider; + private final ILogger iLogger; + private final UltimateEliminator ultimateEliminator; + private final Script interpol; + LogManager log; + + public UltimateEliminatorWrapper(LogManager pLog) { + provider = + org.sosy_lab.java_smt.test.ultimate.UltimateServiceProviderMock + .createUltimateServiceProviderMock(); + iLogger = provider.getLoggingService().getControllerLogger(); + interpol = new SMTInterpol(); + ultimateEliminator = new UltimateEliminator(provider, iLogger, interpol); + ultimateEliminator.setLogic(Logics.AUFNIRA); + log = pLog; + } + + /* + * Simplifies and try to remove the quantifiers from the given term using the UltimateEliminator. + */ + public Term simplify(Term pTerm) { + return ultimateEliminator.simplify(pTerm); + } + + /* + * Parses a string into a term using the UltimateEliminator parser. + */ + public Term parse(String pString) { + return UltimateEliminatorParser.parseImpl(pString, log, ultimateEliminator); + } + + /* + * Dumps the formula in SMT-LIB2 format using the UltimateEliminator parser. + */ + public Appender dumpFormula(Term pFormula) { + return UltimateEliminatorParser.dumpFormula(pFormula); + } +} diff --git a/src/org/sosy_lab/java_smt/test/ultimate/UltimateServiceProviderMock.java b/src/org/sosy_lab/java_smt/test/ultimate/UltimateServiceProviderMock.java new file mode 100644 index 0000000000..db9ed6a639 --- /dev/null +++ b/src/org/sosy_lab/java_smt/test/ultimate/UltimateServiceProviderMock.java @@ -0,0 +1,118 @@ +/* + * This file is part of JavaSMT, + * an API wrapper for a collection of SMT solvers: + * https://github.com/sosy-lab/java-smt + * + * SPDX-FileCopyrightText: 2025 Dirk Beyer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Copyright (C) 2016 Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + * Copyright (C) 2016 University of Freiburg + * + * This file is part of the ULTIMATE JUnit Helper Library. + * + * The ULTIMATE JUnit Helper Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ULTIMATE JUnit Helper Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ULTIMATE JUnit Helper Library. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * If you modify the ULTIMATE JUnit Helper Library, or any covered work, by linking + * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), + * containing parts covered by the terms of the Eclipse Public License, the + * licensors of the ULTIMATE JUnit Helper Library grant you additional permission + * to convey the resulting work. + */ + +package org.sosy_lab.java_smt.test.ultimate; + +import de.uni_freiburg.informatik.ultimate.core.coreplugin.services.ToolchainStorage; +import de.uni_freiburg.informatik.ultimate.core.model.IServiceFactory; +import de.uni_freiburg.informatik.ultimate.core.model.preferences.IPreferenceProvider; +import de.uni_freiburg.informatik.ultimate.core.model.services.IBacktranslationService; +import de.uni_freiburg.informatik.ultimate.core.model.services.ILogger.LogLevel; +import de.uni_freiburg.informatik.ultimate.core.model.services.ILoggingService; +import de.uni_freiburg.informatik.ultimate.core.model.services.IProgressMonitorService; +import de.uni_freiburg.informatik.ultimate.core.model.services.IResultService; +import de.uni_freiburg.informatik.ultimate.core.model.services.IService; +import de.uni_freiburg.informatik.ultimate.core.model.services.IToolchainStorage; +import de.uni_freiburg.informatik.ultimate.core.model.services.IUltimateServiceProvider; + +/** + * @author Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + */ +public class UltimateServiceProviderMock implements IUltimateServiceProvider { + + private final LogLevel mDefaultLevel; + private final IProgressMonitorService mProgressMonitorService = new ProgressMonitorServiceMock(); + private final IToolchainStorage mStorage; + + UltimateServiceProviderMock(final LogLevel defaultLevel) { + mDefaultLevel = defaultLevel; + mStorage = new ToolchainStorage(); + } + + public static IUltimateServiceProvider createUltimateServiceProviderMock() { + return new UltimateServiceProviderMock(LogLevel.DEBUG); + } + + @Override + public IBacktranslationService getBacktranslationService() { + return new BacktranslationServiceMock(); + } + + @Override + public ILoggingService getLoggingService() { + return new LoggingServiceMock(mDefaultLevel); + } + + @Override + public IResultService getResultService() { + return new ResultServiceMock(); + } + + @Override + public IProgressMonitorService getProgressMonitorService() { + return mProgressMonitorService; + } + + @Override + public > T getServiceInstance( + final Class serviceType) { + // never find the matching service + return null; + } + + @Override + public IPreferenceProvider getPreferenceProvider(final String pluginId) { + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public IUltimateServiceProvider registerPreferenceLayer( + final Class creator, final String... pluginIds) { + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public IUltimateServiceProvider registerDefaultPreferenceLayer( + final Class creator, final String... pluginIds) { + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public IToolchainStorage getStorage() { + return mStorage; + } +} diff --git a/src/org/sosy_lab/java_smt/test/ultimate/package-info.java b/src/org/sosy_lab/java_smt/test/ultimate/package-info.java new file mode 100644 index 0000000000..506576cc81 --- /dev/null +++ b/src/org/sosy_lab/java_smt/test/ultimate/package-info.java @@ -0,0 +1,38 @@ +/* + * This file is part of JavaSMT, + * an API wrapper for a collection of SMT solvers: + * https://github.com/sosy-lab/java-smt + * + * SPDX-FileCopyrightText: 2025 Dirk Beyer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Copyright (C) 2016 Daniel Dietsch (dietsch@informatik.uni-freiburg.de) + * Copyright (C) 2016 University of Freiburg + * + * This file is part of the ULTIMATE JUnit Helper Library. + * + * The ULTIMATE JUnit Helper Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ULTIMATE JUnit Helper Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ULTIMATE JUnit Helper Library. If not, see . + * + * Additional permission under GNU GPL version 3 section 7: + * If you modify the ULTIMATE JUnit Helper Library, or any covered work, by linking + * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), + * containing parts covered by the terms of the Eclipse Public License, the + * licensors of the ULTIMATE JUnit Helper Library grant you additional permission + * to convey the resulting work. + */ + +package org.sosy_lab.java_smt.test.ultimate; diff --git a/src/org/sosy_lab/java_smt/utils/UfElimination.java b/src/org/sosy_lab/java_smt/utils/UfElimination.java index 9c3ba7b0a7..e44327e1f6 100644 --- a/src/org/sosy_lab/java_smt/utils/UfElimination.java +++ b/src/org/sosy_lab/java_smt/utils/UfElimination.java @@ -40,7 +40,6 @@ import org.sosy_lab.java_smt.api.FunctionDeclarationKind; import org.sosy_lab.java_smt.api.NumeralFormula; import org.sosy_lab.java_smt.api.NumeralFormula.IntegerFormula; -import org.sosy_lab.java_smt.api.QuantifiedFormulaManager; import org.sosy_lab.java_smt.api.QuantifiedFormulaManager.Quantifier; import org.sosy_lab.java_smt.api.StringFormula; import org.sosy_lab.java_smt.api.visitors.DefaultFormulaVisitor; @@ -291,7 +290,7 @@ public Integer visitFunction( @Override public Integer visitQuantifier( BooleanFormula pF, - QuantifiedFormulaManager.Quantifier pQ, + Quantifier pQ, List pBoundVariables, BooleanFormula pBody) { return fmgr.visit(pBody, this);