|
| 1 | +# Copyright 2024 The Cirq Developers |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +"""Provides a method to do z-phase calibration for excitation-preserving gates.""" |
| 16 | +from typing import Union, Optional, Sequence, Tuple, Dict, TYPE_CHECKING |
| 17 | +import multiprocessing |
| 18 | +import multiprocessing.pool |
| 19 | + |
| 20 | +import matplotlib.pyplot as plt |
| 21 | +import numpy as np |
| 22 | + |
| 23 | +from cirq.experiments import xeb_fitting |
| 24 | +from cirq.experiments.two_qubit_xeb import parallel_xeb_workflow |
| 25 | +from cirq import ops |
| 26 | + |
| 27 | +if TYPE_CHECKING: |
| 28 | + import cirq |
| 29 | + import pandas as pd |
| 30 | + |
| 31 | + |
| 32 | +def z_phase_calibration_workflow( |
| 33 | + sampler: 'cirq.Sampler', |
| 34 | + qubits: Optional[Sequence['cirq.GridQubit']] = None, |
| 35 | + two_qubit_gate: 'cirq.Gate' = ops.CZ, |
| 36 | + options: Optional[xeb_fitting.XEBPhasedFSimCharacterizationOptions] = None, |
| 37 | + n_repetitions: int = 10**4, |
| 38 | + n_combinations: int = 10, |
| 39 | + n_circuits: int = 20, |
| 40 | + cycle_depths: Sequence[int] = tuple(np.arange(3, 100, 20)), |
| 41 | + random_state: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None, |
| 42 | + atol: float = 1e-3, |
| 43 | + num_workers_or_pool: Union[int, 'multiprocessing.pool.Pool'] = -1, |
| 44 | +) -> Tuple[xeb_fitting.XEBCharacterizationResult, 'pd.DataFrame']: |
| 45 | + """Perform z-phase calibration for excitation-preserving gates. |
| 46 | +
|
| 47 | + For a given excitation-preserving two-qubit gate we assume an error model that can be described |
| 48 | + using Z-rotations: |
| 49 | + 0: ───Rz(a)───two_qubit_gate───Rz(c)─── |
| 50 | + │ |
| 51 | + 1: ───Rz(b)───two_qubit_gate───Rz(d)─── |
| 52 | + for some angles a, b, c, and d. |
| 53 | +
|
| 54 | + Since the two-qubit gate is a excitation-preserving-gate, it can be represented by an FSimGate |
| 55 | + and the effect of rotations turns it into a PhasedFSimGate. Using XEB-data we find the |
| 56 | + PhasedFSimGate parameters that minimize the infidelity of the gate. |
| 57 | +
|
| 58 | + References: |
| 59 | + - https://arxiv.org/abs/2001.08343 |
| 60 | + - https://arxiv.org/abs/2010.07965 |
| 61 | + - https://arxiv.org/abs/1910.11333 |
| 62 | +
|
| 63 | + Args: |
| 64 | + sampler: The quantum engine or simulator to run the circuits. |
| 65 | + qubits: Qubits to use. If none, use all qubits on the sampler's device. |
| 66 | + two_qubit_gate: The entangling gate to use. |
| 67 | + options: The XEB-fitting options. If None, calibrate only the three phase angles |
| 68 | + (chi, gamma, zeta) using the representation of a two-qubit gate as an FSimGate |
| 69 | + for the initial guess. |
| 70 | + n_repetitions: The number of repetitions to use. |
| 71 | + n_combinations: The number of combinations to generate. |
| 72 | + n_circuits: The number of circuits to generate. |
| 73 | + cycle_depths: The cycle depths to use. |
| 74 | + random_state: The random state to use. |
| 75 | + atol: Absolute tolerance to be used by the minimizer. |
| 76 | + num_workers_or_pool: An optional multi-processing pool or number of workers. |
| 77 | + A zero value means no multiprocessing. |
| 78 | + A positive integer value will create a pool with the given number of workers. |
| 79 | + A negative value will create pool with maximum number of workers. |
| 80 | + Returns: |
| 81 | + - An `XEBCharacterizationResult` object that contains the calibration result. |
| 82 | + - A `pd.DataFrame` comparing the before and after fidelities. |
| 83 | + """ |
| 84 | + |
| 85 | + pool: Optional['multiprocessing.pool.Pool'] = None |
| 86 | + local_pool = False |
| 87 | + if isinstance(num_workers_or_pool, multiprocessing.pool.Pool): |
| 88 | + pool = num_workers_or_pool # pragma: no cover |
| 89 | + elif num_workers_or_pool != 0: |
| 90 | + pool = multiprocessing.Pool(num_workers_or_pool if num_workers_or_pool > 0 else None) |
| 91 | + local_pool = True |
| 92 | + |
| 93 | + fids_df_0, circuits, sampled_df = parallel_xeb_workflow( |
| 94 | + sampler=sampler, |
| 95 | + qubits=qubits, |
| 96 | + entangling_gate=two_qubit_gate, |
| 97 | + n_repetitions=n_repetitions, |
| 98 | + cycle_depths=cycle_depths, |
| 99 | + n_circuits=n_circuits, |
| 100 | + n_combinations=n_combinations, |
| 101 | + random_state=random_state, |
| 102 | + pool=pool, |
| 103 | + ) |
| 104 | + |
| 105 | + if options is None: |
| 106 | + options = xeb_fitting.XEBPhasedFSimCharacterizationOptions( |
| 107 | + characterize_chi=True, |
| 108 | + characterize_gamma=True, |
| 109 | + characterize_zeta=True, |
| 110 | + characterize_theta=False, |
| 111 | + characterize_phi=False, |
| 112 | + ).with_defaults_from_gate(two_qubit_gate) |
| 113 | + |
| 114 | + p_circuits = [ |
| 115 | + xeb_fitting.parameterize_circuit(circuit, options, ops.GateFamily(two_qubit_gate)) |
| 116 | + for circuit in circuits |
| 117 | + ] |
| 118 | + |
| 119 | + result = xeb_fitting.characterize_phased_fsim_parameters_with_xeb_by_pair( |
| 120 | + sampled_df=sampled_df, |
| 121 | + parameterized_circuits=p_circuits, |
| 122 | + cycle_depths=cycle_depths, |
| 123 | + options=options, |
| 124 | + fatol=atol, |
| 125 | + xatol=atol, |
| 126 | + pool=pool, |
| 127 | + ) |
| 128 | + |
| 129 | + before_after = xeb_fitting.before_and_after_characterization( |
| 130 | + fids_df_0, characterization_result=result |
| 131 | + ) |
| 132 | + |
| 133 | + if local_pool: |
| 134 | + assert isinstance(pool, multiprocessing.pool.Pool) |
| 135 | + pool.close() |
| 136 | + return result, before_after |
| 137 | + |
| 138 | + |
| 139 | +def calibrate_z_phases( |
| 140 | + sampler: 'cirq.Sampler', |
| 141 | + qubits: Optional[Sequence['cirq.GridQubit']] = None, |
| 142 | + two_qubit_gate: 'cirq.Gate' = ops.CZ, |
| 143 | + options: Optional[xeb_fitting.XEBPhasedFSimCharacterizationOptions] = None, |
| 144 | + n_repetitions: int = 10**4, |
| 145 | + n_combinations: int = 10, |
| 146 | + n_circuits: int = 20, |
| 147 | + cycle_depths: Sequence[int] = tuple(np.arange(3, 100, 20)), |
| 148 | + random_state: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None, |
| 149 | + atol: float = 1e-3, |
| 150 | + num_workers_or_pool: Union[int, 'multiprocessing.pool.Pool'] = -1, |
| 151 | +) -> Dict[Tuple['cirq.Qid', 'cirq.Qid'], 'cirq.PhasedFSimGate']: |
| 152 | + """Perform z-phase calibration for excitation-preserving gates. |
| 153 | +
|
| 154 | + For a given excitation-preserving two-qubit gate we assume an error model that can be described |
| 155 | + using Z-rotations: |
| 156 | + 0: ───Rz(a)───two_qubit_gate───Rz(c)─── |
| 157 | + │ |
| 158 | + 1: ───Rz(b)───two_qubit_gate───Rz(d)─── |
| 159 | + for some angles a, b, c, and d. |
| 160 | +
|
| 161 | + Since the two-qubit gate is a excitation-preserving gate, it can be represented by an FSimGate |
| 162 | + and the effect of rotations turns it into a PhasedFSimGate. Using XEB-data we find the |
| 163 | + PhasedFSimGate parameters that minimize the infidelity of the gate. |
| 164 | +
|
| 165 | + References: |
| 166 | + - https://arxiv.org/abs/2001.08343 |
| 167 | + - https://arxiv.org/abs/2010.07965 |
| 168 | + - https://arxiv.org/abs/1910.11333 |
| 169 | +
|
| 170 | + Args: |
| 171 | + sampler: The quantum engine or simulator to run the circuits. |
| 172 | + qubits: Qubits to use. If none, use all qubits on the sampler's device. |
| 173 | + two_qubit_gate: The entangling gate to use. |
| 174 | + options: The XEB-fitting options. If None, calibrate only the three phase angles |
| 175 | + (chi, gamma, zeta) using the representation of a two-qubit gate as an FSimGate |
| 176 | + for the initial guess. |
| 177 | + n_repetitions: The number of repetitions to use. |
| 178 | + n_combinations: The number of combinations to generate. |
| 179 | + n_circuits: The number of circuits to generate. |
| 180 | + cycle_depths: The cycle depths to use. |
| 181 | + random_state: The random state to use. |
| 182 | + atol: Absolute tolerance to be used by the minimizer. |
| 183 | + num_workers_or_pool: An optional multi-processing pool or number of workers. |
| 184 | + A zero value means no multiprocessing. |
| 185 | + A positive integer value will create a pool with the given number of workers. |
| 186 | + A negative value will create pool with maximum number of workers. |
| 187 | +
|
| 188 | + Returns: |
| 189 | + - A dictionary mapping qubit pairs to the calibrated PhasedFSimGates. |
| 190 | + """ |
| 191 | + |
| 192 | + if options is None: |
| 193 | + options = xeb_fitting.XEBPhasedFSimCharacterizationOptions( |
| 194 | + characterize_chi=True, |
| 195 | + characterize_gamma=True, |
| 196 | + characterize_zeta=True, |
| 197 | + characterize_theta=False, |
| 198 | + characterize_phi=False, |
| 199 | + ).with_defaults_from_gate(two_qubit_gate) |
| 200 | + |
| 201 | + result, _ = z_phase_calibration_workflow( |
| 202 | + sampler=sampler, |
| 203 | + qubits=qubits, |
| 204 | + two_qubit_gate=two_qubit_gate, |
| 205 | + options=options, |
| 206 | + n_repetitions=n_repetitions, |
| 207 | + n_combinations=n_combinations, |
| 208 | + n_circuits=n_circuits, |
| 209 | + cycle_depths=cycle_depths, |
| 210 | + random_state=random_state, |
| 211 | + atol=atol, |
| 212 | + num_workers_or_pool=num_workers_or_pool, |
| 213 | + ) |
| 214 | + |
| 215 | + gates = {} |
| 216 | + for pair, params in result.final_params.items(): |
| 217 | + params['theta'] = params.get('theta', options.theta_default or 0) |
| 218 | + params['phi'] = params.get('phi', options.phi_default or 0) |
| 219 | + params['zeta'] = params.get('zeta', options.zeta_default or 0) |
| 220 | + params['chi'] = params.get('chi', options.chi_default or 0) |
| 221 | + params['gamma'] = params.get('gamma', options.gamma_default or 0) |
| 222 | + gates[pair] = ops.PhasedFSimGate(**params) |
| 223 | + return gates |
| 224 | + |
| 225 | + |
| 226 | +def plot_z_phase_calibration_result( |
| 227 | + before_after_df: 'pd.DataFrame', |
| 228 | + axes: Optional[np.ndarray[Sequence[Sequence['plt.Axes']], np.dtype[np.object_]]] = None, |
| 229 | + pairs: Optional[Sequence[Tuple['cirq.Qid', 'cirq.Qid']]] = None, |
| 230 | + *, |
| 231 | + with_error_bars: bool = False, |
| 232 | +) -> np.ndarray[Sequence[Sequence['plt.Axes']], np.dtype[np.object_]]: |
| 233 | + """A helper method to plot the result of running z-phase calibration. |
| 234 | +
|
| 235 | + Note that the plotted fidelity is a statistical estimate of the true fidelity and as a result |
| 236 | + may be outside the [0, 1] range. |
| 237 | +
|
| 238 | + Args: |
| 239 | + before_after_df: The second return object of running `z_phase_calibration_workflow`. |
| 240 | + axes: And ndarray of the axes to plot on. |
| 241 | + The number of axes is expected to be >= number of qubit pairs. |
| 242 | + pairs: If provided, only the given pairs are plotted. |
| 243 | + with_error_bars: Whether to add error bars or not. |
| 244 | + The width of the bar is an upper bound on standard variation of the estimated fidelity. |
| 245 | + """ |
| 246 | + if pairs is None: |
| 247 | + pairs = before_after_df.index |
| 248 | + if axes is None: |
| 249 | + # Create a 16x9 rectangle. |
| 250 | + ncols = int(np.ceil(np.sqrt(9 / 16 * len(pairs)))) |
| 251 | + nrows = (len(pairs) + ncols - 1) // ncols |
| 252 | + _, axes = plt.subplots(nrows=nrows, ncols=ncols) |
| 253 | + axes = axes if isinstance(axes, np.ndarray) else np.array(axes) |
| 254 | + for pair, ax in zip(pairs, axes.flatten()): |
| 255 | + row = before_after_df.loc[[pair]].iloc[0] |
| 256 | + ax.errorbar( |
| 257 | + row.cycle_depths_0, |
| 258 | + row.fidelities_0, |
| 259 | + yerr=row.layer_fid_std_0 * with_error_bars, |
| 260 | + label='original', |
| 261 | + ) |
| 262 | + ax.errorbar( |
| 263 | + row.cycle_depths_0, |
| 264 | + row.fidelities_c, |
| 265 | + yerr=row.layer_fid_std_c * with_error_bars, |
| 266 | + label='calibrated', |
| 267 | + ) |
| 268 | + ax.axhline(1, linestyle='--') |
| 269 | + ax.set_xlabel('cycle depth') |
| 270 | + ax.set_ylabel('fidelity estimate') |
| 271 | + ax.set_title('-'.join(str(q) for q in pair)) |
| 272 | + ax.legend() |
| 273 | + return axes |
0 commit comments