# Basic cvc5 tutorial (pythonic API)
This is a gentle beginner's guide on how to use the pythonic API for cvc5. A more advanced programmer may prefer to consult the [cvc5 documentation](https://cvc5.github.io/docs/cvc5-1.0.2/api/python/pythonic/pythonic.html) directly.
I also recommend checking out [z3](https://z3prover.github.io/papers/programmingz3.html) and seeing how it compares to cvc5 for your desired application. The python API is mostly identical, and sometimes z3 outperforms cvc5.
## What is cvc5?
[cvc5](https://cvc5.github.io/) is a **SMT solver**. SMT stands for "satisfiability modulo theories". In short, this means that cvc5 can determine whether certain kinds of simple mathematical constraints are satisfiable or unsatisfiable.
**Satisfiable** means there exist values you can set your variables to make all of the provided constraints true. For example, $x+y+z = 5$ and $x-y-z = 1$ are satisfiable by $x=3, y=2, z=0$ (but there are many other solutions).
* Accordingly, if it is impossible to set your variables to make all of the provided constraints simultaneously true, then your constraints are *unsatisfiable*. For example, $x^2 + 2x + 3 < 0$ is unsatisfiable.
* (Note that SMT solvers can also be used to show that a statement always holds: just show that $\texttt{Not}(\text{statement})$ is unsatisfiable.)
**Modulo theories** is a fancy way of saying "SMT solvers only know certain simple parts of math". In logic, a (first-order) [*theory*](https://en.wikipedia.org/wiki/Theory_(mathematical_logic)) is roughly speaking a collection of true statements using certain mathematical notation. For example, $\forall x, y, z,\ x \leq y \wedge y \leq z \implies x \leq z$ is true in the theory of orders: no matter what kinds of items you compare, this statement always holds.
* cvc5 can handle the theories of basic logic and arithmetic, [and a few more things](https://cvc5.github.io/docs/cvc5-1.0.2/api/python/pythonic/pythonic.html), but certainly not everything (in part due to [the undecidability of many theories](https://en.wikipedia.org/wiki/Decidability_(logic))).
The **cvc5 pythonic API** lets you input your constraints into the solver using Python. This is nice because by default, cvc5 input files must be written in specialized languages such as SMT-LIBv2. With the pythonic API, you sacrifice a bit of functionality (see [here](https://cvc5.github.io/docs/cvc5-1.0.2/api/python/pythonic/pythonic.html) for details), but for quick applications it is often easier to use. (To compare the pythonic API with raw SMT-LIBv2, see [this examples page](https://cvc5.github.io/docs/cvc5-1.0.2/examples/quickstart.html).)
### Starter example
For example, suppose you want to determine whether you want to solve the following puzzle:
> Find distinct 0-9 digit values for each letter in the following equation to make it true: $SEND + MORE = MONEY$. (Also $M \neq 0$.)
In cvc5.pythonic you can solve the puzzle using this code:
```python=
from cvc5.pythonic import *
def to_num(vars : list) -> int:
"""
Given list of vars, returns a number using the vars as digits.
Example: [s,e,n,d] -> 1000*s + 100*e + 10*n + 1*d
"""
pows = [10**i for i in range(len(vars)-1, -1, -1)]
return sum([vars[i] * pows[i] for i in range(len(vars))])
if __name__ == "__main__":
vars = Ints('s e n d m o r y')
s, e, n, d, m, o, r, y = vars
solver = Solver()
for var in vars:
solver.add(var >= 0, var <= 9)
solver.add(Distinct(vars))
solver.add( m != 0 )
solver.add( to_num([s,e,n,d]) + to_num([m,o,r,e]) == to_num([m,o,n,e,y]) )
print(solver.check())
if solver.check() == sat:
m = solver.model()
print(m)
```
which outputs:
```python
sat
[d = 7, e = 5, m = 1, n = 6, o = 0, r = 8, s = 9, y = 2]
```
### Should you use cvc5 or another tool?
Use cvc5 (or another SMT solver like z3) if:
* You want to determine whether some constraints are *satisfiable* or *unsatisfiable*.
* Your constraints can be expressed using simple logic, arithmetic, set operations, and [other theories implemented by cvc5](https://cvc5.github.io/docs/cvc5-1.0.2/api/python/pythonic/pythonic.html). (See [the limitations of cvc5 section](#Quirks-and-limitations-of-cvc5) for more information.)
Situations where cvc5 is not suitable:
* Have a pure boolean logic formula like $(x_1 \wedge x_2) \vee (\neg x_3 \vee x_4) \vee \dots$? Use a [SAT solver](https://en.wikipedia.org/wiki/SAT_solver).
* Have a LP, MIP, or MINLP? Use a solver like [SCIP](https://scipopt.org/).
* Have complicated mathematical operations? Use tools like [SageMath](https://www.sagemath.org/), [Lean's mathlib](https://github.com/leanprover-community/mathlib), or [Mathematica](https://www.wolfram.com/mathematica/).
## Installing cvc5 (pythonic API)
To install the pythonic API of cvc5, run the following command:
```
pip install cvc5
```
To test that cvc5 is working, create a file `sendmoremoney.py`, write inside it the contents of the $SEND+MORE=MONEY$ program above, and run
```
python3 sendmoremoney.py
```
### (if applicable) fixing a bug with `pythonic_printer.py`
:::warning
Do you get an error `Exception: Cannot print: Kind.CONST_INTEGER`? Here is how to fix that:
1. Open the file `cd ~/.local/lib/python3.10/site-packages/cvc5/pythonic/pythonic_printer.py`
2. There should be a function `_op_name(a)` which looks like:
```python=
def _op_name(a):
k = a.kind()
n = _cvc5_kinds_to_str.get(k, None)
if n is None:
if k in [Kind.CONSTANT, Kind.CONST_FLOATINGPOINT, Kind.CONST_ROUNDINGMODE, Kind.VARIABLE, Kind.UNINTERPRETED_SORT_VALUE, Kind.PI]:
return str(a.ast)
if k == Kind.INTERNAL_KIND:
# Hack to handle DT selectors and constructors
return str(a.ast)
if isinstance(a, cvc.FuncDeclRef):
f = a
else:
raise Exception("Cannot print: " + str(k))
return f.name()
else:
return n
```
Replace it with this code instead:
```python=
def _op_name(a):
k = a.kind()
n = _cvc5_kinds_to_str.get(k, None)
if n is None:
if k in [Kind.CONSTANT, Kind.CONST_FLOATINGPOINT, Kind.CONST_ROUNDINGMODE, Kind.VARIABLE, Kind.UNINTERPRETED_SORT_VALUE, Kind.PI]:
return str(a.ast)
if k == Kind.INTERNAL_KIND:
# Hack to handle DT selectors and constructors
return str(a.ast)
if isinstance(a, cvc.FuncDeclRef):
f = a
if k == Kind.CONST_INTEGER: # Added line to fix bug
return str(a.ast) # Added line to fix bug
else:
raise Exception("Cannot print: " + str(k))
return f.name()
else:
return n
```
This may display the integer `1` like `1()` instead. If you figure out how to fix this I would be delighted to hear about it.
:::
## Basic cvc5 pythonic API commands
### Setting up a solver
Here is some boilerplate code to get you started.
```python=
from cvc5.pythonic import *
if __name__ == "__main__":
# Set up a "solver" object.
solver = Solver()
# Define variables and put constraints in solver object.
x, y = Ints('x y')
solver.add(x + y == 5)
# Check whether the constraints supplied are sat or unsat.
print(solver.check())
# If the constraints are sat, display concrete values which
# make them satisfiable.
if solver.check() == sat:
m = solver.model()
print(m)
```
Output:
```python
sat
[x = 5, y = 0]
```
### Defining variables
You can define singleton variables like:
```python=
p = Bool('p')
x = Int('x')
y = Real('y')
```
To define a list of variables at a time, use:
```python=
p1, p2 = Bools('p1 p2')
x1, x2 = Ints('x1 x2')
some_reals = Reals('r1 r2 r3 r4')
# some_reals is a list [r1, r2, r3, r4]
```
You can use Python data structures to get $n$ variables at a time. For example:
```python=
n = 5
nums = []
for i in range(n):
nums.append(Int(f"x[{i}]"))
# nums is a list [x[1], ..., x[n]] of Ints
```
### Including constraints
Each `solver.add()` line specifies a constraint that must be satisfied.
Write equations with `==` and inequalities with `>, <, >=, <=, !=`:
```python=
x, y, z = Ints('x y z')
solver.add(x + y == z)
solver.add(x >= y, y < 2) # this requires that x >= y AND y < 2 hold.
```
You can also specify `And`s, `Or`s, and `Not`s of expressions:
```python=
solver.add( Not( x + y == 3 ) )
solver.add( Or(x >= y, y < 2) ) # This requires that x >= y OR y < 2 hold.
```
If you want a list of variables to be distinct, use `Distinct`:
```python=
a, b = Ints('a b')
nums = Ints('x y z')
solver.add(Distinct(a, b)) # a != b
solver.add(Distinct(nums)) # All elements in nums are distinct
```
You can also include constraints involving [bit-vectors](https://cvc5.github.io/docs/cvc5-1.0.2/api/python/pythonic/bitvec.html), [arrays](https://cvc5.github.io/docs/cvc5-1.0.2/api/python/pythonic/array.html), and [sets](https://cvc5.github.io/docs/cvc5-1.0.2/api/python/pythonic/set.html).
### Determining satisfiability
Once you have run all of your `solver.add()` commands, you can run `solver.check()` to determine satisfiability. This should output `sat` or `unsat` (or if you are unlucky, `unknown`).
```python=
from cvc5.pythonic import *
if __name__ == "__main__":
solver = Solver()
x, y = Ints('x y')
solver.add( x + y == 5 )
print(solver.check()) # Output: sat
```
```python=
from cvc5.pythonic import *
if __name__ == "__main__":
solver = Solver()
x, y = Ints('x y')
solver.add( x + y == 5 )
solver.add( x == y )
print(solver.check()) # Output: unsat
```
### Finding values which satisfy constraints
In cvc5, any concrete values which satisfy the constraints inputted into the solver are called a *model*. When `solver.check()` returns `sat`, then you can call `solver.model()` to determine values which satisfy the constraints:
```python=
from cvc5.pythonic import *
if __name__ == "__main__":
solver = Solver()
x, y = Ints('x y')
solver.add(x + y == 5)
print(solver.check())
if solver.check() == sat:
m = solver.model()
print(m)
```
Output:
```python
sat
[x = 5, y = 0]
```
In addition to storing values for the variables in the constraints, the model `m = solver.model()` is also able to compute related quantities:
```python=
print(f"Whole model: {m}")
print(f"x = {m[x]}")
print(f"x + y = {m[x+y]}")
print(f"2*x + 3*y = {m[2*x + 3*y]}")
```
Output:
```python=
Whole model: [x = 5, y = 0]
x = 5
x + y = 5
2*x + 3*y = 10
```
## Quirks and limitations of cvc5
### The word `Sort` in cvc5
In cvc5, the word `Sort` means what `type` means in Python. For example, `IntVal("1").sort()` returns `Int`, and `ArraySort()` specifies the types of elements contained in your cvc5 array.
### `Ints` only knows very basic facts about the integers
When `x, y = Ints('x y')`, cvc5 knows that `2*x == 2*y + 1` is `unsat` (makes sense: no even integer can be equal to an odd integer).
Here's another true fact: the sum of two perfect squares can never be 3 mod 4. (This is because perfect squares can only be 0 or 1 mod 4, so the sum of *two* squares can only be 0, 1, or 2 mod 4.) However, cvc5 does not seem to know this. On my computer, checking satisfiability of `x*x + y*y == 4*z + 3` for `x, y, z = Ints('x y z')` hangs.
### `Reals` less reliable than `Ints`
On my computer, `x**2 + 2*x + 1 < 0` returns `unsat` when `x = Int('x')`, but when `x = Real('x')`, the solver hangs. ([z3](https://z3prover.github.io/papers/programmingz3.html) works fine for both.)
## Conclusion
This was a very quick overview of how to get started with the pythonic API for cvc5. To dive deeper, I recommend you consult [the documentation](https://cvc5.github.io/docs/cvc5-1.0.2/api/python/pythonic/pythonic.html). Unfortunately these is limited information online about cvc5 (e.g. tiny Stack Overflow footprint). As a substitute, large language models such as ChatGPT can be useful to help you find where to look in the documentation, or elaborate on what a particular function does.