Change output of getRealValue to a fraction. (#6692)
authorAlex Ozdemir <aozdemir@hmc.edu>
Tue, 8 Jun 2021 06:42:59 +0000 (23:42 -0700)
committerGitHub <noreply@github.com>
Tue, 8 Jun 2021 06:42:59 +0000 (06:42 +0000)
Our documentation for `toPythonObj` says that real values are represented as Fractions. However, getRealValue yields a float approximation thereof.

We should probably stick to Fractions, since they allow us to precisely capture values in LRA. Also, that's more aligned with the C++ API, which returns a string representation of the (unapproximated) Rational.

Also, document some (potential) weirdness with calling mkReal on floating-point values.

src/api/python/cvc5.pxi
test/python/unit/api/test_term.py
test/python/unit/api/test_to_python_obj.py

index 3339543f3598d903ddf5ac86b23127e2a0563c0e..258005207338da24a56c4881513aab540d3f357a 100644 (file)
@@ -740,6 +740,21 @@ cdef class Solver:
         return term
 
     def mkReal(self, val, den=None):
+        '''
+        Make a real number term.
+
+        Really, makes a rational term.
+
+        Can be used in various forms.
+        * Given a string "N/D" constructs the corresponding rational.
+        * Given a string "W.D" constructs the reduction of (W * P + D)/P, where
+          P is the appropriate power of 10.
+        * Given a float f, constructs the rational matching f's string
+          representation. This means that mkReal(0.3) gives 3/10 and not the
+          IEEE-754 approximation of 3/10.
+        * Given a string "W" or an integer, constructs that integer.
+        * Given two strings and/or integers N and D, constructs N/D.
+        '''
         cdef Term term = Term(self)
         if den is None:
             term.cterm = self.csolver.mkReal(str(val).encode())
@@ -1766,12 +1781,14 @@ cdef class Term:
         return self.cterm.isRealValue()
 
     def getRealValue(self):
-        return float(Fraction(self.cterm.getRealValue().decode()))
+        '''Returns the value of a real term as a Fraction'''
+        return Fraction(self.cterm.getRealValue().decode())
 
     def isBitVectorValue(self):
         return self.cterm.isBitVectorValue()
 
     def getBitVectorValue(self, base = 2):
+        '''Returns the value of a bit-vector term as a 0/1 string'''
         return self.cterm.getBitVectorValue(base).decode()
 
     def toPythonObj(self):
index 32813e17f390a281c167c56d4de059a43f23feb3..e7818807908fb9c1fba416a777deb46bd8ad32cc 100644 (file)
@@ -15,6 +15,7 @@ import pytest
 import pycvc5
 from pycvc5 import kinds
 from pycvc5 import Sort, Term
+from fractions import Fraction
 
 
 @pytest.fixture
@@ -1145,12 +1146,27 @@ def test_get_real(solver):
     assert 0 == real1.getRealValue()
     assert 0 == real2.getRealValue()
     assert -17 == real3.getRealValue()
-    assert -3 / 5 == real4.getRealValue()
-    assert 127 / 10 == real5.getRealValue()
-    assert 1 / 4294967297 == real6.getRealValue()
+    assert Fraction(-3, 5) == real4.getRealValue()
+    assert Fraction(127, 10) == real5.getRealValue()
+    assert Fraction(1, 4294967297) == real6.getRealValue()
     assert 4294967297 == real7.getRealValue()
-    assert 1 / 18446744073709551617 == real8.getRealValue()
-    assert float(18446744073709551617) == real9.getRealValue()
+    assert Fraction(1, 18446744073709551617) == real8.getRealValue()
+    assert Fraction(18446744073709551617, 1) == real9.getRealValue()
+
+    # Check denominator too large for float
+    num = 1
+    den = 2 ** 64 + 1
+    real_big = solver.mkReal(num, den)
+    assert real_big.isRealValue()
+    assert Fraction(num, den) == real_big.getRealValue()
+
+    # Check that we're treating floats as decimal aproximations rather than
+    # IEEE-754-specified values.
+    real_decimal = solver.mkReal(0.3)
+    assert real_decimal.isRealValue()
+    assert Fraction("0.3") == real_decimal.getRealValue()
+    assert Fraction(0.3) == Fraction(5404319552844595, 18014398509481984)
+    assert Fraction(0.3) != real_decimal.getRealValue()
 
 
 def test_get_boolean(solver):
index 2ba685d5072e6faaffb200e6d3bb67e11391c42d..bb30fae8fc8b98239785a7c20a895e51864bdb6f 100644 (file)
@@ -22,8 +22,8 @@ def testGetBool():
     solver = pycvc5.Solver()
     t = solver.mkTrue()
     f = solver.mkFalse()
-    assert t.toPythonObj() == True
-    assert f.toPythonObj() == False
+    assert t.toPythonObj() is True
+    assert f.toPythonObj() is False
 
 
 def testGetInt():
@@ -115,4 +115,4 @@ def testGetValueReal():
     xval = solver.getValue(x)
     yval = solver.getValue(y)
     assert xval.toPythonObj() == Fraction("6")
-    assert yval.toPythonObj() == float(Fraction("8.33"))
+    assert yval.toPythonObj() == Fraction("8.33")