diff --git a/src/qibocal/protocols/__init__.py b/src/qibocal/protocols/__init__.py index a46fb5c07..63ff0687c 100644 --- a/src/qibocal/protocols/__init__.py +++ b/src/qibocal/protocols/__init__.py @@ -46,6 +46,7 @@ from .randomized_benchmarking.filtered_rb import filtered_rb from .randomized_benchmarking.standard_rb import standard_rb from .randomized_benchmarking.standard_rb_2q import standard_rb_2q +from .randomized_benchmarking.standard_rb_2q_inter import standard_rb_2q_inter from .readout_characterization import readout_characterization from .readout_mitigation_matrix import readout_mitigation_matrix from .readout_optimization.resonator_amplitude import resonator_amplitude @@ -145,5 +146,6 @@ "rabi_length_frequency", "rabi_length_frequency_signal", "standard_rb_2q", + "standard_rb_2q_inter", "optimize_two_qubit_gate", ] diff --git a/src/qibocal/protocols/randomized_benchmarking/filtered_rb.py b/src/qibocal/protocols/randomized_benchmarking/filtered_rb.py index 95f041322..6c949bb73 100644 --- a/src/qibocal/protocols/randomized_benchmarking/filtered_rb.py +++ b/src/qibocal/protocols/randomized_benchmarking/filtered_rb.py @@ -42,7 +42,7 @@ def _acquisition( RBData: The depths, samples and ground state probability of each experiment in the scan. """ - return rb_acquisition(params, targets, platform, add_inverse_layer=False) + return rb_acquisition(params, platform, targets, add_inverse_layer=False) def _fit(data: RBData) -> FilteredRBResult: diff --git a/src/qibocal/protocols/randomized_benchmarking/standard_rb.py b/src/qibocal/protocols/randomized_benchmarking/standard_rb.py index aa1374b31..0788afabf 100644 --- a/src/qibocal/protocols/randomized_benchmarking/standard_rb.py +++ b/src/qibocal/protocols/randomized_benchmarking/standard_rb.py @@ -88,7 +88,7 @@ def _acquisition( RBData: The depths, samples and ground state probability of each experiment in the scan. """ - return rb_acquisition(params, targets, platform) + return rb_acquisition(params, platform, targets) def _fit(data: RBData) -> StandardRBResult: @@ -120,7 +120,6 @@ def _plot( """ if isinstance(target, list): target = tuple(target) - qubit = target fig = go.Figure() fitting_report = "" diff --git a/src/qibocal/protocols/randomized_benchmarking/standard_rb_2q.py b/src/qibocal/protocols/randomized_benchmarking/standard_rb_2q.py index 2f055df10..79cb260b9 100644 --- a/src/qibocal/protocols/randomized_benchmarking/standard_rb_2q.py +++ b/src/qibocal/protocols/randomized_benchmarking/standard_rb_2q.py @@ -32,7 +32,7 @@ def _acquisition( ) -> RB2QData: """Data acquisition for two qubit Standard Randomized Benchmarking.""" - return twoq_rb_acquisition(params, targets, platform) + return twoq_rb_acquisition(params, platform, targets) def _fit(data: RB2QData) -> StandardRBResult: diff --git a/src/qibocal/protocols/randomized_benchmarking/standard_rb_2q_inter.py b/src/qibocal/protocols/randomized_benchmarking/standard_rb_2q_inter.py new file mode 100644 index 000000000..c6a657e05 --- /dev/null +++ b/src/qibocal/protocols/randomized_benchmarking/standard_rb_2q_inter.py @@ -0,0 +1,96 @@ +from dataclasses import dataclass, fields + +import numpy as np +from qibolab.platform import Platform +from qibolab.qubits import QubitPairId + +from qibocal.auto.operation import Routine +from qibocal.protocols.randomized_benchmarking.standard_rb import _plot +from qibocal.protocols.randomized_benchmarking.standard_rb_2q import ( + StandardRB2QParameters, +) + +from .utils import RB2QInterData, StandardRBResult, fit, twoq_rb_acquisition + + +@dataclass +class StandardRB2QInterParameters(StandardRB2QParameters): + """Parameters for the standard 2q randomized benchmarking protocol.""" + + interleave: str = "CZ" + """Gate to interleave""" + + +@dataclass +class StandardRB2QInterResult(StandardRBResult): + """Standard RB outputs.""" + + fidelity_cz: dict[QubitPairId, list] = None + """The overall fidelity for the CZ gate and its uncertainty.""" + + def __contains__(self, value: QubitPairId): + if isinstance(value, list): + value = tuple(value) + return all( + value in getattr(self, field.name) + for field in fields(self) + if isinstance(getattr(self, field.name), dict) + and field.name != "fidelity_cz" + ) + + +def _acquisition( + params: StandardRB2QInterParameters, + platform: Platform, + targets: list[QubitPairId], +) -> RB2QInterData: + """Data acquisition for two qubit Interleaved Randomized Benchmarking.""" + + data = twoq_rb_acquisition(params, platform, targets, interleave=params.interleave) + + fidelity = {} + for target in targets: + fidelity[target] = platform.pairs[target].gate_fidelity + data.fidelity = fidelity + + return data + + +def _fit(data: RB2QInterData) -> StandardRB2QInterResult: + """Takes a data frame, extracts the depths and the signal and fits it with an + exponential function y = Ap^x+B. + + Args: + data: Data from the data acquisition stage. + + Returns: + StandardRB2QInterResult: Aggregated and processed data. + """ + + qubits = data.pairs + results = fit(qubits, data) + + fidelity_cz = {} + for qubit in qubits: + if qubit in data.fidelity and data.fidelity[qubit] is not None: + fid_cz = results.fidelity[qubit] / data.fidelity[qubit][0] + uncertainty_cz = np.sqrt( + 1 + / data.fidelity[qubit][0] ** 2 + * results.fit_uncertainties[qubit][1] ** 2 + + (results.fidelity[qubit] / data.fidelity[qubit][0] ** 2) ** 2 + * data.fidelity[qubit][1] ** 2 + ) + fidelity_cz[qubit] = [fid_cz, uncertainty_cz] + + return StandardRB2QInterResult( + results.fidelity, + results.pulse_fidelity, + results.fit_parameters, + results.fit_uncertainties, + results.error_bars, + fidelity_cz, + ) + + +standard_rb_2q_inter = Routine(_acquisition, _fit, _plot) diff --git a/src/qibocal/protocols/randomized_benchmarking/utils.py b/src/qibocal/protocols/randomized_benchmarking/utils.py index 35e17fb11..381056e8f 100644 --- a/src/qibocal/protocols/randomized_benchmarking/utils.py +++ b/src/qibocal/protocols/randomized_benchmarking/utils.py @@ -122,6 +122,7 @@ def random_circuits( inverse_layer=True, single_qubit=True, file_inv=pathlib.Path(), + interleave=None, ) -> Iterable: """Returns random (self-inverting) Clifford circuits.""" @@ -129,7 +130,7 @@ def random_circuits( indexes = defaultdict(list) for _ in range(niter): for target in targets: - circuit, random_index = layer_circuit(rb_gen, depth, target) + circuit, random_index = layer_circuit(rb_gen, depth, target, interleave) if inverse_layer: add_inverse_layer(circuit, rb_gen, single_qubit, file_inv) add_measurement_layer(circuit) @@ -307,6 +308,14 @@ def extract_probabilities(self, qubits): return probs +@dataclass +class RB2QInterData(RB2QData): + """The output of the acquisition function.""" + + fidelity: dict[QubitPairId, list] = field(default_factory=dict) + """The interleaved fidelity of this qubit.""" + + @dataclass class StandardRBResult(Results): """Standard RB outputs.""" @@ -319,13 +328,17 @@ class StandardRBResult(Results): """Raw fitting parameters.""" fit_uncertainties: dict[QubitId, list[float]] """Fitting parameters uncertainties.""" - error_bars: dict[QubitId, Optional[Union[float, list[float]]]] = None + error_bars: dict[QubitId, Optional[Union[float, list[float]]]] = field( + default_factory=dict + ) """Error bars for y.""" def setup( params: Parameters, + platform: Platform, single_qubit: bool = True, + interleave: Optional[str] = None, ): """ Set up the randomized benchmarking experiment backend, noise model and data class. @@ -333,12 +346,14 @@ def setup( Args: params (Parameters): The parameters for the experiment. single_qubit (bool, optional): Flag indicating whether the experiment is for a single qubit or two qubits. Defaults to True. + interleave: (str, optional): The type of interleaving to apply. Defaults to None. Returns: tuple: A tuple containing the experiment data, noise model, and backend. """ backend = GlobalBackend() + backend.platform = platform # For simulations, a noise model can be added. noise_model = None if params.noise_model is not None: @@ -351,7 +366,12 @@ def setup( noise_model = getattr(noisemodels, params.noise_model)(params.noise_params) params.noise_params = noise_model.params.tolist() # Set up the scan (here an iterator of circuits of random clifford gates with an inverse). - cls = RBData if single_qubit else RB2QData + if single_qubit: + cls = RBData + elif interleave is not None: + cls = RB2QInterData + else: + cls = RB2QData data = cls( depths=params.depths, uncertainties=params.uncertainties, @@ -405,6 +425,7 @@ def get_circuits( add_inverse_layer, single_qubit, inv_file, + interleave, ) circuits.extend(circuits_depth) @@ -464,8 +485,8 @@ def execute_circuits(circuits, targets, params, backend, single_qubit=True): def rb_acquisition( params: Parameters, - targets: list[QubitId], platform: Platform, + targets: list[QubitId], add_inverse_layer: bool = True, interleave: str = None, ) -> RBData: @@ -482,8 +503,7 @@ def rb_acquisition( Returns: RBData: The depths, samples, and ground state probability of each experiment in the scan. """ - data, noise_model, backend = setup(params, single_qubit=True) - backend.platform = platform + data, noise_model, backend = setup(params, platform, single_qubit=True) circuits, indexes, npulses_per_clifford = get_circuits( params, targets, add_inverse_layer, interleave, noise_model, single_qubit=True ) @@ -512,11 +532,11 @@ def rb_acquisition( def twoq_rb_acquisition( params: Parameters, - targets: list[QubitPairId], platform: Platform, + targets: list[QubitPairId], add_inverse_layer: bool = True, interleave: str = None, -) -> RB2QData: +) -> Union[RB2QData, RB2QInterData]: """ The data acquisition stage of two qubit Standard Randomized Benchmarking. @@ -530,8 +550,7 @@ def twoq_rb_acquisition( RB2QData: The acquired data for two qubit randomized benchmarking. """ - data, noise_model, backend = setup(params, single_qubit=False) - backend.platform = platform + data, noise_model, backend = setup(params, platform, single_qubit=False) circuits, indexes, npulses_per_clifford = get_circuits( params, targets, add_inverse_layer, interleave, noise_model, single_qubit=False ) @@ -564,13 +583,16 @@ def twoq_rb_acquisition( return data -def layer_circuit(rb_gen: Callable, depth: int, target) -> tuple[Circuit, dict]: +def layer_circuit( + rb_gen: Callable, depth: int, target, interleave: str = None +) -> tuple[Circuit, dict]: """Creates a circuit of `depth` layers from a generator `layer_gen` yielding `Circuit` or `Gate` and a dictionary with random indexes used to select the clifford gates. Args: layer_gen (Callable): Should return gates or a full circuit specifying a layer. depth (int): Number of layers. + interleave (str, optional): Interleaving pattern for the circuits. Defaults to None. Returns: Circuit: with `depth` many layers. @@ -587,14 +609,19 @@ def layer_circuit(rb_gen: Callable, depth: int, target) -> tuple[Circuit, dict]: for _ in range(depth): # Generate a layer. new_layer, random_index = rb_gen_layer + random_indexes.append(random_index) new_circuit = Circuit(nqubits) if nqubits == 1: new_circuit.add(new_layer) elif nqubits == 2: for gate in new_layer: new_circuit.add(gate) - - random_indexes.append(random_index) + # FIXME: General interleave + if interleave == "CZ": + interleaved_clifford = rb_gen.two_qubit_cliffords["13"] + interleaved_clifford_gate = clifford2gates(interleaved_clifford) + new_circuit.add(interleaved_clifford_gate) + random_indexes.append("13") if full_circuit is None: # instantiate in first loop full_circuit = Circuit(new_circuit.nqubits) diff --git a/tests/runcards/protocols.yml b/tests/runcards/protocols.yml index b06e0ac12..a74cc4b21 100644 --- a/tests/runcards/protocols.yml +++ b/tests/runcards/protocols.yml @@ -701,6 +701,14 @@ actions: niter: 5 nshots: 50 + - id: standard rb 2q interleaved + operation: standard_rb_2q_inter + targets: [[0,2]] + parameters: + depths: [1, 2, 3, 5] + niter: 5 + nshots: 50 + - id: chevron cz operation: chevron targets: [[0, 2],[1,2]]