Skip to content

Commit 7c0179d

Browse files
authored
Add cirq.SqrtIswapTargetGateset for (parameterized & non-parameterized) compilation to sqrt iswaps. (quantumlib#5025)
* Move parameterized decomposition to analytical decomposers and split PR to remove deprecations * Add equality tests
1 parent 05649c2 commit 7c0179d

11 files changed

+793
-0
lines changed

cirq/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,8 +384,10 @@
384384
merge_single_qubit_gates_to_phxz,
385385
merge_single_qubit_moments_to_phxz,
386386
optimize_for_target_gateset,
387+
parameterized_2q_op_to_sqrt_iswap_operations,
387388
prepare_two_qubit_state_using_cz,
388389
prepare_two_qubit_state_using_sqrt_iswap,
390+
SqrtIswapTargetGateset,
389391
single_qubit_matrix_to_gates,
390392
single_qubit_matrix_to_pauli_rotations,
391393
single_qubit_matrix_to_phased_x_z,

cirq/json_resolver_cache.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ def _parallel_gate_op(gate, qubits):
156156
'SingleQubitCliffordGate': cirq.SingleQubitCliffordGate,
157157
'SingleQubitPauliStringGateOperation': cirq.SingleQubitPauliStringGateOperation,
158158
'SingleQubitReadoutCalibrationResult': cirq.experiments.SingleQubitReadoutCalibrationResult,
159+
'SqrtIswapTargetGateset': cirq.SqrtIswapTargetGateset,
159160
'StabilizerStateChForm': cirq.StabilizerStateChForm,
160161
'StatePreparationChannel': cirq.StatePreparationChannel,
161162
'SwapPowGate': cirq.SwapPowGate,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[
2+
{
3+
"cirq_type": "SqrtIswapTargetGateset",
4+
"atol": 1e-08,
5+
"required_sqrt_iswap_count": null,
6+
"use_sqrt_iswap_inv": false
7+
},
8+
{
9+
"cirq_type": "SqrtIswapTargetGateset",
10+
"atol": 1e-08,
11+
"required_sqrt_iswap_count": 1,
12+
"use_sqrt_iswap_inv": false
13+
},
14+
{
15+
"cirq_type": "SqrtIswapTargetGateset",
16+
"atol": 1e-06,
17+
"required_sqrt_iswap_count": 2,
18+
"use_sqrt_iswap_inv": true
19+
}
20+
]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
cirq.SqrtIswapTargetGateset(
3+
atol=1e-08, required_sqrt_iswap_count=None, use_sqrt_iswap_inv=False
4+
),
5+
cirq.SqrtIswapTargetGateset(atol=1e-08, required_sqrt_iswap_count=1, use_sqrt_iswap_inv=False),
6+
cirq.SqrtIswapTargetGateset(atol=1e-06, required_sqrt_iswap_count=2, use_sqrt_iswap_inv=True),
7+
]

cirq/transformers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
decompose_multi_controlled_rotation,
2323
decompose_two_qubit_interaction_into_four_fsim_gates,
2424
is_negligible_turn,
25+
parameterized_2q_op_to_sqrt_iswap_operations,
2526
prepare_two_qubit_state_using_cz,
2627
prepare_two_qubit_state_using_sqrt_iswap,
2728
single_qubit_matrix_to_gates,
@@ -44,6 +45,7 @@
4445
from cirq.transformers.target_gatesets import (
4546
CompilationTargetGateset,
4647
CZTargetGateset,
48+
SqrtIswapTargetGateset,
4749
TwoQubitCompilationTargetGateset,
4850
)
4951

cirq/transformers/analytical_decompositions/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
)
5252

5353
from cirq.transformers.analytical_decompositions.two_qubit_to_sqrt_iswap import (
54+
parameterized_2q_op_to_sqrt_iswap_operations,
5455
two_qubit_matrix_to_sqrt_iswap_operations,
5556
)
5657

cirq/transformers/analytical_decompositions/two_qubit_to_sqrt_iswap.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from typing import Optional, Sequence, Tuple, TYPE_CHECKING
2424

2525
import numpy as np
26+
import sympy
2627

2728
from cirq import circuits, ops, linalg, protocols
2829
from cirq.transformers.analytical_decompositions import single_qubit_decompositions
@@ -32,6 +33,201 @@
3233
import cirq
3334

3435

36+
def parameterized_2q_op_to_sqrt_iswap_operations(
37+
op: 'cirq.Operation', *, use_sqrt_iswap_inv: bool = False
38+
) -> protocols.decompose_protocol.DecomposeResult:
39+
"""Tries to decompose a parameterized 2q operation into √iSWAP's + parameterized 1q rotations.
40+
41+
Currently only supports decomposing the following gates:
42+
a) `cirq.CZPowGate`
43+
b) `cirq.SwapPowGate`
44+
c) `cirq.ISwapPowGate`
45+
d) `cirq.FSimGate`
46+
47+
Args:
48+
op: Parameterized two qubit operation to be decomposed into sqrt-iswaps.
49+
use_sqrt_iswap_inv: If True, `cirq.SQRT_ISWAP_INV` is used as the target 2q gate, instead
50+
of `cirq.SQRT_ISWAP`.
51+
52+
Returns:
53+
A parameterized `cirq.OP_TREE` implementing `op` using only `cirq.SQRT_ISWAP`
54+
(or `cirq.SQRT_ISWAP_INV`) and parameterized single qubit rotations OR
55+
None or NotImplemented if decomposition of `op` is not known.
56+
"""
57+
gate = op.gate
58+
q0, q1 = op.qubits
59+
60+
if isinstance(gate, ops.CZPowGate):
61+
return _cphase_symbols_to_sqrt_iswap(q0, q1, gate.exponent, use_sqrt_iswap_inv)
62+
if isinstance(gate, ops.SwapPowGate):
63+
return _swap_symbols_to_sqrt_iswap(q0, q1, gate.exponent, use_sqrt_iswap_inv)
64+
if isinstance(gate, ops.ISwapPowGate):
65+
return _iswap_symbols_to_sqrt_iswap(q0, q1, gate.exponent, use_sqrt_iswap_inv)
66+
if isinstance(gate, ops.FSimGate):
67+
return _fsim_symbols_to_sqrt_iswap(q0, q1, gate.theta, gate.phi, use_sqrt_iswap_inv)
68+
return NotImplemented
69+
70+
71+
def _sqrt_iswap_inv(
72+
a: 'cirq.Qid', b: 'cirq.Qid', use_sqrt_iswap_inv: bool = True
73+
) -> 'cirq.OP_TREE':
74+
"""Optree implementing `cirq.SQRT_ISWAP_INV(a, b)` using √iSWAPs.
75+
76+
Args:
77+
a: The first qubit.
78+
b: The second qubit.
79+
use_sqrt_iswap_inv: If True, `cirq.SQRT_ISWAP_INV` is used instead of `cirq.SQRT_ISWAP`.
80+
81+
Returns:
82+
`cirq.SQRT_ISWAP_INV(a, b)` or equivalent unitary implemented using `cirq.SQRT_ISWAP`.
83+
"""
84+
return (
85+
ops.SQRT_ISWAP_INV(a, b)
86+
if use_sqrt_iswap_inv
87+
else [ops.Z(a), ops.SQRT_ISWAP(a, b), ops.Z(a)]
88+
)
89+
90+
91+
def _cphase_symbols_to_sqrt_iswap(
92+
a: 'cirq.Qid', b: 'cirq.Qid', turns: 'cirq.TParamVal', use_sqrt_iswap_inv: bool = True
93+
):
94+
"""Implements `cirq.CZ(a, b) ** turns` using two √iSWAPs and single qubit rotations.
95+
96+
Output unitary:
97+
[[1, 0, 0, 0],
98+
[0, 1, 0, 0],
99+
[0, 0, 1, 0],
100+
[0, 0, 0, g]]
101+
where:
102+
g = exp(i·π·t).
103+
104+
Args:
105+
a: The first qubit.
106+
b: The second qubit.
107+
turns: The rotational angle (t) that specifies the gate, where
108+
g = exp(i·π·t/2).
109+
use_sqrt_iswap_inv: If True, `cirq.SQRT_ISWAP_INV` is used instead of `cirq.SQRT_ISWAP`.
110+
111+
Yields:
112+
A `cirq.OP_TREE` representing the decomposition.
113+
"""
114+
theta = sympy.Mod(turns, 2.0) * sympy.pi
115+
116+
# -1 if theta > pi. Adds a hacky fudge factor so theta=pi is not 0
117+
sign = sympy.sign(sympy.pi - theta + 1e-9)
118+
119+
# For sign = 1: theta. For sign = -1, 2pi-theta
120+
theta_prime = (sympy.pi - sign * sympy.pi) + sign * theta
121+
122+
phi = sympy.asin(np.sqrt(2) * sympy.sin(theta_prime / 4))
123+
xi = sympy.atan(sympy.tan(phi) / np.sqrt(2))
124+
125+
yield ops.rz(sign * 0.5 * theta_prime).on(a)
126+
yield ops.rz(sign * 0.5 * theta_prime).on(b)
127+
yield ops.rx(xi).on(a)
128+
yield ops.X(b) ** (-sign * 0.5)
129+
yield _sqrt_iswap_inv(a, b, use_sqrt_iswap_inv)
130+
yield ops.rx(-2 * phi).on(a)
131+
yield ops.Z(a)
132+
yield _sqrt_iswap_inv(a, b, use_sqrt_iswap_inv)
133+
yield ops.Z(a)
134+
yield ops.rx(xi).on(a)
135+
yield ops.X(b) ** (sign * 0.5)
136+
137+
138+
def _swap_symbols_to_sqrt_iswap(
139+
a: 'cirq.Qid', b: 'cirq.Qid', turns: 'cirq.TParamVal', use_sqrt_iswap_inv: bool = True
140+
):
141+
"""Implements `cirq.SWAP(a, b) ** turns` using two √iSWAPs and single qubit rotations.
142+
143+
Output unitary:
144+
[[1, 0, 0, 0],
145+
[0, g·c, -i·g·s, 0],
146+
[0, -i·g·s, g·c, 0],
147+
[0, 0, 0, 1]]
148+
where:
149+
c = cos(π·t/2), s = sin(π·t/2), g = exp(i·π·t/2).
150+
151+
Args:
152+
a: The first qubit.
153+
b: The second qubit.
154+
turns: The rotational angle (t) that specifies the gate, where
155+
c = cos(π·t/2), s = sin(π·t/2), g = exp(i·π·t/2).
156+
use_sqrt_iswap_inv: If True, `cirq.SQRT_ISWAP_INV` is used instead of `cirq.SQRT_ISWAP`.
157+
158+
Yields:
159+
A `cirq.OP_TREE` representing the decomposition.
160+
"""
161+
yield ops.Z(a) ** 1.25
162+
yield ops.Z(b) ** -0.25
163+
yield _sqrt_iswap_inv(a, b, use_sqrt_iswap_inv)
164+
yield ops.Z(a) ** (-turns / 2 + 1)
165+
yield ops.Z(b) ** (turns / 2)
166+
yield _sqrt_iswap_inv(a, b, use_sqrt_iswap_inv)
167+
yield ops.Z(a) ** (turns / 2 - 0.25)
168+
yield ops.Z(b) ** (turns / 2 + 0.25)
169+
yield _cphase_symbols_to_sqrt_iswap(a, b, -turns, use_sqrt_iswap_inv)
170+
171+
172+
def _iswap_symbols_to_sqrt_iswap(
173+
a: 'cirq.Qid', b: 'cirq.Qid', turns: 'cirq.TParamVal', use_sqrt_iswap_inv: bool = True
174+
):
175+
"""Implements `cirq.ISWAP(a, b) ** turns` using two √iSWAPs and single qubit rotations.
176+
177+
Output unitary:
178+
[[1 0 0 0],
179+
[0 c is 0],
180+
[0 is c 0],
181+
[0 0 0 1]]
182+
where c = cos(π·t/2), s = sin(π·t/2).
183+
184+
Args:
185+
a: The first qubit.
186+
b: The second qubit.
187+
turns: The rotational angle (t) that specifies the gate, where
188+
c = cos(π·t/2), s = sin(π·t/2).
189+
use_sqrt_iswap_inv: If True, `cirq.SQRT_ISWAP_INV` is used instead of `cirq.SQRT_ISWAP`.
190+
191+
Yields:
192+
A `cirq.OP_TREE` representing the decomposition.
193+
"""
194+
yield ops.Z(a) ** 0.75
195+
yield ops.Z(b) ** 0.25
196+
yield _sqrt_iswap_inv(a, b, use_sqrt_iswap_inv)
197+
yield ops.Z(a) ** (-turns / 2 + 1)
198+
yield ops.Z(b) ** (turns / 2)
199+
yield _sqrt_iswap_inv(a, b, use_sqrt_iswap_inv)
200+
yield ops.Z(a) ** 0.25
201+
yield ops.Z(b) ** -0.25
202+
203+
204+
def _fsim_symbols_to_sqrt_iswap(
205+
a: 'cirq.Qid',
206+
b: 'cirq.Qid',
207+
theta: 'cirq.TParamVal',
208+
phi: 'cirq.TParamVal',
209+
use_sqrt_iswap_inv: bool = True,
210+
):
211+
"""Implements `cirq.FSimGate(theta, phi)(a, b)` using two √iSWAPs and single qubit rotations.
212+
213+
FSimGate(θ, φ) = ISWAP**(-2θ/π) CZPowGate(exponent=-φ/π)
214+
215+
Args:
216+
a: The first qubit.
217+
b: The second qubit.
218+
theta: Swap angle on the ``|01⟩`` ``|10⟩`` subspace, in radians.
219+
phi: Controlled phase angle, in radians.
220+
use_sqrt_iswap_inv: If True, `cirq.SQRT_ISWAP_INV` is used instead of `cirq.SQRT_ISWAP`.
221+
222+
Yields:
223+
A `cirq.OP_TREE` representing the decomposition.
224+
"""
225+
if theta != 0.0:
226+
yield _iswap_symbols_to_sqrt_iswap(a, b, -2 * theta / np.pi, use_sqrt_iswap_inv)
227+
if phi != 0.0:
228+
yield _cphase_symbols_to_sqrt_iswap(a, b, -phi / np.pi, use_sqrt_iswap_inv)
229+
230+
35231
def two_qubit_matrix_to_sqrt_iswap_operations(
36232
q0: 'cirq.Qid',
37233
q1: 'cirq.Qid',

cirq/transformers/analytical_decompositions/two_qubit_to_sqrt_iswap_test.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import pytest
1818

1919
import cirq
20+
import sympy
2021

2122
ALLOW_DEPRECATION_IN_TEST = 'ALLOW_DEPRECATION_IN_TEST'
2223

@@ -258,6 +259,61 @@ def assert_specific_sqrt_iswap_count(operations, count):
258259
assert actual == count, f'Incorrect sqrt-iSWAP count. Expected {count} but got {actual}.'
259260

260261

262+
@pytest.mark.parametrize(
263+
'gate',
264+
[
265+
cirq.ISwapPowGate(exponent=sympy.Symbol('t')),
266+
cirq.SwapPowGate(exponent=sympy.Symbol('t')),
267+
cirq.CZPowGate(exponent=sympy.Symbol('t')),
268+
],
269+
)
270+
def test_two_qubit_gates_with_symbols(gate: cirq.Gate):
271+
op = gate(*cirq.LineQubit.range(2))
272+
c_new_sqrt_iswap = cirq.Circuit(cirq.parameterized_2q_op_to_sqrt_iswap_operations(op))
273+
c_new_sqrt_iswap_inv = cirq.Circuit(
274+
cirq.parameterized_2q_op_to_sqrt_iswap_operations(op, use_sqrt_iswap_inv=True)
275+
)
276+
# Check if unitaries are the same
277+
for val in np.linspace(0, 2 * np.pi, 12):
278+
cirq.testing.assert_allclose_up_to_global_phase(
279+
cirq.unitary(cirq.resolve_parameters(op, {'t': val})),
280+
cirq.unitary(cirq.resolve_parameters(c_new_sqrt_iswap, {'t': val})),
281+
atol=1e-6,
282+
)
283+
cirq.testing.assert_allclose_up_to_global_phase(
284+
cirq.unitary(cirq.resolve_parameters(op, {'t': val})),
285+
cirq.unitary(cirq.resolve_parameters(c_new_sqrt_iswap_inv, {'t': val})),
286+
atol=1e-6,
287+
)
288+
289+
290+
def test_fsim_gate_with_symbols():
291+
theta, phi = sympy.symbols(['theta', 'phi'])
292+
op = cirq.FSimGate(theta=theta, phi=phi).on(*cirq.LineQubit.range(2))
293+
c_new_sqrt_iswap = cirq.Circuit(cirq.parameterized_2q_op_to_sqrt_iswap_operations(op))
294+
c_new_sqrt_iswap_inv = cirq.Circuit(
295+
cirq.parameterized_2q_op_to_sqrt_iswap_operations(op, use_sqrt_iswap_inv=True)
296+
)
297+
for theta_val in np.linspace(0, 2 * np.pi, 12):
298+
for phi_val in np.linspace(0, 2 * np.pi, 12):
299+
cirq.testing.assert_allclose_up_to_global_phase(
300+
cirq.unitary(cirq.resolve_parameters(op, {'theta': theta_val, 'phi': phi_val})),
301+
cirq.unitary(
302+
cirq.resolve_parameters(c_new_sqrt_iswap, {'theta': theta_val, 'phi': phi_val})
303+
),
304+
atol=1e-6,
305+
)
306+
cirq.testing.assert_allclose_up_to_global_phase(
307+
cirq.unitary(cirq.resolve_parameters(op, {'theta': theta_val, 'phi': phi_val})),
308+
cirq.unitary(
309+
cirq.resolve_parameters(
310+
c_new_sqrt_iswap_inv, {'theta': theta_val, 'phi': phi_val}
311+
)
312+
),
313+
atol=1e-6,
314+
)
315+
316+
261317
@pytest.mark.parametrize('cnt', [-1, 4, 10])
262318
def test_invalid_required_sqrt_iswap_count(cnt):
263319
u = TWO_SQRT_ISWAP_UNITARIES[0]

cirq/transformers/target_gatesets/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@
2020
)
2121

2222
from cirq.transformers.target_gatesets.cz_gateset import CZTargetGateset
23+
24+
from cirq.transformers.target_gatesets.sqrt_iswap_gateset import SqrtIswapTargetGateset

0 commit comments

Comments
 (0)