Repository: rigetticomputing/pyquil
Pull request #191 (2017-11-17 11:38:23)

Print parameters nearly equal to pi, pi/2, etc. with pi #184
This is a fix for issue #184.  I added a function called check_for_pi which will see if a number is close to r * pi for some reduced rational number r = p/q with q <= 8.  I was unable to pass the test without adding [flake8 --ignore=E226](https://github.com/PyCQA/pycodestyle/issues/665) to the tox.ini file.  In particular, the error message I get is,
```
E226 missing whitespace around arithmetic operator
```
However, by adding ,
```
flake8 --ignore=E226
```
all tests are able to pass (not sure how import this parameter is to the team).
I also added test cases to the file, and fixed the parts of the test cases which fail (these test cases only failed in the obvious ways due to the new feature).

I should note that the following is now changed from before as well (its actually a special case, but I wasn't sure if it could have any surprising consequences).

```
input
===========
p = Program()
p += RZ(0., 0)
print(p)

output
===========
RZ(0) 0


as opposed to
===========
RZ(0.0) 0
```

dondi314: Thanks for the suggestion.  I also removed flake8 --ignore=E226 from tox.ini (reverted it back to the original) and it builds successfully now.  The problem was with white spaces between my division operators.

Patch:
diff --git a/pyquil/quilbase.py b/pyquil/quilbase.py
index f23576a..dd92242 100644
--- a/pyquil/quilbase.py
+++ b/pyquil/quilbase.py
@@ -20,6 +20,7 @@ Contains the core pyQuil objects that correspond to Quil instructions.
 import numpy as np
 from .slot import Slot
 from six import integer_types, string_types
+from fractions import Fraction
 
 
 class QuilAtom(object):
@@ -513,8 +514,10 @@ def format_parameter(element):
 
     :param element: {int, float, long, complex, Slot} Formats a parameter for Quil output.
     """
-    if isinstance(element, integer_types) or isinstance(element, float):
+    if isinstance(element, integer_types):
         return repr(element)
+    elif isinstance(element, float):
+        return check_for_pi(element)
     elif isinstance(element, complex):
         r = element.real
         i = element.imag
@@ -527,6 +530,33 @@ def format_parameter(element):
     assert False, "Invalid parameter: %r" % element
 
 
+def check_for_pi(element):
+    """
+    Check to see if there exists a rational number r = p/q
+    in reduced form for which the difference between element/np.pi
+    and r is small and q <= 8.
+
+    :param element: float
+    :return element: pretty print string if true, else standard representation.
+    """
+    frac = Fraction(element / np.pi).limit_denominator(8)
+    num, den = frac.numerator, frac.denominator
+    sign = "-" if num < 0 else ""
+    if np.isclose(num / float(den), element / np.pi):
+        if num == 0:
+            return "0"
+        elif abs(num) == 1 and den == 1:
+            return sign + "pi"
+        elif abs(num) == 1:
+            return sign + "pi/" + repr(den)
+        elif den == 1:
+            return repr(num) + "*pi"
+        else:
+            return repr(num) + "*pi/" + repr(den)
+    else:
+        return repr(element)
+
+
 def unpack_qubit(qubit):
     """
     Get a qubit from an object.


Test patch:
diff --git a/pyquil/tests/test_quil.py b/pyquil/tests/test_quil.py
index daef682..be8682c 100644
--- a/pyquil/tests/test_quil.py
+++ b/pyquil/tests/test_quil.py
@@ -40,7 +40,7 @@ def test_gate():
 def test_defgate():
     dg = DefGate("TEST", np.array([[1., 0.],
                                    [0., 1.]]))
-    assert dg.out() == "DEFGATE TEST:\n    1.0, 0.0\n    0.0, 1.0\n"
+    assert dg.out() == "DEFGATE TEST:\n    1.0, 0\n    0, 1.0\n"
     test = dg.get_constructor()
     tg = test(Qubit(1), Qubit(2))
     assert tg.out() == "TEST 1 2"
@@ -64,7 +64,7 @@ def test_defgate_non_unitary_should_throw_error():
 
 def test_defgate_param():
     dgp = DefGate("TEST", [[1., 0.], [0., 1.]])
-    assert dgp.out() == "DEFGATE TEST:\n    1.0, 0.0\n    0.0, 1.0\n"
+    assert dgp.out() == "DEFGATE TEST:\n    1.0, 0\n    0, 1.0\n"
     test = dgp.get_constructor()
     tg = test(Qubit(1))
     assert tg.out() == "TEST 1"
@@ -236,21 +236,21 @@ def test_dagger():
                        RZ(pi, 0), CPHASE(pi, 0, 1),
                        CPHASE00(pi, 0, 1), CPHASE01(pi, 0, 1),
                        CPHASE10(pi, 0, 1), PSWAP(pi, 0, 1))
-    assert p.dagger().out() == 'PSWAP(-3.141592653589793) 0 1\n' \
-                               'CPHASE10(-3.141592653589793) 0 1\n' \
-                               'CPHASE01(-3.141592653589793) 0 1\n' \
-                               'CPHASE00(-3.141592653589793) 0 1\n' \
-                               'CPHASE(-3.141592653589793) 0 1\n' \
-                               'RZ(-3.141592653589793) 0\n' \
-                               'RY(-3.141592653589793) 0\n' \
-                               'RX(-3.141592653589793) 0\n' \
-                               'PHASE(-3.141592653589793) 0\n'
+    assert p.dagger().out() == 'PSWAP(-pi) 0 1\n' \
+                               'CPHASE10(-pi) 0 1\n' \
+                               'CPHASE01(-pi) 0 1\n' \
+                               'CPHASE00(-pi) 0 1\n' \
+                               'CPHASE(-pi) 0 1\n' \
+                               'RZ(-pi) 0\n' \
+                               'RY(-pi) 0\n' \
+                               'RX(-pi) 0\n' \
+                               'PHASE(-pi) 0\n'
 
     # these gates are special cases
     p = Program().inst(S(0), T(0), ISWAP(0, 1))
-    assert p.dagger().out() == 'PSWAP(1.5707963267948966) 0 1\n' \
-                               'RZ(0.7853981633974483) 0\n' \
-                               'PHASE(-1.5707963267948966) 0\n'
+    assert p.dagger().out() == 'PSWAP(pi/2) 0 1\n' \
+                               'RZ(pi/4) 0\n' \
+                               'PHASE(-pi/2) 0\n'
 
     # must invert defined gates
     G = np.array([[0, 1], [0 + 1j, 0]])
@@ -294,14 +294,14 @@ def test_phases():
     p = Program(PHASE(np.pi)(1), CPHASE00(np.pi)(0, 1), CPHASE01(np.pi)(0, 1),
                 CPHASE10(np.pi)(0, 1),
                 CPHASE(np.pi)(0, 1))
-    assert p.out() == 'PHASE(3.141592653589793) 1\nCPHASE00(3.141592653589793) 0 1\n' \
-                      'CPHASE01(3.141592653589793) 0 1\nCPHASE10(3.141592653589793) 0 1\n' \
-                      'CPHASE(3.141592653589793) 0 1\n'
+    assert p.out() == 'PHASE(pi) 1\nCPHASE00(pi) 0 1\n' \
+                      'CPHASE01(pi) 0 1\nCPHASE10(pi) 0 1\n' \
+                      'CPHASE(pi) 0 1\n'
 
 
 def test_swaps():
     p = Program(SWAP(0, 1), CSWAP(0, 1, 2), ISWAP(0, 1), PSWAP(np.pi)(0, 1))
-    assert p.out() == 'SWAP 0 1\nCSWAP 0 1 2\nISWAP 0 1\nPSWAP(3.141592653589793) 0 1\n'
+    assert p.out() == 'SWAP 0 1\nCSWAP 0 1 2\nISWAP 0 1\nPSWAP(pi) 0 1\n'
 
 
 def test_def_gate():
@@ -350,8 +350,8 @@ def test_define_qft():
 
     prog = state_prep + qft3(0, 1, 2)
     output = prog.out()
-    assert output == 'X 0\nH 2\nCPHASE(1.5707963267948966) 1 2\nH 1\nCPHASE(0.7853981633974483) 0 ' \
-                     '2\nCPHASE(1.5707963267948966) 0 1\nH 0\nSWAP 0 2\n'
+    assert output == 'X 0\nH 2\nCPHASE(pi/2) 1 2\nH 1\nCPHASE(pi/4) 0 ' \
+                     '2\nCPHASE(pi/2) 0 1\nH 0\nSWAP 0 2\n'
 
 
 def test_control_flows():
@@ -574,3 +574,14 @@ def test_inline_alloc():
     p = Program()
     p += H(p.alloc())
     assert p.out() == "H 0\n"
+
+
+# https://github.com/rigetticomputing/pyquil/issues/184
+def test_pretty_print_pi():
+    p = Program()
+    p += [RZ(0., 0), RZ(pi, 1), RZ(-pi, 2)]
+    p += [RZ(2 * pi / 3., 3), RZ(pi / 9., 4), RZ(pi / 8., 5)]
+    p += CPHASE00(-90 * pi / 2., 0, 1)
+    assert p.out() == 'RZ(0) 0\nRZ(pi) 1\nRZ(-pi) 2\nRZ(2*pi/3) 3\n' \
+                      'RZ(0.3490658503988659) 4\n' \
+                      'RZ(pi/8) 5\nCPHASE00(-45*pi) 0 1\n'