Skip to content

Commit

Permalink
Add cirq.SqrtIswapTargetGateset for (parameterized & non-parameteri…
Browse files Browse the repository at this point in the history
…zed) compilation to sqrt iswaps. (quantumlib#5025)

* Move parameterized decomposition to analytical decomposers and split PR to remove deprecations

* Add equality tests
  • Loading branch information
tanujkhattar authored and rht committed May 1, 2023
1 parent 80fbfa2 commit 8435378
Show file tree
Hide file tree
Showing 11 changed files with 793 additions and 0 deletions.
2 changes: 2 additions & 0 deletions cirq-core/cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,10 @@
merge_single_qubit_gates_to_phxz,
merge_single_qubit_moments_to_phxz,
optimize_for_target_gateset,
parameterized_2q_op_to_sqrt_iswap_operations,
prepare_two_qubit_state_using_cz,
prepare_two_qubit_state_using_sqrt_iswap,
SqrtIswapTargetGateset,
single_qubit_matrix_to_gates,
single_qubit_matrix_to_pauli_rotations,
single_qubit_matrix_to_phased_x_z,
Expand Down
1 change: 1 addition & 0 deletions cirq-core/cirq/json_resolver_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ def _parallel_gate_op(gate, qubits):
'SingleQubitCliffordGate': cirq.SingleQubitCliffordGate,
'SingleQubitPauliStringGateOperation': cirq.SingleQubitPauliStringGateOperation,
'SingleQubitReadoutCalibrationResult': cirq.experiments.SingleQubitReadoutCalibrationResult,
'SqrtIswapTargetGateset': cirq.SqrtIswapTargetGateset,
'StabilizerStateChForm': cirq.StabilizerStateChForm,
'StatePreparationChannel': cirq.StatePreparationChannel,
'SwapPowGate': cirq.SwapPowGate,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[
{
"cirq_type": "SqrtIswapTargetGateset",
"atol": 1e-08,
"required_sqrt_iswap_count": null,
"use_sqrt_iswap_inv": false
},
{
"cirq_type": "SqrtIswapTargetGateset",
"atol": 1e-08,
"required_sqrt_iswap_count": 1,
"use_sqrt_iswap_inv": false
},
{
"cirq_type": "SqrtIswapTargetGateset",
"atol": 1e-06,
"required_sqrt_iswap_count": 2,
"use_sqrt_iswap_inv": true
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
cirq.SqrtIswapTargetGateset(
atol=1e-08, required_sqrt_iswap_count=None, use_sqrt_iswap_inv=False
),
cirq.SqrtIswapTargetGateset(atol=1e-08, required_sqrt_iswap_count=1, use_sqrt_iswap_inv=False),
cirq.SqrtIswapTargetGateset(atol=1e-06, required_sqrt_iswap_count=2, use_sqrt_iswap_inv=True),
]
2 changes: 2 additions & 0 deletions cirq-core/cirq/transformers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
decompose_multi_controlled_rotation,
decompose_two_qubit_interaction_into_four_fsim_gates,
is_negligible_turn,
parameterized_2q_op_to_sqrt_iswap_operations,
prepare_two_qubit_state_using_cz,
prepare_two_qubit_state_using_sqrt_iswap,
single_qubit_matrix_to_gates,
Expand All @@ -44,6 +45,7 @@
from cirq.transformers.target_gatesets import (
CompilationTargetGateset,
CZTargetGateset,
SqrtIswapTargetGateset,
TwoQubitCompilationTargetGateset,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
)

from cirq.transformers.analytical_decompositions.two_qubit_to_sqrt_iswap import (
parameterized_2q_op_to_sqrt_iswap_operations,
two_qubit_matrix_to_sqrt_iswap_operations,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from typing import Optional, Sequence, Tuple, TYPE_CHECKING

import numpy as np
import sympy

from cirq import circuits, ops, linalg, protocols
from cirq.transformers.analytical_decompositions import single_qubit_decompositions
Expand All @@ -32,6 +33,201 @@
import cirq


def parameterized_2q_op_to_sqrt_iswap_operations(
op: 'cirq.Operation', *, use_sqrt_iswap_inv: bool = False
) -> protocols.decompose_protocol.DecomposeResult:
"""Tries to decompose a parameterized 2q operation into √iSWAP's + parameterized 1q rotations.
Currently only supports decomposing the following gates:
a) `cirq.CZPowGate`
b) `cirq.SwapPowGate`
c) `cirq.ISwapPowGate`
d) `cirq.FSimGate`
Args:
op: Parameterized two qubit operation to be decomposed into sqrt-iswaps.
use_sqrt_iswap_inv: If True, `cirq.SQRT_ISWAP_INV` is used as the target 2q gate, instead
of `cirq.SQRT_ISWAP`.
Returns:
A parameterized `cirq.OP_TREE` implementing `op` using only `cirq.SQRT_ISWAP`
(or `cirq.SQRT_ISWAP_INV`) and parameterized single qubit rotations OR
None or NotImplemented if decomposition of `op` is not known.
"""
gate = op.gate
q0, q1 = op.qubits

if isinstance(gate, ops.CZPowGate):
return _cphase_symbols_to_sqrt_iswap(q0, q1, gate.exponent, use_sqrt_iswap_inv)
if isinstance(gate, ops.SwapPowGate):
return _swap_symbols_to_sqrt_iswap(q0, q1, gate.exponent, use_sqrt_iswap_inv)
if isinstance(gate, ops.ISwapPowGate):
return _iswap_symbols_to_sqrt_iswap(q0, q1, gate.exponent, use_sqrt_iswap_inv)
if isinstance(gate, ops.FSimGate):
return _fsim_symbols_to_sqrt_iswap(q0, q1, gate.theta, gate.phi, use_sqrt_iswap_inv)
return NotImplemented


def _sqrt_iswap_inv(
a: 'cirq.Qid', b: 'cirq.Qid', use_sqrt_iswap_inv: bool = True
) -> 'cirq.OP_TREE':
"""Optree implementing `cirq.SQRT_ISWAP_INV(a, b)` using √iSWAPs.
Args:
a: The first qubit.
b: The second qubit.
use_sqrt_iswap_inv: If True, `cirq.SQRT_ISWAP_INV` is used instead of `cirq.SQRT_ISWAP`.
Returns:
`cirq.SQRT_ISWAP_INV(a, b)` or equivalent unitary implemented using `cirq.SQRT_ISWAP`.
"""
return (
ops.SQRT_ISWAP_INV(a, b)
if use_sqrt_iswap_inv
else [ops.Z(a), ops.SQRT_ISWAP(a, b), ops.Z(a)]
)


def _cphase_symbols_to_sqrt_iswap(
a: 'cirq.Qid', b: 'cirq.Qid', turns: 'cirq.TParamVal', use_sqrt_iswap_inv: bool = True
):
"""Implements `cirq.CZ(a, b) ** turns` using two √iSWAPs and single qubit rotations.
Output unitary:
[[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, g]]
where:
g = exp(i·π·t).
Args:
a: The first qubit.
b: The second qubit.
turns: The rotational angle (t) that specifies the gate, where
g = exp(i·π·t/2).
use_sqrt_iswap_inv: If True, `cirq.SQRT_ISWAP_INV` is used instead of `cirq.SQRT_ISWAP`.
Yields:
A `cirq.OP_TREE` representing the decomposition.
"""
theta = sympy.Mod(turns, 2.0) * sympy.pi

# -1 if theta > pi. Adds a hacky fudge factor so theta=pi is not 0
sign = sympy.sign(sympy.pi - theta + 1e-9)

# For sign = 1: theta. For sign = -1, 2pi-theta
theta_prime = (sympy.pi - sign * sympy.pi) + sign * theta

phi = sympy.asin(np.sqrt(2) * sympy.sin(theta_prime / 4))
xi = sympy.atan(sympy.tan(phi) / np.sqrt(2))

yield ops.rz(sign * 0.5 * theta_prime).on(a)
yield ops.rz(sign * 0.5 * theta_prime).on(b)
yield ops.rx(xi).on(a)
yield ops.X(b) ** (-sign * 0.5)
yield _sqrt_iswap_inv(a, b, use_sqrt_iswap_inv)
yield ops.rx(-2 * phi).on(a)
yield ops.Z(a)
yield _sqrt_iswap_inv(a, b, use_sqrt_iswap_inv)
yield ops.Z(a)
yield ops.rx(xi).on(a)
yield ops.X(b) ** (sign * 0.5)


def _swap_symbols_to_sqrt_iswap(
a: 'cirq.Qid', b: 'cirq.Qid', turns: 'cirq.TParamVal', use_sqrt_iswap_inv: bool = True
):
"""Implements `cirq.SWAP(a, b) ** turns` using two √iSWAPs and single qubit rotations.
Output unitary:
[[1, 0, 0, 0],
[0, g·c, -i·g·s, 0],
[0, -i·g·s, g·c, 0],
[0, 0, 0, 1]]
where:
c = cos(π·t/2), s = sin(π·t/2), g = exp(i·π·t/2).
Args:
a: The first qubit.
b: The second qubit.
turns: The rotational angle (t) that specifies the gate, where
c = cos(π·t/2), s = sin(π·t/2), g = exp(i·π·t/2).
use_sqrt_iswap_inv: If True, `cirq.SQRT_ISWAP_INV` is used instead of `cirq.SQRT_ISWAP`.
Yields:
A `cirq.OP_TREE` representing the decomposition.
"""
yield ops.Z(a) ** 1.25
yield ops.Z(b) ** -0.25
yield _sqrt_iswap_inv(a, b, use_sqrt_iswap_inv)
yield ops.Z(a) ** (-turns / 2 + 1)
yield ops.Z(b) ** (turns / 2)
yield _sqrt_iswap_inv(a, b, use_sqrt_iswap_inv)
yield ops.Z(a) ** (turns / 2 - 0.25)
yield ops.Z(b) ** (turns / 2 + 0.25)
yield _cphase_symbols_to_sqrt_iswap(a, b, -turns, use_sqrt_iswap_inv)


def _iswap_symbols_to_sqrt_iswap(
a: 'cirq.Qid', b: 'cirq.Qid', turns: 'cirq.TParamVal', use_sqrt_iswap_inv: bool = True
):
"""Implements `cirq.ISWAP(a, b) ** turns` using two √iSWAPs and single qubit rotations.
Output unitary:
[[1 0 0 0],
[0 c is 0],
[0 is c 0],
[0 0 0 1]]
where c = cos(π·t/2), s = sin(π·t/2).
Args:
a: The first qubit.
b: The second qubit.
turns: The rotational angle (t) that specifies the gate, where
c = cos(π·t/2), s = sin(π·t/2).
use_sqrt_iswap_inv: If True, `cirq.SQRT_ISWAP_INV` is used instead of `cirq.SQRT_ISWAP`.
Yields:
A `cirq.OP_TREE` representing the decomposition.
"""
yield ops.Z(a) ** 0.75
yield ops.Z(b) ** 0.25
yield _sqrt_iswap_inv(a, b, use_sqrt_iswap_inv)
yield ops.Z(a) ** (-turns / 2 + 1)
yield ops.Z(b) ** (turns / 2)
yield _sqrt_iswap_inv(a, b, use_sqrt_iswap_inv)
yield ops.Z(a) ** 0.25
yield ops.Z(b) ** -0.25


def _fsim_symbols_to_sqrt_iswap(
a: 'cirq.Qid',
b: 'cirq.Qid',
theta: 'cirq.TParamVal',
phi: 'cirq.TParamVal',
use_sqrt_iswap_inv: bool = True,
):
"""Implements `cirq.FSimGate(theta, phi)(a, b)` using two √iSWAPs and single qubit rotations.
FSimGate(θ, φ) = ISWAP**(-2θ/π) CZPowGate(exponent=-φ/π)
Args:
a: The first qubit.
b: The second qubit.
theta: Swap angle on the ``|01⟩`` ``|10⟩`` subspace, in radians.
phi: Controlled phase angle, in radians.
use_sqrt_iswap_inv: If True, `cirq.SQRT_ISWAP_INV` is used instead of `cirq.SQRT_ISWAP`.
Yields:
A `cirq.OP_TREE` representing the decomposition.
"""
if theta != 0.0:
yield _iswap_symbols_to_sqrt_iswap(a, b, -2 * theta / np.pi, use_sqrt_iswap_inv)
if phi != 0.0:
yield _cphase_symbols_to_sqrt_iswap(a, b, -phi / np.pi, use_sqrt_iswap_inv)


def two_qubit_matrix_to_sqrt_iswap_operations(
q0: 'cirq.Qid',
q1: 'cirq.Qid',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import pytest

import cirq
import sympy

ALLOW_DEPRECATION_IN_TEST = 'ALLOW_DEPRECATION_IN_TEST'

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


@pytest.mark.parametrize(
'gate',
[
cirq.ISwapPowGate(exponent=sympy.Symbol('t')),
cirq.SwapPowGate(exponent=sympy.Symbol('t')),
cirq.CZPowGate(exponent=sympy.Symbol('t')),
],
)
def test_two_qubit_gates_with_symbols(gate: cirq.Gate):
op = gate(*cirq.LineQubit.range(2))
c_new_sqrt_iswap = cirq.Circuit(cirq.parameterized_2q_op_to_sqrt_iswap_operations(op))
c_new_sqrt_iswap_inv = cirq.Circuit(
cirq.parameterized_2q_op_to_sqrt_iswap_operations(op, use_sqrt_iswap_inv=True)
)
# Check if unitaries are the same
for val in np.linspace(0, 2 * np.pi, 12):
cirq.testing.assert_allclose_up_to_global_phase(
cirq.unitary(cirq.resolve_parameters(op, {'t': val})),
cirq.unitary(cirq.resolve_parameters(c_new_sqrt_iswap, {'t': val})),
atol=1e-6,
)
cirq.testing.assert_allclose_up_to_global_phase(
cirq.unitary(cirq.resolve_parameters(op, {'t': val})),
cirq.unitary(cirq.resolve_parameters(c_new_sqrt_iswap_inv, {'t': val})),
atol=1e-6,
)


def test_fsim_gate_with_symbols():
theta, phi = sympy.symbols(['theta', 'phi'])
op = cirq.FSimGate(theta=theta, phi=phi).on(*cirq.LineQubit.range(2))
c_new_sqrt_iswap = cirq.Circuit(cirq.parameterized_2q_op_to_sqrt_iswap_operations(op))
c_new_sqrt_iswap_inv = cirq.Circuit(
cirq.parameterized_2q_op_to_sqrt_iswap_operations(op, use_sqrt_iswap_inv=True)
)
for theta_val in np.linspace(0, 2 * np.pi, 12):
for phi_val in np.linspace(0, 2 * np.pi, 12):
cirq.testing.assert_allclose_up_to_global_phase(
cirq.unitary(cirq.resolve_parameters(op, {'theta': theta_val, 'phi': phi_val})),
cirq.unitary(
cirq.resolve_parameters(c_new_sqrt_iswap, {'theta': theta_val, 'phi': phi_val})
),
atol=1e-6,
)
cirq.testing.assert_allclose_up_to_global_phase(
cirq.unitary(cirq.resolve_parameters(op, {'theta': theta_val, 'phi': phi_val})),
cirq.unitary(
cirq.resolve_parameters(
c_new_sqrt_iswap_inv, {'theta': theta_val, 'phi': phi_val}
)
),
atol=1e-6,
)


@pytest.mark.parametrize('cnt', [-1, 4, 10])
def test_invalid_required_sqrt_iswap_count(cnt):
u = TWO_SQRT_ISWAP_UNITARIES[0]
Expand Down
2 changes: 2 additions & 0 deletions cirq-core/cirq/transformers/target_gatesets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@
)

from cirq.transformers.target_gatesets.cz_gateset import CZTargetGateset

from cirq.transformers.target_gatesets.sqrt_iswap_gateset import SqrtIswapTargetGateset
Loading

0 comments on commit 8435378

Please sign in to comment.