Adding some missing python API methods and tests (#8441)
authoryoni206 <yoni206@users.noreply.github.com>
Wed, 30 Mar 2022 06:05:49 +0000 (09:05 +0300)
committerGitHub <noreply@github.com>
Wed, 30 Mar 2022 06:05:49 +0000 (06:05 +0000)
This PR adds methods to the python API, as well as tests for the new methods and other tests for existing methods, translated from solver_black.cpp.

src/api/python/cvc5.pxd
src/api/python/cvc5.pxi
test/unit/api/python/test_solver.py

index ae92c4a82265a331c075f0ce63092fc7d760ff7a..5cc6f1c658b7c069a9a8e1a0693f7b5921bd9e65 100644 (file)
@@ -6,6 +6,7 @@ from libcpp.map cimport map as c_map
 from libcpp.set cimport set
 from libcpp.string cimport string
 from libcpp.vector cimport vector
+from libcpp.map cimport map
 from libcpp.pair cimport pair
 from cvc5kinds cimport Kind
 from cvc5types cimport RoundingMode
@@ -245,6 +246,7 @@ cdef extern from "api/cpp/cvc5.h" namespace "cvc5":
         Term declareSygusVar(Sort sort, const string& symbol) except +
         Term declareSygusVar(Sort sort) except +
         void addSygusConstraint(Term term) except +
+        void addSygusAssume(Term term) except +
         void addSygusInvConstraint(Term inv_f, Term pre_f, Term trans_f, Term post_f) except +
         Term synthFun(const string& symbol, const vector[Term]& bound_vars, Sort sort) except +
         Term synthFun(const string& symbol, const vector[Term]& bound_vars, Sort sort, Grammar grammar) except +
@@ -327,8 +329,11 @@ cdef extern from "api/cpp/cvc5.h" namespace "cvc5":
         OptionInfo getOptionInfo(const string& option) except +
         vector[Term] getUnsatAssumptions() except +
         vector[Term] getUnsatCore() except +
+        map[Term,Term] getDifficulty() except +
         Term getValue(Term term) except +
         vector[Term] getValue(const vector[Term]& terms) except +
+        Term getQuantifierElimination(const Term& q) except +
+        Term getQuantifierEliminationDisjunct(const Term& q) except +
         vector[Term] getModelDomainElements(Sort sort) except +
         bint isModelCoreSymbol(Term v) except +
         string getModel(const vector[Sort]& sorts,
index a4c85a768a889e660f1f5aecc861cc0b1764b46f..81809b5647afc911fa876d4159a6423b79417178 100644 (file)
@@ -1602,6 +1602,20 @@ cdef class Solver:
         """
         self.csolver.addSygusConstraint(t.cterm)
 
+    def addSygusAssume(self, Term t):
+        """
+        Add a formula to the set of Sygus assumptions.
+
+        SyGuS v2:
+
+        .. code-block:: smtlib
+
+            ( assume <term> )
+
+        :param term: the formuula to add as an assumption
+        """
+        self.csolver.addSygusAssume(t.cterm)
+
     def addSygusInvConstraint(self, Term inv_f, Term pre_f, Term trans_f, Term post_f):
         """
         Add a set of SyGuS constraints to the current state that correspond to an
@@ -1999,7 +2013,8 @@ cdef class Solver:
         return assertions
 
     def getInfo(self, str flag):
-        """Get info from the solver.
+        """
+        Get info from the solver.
 
         SMT-LIB:
 
@@ -2104,6 +2119,15 @@ cdef class Solver:
             res['modes'] = [s.decode() for s in mi.modes]
         return res
 
+    def getOptionNames(self):
+       """Get all option names that can be used with `setOption`, `getOption` and `getOptionInfo`.
+       :return: all option names
+       """
+       result = []
+       for n in self.csolver.getOptionNames():
+           result += [n.decode()]
+       return result
+
     def getUnsatAssumptions(self):
         """
         Get the set of unsat ("failed") assumptions.
@@ -2152,6 +2176,30 @@ cdef class Solver:
             core.append(term)
         return core
 
+    def getDifficulty(self):
+        """
+            Get a difficulty estimate for an asserted formula. This method is intended to be called immediately after 
+            any response to a checkSat.
+
+            .. warning:: This method is experimental and may change in future
+                         versions.
+
+            :return: a map from (a subset of) the input assertions to a real value that is an estimate of how difficult each assertion was to solver. Unmentioned assertions can be assumed to have zero difficulty.
+        """
+        diffi = {}
+        for p in self.csolver.getDifficulty():
+            k = p.first
+            v = p.second
+
+            termk = Term(self)
+            termk.cterm = k
+
+            termv = Term(self)
+            termv.cterm = v
+
+            diffi[termk] = termv
+        return diffi
+
     def getValue(self, Term t):
         """Get the value of the given term in the current model.
 
@@ -2200,6 +2248,88 @@ cdef class Solver:
         """
         return self.csolver.isModelCoreSymbol(v.cterm)
 
+    def getQuantifierElimination(self, Term term):
+        """Do quantifier elimination.
+
+        SMT-LIB:
+
+        .. code-block:: smtlib
+
+            ( get-qe <q> )
+
+        Requires a logic that supports quantifier elimination.
+        Currently, the only logics supported by quantifier elimination
+        are LRA and LIA.
+
+        .. warning:: This method is experimental and may change in future
+                         versions.
+
+        :param q: a quantified formula of the form
+                :math:`Q\bar{x}_1... Q\bar{x}_n. P( x_1...x_i, y_1...y_j)'
+                where
+                :math:'Q\bar{x}' is a set of quantified variables of the form
+                :math:'Q x_1...x_k' and
+                :math:'P( x_1...x_i, y_1...y_j )' is a quantifier-free formula
+        :return: a formula :math:'\phi'  such that, given the current set of formulas
+               :math:'A asserted to this solver:
+               - :math:'(A \wedge q)' :math:'(A \wedge \phi)' are equivalent
+               - :math:'\phi' is quantifier-free formula containing only free
+                 variables in :math:'y_1...y_n'.
+        """
+        cdef Term result = Term(self)
+        result.cterm = self.csolver.getQuantifierElimination(term.cterm)
+        return result
+
+    def getQuantifierEliminationDisjunct(self, Term term):
+        """Do partial quantifier elimination, which can be used for incrementally computing
+        the result of a quantifier elimination.
+
+        SMT-LIB:
+
+        .. code-block:: smtlib
+
+            ( get-qe-disjunct <q> )
+
+        Requires a logic that supports quantifier elimination.
+        Currently, the only logics supported by quantifier elimination
+        are LRA and LIA.
+            
+       .. warning:: This method is experimental and may change in future
+                         versions.
+        
+           :param q: a quantified formula of the form
+                   @f$Q\bar{x}_1... Q\bar{x}_n. P( x_1...x_i, y_1...y_j)@f$
+                   where
+                   @f$Q\bar{x}@f$ is a set of quantified variables of the form
+                   @f$Q x_1...x_k@f$ and
+                   @f$P( x_1...x_i, y_1...y_j )@f$ is a quantifier-free formula
+           :return: a formula @f$\phi@f$ such that, given the current set of formulas
+                  @f$A@f$ asserted to this solver:
+                  - @f$(A \wedge q \implies A \wedge \phi)@f$ if @f$Q@f$ is
+                    @f$\forall@f$, and @f$(A \wedge \phi \implies A \wedge q)@f$ if
+                    @f$Q@f$ is @f$\exists@f$
+                  - @f$\phi@f$ is quantifier-free formula containing only free
+                    variables in @f$y_1...y_n@f$
+                  - If @f$Q@f$ is @f$\exists@f$, let @f$(A \wedge Q_n)@f$ be the
+                    formula
+                    @f$(A \wedge \neg (\phi \wedge Q_1) \wedge ... \wedge
+                    \neg (\phi \wedge Q_n))@f$
+                    where for each @f$i = 1...n@f$,
+                    formula @f$(\phi \wedge Q_i)@f$ is the result of calling
+                    Solver::getQuantifierEliminationDisjunct() for @f$q@f$ with the
+                    set of assertions @f$(A \wedge Q_{i-1})@f$.
+                    Similarly, if @f$Q@f$ is @f$\forall@f$, then let
+                    @f$(A \wedge Q_n)@f$ be
+                    @f$(A \wedge (\phi \wedge Q_1) \wedge ... \wedge (\phi \wedge
+                    Q_n))@f$
+                    where @f$(\phi \wedge Q_i)@f$ is the same as above.
+                    In either case, we have that @f$(\phi \wedge Q_j)@f$ will
+                    eventually be true or false, for some finite j.
+        """
+        cdef Term result = Term(self)
+        result.cterm = self.csolver.getQuantifierEliminationDisjunct(term.cterm)
+        return result
+    
     def getModel(self, sorts, consts):
         """Get the model
 
index 9ba032f0a94282394121c00666b34a3d4d4f8731..f07aa8e7438f4127152d7f62c83d642bbe8c11e7 100644 (file)
@@ -1600,6 +1600,7 @@ def test_block_model3(solver):
     with pytest.raises(RuntimeError):
         solver.blockModel()
 
+
 def test_block_model4(solver):
     solver.setOption("produce-models", "true")
     solver.setOption("block-models", "literals")
@@ -1966,6 +1967,20 @@ def test_add_sygus_constraint(solver):
     with pytest.raises(RuntimeError):
         slv.addSygusConstraint(boolTerm)
 
+def test_add_sygus_assume(solver):
+    solver.setOption("sygus", "true")
+    nullTerm = cvc5.Term(solver)
+    boolTerm = solver.mkBoolean(False)
+    intTerm = solver.mkInteger(1)
+    solver.addSygusAssume(boolTerm)
+    with pytest.raises(RuntimeError):
+        solver.addSygusAssume(nullTerm)
+    with pytest.raises(RuntimeError):
+        solver.addSygusAssume(intTerm)
+    slv = cvc5.Solver()
+    with pytest.raises(RuntimeError):
+        slv.addSygusAssume(boolTerm)
+
 
 def test_add_sygus_inv_constraint(solver):
     solver.setOption("sygus", "true")
@@ -2265,6 +2280,20 @@ def test_get_model_domain_elements(solver):
     with pytest.raises(RuntimeError):
         solver.getModelDomainElements(intSort)
 
+def test_get_model_domain_elements2(solver):
+    solver.setOption("produce-models", "true")
+    solver.setOption("finite-model-find", "true")
+    uSort = solver.mkUninterpretedSort("u")
+    x = solver.mkVar(uSort, "x")
+    y = solver.mkVar(uSort, "y")
+    eq = solver.mkTerm(Kind.Equal, x, y)
+    bvl = solver.mkTerm(Kind.VariableList, x, y)
+    f = solver.mkTerm(Kind.Forall, bvl, eq)
+    solver.assertFormula(f)
+    solver.checkSat()
+    solver.getModelDomainElements(uSort)
+    assert len(solver.getModelDomainElements(uSort)) == 1
+
 
 def test_get_synth_solutions(solver):
     solver.setOption("sygus", "true")
@@ -2587,3 +2616,102 @@ def test_tuple_project(solver):
 
         assert "((_ tuple.project 0 3 2 0 1 2) (tuple true 3 \"C\" (set.singleton \"Z\")))" == str(
             projection)
+
+
+
+def test_get_data_type_arity(solver):
+  ctor1 = solver.mkDatatypeConstructorDecl("_x21");
+  ctor2 = solver.mkDatatypeConstructorDecl("_x31");
+  s3 = solver.declareDatatype("_x17", [ctor1, ctor2]);
+  assert s3.getDatatypeArity() == 0
+
+
+def test_get_difficulty(solver):
+  solver.setOption("produce-difficulty", "true")
+  # cannot ask before a check sat
+  with pytest.raises(RuntimeError):
+      solver.getDifficulty()
+  solver.checkSat()
+  solver.getDifficulty()
+
+def test_get_difficulty2(solver):
+  solver.checkSat()
+  with pytest.raises(RuntimeError):
+  # option is not set
+      solver.getDifficulty()
+
+def test_get_difficulty3(solver):
+  solver.setOption("produce-difficulty", "true")
+  intSort = solver.getIntegerSort()
+  x = solver.mkConst(intSort, "x")
+  zero = solver.mkInteger(0)
+  ten = solver.mkInteger(10)
+  f0 = solver.mkTerm(Kind.Geq, x, ten)
+  f1 = solver.mkTerm(Kind.Geq, zero, x)
+  solver.checkSat()
+  dmap = solver.getDifficulty()
+  # difficulty should map assertions to integer values
+  for key, value in dmap.items():
+    assert key == f0 or key == f1
+    assert value.getKind() == Kind.CONST_RATIONAL
+
+def test_get_model(solver):
+    solver.setOption("produce-models", "true")
+    uSort = solver.mkUninterpretedSort("u")
+    x = solver.mkConst(uSort, "x")
+    y = solver.mkConst(uSort, "y")
+    z = solver.mkConst(uSort, "z")
+    f = solver.mkTerm(Kind.Not, solver.mkTerm(Kind.Equal, x, y));
+    solver.assertFormula(f)
+    solver.checkSat()
+    sorts = [uSort]
+    terms = [x, y]
+    solver.getModel(sorts, terms)
+    null = cvc5.Term(solver)
+    terms += [null]
+    with pytest.raises(RuntimeError):
+        solver.getModel(sorts, terms)
+
+def test_get_model2(solver):
+    solver.setOption("produce-models", "true")
+    sorts = []
+    terms = []
+    with pytest.raises(RuntimeError):
+        solver.getModel(sorts, terms)
+
+def test_get_model_3(solver):
+    solver.setOption("produce-models", "true")
+    sorts = []
+    terms = []
+    solver.checkSat()
+    solver.getModel(sorts, terms)
+    integer = solver.getIntegerSort()
+    sorts += [integer]
+    with pytest.raises(RuntimeError):
+        solver.getModel(sorts, terms)
+
+def test_get_option_names(solver):
+    names = solver.getOptionNames()
+    assert len(names) > 100
+    assert "verbose" in names
+    assert "foobar" not in names
+
+def test_get_quantifier_elimination(solver):
+    x = solver.mkVar(solver.getBooleanSort(), "x")
+    forall = solver.mkTerm(Kind.Forall, solver.mkTerm(Kind.VariableList, x), solver.mkTerm(Kind.Or, x, solver.mkTerm(Kind.Not, x)))
+    with pytest.raises(RuntimeError):
+        solver.getQuantifierElimination(cvc5.Term(solver))
+    with pytest.raises(RuntimeError):
+        solver.getQuantifierElimination(cvc5.Solver().mkBoolean(False))
+    solver.getQuantifierElimination(forall)
+
+def test_get_quantifier_elimination_disjunct(solver):
+    x = solver.mkVar(solver.getBooleanSort(), "x")
+    forall = solver.mkTerm(Kind.Forall, solver.mkTerm(Kind.VariableList, x), solver.mkTerm(Kind.Or, x, solver.mkTerm(Kind.Not, x)))
+    with pytest.raises(RuntimeError):
+        solver.getQuantifierEliminationDisjunct(cvc5.Term(solver))
+    with pytest.raises(RuntimeError):
+        solver.getQuantifierEliminationDisjunct(cvc5.Solver().mkBoolean(False))
+    solver.getQuantifierEliminationDisjunct(forall)
+    
+