Quickstart Guide

First, create a cvc5 Solver instance:

  Solver solver;

We will ask the solver to produce models and unsat cores in the following, and for this we have to enable the following options.

  solver.setOption("produce-models", "true");
  solver.setOption("produce-unsat-cores", "true");

Next we set the logic. The simplest way to set a logic for the solver is to choose “ALL”. This enables all logics in the solver. Alternatively, "QF_ALL" enables all logics without quantifiers. To optimize the solver’s behavior for a more specific logic, use the logic name, e.g. "QF_BV" or "QF_AUFBV" .

  solver.setLogic("ALL");

In the following, we will define constraints of reals and integers. For this, we first query the solver for the corresponding sorts.

  Sort realSort = solver.getRealSort();
  Sort intSort = solver.getIntegerSort();

Now, we create two constants x and y of sort Real , and two constants a and b of sort Integer . Notice that these are symbolic constants, but not actual values.

  Term x = solver.mkConst(realSort, "x");
  Term y = solver.mkConst(realSort, "y");
  Term a = solver.mkConst(intSort, "a");
  Term b = solver.mkConst(intSort, "b");

We define the following constraints regarding x and y :

\[(0 < x) \wedge (0 < y) \wedge (x + y < 1) \wedge (x \leq y)\]

We construct the required terms and assert them as follows:

  // Formally, constraints are also terms. Their sort is Boolean.
  // We will construct these constraints gradually,
  // by defining each of their components.
  // We start with the constant numerals 0 and 1:
  Term zero = solver.mkReal(0);
  Term one = solver.mkReal(1);

  // Next, we construct the term x + y
  Term xPlusY = solver.mkTerm(ADD, {x, y});

  // Now we can define the constraints.
  // They use the operators +, <=, and <.
  // In the API, these are denoted by ADD, LEQ, and LT.
  // A list of available operators is available in:
  // src/api/cpp/cvc5_kind.h
  Term constraint1 = solver.mkTerm(LT, {zero, x});
  Term constraint2 = solver.mkTerm(LT, {zero, y});
  Term constraint3 = solver.mkTerm(LT, {xPlusY, one});
  Term constraint4 = solver.mkTerm(LEQ, {x, y});

  // Now we assert the constraints to the solver.
  solver.assertFormula(constraint1);
  solver.assertFormula(constraint2);
  solver.assertFormula(constraint3);
  solver.assertFormula(constraint4);

Now we check if the asserted formula is satisfiable, that is, we check if there exist values of sort Real for x and y that satisfy all the constraints.

  Result r1 = solver.checkSat();

The result we get from this satisfiability check is either sat , unsat or unknown . It’s status can be queried via cvc5::Result::isSat() , cvc5::Result::isUnsat() and cvc5::Result::isSatUnknown() . Alternatively, it can also be printed.

  std::cout << "expected: sat" << std::endl;
  std::cout << "result: " << r1 << std::endl;

This will print:

expected: sat
result: sat

Now, we query the solver for the values for x and y that satisfy the constraints.

  Term xVal = solver.getValue(x);
  Term yVal = solver.getValue(y);

It is also possible to get values for terms that do not appear in the original formula.

  Term xMinusY = solver.mkTerm(SUB, {x, y});
  Term xMinusYVal = solver.getValue(xMinusY);

We can retrieve the string representation of these values as follows.

  std::string xStr = xVal.getRealValue();
  std::string yStr = yVal.getRealValue();
  std::string xMinusYStr = xMinusYVal.getRealValue();

  std::cout << "value for x: " << xStr << std::endl;
  std::cout << "value for y: " << yStr << std::endl;
  std::cout << "value for x - y: " << xMinusYStr << std::endl;

This will print the following:

value for x: 1/6
value for y: 1/6
value for x - y: 0.0

We can convert these values to C++ types.

  // Further, we can convert the values to cpp types
  std::pair<int64_t, uint64_t> xPair = xVal.getReal64Value();
  std::pair<int64_t, uint64_t> yPair = yVal.getReal64Value();
  std::pair<int64_t, uint64_t> xMinusYPair = xMinusYVal.getReal64Value();

  std::cout << "value for x: " << xPair.first << "/" << xPair.second << std::endl;
  std::cout << "value for y: " << yPair.first << "/" << yPair.second << std::endl;
  std::cout << "value for x - y: " << xMinusYPair.first << "/" << xMinusYPair.second << std::endl;

Another way to independently compute the value of x - y would be to perform the (rational) arithmetic manually. However, for more complex terms, it is easier to let the solver do the evaluation.

  std::pair<int64_t, uint64_t> xMinusYComputed = {
    xPair.first * yPair.second - xPair.second * yPair.first,
    xPair.second * yPair.second
  };
  uint64_t g = std::gcd(xMinusYComputed.first, xMinusYComputed.second);
  xMinusYComputed = { xMinusYComputed.first / g, xMinusYComputed.second / g };
  if (xMinusYComputed == xMinusYPair)
  {
    std::cout << "computed correctly" << std::endl;
  }
  else
  {
    std::cout << "computed incorrectly" << std::endl;
  }

This will print:

computed correctly

Next, we will check satisfiability of the same formula, only this time over integer variables a and b . For this, we first reset the assertions added to the solver.

  solver.resetAssertions();

Next, we assert the same assertions as above, but with integers. This time, we inline the construction of terms to the assertion command.

  solver.assertFormula(solver.mkTerm(LT, {solver.mkInteger(0), a}));
  solver.assertFormula(solver.mkTerm(LT, {solver.mkInteger(0), b}));
  solver.assertFormula(
      solver.mkTerm(LT, {solver.mkTerm(ADD, {a, b}), solver.mkInteger(1)}));
  solver.assertFormula(solver.mkTerm(LEQ, {a, b}));

Now, we check whether the revised assertion is satisfiable.

  Result r2 = solver.checkSat();
  std::cout << "expected: unsat" << std::endl;
  std::cout << "result: " << r2 << std::endl;

This time the asserted formula is unsatisfiable:

expected: unsat
result: unsat

We can query the solver for an unsatisfiable core, that is, a subset of the assertions that is already unsatisfiable.

  std::vector<Term> unsatCore = solver.getUnsatCore();
  std::cout << "unsat core size: " << unsatCore.size() << std::endl;
  std::cout << "unsat core: " << std::endl;
  for (const Term& t : unsatCore)
  {
    std::cout << t << std::endl;
  }

This will print:

unsat core size: 3
unsat core:
(< 0 a)
(< 0 b)
(< (+ a b) 1)

Example

examples/api/cpp/quickstart.cpp

  1 /******************************************************************************
  2  * Top contributors (to current version):
  3  *   Yoni Zohar, Gereon Kremer, Mathias Preiner
  4  *
  5  * This file is part of the cvc5 project.
  6  *
  7  * Copyright (c) 2009-2022 by the authors listed in the file AUTHORS
  8  * in the top-level source directory and their institutional affiliations.
  9  * All rights reserved.  See the file COPYING in the top-level source
 10  * directory for licensing information.
 11  * ****************************************************************************
 12  *
 13  * A simple demonstration of the api capabilities of cvc5.
 14  *
 15  */
 16 
 17 #include <cvc5/cvc5.h>
 18 
 19 #include <iostream>
 20 #include <numeric>
 21 
 22 using namespace cvc5;
 23 
 24 int main()
 25 {
 26   // Create a solver
 27   Solver solver;
 28 
 29   // We will ask the solver to produce models and unsat cores,
 30   // hence these options should be turned on.
 31   solver.setOption("produce-models", "true");
 32   solver.setOption("produce-unsat-cores", "true");
 33 
 34   // The simplest way to set a logic for the solver is to choose "ALL".
 35   // This enables all logics in the solver.
 36   // Alternatively, "QF_ALL" enables all logics without quantifiers.
 37   // To optimize the solver's behavior for a more specific logic,
 38   // use the logic name, e.g. "QF_BV" or "QF_AUFBV".
 39 
 40   // Set the logic
 41   solver.setLogic("ALL");
 42 
 43   // In this example, we will define constraints over reals and integers.
 44   // Hence, we first obtain the corresponding sorts.
 45   Sort realSort = solver.getRealSort();
 46   Sort intSort = solver.getIntegerSort();
 47 
 48   // x and y will be real variables, while a and b will be integer variables.
 49   // Formally, their cpp type is Term,
 50   // and they are called "constants" in SMT jargon:
 51   Term x = solver.mkConst(realSort, "x");
 52   Term y = solver.mkConst(realSort, "y");
 53   Term a = solver.mkConst(intSort, "a");
 54   Term b = solver.mkConst(intSort, "b");
 55 
 56   // Our constraints regarding x and y will be:
 57   //
 58   //   (1)  0 < x
 59   //   (2)  0 < y
 60   //   (3)  x + y < 1
 61   //   (4)  x <= y
 62   //
 63 
 64   // Formally, constraints are also terms. Their sort is Boolean.
 65   // We will construct these constraints gradually,
 66   // by defining each of their components.
 67   // We start with the constant numerals 0 and 1:
 68   Term zero = solver.mkReal(0);
 69   Term one = solver.mkReal(1);
 70 
 71   // Next, we construct the term x + y
 72   Term xPlusY = solver.mkTerm(ADD, {x, y});
 73 
 74   // Now we can define the constraints.
 75   // They use the operators +, <=, and <.
 76   // In the API, these are denoted by ADD, LEQ, and LT.
 77   // A list of available operators is available in:
 78   // src/api/cpp/cvc5_kind.h
 79   Term constraint1 = solver.mkTerm(LT, {zero, x});
 80   Term constraint2 = solver.mkTerm(LT, {zero, y});
 81   Term constraint3 = solver.mkTerm(LT, {xPlusY, one});
 82   Term constraint4 = solver.mkTerm(LEQ, {x, y});
 83 
 84   // Now we assert the constraints to the solver.
 85   solver.assertFormula(constraint1);
 86   solver.assertFormula(constraint2);
 87   solver.assertFormula(constraint3);
 88   solver.assertFormula(constraint4);
 89 
 90   // Check if the formula is satisfiable, that is,
 91   // are there real values for x and y that satisfy all the constraints?
 92   Result r1 = solver.checkSat();
 93 
 94   // The result is either SAT, UNSAT, or UNKNOWN.
 95   // In this case, it is SAT.
 96   std::cout << "expected: sat" << std::endl;
 97   std::cout << "result: " << r1 << std::endl;
 98 
 99   // We can get the values for x and y that satisfy the constraints.
100   Term xVal = solver.getValue(x);
101   Term yVal = solver.getValue(y);
102 
103   // It is also possible to get values for compound terms,
104   // even if those did not appear in the original formula.
105   Term xMinusY = solver.mkTerm(SUB, {x, y});
106   Term xMinusYVal = solver.getValue(xMinusY);
107 
108   // We can now obtain the string representations of the values.
109   std::string xStr = xVal.getRealValue();
110   std::string yStr = yVal.getRealValue();
111   std::string xMinusYStr = xMinusYVal.getRealValue();
112 
113   std::cout << "value for x: " << xStr << std::endl;
114   std::cout << "value for y: " << yStr << std::endl;
115   std::cout << "value for x - y: " << xMinusYStr << std::endl;
116 
117   // Further, we can convert the values to cpp types
118   std::pair<int64_t, uint64_t> xPair = xVal.getReal64Value();
119   std::pair<int64_t, uint64_t> yPair = yVal.getReal64Value();
120   std::pair<int64_t, uint64_t> xMinusYPair = xMinusYVal.getReal64Value();
121 
122   std::cout << "value for x: " << xPair.first << "/" << xPair.second << std::endl;
123   std::cout << "value for y: " << yPair.first << "/" << yPair.second << std::endl;
124   std::cout << "value for x - y: " << xMinusYPair.first << "/" << xMinusYPair.second << std::endl;
125 
126   // Another way to independently compute the value of x - y would be
127   // to perform the (rational) arithmetic manually.
128   // However, for more complex terms,
129   // it is easier to let the solver do the evaluation.
130   std::pair<int64_t, uint64_t> xMinusYComputed = {
131     xPair.first * yPair.second - xPair.second * yPair.first,
132     xPair.second * yPair.second
133   };
134   uint64_t g = std::gcd(xMinusYComputed.first, xMinusYComputed.second);
135   xMinusYComputed = { xMinusYComputed.first / g, xMinusYComputed.second / g };
136   if (xMinusYComputed == xMinusYPair)
137   {
138     std::cout << "computed correctly" << std::endl;
139   }
140   else
141   {
142     std::cout << "computed incorrectly" << std::endl;
143   }
144 
145   // Next, we will check satisfiability of the same formula,
146   // only this time over integer variables a and b.
147 
148   // We start by resetting assertions added to the solver.
149   solver.resetAssertions();
150 
151   // Next, we assert the same assertions above with integers.
152   // This time, we inline the construction of terms
153   // to the assertion command.
154   solver.assertFormula(solver.mkTerm(LT, {solver.mkInteger(0), a}));
155   solver.assertFormula(solver.mkTerm(LT, {solver.mkInteger(0), b}));
156   solver.assertFormula(
157       solver.mkTerm(LT, {solver.mkTerm(ADD, {a, b}), solver.mkInteger(1)}));
158   solver.assertFormula(solver.mkTerm(LEQ, {a, b}));
159 
160   // We check whether the revised assertion is satisfiable.
161   Result r2 = solver.checkSat();
162 
163   // This time the formula is unsatisfiable
164   std::cout << "expected: unsat" << std::endl;
165   std::cout << "result: " << r2 << std::endl;
166 
167   // We can query the solver for an unsatisfiable core, i.e., a subset
168   // of the assertions that is already unsatisfiable.
169   std::vector<Term> unsatCore = solver.getUnsatCore();
170   std::cout << "unsat core size: " << unsatCore.size() << std::endl;
171   std::cout << "unsat core: " << std::endl;
172   for (const Term& t : unsatCore)
173   {
174     std::cout << t << std::endl;
175   }
176 
177   return 0;
178 }