Repository: quantumlib/Cirq

Issue:
Streamline Z phase errors to noise
PR #5378 adds hardcoded sample Z phase error, but does not document how to construct this data from QCS. We should add a pipeline for passing Z phase error from Floquet calibration to the calibration-to-noise tools and document its usage in the relevant docs.


Discussion:
CC @dstrain115

Resolving patch:
diff --git a/cirq-google/cirq_google/calibration/phased_fsim.py b/cirq-google/cirq_google/calibration/phased_fsim.py
--- a/cirq-google/cirq_google/calibration/phased_fsim.py
+++ b/cirq-google/cirq_google/calibration/phased_fsim.py
@@ -26,6 +26,7 @@
     MutableMapping,
     Optional,
     Tuple,
+    Type,
     TypeVar,
     TYPE_CHECKING,
     Generic,
@@ -40,8 +41,15 @@
 import cirq
 from cirq.experiments.xeb_fitting import XEBPhasedFSimCharacterizationOptions
 from cirq_google.api import v2
-from cirq_google.engine import Calibration, CalibrationLayer, CalibrationResult, Engine, EngineJob
-from cirq_google.ops import FSimGateFamily
+from cirq_google.engine import (
+    Calibration,
+    CalibrationLayer,
+    CalibrationResult,
+    Engine,
+    EngineJob,
+    util,
+)
+from cirq_google.ops import FSimGateFamily, SycamoreGate
 
 if TYPE_CHECKING:
     import cirq_google
@@ -50,6 +58,11 @@
 _FLOQUET_PHASED_FSIM_HANDLER_NAME = 'floquet_phased_fsim_characterization'
 _XEB_PHASED_FSIM_HANDLER_NAME = 'xeb_phased_fsim_characterization'
 _DEFAULT_XEB_CYCLE_DEPTHS = (5, 25, 50, 100, 200, 300)
+# Copied from cirq-google/cirq_google/engine/calibration_to_noise_properties.py
+GATE_ZPHASE_CODE_PAIRS: Dict[Type['cirq.Gate'], str] = {
+    SycamoreGate: 'syc',
+    cirq.ISwapPowGate: 'sqrt_iswap',
+}
 
 T = TypeVar('T')
 
@@ -331,6 +344,38 @@ def _json_dict_(self) -> Dict[str, Any]:
         }
 
 
+def to_zphase_data(results: Iterable[PhasedFSimCalibrationResult]) -> util.ZPhaseDataType:
+    """Packages a collection of results into ZPhaseDataType.
+
+    Args:
+        results: List of results to pack into ZPhaseDataType. If multiple results provide a value
+            for a given (gate, angle, qubits) tuple, only the last one will be kept.
+
+    Returns:
+        A ZPhaseDataType-formatted result representation. This can be used with the
+            calibration-to-noise pipeline for generating noise models.
+
+    Raises:
+        ValueError: if results for a gate other than Sycamore or ISwapPowGate are given.
+    """
+    zphase_data: util.ZPhaseDataType = {}
+    for result in results:
+        gate_type = GATE_ZPHASE_CODE_PAIRS.get(type(result.gate))
+        if gate_type is None:
+            raise ValueError(
+                f"Only 'SycamoreGate' and 'ISwapPowGate' are supported, got {result.gate}"
+            )
+        gate_dict = zphase_data.setdefault(gate_type, {})
+        for qubits, data in result.parameters.items():
+            for angle, value in data.asdict().items():
+                if value is None:
+                    continue
+                angle_dict = gate_dict.setdefault(angle, {})
+                angle_dict[qubits] = value
+
+    return zphase_data
+
+
 def merge_matching_results(
     results: Iterable[PhasedFSimCalibrationResult],
 ) -> Optional[PhasedFSimCalibrationResult]:


Test patch:
diff --git a/cirq-google/cirq_google/calibration/phased_fsim_test.py b/cirq-google/cirq_google/calibration/phased_fsim_test.py
--- a/cirq-google/cirq_google/calibration/phased_fsim_test.py
+++ b/cirq-google/cirq_google/calibration/phased_fsim_test.py
@@ -36,6 +36,7 @@
     PhasedFSimCalibrationResult,
     WITHOUT_CHI_FLOQUET_PHASED_FSIM_CHARACTERIZATION,
     merge_matching_results,
+    to_zphase_data,
     try_convert_gate_to_fsim,
     try_convert_syc_or_sqrt_iswap_to_fsim,
     try_convert_sqrt_iswap_to_fsim,
@@ -625,6 +626,64 @@ def test_get_parameters():
     assert result.get_parameters(q_00, q_03) is None
 
 
+def test_to_zphase_data():
+    q0, q1, q2 = cirq.GridQubit.rect(1, 3)
+    result_1 = PhasedFSimCalibrationResult(
+        {
+            (q0, q1): PhasedFSimCharacterization(zeta=0.1, gamma=0.2),
+            (q1, q2): PhasedFSimCharacterization(zeta=0.3, gamma=0.4),
+        },
+        gate=cirq_google.SycamoreGate(),
+        options=WITHOUT_CHI_FLOQUET_PHASED_FSIM_CHARACTERIZATION,
+    )
+    result_2 = PhasedFSimCalibrationResult(
+        {
+            (q0, q1): PhasedFSimCharacterization(zeta=0.5, gamma=0.6),
+            (q1, q2): PhasedFSimCharacterization(zeta=0.7, gamma=0.8),
+        },
+        gate=cirq.ISwapPowGate(),
+        options=WITHOUT_CHI_FLOQUET_PHASED_FSIM_CHARACTERIZATION,
+    )
+    assert to_zphase_data([result_1, result_2]) == {
+        'syc': {'zeta': {(q0, q1): 0.1, (q1, q2): 0.3}, 'gamma': {(q0, q1): 0.2, (q1, q2): 0.4}},
+        'sqrt_iswap': {
+            'zeta': {(q0, q1): 0.5, (q1, q2): 0.7},
+            'gamma': {(q0, q1): 0.6, (q1, q2): 0.8},
+        },
+    }
+    # Test update and override
+    result_3 = PhasedFSimCalibrationResult(
+        {
+            (q0, q1): PhasedFSimCharacterization(theta=0.01),
+            (q1, q2): PhasedFSimCharacterization(zeta=0.02),
+            (q2, q0): PhasedFSimCharacterization(zeta=0.03, gamma=0.04, theta=0.05),
+        },
+        gate=cirq_google.SycamoreGate(),
+        options=WITHOUT_CHI_FLOQUET_PHASED_FSIM_CHARACTERIZATION,
+    )
+    assert to_zphase_data([result_1, result_3]) == {
+        'syc': {
+            'zeta': {(q0, q1): 0.1, (q1, q2): 0.02, (q2, q0): 0.03},
+            'gamma': {(q0, q1): 0.2, (q1, q2): 0.4, (q2, q0): 0.04},
+            'theta': {(q0, q1): 0.01, (q2, q0): 0.05},
+        }
+    }
+
+
+def test_to_zphase_unknown_gate_raises_error():
+    q0, q1, q2 = cirq.GridQubit.rect(1, 3)
+    result_1 = PhasedFSimCalibrationResult(
+        {
+            (q0, q1): PhasedFSimCharacterization(zeta=0.1, gamma=0.2),
+            (q1, q2): PhasedFSimCharacterization(zeta=0.3, gamma=0.4),
+        },
+        gate=cirq.CZPowGate(),
+        options=WITHOUT_CHI_FLOQUET_PHASED_FSIM_CHARACTERIZATION,
+    )
+    with pytest.raises(ValueError, match="Only 'SycamoreGate' and 'ISwapPowGate' are supported"):
+        _ = to_zphase_data([result_1])
+
+
 def test_merge_matching_results():
     q_00, q_01, q_02, q_03 = [cirq.GridQubit(0, index) for index in range(4)]
     gate = cirq.FSimGate(theta=np.pi / 4, phi=0.0)