From 09a2ea5fac1355756504a85d1b11d8d03d020fc0 Mon Sep 17 00:00:00 2001 From: Dmytro Fedoriaka Date: Thu, 24 Jun 2021 17:28:37 +0100 Subject: [PATCH] Support sqrt(iSWAP) and Sycamore gates in Floquet calibration (#4248) Goal of this PR is that circuits having any of these 2-qubit gates (sqrt(iSWAP), Sycamore) can be calibrated on real hardware. In #4164 I ensured that any FSim-compatible gate can be handled on compensation phase. But on characterization step we can support only these 2 gates, because only they are implemented in hardware (so far). For that: * Added method `try_convert_syc_or_sqrt_iswap_to_fsim` which is restriction of `try_convert_gate_to_fsim` on gates supported by hardware. * Used that method in `workflow.py` as gate translator everywhere where `try_convert_sqrt_iswap_to_fsim` was used. * Fixed a bug in `_merge_into_calibrations` so if there are calibrations for 2 different gates, it wouldn't fail with assertion error, but would append new calibration to the list of calibration. * Modified a test `test_run_zeta_chi_gamma_calibration_for_moments` so the circuit under test contains gates of 2 types. --- .../cirq_google/calibration/__init__.py | 1 + .../cirq_google/calibration/phased_fsim.py | 59 ++++++++++--------- .../calibration/phased_fsim_test.py | 29 ++++++++- .../cirq_google/calibration/workflow.py | 29 +++++---- .../cirq_google/calibration/workflow_test.py | 17 +++--- 5 files changed, 85 insertions(+), 50 deletions(-) diff --git a/cirq-google/cirq_google/calibration/__init__.py b/cirq-google/cirq_google/calibration/__init__.py index c52316e5a49..60c7b39e93f 100644 --- a/cirq-google/cirq_google/calibration/__init__.py +++ b/cirq-google/cirq_google/calibration/__init__.py @@ -35,6 +35,7 @@ WITHOUT_CHI_FLOQUET_PHASED_FSIM_CHARACTERIZATION, merge_matching_results, try_convert_sqrt_iswap_to_fsim, + try_convert_syc_or_sqrt_iswap_to_fsim, ) from cirq_google.calibration.workflow import ( diff --git a/cirq-google/cirq_google/calibration/phased_fsim.py b/cirq-google/cirq_google/calibration/phased_fsim.py index 6c918fa4f47..18b3317c442 100644 --- a/cirq-google/cirq_google/calibration/phased_fsim.py +++ b/cirq-google/cirq_google/calibration/phased_fsim.py @@ -15,6 +15,7 @@ import collections import dataclasses import functools +import math import re from typing import ( Any, @@ -1016,35 +1017,12 @@ def try_convert_sqrt_iswap_to_fsim(gate: cirq.Gate) -> Optional[PhaseCalibratedF either FSimGate, ISWapPowGate, PhasedFSimGate or PhasedISwapPowGate that is equivalent to FSimGate(theta=±π/4, phi=0). None otherwise. """ - if isinstance(gate, cirq.FSimGate): - if not np.isclose(gate.phi, 0.0): - return None - angle = gate.theta - elif isinstance(gate, cirq.ISwapPowGate): - angle = -gate.exponent * np.pi / 2 - elif isinstance(gate, cirq.PhasedFSimGate): - if ( - not np.isclose(gate.zeta, 0.0) - or not np.isclose(gate.chi, 0.0) - or not np.isclose(gate.gamma, 0.0) - or not np.isclose(gate.phi, 0.0) - ): - return None - angle = gate.theta - elif isinstance(gate, cirq.PhasedISwapPowGate): - if not np.isclose(-gate.phase_exponent - 0.5, 0.0): - return None - angle = gate.exponent * np.pi / 2 - else: + result = try_convert_gate_to_fsim(gate) + if result is None: return None - - angle_canonical = angle % (2 * np.pi) - - if np.isclose(angle_canonical, np.pi / 4): - return PhaseCalibratedFSimGate(cirq.FSimGate(theta=np.pi / 4, phi=0.0), 0.0) - elif np.isclose(angle_canonical, 7 * np.pi / 4): - return PhaseCalibratedFSimGate(cirq.FSimGate(theta=np.pi / 4, phi=0.0), 0.5) - + engine_gate = result.engine_gate + if math.isclose(engine_gate.theta, np.pi / 4) and math.isclose(engine_gate.phi, 0.0): + return result return None @@ -1097,3 +1075,28 @@ def try_convert_gate_to_fsim(gate: cirq.Gate) -> Optional[PhaseCalibratedFSimGat phase_exponent = phase_exponent + 0.5 phase_exponent %= 1 return PhaseCalibratedFSimGate(cirq.FSimGate(theta=theta, phi=phi), phase_exponent) + + +def try_convert_syc_or_sqrt_iswap_to_fsim( + gate: cirq.Gate, +) -> Optional[PhaseCalibratedFSimGate]: + """Converts a gate to equivalent PhaseCalibratedFSimGate if possible. + + Args: + gate: Gate to convert. + + Returns: + Equivalent PhaseCalibratedFSimGate if its `engine_gate` is Sycamore or inverse sqrt(iSWAP) + gate. Otherwise returns None. + """ + result = try_convert_gate_to_fsim(gate) + if result is None: + return None + engine_gate = result.engine_gate + if math.isclose(engine_gate.theta, np.pi / 2) and math.isclose(engine_gate.phi, np.pi / 6): + # Sycamore gate. + return result + if math.isclose(engine_gate.theta, np.pi / 4) and math.isclose(engine_gate.phi, 0.0): + # Inverse sqrt(iSWAP) gate. + return result + return None diff --git a/cirq-google/cirq_google/calibration/phased_fsim_test.py b/cirq-google/cirq_google/calibration/phased_fsim_test.py index b1903c8b426..52c0703d636 100644 --- a/cirq-google/cirq_google/calibration/phased_fsim_test.py +++ b/cirq-google/cirq_google/calibration/phased_fsim_test.py @@ -37,6 +37,7 @@ WITHOUT_CHI_FLOQUET_PHASED_FSIM_CHARACTERIZATION, merge_matching_results, try_convert_gate_to_fsim, + try_convert_syc_or_sqrt_iswap_to_fsim, try_convert_sqrt_iswap_to_fsim, XEBPhasedFSimCalibrationRequest, XEBPhasedFSimCalibrationOptions, @@ -767,7 +768,7 @@ def test_try_convert_sqrt_iswap_to_fsim_converts_correctly(): expected, 0.0 ) assert ( - try_convert_sqrt_iswap_to_fsim(cirq.PhasedISwapPowGate(exponent=-0.5, phase_exponent=0.1)) + try_convert_sqrt_iswap_to_fsim(cirq.PhasedISwapPowGate(exponent=-0.6, phase_exponent=0.1)) is None ) @@ -949,11 +950,33 @@ def check(gate: cirq.Gate, expected: PhaseCalibratedFSimGate): assert try_convert_gate_to_fsim(cirq.CZPowGate(exponent=x)) is None +def test_try_convert_syc_or_sqrt_iswap_to_fsim(): + def check_converts(gate: cirq.Gate): + result = try_convert_syc_or_sqrt_iswap_to_fsim(gate) + assert np.allclose(cirq.unitary(gate), cirq.unitary(result)) + + def check_none(gate: cirq.Gate): + assert try_convert_syc_or_sqrt_iswap_to_fsim(gate) is None + + check_converts(cirq_google.ops.SYC) + check_converts(cirq.FSimGate(np.pi / 2, np.pi / 6)) + check_none(cirq.FSimGate(0, np.pi)) + check_converts(cirq.FSimGate(np.pi / 4, 0.0)) + check_none(cirq.FSimGate(0.2, 0.3)) + check_converts(cirq.ISwapPowGate(exponent=0.5)) + check_converts(cirq.ISwapPowGate(exponent=-0.5)) + check_none(cirq.ISwapPowGate(exponent=0.3)) + check_converts(cirq.PhasedFSimGate(theta=np.pi / 4, phi=0.0, chi=0.7)) + check_none(cirq.PhasedFSimGate(theta=0.3, phi=0.4)) + check_converts(cirq.PhasedISwapPowGate(exponent=0.5, phase_exponent=0.75)) + check_none(cirq.PhasedISwapPowGate(exponent=0.4, phase_exponent=0.75)) + check_none(cirq.ops.CZPowGate(exponent=1.0)) + check_none(cirq.CX) + + # Test that try_convert_gate_to_fsim is extension of try_convert_sqrt_iswap_to_fsim. # In other words, that both function return the same result for all gates on which # try_convert_sqrt_iswap_to_fsim is defined. -# TODO: instead of having multiple gate translators, we should have one, the most general, and -# restrict it to different gate sets. def test_gate_translators_are_consistent(): def check(gate): result1 = try_convert_gate_to_fsim(gate) diff --git a/cirq-google/cirq_google/calibration/workflow.py b/cirq-google/cirq_google/calibration/workflow.py index 1fc26614c87..09c0e3e6b1c 100644 --- a/cirq-google/cirq_google/calibration/workflow.py +++ b/cirq-google/cirq_google/calibration/workflow.py @@ -41,7 +41,7 @@ THETA_ZETA_GAMMA_FLOQUET_PHASED_FSIM_CHARACTERIZATION, merge_matching_results, try_convert_gate_to_fsim, - try_convert_sqrt_iswap_to_fsim, + try_convert_syc_or_sqrt_iswap_to_fsim, PhasedFSimCalibrationOptions, RequestT, LocalXEBPhasedFSimCalibrationRequest, @@ -73,7 +73,7 @@ def prepare_characterization_for_moment( *, gates_translator: Callable[ [cirq.Gate], Optional[PhaseCalibratedFSimGate] - ] = try_convert_sqrt_iswap_to_fsim, + ] = try_convert_syc_or_sqrt_iswap_to_fsim, canonicalize_pairs: bool = False, sort_pairs: bool = False, ) -> Optional[RequestT]: @@ -116,7 +116,7 @@ def prepare_floquet_characterization_for_moment( options: FloquetPhasedFSimCalibrationOptions, gates_translator: Callable[ [cirq.Gate], Optional[PhaseCalibratedFSimGate] - ] = try_convert_sqrt_iswap_to_fsim, + ] = try_convert_syc_or_sqrt_iswap_to_fsim, canonicalize_pairs: bool = False, sort_pairs: bool = False, ) -> Optional[FloquetPhasedFSimCalibrationRequest]: @@ -271,7 +271,7 @@ def prepare_characterization_for_moments( *, gates_translator: Callable[ [cirq.Gate], Optional[PhaseCalibratedFSimGate] - ] = try_convert_sqrt_iswap_to_fsim, + ] = try_convert_syc_or_sqrt_iswap_to_fsim, merge_subsets: bool = True, initial: Optional[Sequence[RequestT]] = None, ) -> Tuple[CircuitWithCalibration, List[RequestT]]: @@ -345,7 +345,7 @@ def prepare_characterization_for_circuits_moments( *, gates_translator: Callable[ [cirq.Gate], Optional[PhaseCalibratedFSimGate] - ] = try_convert_sqrt_iswap_to_fsim, + ] = try_convert_syc_or_sqrt_iswap_to_fsim, merge_subsets: bool = True, initial: Optional[Sequence[RequestT]] = None, ) -> Tuple[List[CircuitWithCalibration], List[RequestT]]: @@ -406,7 +406,7 @@ def prepare_floquet_characterization_for_moments( options: FloquetPhasedFSimCalibrationOptions = WITHOUT_CHI_FLOQUET_PHASED_FSIM_CHARACTERIZATION, gates_translator: Callable[ [cirq.Gate], Optional[PhaseCalibratedFSimGate] - ] = try_convert_sqrt_iswap_to_fsim, + ] = try_convert_syc_or_sqrt_iswap_to_fsim, merge_subsets: bool = True, initial: Optional[Sequence[FloquetPhasedFSimCalibrationRequest]] = None, ) -> Tuple[CircuitWithCalibration, List[FloquetPhasedFSimCalibrationRequest]]: @@ -466,7 +466,7 @@ def prepare_characterization_for_operations( *, gates_translator: Callable[ [cirq.Gate], Optional[PhaseCalibratedFSimGate] - ] = try_convert_sqrt_iswap_to_fsim, + ] = try_convert_syc_or_sqrt_iswap_to_fsim, permit_mixed_moments: bool = False, ) -> List[RequestT]: """Extracts a minimal set of characterization requests necessary to characterize all the @@ -531,7 +531,7 @@ def prepare_floquet_characterization_for_operations( options: FloquetPhasedFSimCalibrationOptions = WITHOUT_CHI_FLOQUET_PHASED_FSIM_CHARACTERIZATION, gates_translator: Callable[ [cirq.Gate], Optional[PhaseCalibratedFSimGate] - ] = try_convert_sqrt_iswap_to_fsim, + ] = try_convert_syc_or_sqrt_iswap_to_fsim, permit_mixed_moments: bool = False, ) -> List[FloquetPhasedFSimCalibrationRequest]: """Extracts a minimal set of Floquet characterization requests necessary to characterize all the @@ -679,8 +679,12 @@ def _merge_into_calibrations( """ new_pairs = set(calibration.pairs) for index in pairs_map.values(): - assert calibration.gate == calibrations[index].gate - assert calibration.options == calibrations[index].options + can_merge = ( + calibration.gate == calibrations[index].gate + and calibration.options == calibrations[index].options + ) + if not can_merge: + continue existing_pairs = calibrations[index].pairs if new_pairs.issubset(existing_pairs): return index @@ -1057,6 +1061,7 @@ def from_characterization( """Creates an operation that compensates for zeta, chi and gamma angles of the supplied gate and characterization. + Args: Args: qubits: Qubits that the gate should act on. gate_calibration: Original, imperfect gate that is supposed to run on the hardware @@ -1081,7 +1086,7 @@ def run_floquet_characterization_for_moments( options: FloquetPhasedFSimCalibrationOptions = WITHOUT_CHI_FLOQUET_PHASED_FSIM_CHARACTERIZATION, gates_translator: Callable[ [cirq.Gate], Optional[PhaseCalibratedFSimGate] - ] = try_convert_sqrt_iswap_to_fsim, + ] = try_convert_syc_or_sqrt_iswap_to_fsim, merge_subsets: bool = True, max_layers_per_request: int = 1, progress_func: Optional[Callable[[int, int], None]] = None, @@ -1147,7 +1152,7 @@ def run_zeta_chi_gamma_compensation_for_moments( ), gates_translator: Callable[ [cirq.Gate], Optional[PhaseCalibratedFSimGate] - ] = try_convert_sqrt_iswap_to_fsim, + ] = try_convert_syc_or_sqrt_iswap_to_fsim, merge_subsets: bool = True, max_layers_per_request: int = 1, progress_func: Optional[Callable[[int, int], None]] = None, diff --git a/cirq-google/cirq_google/calibration/workflow_test.py b/cirq-google/cirq_google/calibration/workflow_test.py index 5d263747292..6f773db0c6f 100644 --- a/cirq-google/cirq_google/calibration/workflow_test.py +++ b/cirq-google/cirq_google/calibration/workflow_test.py @@ -45,6 +45,9 @@ SQRT_ISWAP_INV_PARAMETERS = cirq_google.PhasedFSimCharacterization( theta=np.pi / 4, zeta=0.0, chi=0.0, gamma=0.0, phi=0.0 ) +SYCAMORE_PARAMETERS = cirq_google.PhasedFSimCharacterization( + theta=np.pi / 2, zeta=0.0, chi=0.0, gamma=0.0, phi=np.pi / 6 +) SQRT_ISWAP_GATE = cirq.FSimGate(7 * np.pi / 4, 0.0) SQRT_ISWAP_INV_GATE = cirq.FSimGate(np.pi / 4, 0.0) @@ -1462,11 +1465,11 @@ def test_run_zeta_chi_gamma_calibration_for_moments() -> None: parameters_cd = cirq_google.PhasedFSimCharacterization(zeta=0.2, chi=0.3, gamma=0.4) a, b, c, d = cirq.LineQubit.range(4) - engine_simulator = cirq_google.PhasedFSimEngineSimulator.create_from_dictionary_sqrt_iswap( + engine_simulator = cirq_google.PhasedFSimEngineSimulator.create_from_dictionary( parameters={ - (a, b): parameters_ab.merge_with(SQRT_ISWAP_INV_PARAMETERS), - (b, c): parameters_bc.merge_with(SQRT_ISWAP_INV_PARAMETERS), - (c, d): parameters_cd.merge_with(SQRT_ISWAP_INV_PARAMETERS), + (a, b): {SQRT_ISWAP_INV_GATE: parameters_ab.merge_with(SQRT_ISWAP_INV_PARAMETERS)}, + (b, c): {cirq_google.ops.SYC: parameters_bc.merge_with(SYCAMORE_PARAMETERS)}, + (c, d): {SQRT_ISWAP_INV_GATE: parameters_cd.merge_with(SQRT_ISWAP_INV_PARAMETERS)}, } ) @@ -1474,7 +1477,7 @@ def test_run_zeta_chi_gamma_calibration_for_moments() -> None: [ [cirq.X(a), cirq.Y(c)], [SQRT_ISWAP_INV_GATE.on(a, b), SQRT_ISWAP_INV_GATE.on(c, d)], - [SQRT_ISWAP_INV_GATE.on(b, c)], + [cirq_google.ops.SYC.on(b, c)], ] ) @@ -1490,7 +1493,7 @@ def test_run_zeta_chi_gamma_calibration_for_moments() -> None: circuit, engine_simulator, processor_id=None, - gate_set=cirq_google.SQRT_ISWAP_GATESET, + gate_set=cirq_google.FSIM_GATESET, options=options, ) @@ -1505,7 +1508,7 @@ def test_run_zeta_chi_gamma_calibration_for_moments() -> None: options=options, ), cirq_google.PhasedFSimCalibrationResult( - gate=SQRT_ISWAP_INV_GATE, parameters={(b, c): parameters_bc}, options=options + gate=cirq_google.ops.SYC, parameters={(b, c): parameters_bc}, options=options ), ] assert calibrated_circuit.moment_to_calibration == [None, None, 0, None, None, 1, None]