Skip to content

Commit

Permalink
Add missing factor of pi to parameterized Quil gate output (#3206)
Browse files Browse the repository at this point in the history
This PR addresses a bug in Quil output.

The current code doesn't distinguish between Cirq's `PowGate` exponents and the angle parameters for Quil gates, but they differ by a factor of pi. This is verified by comparing program unitaries before and after conversion to Quil (see below).

Adds a test for this change that depends on pyQuil, so `pyquil` is added to `pip-list-dev-tools.txt`, but in the event that pyQuil is not available for import, the test is [skipped](https://docs.pytest.org/en/latest/skipping.html#skipping-on-a-missing-import-dependency).

Additionally sneaks in a change to Quil output for `ISwapPowGate` -- uses the native `XY` Quil gate when the exponent of the `ISwapPowGate` does not equal 1.

```python
In [1]: import numpy as np
   ...: from pyquil import Program
   ...: from pyquil.simulation.tools import program_unitary
   ...:
   ...: from cirq import Circuit, LineQubit, QuilOutput, rx
   ...:
   ...: q0 = LineQubit(0)
   ...:
   ...: # rx(pi/2) creates an XPowGate with exponent 0.5 (and global_shift -0.5) 
   ...: circ = Circuit(rx(np.pi/2)(q0))
   ...: print(circ)
   ...:
   ...: pyquil_program = Program(str(QuilOutput(circ, (q0,))))
   ...: print(pyquil_program)
   ...:
   ...: pyquil_unitary = program_unitary(pyquil_program, n_qubits=1)
   ...: print(pyquil_unitary)
   ...:
   ...: cirq_unitary = circ.unitary()
   ...: print(cirq_unitary)
   ...:
   ...: np.allclose(pyquil_unitary, cirq_unitary)
```

on `master`:

```
0: ───Rx(0.5π)───
RX(0.5) 0

[[0.96891242+0.j         0.        -0.24740396j]
 [0.        -0.24740396j 0.96891242+0.j        ]]
[[0.70710678+0.j         0.        -0.70710678j]
 [0.        -0.70710678j 0.70710678+0.j        ]]
Out[1]: False
```

this PR:

```
0: ───Rx(0.5π)───
RX(pi/2) 0

[[0.70710678+0.j         0.        -0.70710678j]
 [0.        -0.70710678j 0.70710678+0.j        ]]
[[0.70710678+0.j         0.        -0.70710678j]
 [0.        -0.70710678j 0.70710678+0.j        ]]
Out[1]: True
```

Related to #1742, #2386, #2983
  • Loading branch information
karalekas committed Aug 12, 2020
1 parent 0caaeb5 commit b878d06
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 68 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ docs/generated

# Default pycharm virtual env
.venv/

# pyenv configuration
.python-version
126 changes: 73 additions & 53 deletions cirq/circuits/quil_output_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ def test_single_gate_no_parameter():
def test_single_gate_with_parameter():
q0, = _make_qubits(1)
output = cirq.QuilOutput((cirq.X(q0)**0.5,), (q0,))
assert (str(output) == """# Created using Cirq.
assert (str(output) == f"""# Created using Cirq.
RX(0.5) 0\n""")
RX({np.pi / 2}) 0\n""")


def test_single_gate_named_qubit():
Expand All @@ -52,11 +52,11 @@ def test_single_gate_named_qubit():
def test_h_gate_with_parameter():
q0, = _make_qubits(1)
output = cirq.QuilOutput((cirq.H(q0)**0.25,), (q0,))
assert (str(output) == """# Created using Cirq.
assert (str(output) == f"""# Created using Cirq.
RY(0.25) 0
RX(0.25) 0
RY(-0.25) 0\n""")
RY({np.pi / 4}) 0
RX({np.pi / 4}) 0
RY({-np.pi / 4}) 0\n""")


def test_save_to_file(tmpdir):
Expand Down Expand Up @@ -184,108 +184,101 @@ def test_i_swap_with_power():
q0,
q1,
))
assert str(output) == """# Created using Cirq.
assert str(output) == f"""# Created using Cirq.
CNOT 0 1
H 0
CNOT 1 0
RZ(0.125) 0
CNOT 1 0
RZ(-0.125) 0
H 0
CNOT 0 1
XY({np.pi / 4}) 0 1
"""


def test_all_operations():
qubits = tuple(_make_qubits(5))
operations = _all_operations(*qubits, include_measurements=False)
output = cirq.QuilOutput(operations, qubits)
assert (str(output) == """# Created using Cirq.
assert (str(output) == f"""# Created using Cirq.
DECLARE m0 BIT[1]
DECLARE m1 BIT[1]
DECLARE m2 BIT[1]
DECLARE m3 BIT[3]
Z 0
RZ(0.625) 0
RZ({5 * np.pi / 8}) 0
Y 0
RY(0.375) 0
RY({3 * np.pi / 8}) 0
X 0
RX(0.875) 0
RX({7 * np.pi / 8}) 0
H 1
CZ 0 1
CPHASE(0.25) 0 1
CPHASE({np.pi / 4}) 0 1
CNOT 0 1
RY(-0.5) 1
CPHASE(0.5) 0 1
RY(0.5) 1
RY({-np.pi / 2}) 1
CPHASE({np.pi / 2}) 0 1
RY({np.pi / 2}) 1
SWAP 0 1
PSWAP(0.75) 0 1
PSWAP({3 * np.pi / 4}) 0 1
H 2
CCNOT 0 1 2
H 2
CCNOT 0 1 2
RZ(0.125) 0
RZ(0.125) 1
RZ(0.125) 2
RZ({np.pi / 8}) 0
RZ({np.pi / 8}) 1
RZ({np.pi / 8}) 2
CNOT 0 1
CNOT 1 2
RZ(-0.125) 1
RZ(0.125) 2
RZ({-np.pi / 8}) 1
RZ({np.pi / 8}) 2
CNOT 0 1
CNOT 1 2
RZ(-0.125) 2
RZ({-np.pi / 8}) 2
CNOT 0 1
CNOT 1 2
RZ(-0.125) 2
RZ({-np.pi / 8}) 2
CNOT 0 1
CNOT 1 2
H 2
RZ(0.125) 0
RZ(0.125) 1
RZ(0.125) 2
RZ({np.pi / 8}) 0
RZ({np.pi / 8}) 1
RZ({np.pi / 8}) 2
CNOT 0 1
CNOT 1 2
RZ(-0.125) 1
RZ(0.125) 2
RZ({-np.pi / 8}) 1
RZ({np.pi / 8}) 2
CNOT 0 1
CNOT 1 2
RZ(-0.125) 2
RZ({-np.pi / 8}) 2
CNOT 0 1
CNOT 1 2
RZ(-0.125) 2
RZ({-np.pi / 8}) 2
CNOT 0 1
CNOT 1 2
H 2
CSWAP 0 1 2
X 0
X 1
RX(0.75) 0
RX(0.75) 1
RX({3 * np.pi / 4}) 0
RX({3 * np.pi / 4}) 1
Y 0
Y 1
RY(0.75) 0
RY(0.75) 1
RY({3 * np.pi / 4}) 0
RY({3 * np.pi / 4}) 1
Z 0
Z 1
RZ(0.75) 0
RZ(0.75) 1
RZ({3 * np.pi / 4}) 0
RZ({3 * np.pi / 4}) 1
I 0
I 0
I 1
I 2
ISWAP 2 0
RZ(-0.111) 1
RX(0.25) 1
RZ(0.111) 1
RZ(-0.333) 1
RX(0.5) 1
RZ(0.333) 1
RZ(-0.777) 1
RX(-0.5) 1
RZ(0.777) 1
RZ({-0.111 * np.pi}) 1
RX({np.pi / 4}) 1
RZ({0.111 * np.pi}) 1
RZ({-0.333 * np.pi}) 1
RX({np.pi / 2}) 1
RZ({0.333 * np.pi}) 1
RZ({-0.777 * np.pi}) 1
RX({-np.pi / 2}) 1
RZ({0.777 * np.pi}) 1
WAIT
MEASURE 0 m0[0]
MEASURE 2 m1[0]
Expand Down Expand Up @@ -358,3 +351,30 @@ def test_pauli_interaction_gate():
CZ 0 1
"""


def test_equivalent_unitaries():
"""This test covers the factor of pi change. However, it will be skipped
if pyquil is unavailable for import.
References:
https://docs.pytest.org/en/latest/skipping.html#skipping-on-a-missing-import-dependency
"""
pyquil = pytest.importorskip("pyquil")
pyquil_simulation_tools = pytest.importorskip("pyquil.simulation.tools")
q0, q1 = _make_qubits(2)
operations = [
cirq.XPowGate(exponent=0.5, global_shift=-0.5)(q0),
cirq.YPowGate(exponent=0.5, global_shift=-0.5)(q0),
cirq.ZPowGate(exponent=0.5, global_shift=-0.5)(q0),
cirq.CZPowGate(exponent=0.5)(q0, q1),
cirq.ISwapPowGate(exponent=0.5)(q0, q1),
]
output = cirq.QuilOutput(operations, (q0, q1))
program = pyquil.Program(str(output))
pyquil_unitary = pyquil_simulation_tools.program_unitary(program,
n_qubits=2)
# Qubit ordering differs between pyQuil and Cirq.
cirq_unitary = cirq.Circuit(cirq.SWAP(q0, q1), operations,
cirq.SWAP(q0, q1)).unitary()
assert np.allclose(pyquil_unitary, cirq_unitary)
17 changes: 10 additions & 7 deletions cirq/ops/common_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,8 @@ def _quil_(self, qubits: Tuple['cirq.Qid', ...],
formatter: 'cirq.QuilFormatter') -> Optional[str]:
if self._exponent == 1:
return formatter.format('X {0}\n', qubits[0])
return formatter.format('RX({0}) {1}\n', self._exponent, qubits[0])
return formatter.format('RX({0}) {1}\n', self._exponent * np.pi,
qubits[0])

@property
def phase_exponent(self):
Expand Down Expand Up @@ -387,7 +388,8 @@ def _quil_(self, qubits: Tuple['cirq.Qid', ...],
formatter: 'cirq.QuilFormatter') -> Optional[str]:
if self._exponent == 1:
return formatter.format('Y {0}\n', qubits[0])
return formatter.format('RY({0}) {1}\n', self._exponent, qubits[0])
return formatter.format('RY({0}) {1}\n', self._exponent * np.pi,
qubits[0])

@property
def phase_exponent(self):
Expand Down Expand Up @@ -620,8 +622,8 @@ def _quil_(self, qubits: Tuple['cirq.Qid', ...],
formatter: 'cirq.QuilFormatter') -> Optional[str]:
if self._exponent == 1:
return formatter.format('Z {0}\n', qubits[0])

return formatter.format('RZ({0}) {1}\n', self._exponent, qubits[0])
return formatter.format('RZ({0}) {1}\n', self._exponent * np.pi,
qubits[0])

def __str__(self) -> str:
if self._global_shift == -0.5:
Expand Down Expand Up @@ -801,8 +803,9 @@ def _quil_(self, qubits: Tuple['cirq.Qid', ...],
formatter: 'cirq.QuilFormatter') -> Optional[str]:
if self._exponent == 1:
return formatter.format('H {0}\n', qubits[0])
return formatter.format('RY({0}) {3}\nRX({1}) {3}\nRY({2}) {3}\n', 0.25,
self._exponent, -0.25, qubits[0])
return formatter.format('RY({0}) {3}\nRX({1}) {3}\nRY({2}) {3}\n',
0.25 * np.pi, self._exponent * np.pi,
-0.25 * np.pi, qubits[0])

def _has_stabilizer_effect_(self) -> Optional[bool]:
if self._is_parameterized_():
Expand Down Expand Up @@ -978,7 +981,7 @@ def _quil_(self, qubits: Tuple['cirq.Qid', ...],
formatter: 'cirq.QuilFormatter') -> Optional[str]:
if self._exponent == 1:
return formatter.format('CZ {0} {1}\n', qubits[0], qubits[1])
return formatter.format('CPHASE({0}) {1} {2}\n', self._exponent,
return formatter.format('CPHASE({0}) {1} {2}\n', self._exponent * np.pi,
qubits[0], qubits[1])

def _has_stabilizer_effect_(self) -> Optional[bool]:
Expand Down
15 changes: 9 additions & 6 deletions cirq/ops/parity_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@ def _quil_(self, qubits: Tuple['cirq.Qid', ...],
formatter: 'cirq.QuilFormatter') -> Optional[str]:
if self._exponent == 1:
return formatter.format('X {0}\nX {1}\n', qubits[0], qubits[1])
return formatter.format('RX({0}) {1}\nRX({2}) {3}\n', self._exponent,
qubits[0], self._exponent, qubits[1])
return formatter.format('RX({0}) {1}\nRX({2}) {3}\n',
self._exponent * np.pi, qubits[0],
self._exponent * np.pi, qubits[1])

def __str__(self) -> str:
if self.exponent == 1:
Expand Down Expand Up @@ -188,8 +189,9 @@ def _quil_(self, qubits: Tuple['cirq.Qid', ...],
if self._exponent == 1:
return formatter.format('Y {0}\nY {1}\n', qubits[0], qubits[1])

return formatter.format('RY({0}) {1}\nRY({2}) {3}\n', self._exponent,
qubits[0], self._exponent, qubits[1])
return formatter.format('RY({0}) {1}\nRY({2}) {3}\n',
self._exponent * np.pi, qubits[0],
self._exponent * np.pi, qubits[1])

def __str__(self) -> str:
if self._exponent == 1:
Expand Down Expand Up @@ -269,8 +271,9 @@ def _quil_(self, qubits: Tuple['cirq.Qid', ...],
if self._exponent == 1:
return formatter.format('Z {0}\nZ {1}\n', qubits[0], qubits[1])

return formatter.format('RZ({0}) {1}\nRZ({2}) {3}\n', self._exponent,
qubits[0], self._exponent, qubits[1])
return formatter.format('RZ({0}) {1}\nRZ({2}) {3}\n',
self._exponent * np.pi, qubits[0],
self._exponent * np.pi, qubits[1])

def __str__(self) -> str:
if self._exponent == 1:
Expand Down
5 changes: 3 additions & 2 deletions cirq/ops/swap_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def _quil_(self, qubits: Tuple['cirq.Qid', ...],
formatter: 'cirq.QuilFormatter') -> Optional[str]:
if self._exponent == 1:
return formatter.format('SWAP {0} {1}\n', qubits[0], qubits[1])
return formatter.format('PSWAP({0}) {1} {2}\n', self._exponent,
return formatter.format('PSWAP({0}) {1} {2}\n', self._exponent * np.pi,
qubits[0], qubits[1])

def __str__(self) -> str:
Expand Down Expand Up @@ -262,7 +262,8 @@ def _quil_(self, qubits: Tuple['cirq.Qid', ...],
formatter: 'cirq.QuilFormatter') -> Optional[str]:
if self._exponent == 1:
return formatter.format('ISWAP {0} {1}\n', qubits[0], qubits[1])
return None #ISwap with rotation not supported in Quil
return formatter.format('XY({0}) {1} {2}\n', self._exponent * np.pi,
qubits[0], qubits[1])


def riswap(rads: value.TParamVal) -> ISwapPowGate:
Expand Down
3 changes: 3 additions & 0 deletions dev_tools/conf/pip-list-dev-tools.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ mypy-protobuf==1.10
# For uploading packages to pypi.
twine

# For verifying behavior of Quil output.
pyquil~=2.21.0

# For verifying behavior of qasm output.
qiskit~=0.13.0

Expand Down

0 comments on commit b878d06

Please sign in to comment.