Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cirq.SqrtIswapTargetGateset for (parameterized & non-parameterized) compilation to sqrt iswaps. #5025

Merged
merged 3 commits into from
Feb 28, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Comment on lines +53 to +55
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it an op_tree or a DecomposeResult ? (from fxn return type hint)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DecomposeResult is Union[cirq.OP_TREE, None, NotImplementedType] -- the docstring covers all 3 cases.

"""
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