Skip to content

Commit

Permalink
Add support for WaitGate and inverse of sqrt(iSWAP) to characterizati…
Browse files Browse the repository at this point in the history
…on and compilation (#3750)

* Change definition of run_floquet_characterization_for_circuit

* Remove the dummy progress step

* Apply Doug's comments

* Reformat file

* Remove support for json serialization

* Add note to add JSON serialization

* Fix documentation

* Remove support for JSON serialization

* Fix linter errors

* Add documentation for request and result objects

* Add documentation for run_characaterizations

* Remove handler_name argument

* Fix type for pairs field

* Add JSON tests for PhasedFSimCharacterization.

* Revert "Remove support for json serialization"

This reverts commit 7bb1e34.

* fix formatting

* Add tests for encoding and decoding of calibration requests/results.

* Fix lint error

* Fix lint errors

* Fix merge issues

* Remove gate set from dataclasses

* Add reference to mypy import issue

* Add JSON serialization for all Floquet calibration objects

* Formatting

* Add tests fro get_parameters

* Increase test coverage a bit more.

* Add copyrights and one more trivial test

* Add copyright notices

* Fix remaining test coverage by providing a test for run_characterization

* Remove redundant gate_set arguments.

* Add docstrings and reformat code

* Add docstrings

* Fix linter errors

* Addressed review comments

* Remove debug line.

* Fix type checks

* Restore abstract class for request container

* Format files

* Fix simulator abstract method

* Fix formatting

* Ignore type check for abstract dataclass

* Ignore type check for abstract dataclass

* Fix formatting

* Fix formatting

* Fix linter errors

* Fix passed parameters

* Remove TODOs

* Remove TODOs

* Remove TODOs

* Fixes to json handling

* Add run_floquet_characterization_for_circuit test

* Fix typing issue

* Clarify documentation for gate attribute.

* Better names for options creators

* Add constant for default options

* Fix engine simulator tests

* Add support for WaitGates

* Remove Floquet-specific characterization result.

* Fixes after merge

* Fixes after merge

* Fixes after merge

* Add incomplete support for ISWAP ** 0.5 gates

* Fix override method

* Add docstring and rename qubit_pairs

* Add sqrt_iswap_gates_translator to a public interface

* Rename request preparation methods

* Number of minor improvements

* Fix typing errors

* Ignore lru_cache in mypy

* Workaround for mypy issues

* Fix formatting

* Improve sqrt_iswap_gates_translator

* Add tests for try_convert_sqrt_iswap_to_fsim

* Make characterization options constants

* Better naming

* Better variable naming

* Exclude constants from serialization

* Add missing coverage tests

* Fix coverage tests

* Refactor make_floquet_request_for_circuit

* Improve naming

* Fix typing error

* Restore missing import

* Include WaitGate in zphases corrections

* Add test for calibration

* Fix merge mistake

* Fixes after merge

* Downgrade python fstring

* Fix typing errors

* Fix serialization tests

* Clean up gate generation

* Improve coverage tests

* Add more tests

* Add missing tests

* Add missing tests

* Provide missing documentation

* Fix lint errors

* Add missing tests

* Fix simulator bug and add missing test coverage

* Fix linter errors

* Add missing coverage test

* Fix coverage

* Fix coverage test

* Remove unused variable

* Fix formatting

* Improve documentation and arguments

* Move the converter to the top level

* Fix formatting

* Further fixes

* Increase test tolerance

* Increase test tolerance

* Refactor phased calibration

* Improve the documentation and method signatures

* Add override creator to options.

* Clean up method return signatures

* Fix docstring

* Fix mypy issues

* Fix json test

* Better naming

* Add tests to cover coverage requirements

* Fix json tests

* Add coverage tests

* Add dosctrings and tests

* Fix formatting

* Improve phase exponent handling

* Fix formatting

* Readibility improvements

* Fix docstring

* Readability improvements

* Add support for periodic values of theta

* Better syntax

* Gather compensation logic in one place

* Fix formatting

* Rename dataclass

* Add more tests and fix a bug

* Update cirq/google/calibration/phased_fsim.py

Co-authored-by: Matthew Harrigan <matthew.harrigan@outlook.com>

Co-authored-by: Doug Strain <dstrain@google.com>
Co-authored-by: Cirq Bot <craiggidney+github+cirqbot@google.com>
Co-authored-by: Matthew Harrigan <matthew.harrigan@outlook.com>
  • Loading branch information
4 people committed Feb 10, 2021
1 parent 18e11b3 commit 2c904da
Show file tree
Hide file tree
Showing 6 changed files with 386 additions and 132 deletions.
28 changes: 18 additions & 10 deletions cirq/google/calibration/engine_simulator.py
Expand Up @@ -38,6 +38,7 @@

from cirq.google.calibration.phased_fsim import (
FloquetPhasedFSimCalibrationRequest,
PhaseCalibratedFSimGate,
IncompatibleMomentError,
PhasedFSimCalibrationRequest,
PhasedFSimCalibrationResult,
Expand Down Expand Up @@ -71,7 +72,9 @@ def __init__(
simulator: Simulator,
*,
drift_generator: ParametersDriftGenerator,
gates_translator: Callable[[Gate], Optional[FSimGate]] = try_convert_sqrt_iswap_to_fsim,
gates_translator: Callable[
[Gate], Optional[PhaseCalibratedFSimGate]
] = try_convert_sqrt_iswap_to_fsim,
) -> None:
"""Initializes the PhasedFSimEngineSimulator.
Expand Down Expand Up @@ -341,13 +344,13 @@ def get_calibrations(
else:
raise ValueError(f'Unsupported calibration request {request}')

translated_gate = self.gates_translator(request.gate)
if translated_gate is None:
translated = self.gates_translator(request.gate)
if translated is None:
raise ValueError(f'Calibration request contains unsupported gate {request.gate}')

parameters = {}
for a, b in request.pairs:
drifted = self.create_gate_with_drift(a, b, translated_gate)
drifted = self.create_gate_with_drift(a, b, translated)
parameters[a, b] = PhasedFSimCharacterization(
theta=drifted.theta if characterize_theta else None,
zeta=drifted.zeta if characterize_zeta else None,
Expand All @@ -364,25 +367,29 @@ def get_calibrations(

return results

def create_gate_with_drift(self, a: Qid, b: Qid, gate: FSimGate) -> PhasedFSimGate:
def create_gate_with_drift(
self, a: Qid, b: Qid, gate_calibration: PhaseCalibratedFSimGate
) -> PhasedFSimGate:
"""Generates a gate with drift for a given gate.
Args:
a: The first qubit.
b: The second qubit.
gate: Gate which a modified version of should be generated.
gate_calibration: Reference gate together with a phase information.
Returns:
A modified gate that includes the drifts induced by internal state of the simulator.
"""
gate = gate_calibration.engine_gate
if (a, b, gate) in self._drifted_parameters:
parameters = self._drifted_parameters[(a, b, gate)]
elif (b, a, gate) in self._drifted_parameters:
parameters = self._drifted_parameters[(b, a, gate)].parameters_for_qubits_swapped()
else:
parameters = self._drift_generator(a, b, gate)
self._drifted_parameters[(a, b, gate)] = parameters
return PhasedFSimGate(**parameters.asdict())

return gate_calibration.as_characterized_phased_fsim_gate(parameters)

def _run(
self, circuit: Circuit, param_resolver: ParamResolver, repetitions: int
Expand Down Expand Up @@ -414,13 +421,14 @@ def optimization_at(
else:
if op.gate is None:
raise IncompatibleMomentError(f'Operation {op} has a missing gate')
translated_gate = self._simulator.gates_translator(op.gate)
if translated_gate is None:
translated = self._simulator.gates_translator(op.gate)
if translated is None:
raise IncompatibleMomentError(
f'Moment contains non-single qubit operation ' f'{op} with unsupported gate'
)

a, b = op.qubits
new_op = self._simulator.create_gate_with_drift(a, b, translated_gate).on(a, b)
new_op = self._simulator.create_gate_with_drift(a, b, translated).on(a, b)

return PointOptimizationSummary(clear_span=1, clear_qubits=op.qubits, new_operations=new_op)

Expand Down
18 changes: 18 additions & 0 deletions cirq/google/calibration/engine_simulator_test.py
Expand Up @@ -128,6 +128,24 @@ def test_ideal_sqrt_iswap_simulates_correctly() -> None:
assert cirq.allclose_up_to_global_phase(actual, expected)


def test_ideal_sqrt_iswap_inverse_simulates_correctly() -> None:
a, b, c, d = cirq.LineQubit.range(4)
circuit = cirq.Circuit(
[
[cirq.X(a), cirq.Y(c)],
[cirq.FSimGate(-np.pi / 4, 0.0).on(a, b), cirq.FSimGate(-np.pi / 4, 0.0).on(c, d)],
[cirq.FSimGate(-np.pi / 4, 0.0).on(b, c)],
]
)

engine_simulator = PhasedFSimEngineSimulator.create_with_ideal_sqrt_iswap()

actual = engine_simulator.final_state_vector(circuit)
expected = cirq.final_state_vector(circuit)

assert cirq.allclose_up_to_global_phase(actual, expected)


def test_ideal_sqrt_iswap_simulates_correctly_invalid_circuit_fails() -> None:
engine_simulator = PhasedFSimEngineSimulator.create_with_ideal_sqrt_iswap()

Expand Down
113 changes: 107 additions & 6 deletions cirq/google/calibration/phased_fsim.py
Expand Up @@ -32,7 +32,17 @@
import numpy as np

from cirq.circuits import Circuit
from cirq.ops import FSimGate, Gate, ISwapPowGate, PhasedFSimGate, PhasedISwapPowGate, Qid
from cirq.ops import (
FSimGate,
Gate,
ISwapPowGate,
Operation,
PhasedFSimGate,
PhasedISwapPowGate,
Qid,
TwoQubitGate,
rz,
)
from cirq.google.api import v2
from cirq.google.engine import CalibrationLayer, CalibrationResult

Expand Down Expand Up @@ -434,15 +444,102 @@ class IncompatibleMomentError(Exception):
"""Error that occurs when a moment is not supported by a calibration routine."""


def try_convert_sqrt_iswap_to_fsim(gate: Gate) -> Optional[FSimGate]:
@dataclasses.dataclass(frozen=True)
class PhaseCalibratedFSimGate:
"""Association of a user gate with gate to calibrate.
This association stores information regarding rotation of the calibrated FSim gate by
phase_exponent p:
(Z^-p ⊗ Z^p) FSim (Z^p ⊗ Z^-p).
The rotation should be reflected back during the compilation after the gate is calibrated and
is equivalent to the shift of -2πp in the χ angle of PhasedFSimGate.
Attributes:
engine_gate: Gate that should be used for calibration purposes.
phase_exponent: Phase rotation exponent p.
"""

engine_gate: FSimGate
phase_exponent: float

def as_characterized_phased_fsim_gate(
self, parameters: PhasedFSimCharacterization
) -> PhasedFSimGate:
"""Creates a PhasedFSimGate which represents the characterized engine_gate but includes
deviations in unitary parameters.
Args:
parameters: The results of characterization of the engine gate.
Returns:
Instance of PhasedFSimGate that executes a gate according to the characterized
parameters of the engine_gate.
"""
return PhasedFSimGate(
theta=parameters.theta,
zeta=parameters.zeta,
chi=parameters.chi - 2 * np.pi * self.phase_exponent,
gamma=parameters.gamma,
phi=parameters.phi,
)

def with_zeta_chi_gamma_compensated(
self,
qubits: Tuple[Qid, Qid],
parameters: PhasedFSimCharacterization,
*,
engine_gate: Optional[TwoQubitGate] = None,
) -> Tuple[Tuple[Operation, ...], ...]:
"""Creates a composite operation that compensates for zeta, chi and gamma angles of the
characterization.
Args:
qubits: Qubits that the gate should act on.
parameters: The results of characterization of the engine gate.
engine_gate: TwoQubitGate that represents the engine gate. When None, the internal
engine_gate of this instance is used. This argument is useful for testing
purposes.
Returns:
Tuple of tuple of operations that describe the compensated gate. The first index
iterates over moments of the composed operation.
"""
assert parameters.zeta is not None, "Zeta value must not be None"
zeta = parameters.zeta

assert parameters.gamma is not None, "Gamma value must not be None"
gamma = parameters.gamma

assert parameters.chi is not None, "Chi value must not be None"
chi = parameters.chi + 2 * np.pi * self.phase_exponent

if engine_gate is None:
engine_gate = self.engine_gate

a, b = qubits

alpha = 0.5 * (zeta + chi)
beta = 0.5 * (zeta - chi)

return (
(rz(0.5 * gamma - alpha).on(a), rz(0.5 * gamma + alpha).on(b)),
(engine_gate.on(a, b),),
(rz(0.5 * gamma - beta).on(a), rz(0.5 * gamma + beta).on(b)),
)


def try_convert_sqrt_iswap_to_fsim(gate: Gate) -> Optional[PhaseCalibratedFSimGate]:
"""Converts an equivalent gate to FSimGate(theta=π/4, phi=0) if possible.
Args:
gate: Gate to verify.
Returns:
FSimGate(theta=π/4, phi=0) if provided gate either FSimGate, ISWapPowGate, PhasedFSimGate
or PhasedISwapPowGate that is equivalent to FSimGate(theta=π/4, phi=0). None otherwise.
FSimGateCalibration with engine_gate FSimGate(theta=π/4, phi=0) if the provided gate is
either FSimGate, ISWapPowGate, PhasedFSimGate or PhasedISwapPowGate that is equivalent to
FSimGate(theta=±π/4, phi=0). None otherwise.
"""
if isinstance(gate, FSimGate):
if not np.isclose(gate.phi, 0.0):
Expand All @@ -466,7 +563,11 @@ def try_convert_sqrt_iswap_to_fsim(gate: Gate) -> Optional[FSimGate]:
else:
return None

if np.isclose(angle, np.pi / 4):
return FSimGate(theta=np.pi / 4, phi=0.0)
angle_canonical = angle % (2 * np.pi)

if np.isclose(angle_canonical, np.pi / 4):
return PhaseCalibratedFSimGate(FSimGate(theta=np.pi / 4, phi=0.0), 0.0)
elif np.isclose(angle_canonical, 7 * np.pi / 4):
return PhaseCalibratedFSimGate(FSimGate(theta=np.pi / 4, phi=0.0), 0.5)

return None
82 changes: 77 additions & 5 deletions cirq/google/calibration/phased_fsim_test.py
Expand Up @@ -19,6 +19,7 @@
ALL_ANGLES_FLOQUET_PHASED_FSIM_CHARACTERIZATION,
FloquetPhasedFSimCalibrationOptions,
FloquetPhasedFSimCalibrationRequest,
PhaseCalibratedFSimGate,
PhasedFSimCharacterization,
PhasedFSimCalibrationResult,
WITHOUT_CHI_FLOQUET_PHASED_FSIM_CHARACTERIZATION,
Expand Down Expand Up @@ -246,7 +247,76 @@ def test_get_parameters():
assert result.get_parameters(q_02, q_03) == PhasedFSimCharacterization(
theta=0.4, zeta=0.5, chi=None, gamma=None, phi=0.6
)
assert result.get_parameters(q_00, q_03) == None
assert result.get_parameters(q_00, q_03) is None


@pytest.mark.parametrize('phase_exponent', np.linspace(0, 1, 5))
def test_phase_calibrated_fsim_gate_as_characterized_phased_fsim_gate(phase_exponent: float):
a, b = cirq.LineQubit.range(2)
ideal_gate = cirq.FSimGate(theta=np.pi / 4, phi=0.0)
characterized_gate = cirq.PhasedFSimGate(
theta=ideal_gate.theta, zeta=0.1, chi=0.2, gamma=0.3, phi=ideal_gate.phi
)
parameters = PhasedFSimCharacterization(
theta=ideal_gate.theta,
zeta=characterized_gate.zeta,
chi=characterized_gate.chi,
gamma=characterized_gate.gamma,
phi=ideal_gate.phi,
)

calibrated = PhaseCalibratedFSimGate(ideal_gate, phase_exponent=phase_exponent)
phased_gate = calibrated.as_characterized_phased_fsim_gate(parameters).on(a, b)

assert np.allclose(
cirq.unitary(phased_gate),
cirq.unitary(
cirq.Circuit(
[
[cirq.Z(a) ** -phase_exponent, cirq.Z(b) ** phase_exponent],
characterized_gate.on(a, b),
[cirq.Z(a) ** phase_exponent, cirq.Z(b) ** -phase_exponent],
]
)
),
)


@pytest.mark.parametrize('phase_exponent', np.linspace(0, 1, 5))
def test_phase_calibrated_fsim_gate_compensated(phase_exponent: float):
a, b = cirq.LineQubit.range(2)
ideal_gate = cirq.FSimGate(theta=np.pi / 4, phi=0.0)
characterized_gate = cirq.PhasedFSimGate(
theta=ideal_gate.theta, zeta=0.1, chi=0.2, gamma=0.3, phi=ideal_gate.phi
)
parameters = PhasedFSimCharacterization(
theta=ideal_gate.theta,
zeta=characterized_gate.zeta,
chi=characterized_gate.chi,
gamma=characterized_gate.gamma,
phi=ideal_gate.phi,
)

calibrated = PhaseCalibratedFSimGate(ideal_gate, phase_exponent=phase_exponent)

# Passing characterized_gate as engine_gate simulates the hardware execution.
operations = calibrated.with_zeta_chi_gamma_compensated(
(a, b), parameters, engine_gate=characterized_gate
)

cirq.testing.assert_allclose_up_to_global_phase(
cirq.unitary(cirq.Circuit(operations)),
cirq.unitary(
cirq.Circuit(
[
[cirq.Z(a) ** -phase_exponent, cirq.Z(b) ** phase_exponent],
ideal_gate.on(a, b),
[cirq.Z(a) ** phase_exponent, cirq.Z(b) ** -phase_exponent],
]
)
),
atol=1e-8,
)


def test_try_convert_sqrt_iswap_to_fsim_converts_correctly():
Expand All @@ -255,26 +325,28 @@ def test_try_convert_sqrt_iswap_to_fsim_converts_correctly():

fsim = cirq.FSimGate(theta=np.pi / 4, phi=0)
assert np.allclose(cirq.unitary(fsim), expected_unitary)
assert try_convert_sqrt_iswap_to_fsim(fsim) == expected
assert try_convert_sqrt_iswap_to_fsim(fsim) == PhaseCalibratedFSimGate(expected, 0.0)
assert try_convert_sqrt_iswap_to_fsim(cirq.FSimGate(theta=np.pi / 4, phi=0.1)) is None
assert try_convert_sqrt_iswap_to_fsim(cirq.FSimGate(theta=np.pi / 3, phi=0)) is None

phased_fsim = cirq.PhasedFSimGate(theta=np.pi / 4, phi=0)
assert np.allclose(cirq.unitary(phased_fsim), expected_unitary)
assert try_convert_sqrt_iswap_to_fsim(phased_fsim) == expected
assert try_convert_sqrt_iswap_to_fsim(phased_fsim) == PhaseCalibratedFSimGate(expected, 0.0)
assert (
try_convert_sqrt_iswap_to_fsim(cirq.PhasedFSimGate(theta=np.pi / 4, zeta=0.1, phi=0))
is None
)

iswap_pow = cirq.ISwapPowGate(exponent=-0.5)
assert np.allclose(cirq.unitary(iswap_pow), expected_unitary)
assert try_convert_sqrt_iswap_to_fsim(iswap_pow) == expected
assert try_convert_sqrt_iswap_to_fsim(iswap_pow) == PhaseCalibratedFSimGate(expected, 0.0)
assert try_convert_sqrt_iswap_to_fsim(cirq.ISwapPowGate(exponent=-0.4)) is None

phased_iswap_pow = cirq.PhasedISwapPowGate(exponent=0.5, phase_exponent=-0.5)
assert np.allclose(cirq.unitary(phased_iswap_pow), expected_unitary)
assert try_convert_sqrt_iswap_to_fsim(phased_iswap_pow) == expected
assert try_convert_sqrt_iswap_to_fsim(phased_iswap_pow) == PhaseCalibratedFSimGate(
expected, 0.0
)
assert (
try_convert_sqrt_iswap_to_fsim(cirq.PhasedISwapPowGate(exponent=-0.5, phase_exponent=0.1))
is None
Expand Down

0 comments on commit 2c904da

Please sign in to comment.