Write-up for Pythonic API quickstart (#8566)
authorAlex Ozdemir <aozdemir@hmc.edu>
Tue, 5 Apr 2022 02:49:30 +0000 (19:49 -0700)
committerGitHub <noreply@github.com>
Tue, 5 Apr 2022 02:49:30 +0000 (02:49 +0000)
26 files changed:
docs/api/python/pythonic/quickstart.rst
docs/conf.py.in
docs/examples/bitvectors.rst
docs/examples/bitvectors_and_arrays.rst
docs/examples/combination.rst
docs/examples/datatypes.rst
docs/examples/extract.rst
docs/examples/floatingpoint.rst
docs/examples/helloworld.rst
docs/examples/lineararith.rst
docs/examples/quickstart.rst
docs/examples/sets.rst
examples/api/python/pythonic/README.md [new file with mode: 0644]
examples/api/python/pythonic/bitvectors.py [new file with mode: 0644]
examples/api/python/pythonic/bitvectors_and_arrays.py [new file with mode: 0644]
examples/api/python/pythonic/combination.py [new file with mode: 0644]
examples/api/python/pythonic/datatypes.py [new file with mode: 0644]
examples/api/python/pythonic/exceptions.py [new file with mode: 0644]
examples/api/python/pythonic/extract.py [new file with mode: 0644]
examples/api/python/pythonic/floating_point.py [new file with mode: 0644]
examples/api/python/pythonic/helloworld.py [new file with mode: 0644]
examples/api/python/pythonic/id.py [new file with mode: 0644]
examples/api/python/pythonic/linear_arith.py [new file with mode: 0644]
examples/api/python/pythonic/quickstart.py [new file with mode: 0644]
examples/api/python/pythonic/sets.py [new file with mode: 0644]
examples/api/python/pythonic/transcendentals.py [new file with mode: 0644]

index 764b1573f44e31583e7d6e0ef2bcdb2d76698456..53a48d5f86aa17f13a90ff566f84f955bf4f1790 100644 (file)
@@ -1,2 +1,74 @@
-Quickstart
-==========
+Quickstart Guide
+================
+
+First, 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.
+
+.. literalinclude:: ../../../../examples/api/python/pythonic/quickstart.py
+     :language: python
+     :lines: 6-7
+
+We define the following constraints regarding ``x`` and ``y``:
+
+.. math::
+
+  (0 < x) \wedge (0 < y) \wedge (x + y < 1) \wedge (x \leq y)
+
+We check whether there is a solution to these constraints:
+
+.. literalinclude:: ../../../../examples/api/python/pythonic/quickstart.py
+     :language: python
+     :lines: 15
+
+In this case, there is, so we get output:
+
+.. code:: text
+
+  [x = 1/6, y = 1/6]
+
+We can also get an explicit model (assignment) for the constraints.
+
+.. literalinclude:: ../../../../examples/api/python/pythonic/quickstart.py
+     :language: python
+     :lines: 19-22
+
+With the model, we can evaluate variables and terms:
+
+.. literalinclude:: ../../../../examples/api/python/pythonic/quickstart.py
+     :language: python
+     :lines: 24-26
+
+This will print:
+
+.. code:: text
+
+  x: 1/6
+  y: 1/6
+  x - y: 0
+
+
+We can also get these values in other forms:
+
+.. literalinclude:: ../../../../examples/api/python/pythonic/quickstart.py
+     :language: python
+     :lines: 29-32
+
+
+Next, we assert the same assertions as above, but with integers.
+This time, there is no solution, so "no solution" is printed.
+
+.. literalinclude:: ../../../../examples/api/python/pythonic/quickstart.py
+     :language: python
+     :lines: 36
+
+
+Example
+-------
+
+.. api-examples::
+    <examples>/api/python/quickstart.py
+    <examples>/api/cpp/quickstart.cpp
+    <examples>/api/java/QuickStart.java
+    <examples>/api/python/pythonic/quickstart.py
+    <examples>/api/smtlib/quickstart.smt2
index c3a25867c2e3e0ccf663fba72e0364ef2cf55b8b..aa51047824a1ab41f319dd299a7af34f2144b55a 100644 (file)
@@ -160,6 +160,11 @@ examples_types = {
                 'lang': 'java',
                 'group': 'java'
         },
+        '^<examples>.*pythonic.*\.py$': {
+                'title': 'Python (pythonic)',
+                'lang': 'python',
+                'group': 'py-pythonicapi'
+        },
         '^<examples>.*\.py$': {
                 'title': 'Python (base)',
                 'lang': 'python',
index fff6b97e1ffc291b7c8593aef2003dccc15619e4..e6009ebd53b502be76b123b400be6095aac88b90 100644 (file)
@@ -5,6 +5,6 @@ Theory of Bit-Vectors
 .. api-examples::
     <examples>/api/cpp/bitvectors.cpp
     <examples>/api/java/BitVectors.java
-    <pythonicapi>/test/pgms/example_bitvectors.py
+    <examples>/api/python/pythonic/bitvectors.py
     <examples>/api/python/bitvectors.py
     <examples>/api/smtlib/bitvectors.smt2
index 30bfc2e9932aa3f942e3c519c369990727a9aea1..90012a28e0c7d89633207e874962a14173f83b99 100644 (file)
@@ -5,6 +5,6 @@ Theory of Bit-Vectors and Arrays
 .. api-examples::
     <examples>/api/cpp/bitvectors_and_arrays.cpp
     <examples>/api/java/BitVectorsAndArrays.java
-    <pythonicapi>/test/pgms/example_bitvectors_and_arrays.py
+    <examples>/api/python/pythonic/bitvectors_and_arrays.py
     <examples>/api/python/bitvectors_and_arrays.py
     <examples>/api/smtlib/bitvectors_and_arrays.smt2
index 1367aeec0a1519e89e7ada8a5c5122c6be9cf5b1..9308d51aee012564400c428be8fe17aec75d1567 100644 (file)
@@ -4,6 +4,6 @@ Theory Combination
 .. api-examples::
     <examples>/api/cpp/combination.cpp
     <examples>/api/java/Combination.java
-    <pythonicapi>/test/pgms/example_combination.py
+    <examples>/api/python/pythonic/combination.py
     <examples>/api/python/combination.py
     <examples>/api/smtlib/combination.smt2
index aaed8f8bb221b1a3fc92592a1e6b4dc2c8302b1d..eb7c2147b49c5858202bf8dad285b42ae3d4dc56 100644 (file)
@@ -5,6 +5,6 @@ Theory of Datatypes
 .. api-examples::
     <examples>/api/cpp/datatypes.cpp
     <examples>/api/java/Datatypes.java
-    <pythonicapi>/test/pgms/example_datatypes.py
+    <examples>/api/python/pythonic/datatypes.py
     <examples>/api/python/datatypes.py
     <examples>/api/smtlib/datatypes.smt2
index c638f8dcf6b751cc49c29f75816bbe369962a402..ce204b8bcea9022cd3fe0d4ab1bc9b75905f7208 100644 (file)
@@ -5,6 +5,6 @@ Theory of Bit-Vectors: :code:`extract`
 .. api-examples::
     <examples>/api/cpp/extract.cpp
     <examples>/api/java/Extract.java
-    <pythonicapi>/test/pgms/example_extract.py
+    <examples>/api/python/pythonic/extract.py
     <examples>/api/python/extract.py
     <examples>/api/smtlib/extract.smt2
index 1e704d4a781da0fe526a9c10177a0c1856eb4611..a5ebc9524bec38babdc165d4121c0b91b6ae32ce 100644 (file)
@@ -5,5 +5,5 @@ Theory of Floating-Points
 .. api-examples::
     <examples>/api/cpp/floating_point_arith.cpp
     <examples>/api/java/FloatingPointArith.java
-    <pythonicapi>/test/pgms/example_floating_point.py
+    <examples>/api/python/pythonic/floating_point.py
     <examples>/api/python/floating_point.py
index 87bbe50f4ca909e27488a04d979ed1d7adc8828d..428e53e5b622559fa4fc70671a1597f54b758909 100644 (file)
@@ -7,6 +7,6 @@ We create a solver, declare a Boolean variable and check whether it is entailed
 .. api-examples::
     <examples>/api/cpp/helloworld.cpp
     <examples>/api/java/HelloWorld.java
-    <pythonicapi>/test/pgms/example_helloworld.py
+    <examples>/api/python/pythonic/helloworld.py
     <examples>/api/python/helloworld.py
     <examples>/api/smtlib/helloworld.smt2
index e561cf17a87dbe7fab85052a83740597136bcea6..96be09b257be996e53638a3f3b07808cb1709fc2 100644 (file)
@@ -9,6 +9,6 @@ The two checks are separated by using :code:`push` and :code:`pop`.
 .. api-examples::
     <examples>/api/cpp/linear_arith.cpp
     <examples>/api/java/LinearArith.java
-    <pythonicapi>/test/pgms/example_linear_arith.py
+    <examples>/api/python/pythonic/linear_arith.py
     <examples>/api/python/linear_arith.py
     <examples>/api/smtlib/linear_arith.smt2
index 3a57736edc1a9c290296e41988cb976a11e51092..57f8dd63f8d18ad8e7dec444ba9614566df5394c 100644 (file)
@@ -5,6 +5,6 @@ Quickstart Example
 .. api-examples::
     <examples>/api/cpp/quickstart.cpp
     <examples>/api/java/QuickStart.java
-    <pythonicapi>/test/pgms/example_quickstart.py
+    <examples>/api/python/pythonic/quickstart.py
     <examples>/api/python/quickstart.py
     <examples>/api/smtlib/quickstart.smt2
index abb7aa9ea10a4bcec6ae664b786fadfe05c43ced..5ba0ca78b67c3a6910c94458c3065342957d269c 100644 (file)
@@ -5,6 +5,6 @@ Theory of Sets
 .. api-examples::
     <examples>/api/cpp/sets.cpp
     <examples>/api/java/Sets.java
-    <pythonicapi>/test/pgms/example_sets.py
+    <examples>/api/python/pythonic/sets.py
     <examples>/api/python/sets.py
     <examples>/api/smtlib/sets.smt2
diff --git a/examples/api/python/pythonic/README.md b/examples/api/python/pythonic/README.md
new file mode 100644 (file)
index 0000000..3eb91dd
--- /dev/null
@@ -0,0 +1,10 @@
+# Notes
+
+These files are copied from [the pythonic API's repository](https://github.com/cvc5/cvc5_pythonic_api).
+
+They were copied with this script:
+
+    for f in ~/$CVC_PYTHONIC/test/pgms/example_*; do; ff=$(echo ${f:t} | sed 's/example_//'); cat $f | sed 's/_pythonic_api/.pythonic/' > examples/api/python/pythonic/$ff ; done;
+
+Notice that the script fixes up the import statements to align with a version
+of the cvc5 python APIs that are packaged together.
diff --git a/examples/api/python/pythonic/bitvectors.py b/examples/api/python/pythonic/bitvectors.py
new file mode 100644 (file)
index 0000000..8e56add
--- /dev/null
@@ -0,0 +1,35 @@
+from cvc5.pythonic import *
+
+if __name__ == '__main__':
+    # The following example has been adapted from the book A Hacker's Delight
+    # by Henry S. Warren.
+    #
+    # Given a variable x that can only have two values, a or b. We want to
+    # assign to x a value other than the current one. The straightforward code
+    # to do that is:
+    #
+    # (0) if (x == a ) x = b;
+    #     else x = a;
+    #
+    # Two more efficient yet equivalent methods are:
+    #
+    # (1) x = a xor b xor x;
+    #
+    # (2) x = a + b - x;
+    #
+    # We will use cvc5 to prove that the three pieces of code above are all
+    # equivalent by encoding the problem in the bit-vector theory.
+
+    x, a, b = BitVecs('x a b', 32)
+    x_is_a_or_b = Or(x == a, x == b)
+
+    # new_x0 set per (0)
+    new_x0 = If(x == a, b, a)
+    # new_x1 set per (1)
+    new_x1 = a ^ b ^ x
+    # new_x2 set per (2)
+    new_x2 = a + b - x
+
+    prove(Implies(x_is_a_or_b, new_x0 == new_x1))
+
+    prove(Implies(x_is_a_or_b, new_x0 == new_x2))
diff --git a/examples/api/python/pythonic/bitvectors_and_arrays.py b/examples/api/python/pythonic/bitvectors_and_arrays.py
new file mode 100644 (file)
index 0000000..199e736
--- /dev/null
@@ -0,0 +1,26 @@
+from cvc5.pythonic import *
+
+if __name__ == '__main__':
+    # Consider the following (where k is some previously defined constant):
+    #
+    #
+    #   Assert (current_array[0] > 0);
+    #   for (unsigned i = 1; i < k; ++i) {
+    #     current_array[i] = 2 * current_array[i - 1];
+    #     Assert (current_array[i-1] < current_array[i]);
+    #   }
+    #
+    # We want to check whether the assertion in the body of the for loop holds
+    # throughout the loop.
+    k = 4
+    idx_bits = int(math.ceil(math.log(k, 2)))
+
+    init_array = Array('init_arr', BitVecSort(idx_bits), BitVecSort(32))
+    array = init_array
+    assertions = []
+    for i in range(1, k):
+        array = Store(array, i, 2 * Select(array, i - 1))
+        assertions.append(Select(array, i - 1) < Select(array, i))
+    # Does *not* hold.
+    # If the first element is large enough, the multiplication overflows.
+    prove(Implies(Select(init_array, 0) > 0, And(*assertions)))
diff --git a/examples/api/python/pythonic/combination.py b/examples/api/python/pythonic/combination.py
new file mode 100644 (file)
index 0000000..4070daa
--- /dev/null
@@ -0,0 +1,35 @@
+from cvc5.pythonic import *
+
+if __name__ == "__main__":
+
+    U = DeclareSort("U")
+    x, y = Consts("x y", U)
+
+    f = Function("f", U, IntSort())
+    p = Function("p", IntSort(), BoolSort())
+
+    assumptions = [f(x) >= 0, f(y) >= 0, f(x) + f(y) <= 1, Not(p(0)), p(f(y))]
+
+    prove(Implies(And(assumptions), x != y))
+
+    s = Solver()
+    s.add(assumptions)
+    assert sat == s.check()
+    m = s.model()
+
+    def print_val(t):
+        print("{}: {}".format(t, m[t]))
+
+    # print the of some terms
+    print_val(f(x))
+    print_val(f(y))
+    print_val(f(x) + f(y))
+    print_val(p(0))
+    print_val(p(f(y)))
+
+    # print value of *all* terms
+    def print_all_subterms(t):
+        print_val(t)
+        for c in t.children():
+            print_all_subterms(c)
+    print_all_subterms(And(assumptions))
diff --git a/examples/api/python/pythonic/datatypes.py b/examples/api/python/pythonic/datatypes.py
new file mode 100644 (file)
index 0000000..79074e4
--- /dev/null
@@ -0,0 +1,44 @@
+from cvc5.pythonic import *
+
+if __name__ == '__main__':
+    # This example builds a simple "cons list" of integers, with
+    # two constructors, "cons" and "nil."
+
+    # Building a datatype consists of two steps.
+    # First, the datatype is specified.
+    # Second, it is "resolved" to an actual sort, at which point function
+    # symbols are assigned to its constructors, selectors, and testers.
+
+    decl = Datatype("list")
+    decl.declare("cons", ("head", IntSort()), ("tail", decl))
+    decl.declare("nil")
+    List = decl.create()
+
+    # Using constructors and selectors:
+    t = List.cons(0, List.nil)
+    print("t is:", t)
+    print("head of t is:", List.head(t))
+    print("after simplify:", simplify(List.head(t)))
+    print()
+
+    # You can iterate over constructors and selectors
+    for i in range(List.num_constructors()):
+        ctor = List.constructor(i)
+        print("ctor:", ctor)
+        for j in range(ctor.arity()):
+            print(" + arg:", ctor.domain(j))
+            print("   + selector:", List.accessor(i, j))
+    print()
+
+    # You can use testers
+    print("t is a 'cons':", simplify(List.is_cons(t)))
+    print()
+
+    # This Python API does not support type parameters or updators for
+    # datatypes. See the base Python API for those features, or construct them
+    # using Python functions/classes.
+
+    a = Int('a')
+    solve(List.head(List.cons(a, List.nil)) > 50)
+
+    prove(Not(List.is_nil(List.cons(a, List.nil))))
diff --git a/examples/api/python/pythonic/exceptions.py b/examples/api/python/pythonic/exceptions.py
new file mode 100644 (file)
index 0000000..68f7f88
--- /dev/null
@@ -0,0 +1,23 @@
+from cvc5.pythonic import *
+
+if __name__ == '__main__':
+    s = Solver()
+    s.set("produce-models", True)
+    try:
+        # invalid option
+        s.set("non-existing-option", True)
+    except:
+        pass
+
+    try:
+        # type error
+        Int("x") + BitVec("a", 5)
+    except:
+        pass
+
+    s += BoolVal(False)
+    s.check()
+    try:
+        s.model()
+    except:
+        pass
diff --git a/examples/api/python/pythonic/extract.py b/examples/api/python/pythonic/extract.py
new file mode 100644 (file)
index 0000000..2923dc8
--- /dev/null
@@ -0,0 +1,15 @@
+from cvc5.pythonic import *
+
+if __name__ == '__main__':
+    x = BitVec('x', 32)
+
+    # Consider the bits of x: 01234567
+    # (we assume 8 bits to make the visualization simple)
+    #
+    # If      1234567
+    # equals  0123456
+    #
+    # then    0 == 7 (by induction over the bits)
+
+    prove(Implies(Extract(31, 1, x) == Extract(30, 0, x),
+                  Extract(31, 31, x) == Extract(0, 0, x)))
diff --git a/examples/api/python/pythonic/floating_point.py b/examples/api/python/pythonic/floating_point.py
new file mode 100644 (file)
index 0000000..556379e
--- /dev/null
@@ -0,0 +1,25 @@
+from cvc5.pythonic import *
+
+if __name__ == "__main__":
+    x, y, z = FPs("x y z", Float32())
+    set_default_rounding_mode(RoundNearestTiesToEven())
+
+    # FP addition is *not* commutative. This finds a counterexample.
+    assert not is_tautology(fpEQ(x + y, y + x))
+
+    # Without NaN or infinities, is is commutative. This proof succeeds.
+    assert is_tautology(
+        Implies(
+            Not(Or(fpIsNaN(x), fpIsNaN(y), fpIsInf(x), fpIsInf(y))), fpEQ(x + y, y + x)
+        )
+    )
+
+    pi = FPVal(+3.14, Float32())
+
+    # FP addition is *not* associative in the range (-pi, pi).
+    assert not is_tautology(
+        Implies(
+            And(x >= -pi, x <= pi, y >= -pi, y <= pi, z >= -pi, z <= pi),
+            fpEQ((x + y) + z, x + (y + z)),
+        )
+    )
diff --git a/examples/api/python/pythonic/helloworld.py b/examples/api/python/pythonic/helloworld.py
new file mode 100644 (file)
index 0000000..e4b0ea9
--- /dev/null
@@ -0,0 +1,5 @@
+from cvc5.pythonic import *
+
+if __name__ == '__main__':
+    var = Bool('Hello World!')
+    solve(var)
diff --git a/examples/api/python/pythonic/id.py b/examples/api/python/pythonic/id.py
new file mode 100644 (file)
index 0000000..2de976f
--- /dev/null
@@ -0,0 +1,12 @@
+from cvc5.pythonic import *
+
+if __name__ == '__main__':
+    A = Set("A", SetSort(IntSort()))
+    B = Set("B", SetSort(IntSort()))
+    C = Set("C", SetSort(IntSort()))
+
+    assert A.get_id() != B.get_id()
+    assert C.get_id() != B.get_id()
+    assert A.get_id() == A.get_id()
+    assert B.get_id() == B.get_id()
+    assert C.get_id() == C.get_id()
diff --git a/examples/api/python/pythonic/linear_arith.py b/examples/api/python/pythonic/linear_arith.py
new file mode 100644 (file)
index 0000000..9241744
--- /dev/null
@@ -0,0 +1,15 @@
+from cvc5.pythonic import *
+
+slv = SolverFor('QF_LIRA')
+
+x = Int('x')
+y = Real('y')
+
+slv += And(x >= 3 * y, x <= y, -2 < x)
+slv.push()
+print(slv.check(y-x <= 2/3))
+slv.pop()
+slv.push()
+slv += y-x == 2/3
+print(slv.check())
+slv.pop()
diff --git a/examples/api/python/pythonic/quickstart.py b/examples/api/python/pythonic/quickstart.py
new file mode 100644 (file)
index 0000000..570c942
--- /dev/null
@@ -0,0 +1,39 @@
+from cvc5.pythonic import *
+
+if __name__ == '__main__':
+
+    # Let's introduce some variables
+    x, y = Reals('x y')
+    a, b = Ints('a b')
+
+    # We will confirm that
+    #  * 0 < x
+    #  * 0 < y
+    #  * x + y < 1
+    #  * x <= y
+    # are satisfiable
+    solve(0 < x, 0 < y, x + y < 1, x <= y)
+
+    # If we get the model (the satisfying assignment) explicitly, we can
+    # evaluate terms under it.
+    s = Solver()
+    s.add(0 < x, 0 < y, x + y < 1, x <= y)
+    assert sat == s.check()
+    m = s.model()
+
+    print('x:', m[x])
+    print('y:', m[y])
+    print('x - y:', m[x - y])
+
+    # We can also get these values in other forms:
+    print('string x:', str(m[x]))
+    print('decimal x:', m[x].as_decimal(4))
+    print('fraction x:', m[x].as_fraction())
+    print('float x:', float(m[x].as_fraction()))
+
+    # The above constraints are *UNSAT* for integer variables.
+    # This reports "no solution"
+    solve(0 < a, 0 < b, a + b < 1, a <= b)
+
+
+
diff --git a/examples/api/python/pythonic/sets.py b/examples/api/python/pythonic/sets.py
new file mode 100644 (file)
index 0000000..bccd86d
--- /dev/null
@@ -0,0 +1,20 @@
+from cvc5.pythonic import *
+
+if __name__ == "__main__":
+    A, B, C = [Set(name, IntSort()) for name in "ABC"]
+
+    # holds
+    prove((A | B) & C == (A & C) | (B & C))
+
+    # holds
+    prove(IsSubset(EmptySet(IntSort()), A))
+
+    # x must be 2.
+    x = Int("x")
+    solve(
+        IsMember(
+            x,
+            (Singleton(IntVal(1)) | Singleton(IntVal(2)))
+            & (Singleton(IntVal(2)) | Singleton(IntVal(3))),
+        )
+    )
diff --git a/examples/api/python/pythonic/transcendentals.py b/examples/api/python/pythonic/transcendentals.py
new file mode 100644 (file)
index 0000000..0d4d04f
--- /dev/null
@@ -0,0 +1,7 @@
+from cvc5.pythonic import *
+
+if __name__ == '__main__':
+    x, y = Reals("x y")
+    solve(x > Pi(),
+            x < 2 * Pi(),
+            y ** 2 < Sine(x))