Repository: rigetti/pyquil

Issue:
Expressions are parsed incorrectly by Defgate
### Code Snippet

```python
import numpy as np
from pyquil.quilbase import DefGate
from pyquil.quilatom import quil_exp, Parameter

phi = Parameter("phi")

rz = np.array(
    [
        [quil_exp(-1j*phi/2), 0],
        [0, quil_exp(+1j*phi/2)],
    ]
)


print(DefGate(name="blarg", matrix=rz, parameters=[phi]))

rz2 = np.array(
    [
        [quil_exp(-1j*phi/2)*np.exp(1j*np.pi/4), 0],
        [0, quil_exp(+1j*phi/2)*np.exp(1j*np.pi/4)],
    ]
)

print(rz2[0,0])
print(DefGate(name="blarg", matrix=rz2, parameters=[phi]))
```

### Error Output

The first defgate outputs as expected.

The second output is the expression, `EXP(-i*%phi/2)*0.7071067811865476+0.7071067811865475i`
While the expression is ambiguous, a closer look reveals that it is structured correctly:
`Mul(Function('EXP',Div(Mul((-0-1j),Parameter('phi')),2),<ufunc 'exp'>),(0.7071067811865476+0.7071067811865475j))
`
The exponential is multiplied by the complex number.

In the defgate, it's clear this is not the case. The expression is the exponential multiplied by the real part of the complex number, while the imaginary part is then added after the fact. Ie:

`(a+bi)*exp(theta)`

vs

`a*exp(theta) + bi`

A closer look at the matrix reveals this to be the case:

`Add(Mul(Function('EXP',Div(Mul((-0-1j),Parameter('phi')),(2+0j)),<ufunc 'exp'>),(0.7071067811865476+0j)),0.7071067811865475j)
`

```
DEFGATE blarg(%phi) AS MATRIX:
	exp((-1.0i*%phi)/2), 0
	0, exp((1.0i*%phi)/2)

EXP(-i*%phi/2)*0.7071067811865476+0.7071067811865475i
DEFGATE blarg(%phi) AS MATRIX:
	(exp((-1.0i*%phi)/2)*0.7071067811865476)+0.7071067811865475i, 0
	0, (exp((1.0i*%phi)/2)*0.7071067811865476)+0.7071067811865475i
```


Comments:

Patch:
diff --git a/pyquil/quilatom.py b/pyquil/quilatom.py
index 7cd7e7c..ab23c12 100644
--- a/pyquil/quilatom.py
+++ b/pyquil/quilatom.py
@@ -791,8 +791,12 @@ def _expression_to_string(expression: ExpressionDesignator) -> str:
             right = "(" + right + ")"
         # If op2 is a float, it will maybe represented as a multiple
         # of pi in right. If that is the case, then we need to take
-        # extra care to insert parens. See gh-943.
-        elif isinstance(expression.op2, float) and (("pi" in right and right != "pi")):
+        # extra care to insert parens. Similarly, complex numbers need
+        # to be wrapped in parens so the imaginary part is captured.
+        # See gh-943,1734.
+        elif (isinstance(expression.op2, float) and (("pi" in right and right != "pi"))) or isinstance(
+            expression.op2, complex
+        ):
             right = "(" + right + ")"
 
         return left + expression.operator + right


Test patch:
diff --git a/test/unit/test_parameters.py b/test/unit/test_parameters.py
index ebbbd73..80dac81 100644
--- a/test/unit/test_parameters.py
+++ b/test/unit/test_parameters.py
@@ -65,12 +65,15 @@ def test_expression_to_string():
     assert str((x + y) - 2) == "%x + %y - 2"
     assert str(x + (y - 2)) == "%x + %y - 2"
 
-    assert str(x ** y ** 2) == "%x^%y^2"
-    assert str(x ** (y ** 2)) == "%x^%y^2"
-    assert str((x ** y) ** 2) == "(%x^%y)^2"
+    assert str(x**y**2) == "%x^%y^2"
+    assert str(x ** (y**2)) == "%x^%y^2"
+    assert str((x**y) ** 2) == "(%x^%y)^2"
 
     assert str(quil_sin(x)) == "SIN(%x)"
     assert str(3 * quil_sin(x + y)) == "3*SIN(%x + %y)"
+    assert (
+        str(quil_exp(-1j * x / 2) * np.exp(1j * np.pi / 4)) == "EXP(-i*%x/2)*(0.7071067811865476+0.7071067811865475i)"
+    )
 
 
 def test_contained_parameters():
@@ -80,7 +83,7 @@ def test_contained_parameters():
     y = Parameter("y")
     assert _contained_parameters(x + y) == {x, y}
 
-    assert _contained_parameters(x ** y ** quil_sin(x * y * 4)) == {x, y}
+    assert _contained_parameters(x**y ** quil_sin(x * y * 4)) == {x, y}
 
 
 def test_eval():
@@ -93,7 +96,7 @@ def test_eval():
     assert substitute(quil_exp(x), {y: 5}) != np.exp(5)
     assert substitute(quil_exp(x), {x: 5}) == np.exp(5)
 
-    assert np.isclose(substitute(quil_sin(x * x ** 2 / y), {x: 5.0, y: 10.0}), np.sin(12.5))
+    assert np.isclose(substitute(quil_sin(x * x**2 / y), {x: 5.0, y: 10.0}), np.sin(12.5))
     assert np.isclose(substitute(quil_sqrt(x), {x: 5.0, y: 10.0}), np.sqrt(5.0))
     assert np.isclose(substitute(quil_cis(x), {x: 5.0, y: 10.0}), np.exp(1j * 5.0))
     assert np.isclose(substitute(x - y, {x: 5.0, y: 10.0}), -5.0)
diff --git a/test/unit/test_rewrite_arithmetic.py b/test/unit/test_rewrite_arithmetic.py
index c846f65..896428a 100644
--- a/test/unit/test_rewrite_arithmetic.py
+++ b/test/unit/test_rewrite_arithmetic.py
@@ -36,7 +36,7 @@ def test_rewrite_arithmetic_duplicate_exprs():
 
     assert response == RewriteArithmeticResponse(
         original_memory_descriptors={"theta": ParameterSpec(length=1, type="REAL")},
-        recalculation_table={ParameterAref(index=0, name="__P1"): "theta[0]*1.5/(2*pi)"},
+        recalculation_table={ParameterAref(index=0, name="__P1"): "theta[0]*(1.5)/(2*pi)"},
         quil=Program("DECLARE __P1 REAL[1]", "DECLARE theta REAL[1]", "RZ(__P1[0]) 0", "RX(__P1[0]) 0").out(),
     )