From 194915b42a24b599141f0ec39fa006e16426e517 Mon Sep 17 00:00:00 2001 From: Shashwat Kumar Date: Fri, 27 Feb 2026 22:58:08 +0000 Subject: [PATCH 01/11] Add RCS experiment --- .../random_circuit_sampling/rcs_experiment.py | 368 ++++++++++++++++++ .../rcs_experiment_test.py | 115 ++++++ 2 files changed, 483 insertions(+) create mode 100644 recirq/random_circuit_sampling/rcs_experiment.py create mode 100644 recirq/random_circuit_sampling/rcs_experiment_test.py diff --git a/recirq/random_circuit_sampling/rcs_experiment.py b/recirq/random_circuit_sampling/rcs_experiment.py new file mode 100644 index 00000000..2da158c0 --- /dev/null +++ b/recirq/random_circuit_sampling/rcs_experiment.py @@ -0,0 +1,368 @@ +# Copyright 2026 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tools for Random Circuit Sampling (RCS) and XEB analysis. + +This module provides an experimental framework to generate, execute, and analyze +Random Circuit Sampling experiments across disjoint qubit patches. It supports +automated PhasedFSim characterization to determine hardware +gates for more accurate fidelity estimation. + +The fidelity is calculated using Linear Cross-Entropy Benchmarking (XEB), as +detailed in: + "Quantum supremacy using a programmable superconducting processor" + Nature 563, 505–510 (2019). + https://www.nature.com/articles/s41586-019-1666-5 + +Main components: + - RCSexperiment: Manages parallel execution of circuit instances across + disjoint grid patches. + - RCSresults: Performs noiseless simulation and computes Linear XEB fidelity. +""" + +import random +from collections import defaultdict +from typing import Dict, Tuple, List, Callable, Optional + +import cirq +import cirq_google +import numpy as np +import networkx as nx +import cirq.contrib.routing as ccr +from tqdm import tqdm + +import cirq.experiments.random_quantum_circuit_generation as rqcg +from cirq.experiments import xeb_fitting +import cirq.experiments.z_phase_calibration as xeb_characterize + + +def characterize_pairs( + sampler: cirq.Sampler, + qubits: List[cirq.GridQubit], + gate: cirq.Gate +) -> Dict[Tuple[cirq.GridQubit, cirq.GridQubit], cirq.PhasedFSimGate]: + """Performs XEB characterization for PhasedFSimGate angles. + + Args: + sampler: The cirq.Sampler used for data collection. + qubits: All qubits involved in the characterization. + gate: The baseline ideal gate to calibrate against, e.g., cirq_google.SYC + + Returns: + A dictionary mapping qubit pairs to their calibrated PhasedFSimGate. + """ + + base_options = xeb_fitting.XEBPhasedFSimCharacterizationOptions( + characterize_theta=True, + characterize_phi=True, + characterize_zeta=True, + characterize_chi=True, + characterize_gamma=True + ) + + options = base_options.with_defaults_from_gate(gate) + + results = xeb_characterize.calibrate_z_phases( + sampler=sampler, + qubits=qubits, + options=options, + ) + + return results + + +def get_calibrated_circuit(circuit: cirq.Circuit, characterization: dict | None) -> cirq.Circuit: + """Replaces ideal 2-qubit gates with calibrated PhasedFSimGate models. + + Args: + circuit: The ideal RCS circuit to be updated. + characterization: A mapping from qubit pairs (edges) to their + measured PhasedFSimGate parameters. + + Returns: + A new cirq.Circuit with gates updated to reflect hardware characterization. + """ + if not characterization: + return circuit + + def map_func(op: cirq.Operation, _): + if len(op.qubits) == 2: + edge = tuple(op.qubits) + gate = characterization.get(edge) or characterization.get(edge[::-1]) + if gate: return gate.on(*op.qubits) + return op + + return cirq.map_operations(circuit, map_func) + + +def make_rcs_circuit( + qubits: List[cirq.GridQubit], + depth: int, + seed: Optional[int] = None, + pattern_name: str = "staggered", + single_qubit_gates: Tuple[cirq.Gate, ...] = ( + cirq.X ** 0.5, + cirq.Y ** 0.5, + cirq.PhasedXPowGate(exponent=0.5, phase_exponent=0.25) + ), + two_qubit_op_factory: Callable[[cirq.Qid, cirq.Qid, random.Random], cirq.Operation] = ( + lambda a, b, _: cirq_google.SYC(a, b) + ) +) -> cirq.Circuit: + """Generates a customizable RCS circuit using official Cirq experiment utilities. + + Args: + qubits: The list of GridQubits to include in the circuit. + depth: The number of interaction layers (cycles). + seed: Random seed for gate selection and tiling reproducibility. + pattern_name: Tiling configuration: "staggered", "half", or "aligned". + single_qubit_gates: Collection of gates for random 1Q rotations. + two_qubit_op_factory: Callable returning the 2Q operation for an edge. + + Returns: + An cirq.Circuit instance. + + Raises: + ValueError: If depth is negative or pattern_name is unrecognized. + """ + if depth < 0: + raise ValueError(f"Depth must be non-negative. Received: {depth}") + + patterns = { + "staggered": cirq.experiments.GRID_STAGGERED_PATTERN, + "half": cirq.experiments.HALF_GRID_STAGGERED_PATTERN, + "aligned": cirq.experiments.GRID_ALIGNED_PATTERN, + } + + if pattern_name not in patterns: + raise ValueError( + f"Invalid pattern_name '{pattern_name}'. Supported: {list(patterns.keys())}") + + circuit = rqcg.random_rotations_between_grid_interaction_layers_circuit( + qubits=qubits, + depth=depth, + two_qubit_op_factory=two_qubit_op_factory, + pattern=patterns[pattern_name], + single_qubit_gates=single_qubit_gates, + add_final_single_qubit_layer=True, + seed=seed + ) + + return circuit + + +class RCSexperiment: + """Manages generation and parallel execution of RCS experiments.""" + + def __init__( + self, + patches: List[List[cirq.GridQubit]], + depths: List[int], + num_instances: int, + pattern_name: str = "staggered", + single_qubit_gates: Optional[Tuple[cirq.Gate, ...]] = None, + two_qubit_gate: cirq.Gate = cirq_google.SYC, + seed: Optional[int] = None + ): + """Initializes experiment and validates patch disjointness and connectivity. + + Args: + patches: List of qubit lists representing disjoint sub-regions. + depths: Cycle depths to execute. + num_instances: Number of random circuit instances per (patch, depth). + pattern_name: Tiling configuration, "staggered", "half", or "aligned". + single_qubit_gates: Optional tuple of 1Q gates to use for rotations. + two_qubit_gate: The ideal entangling gate. + seed: Master seed for reproducibility. + + Raises: + ValueError: If patches are not disjoint. + ValueError: If a patch contains isolated qubits. + ValueError: If a patch is split into islands. + """ + + # Basic Disjointness Check + all_qubits = [q for patch in patches for q in patch] + if len(all_qubits) != len(set(all_qubits)): + raise ValueError("Patches must be disjoint (no shared qubits).") + + # Patch Connectivity and Isolated Qubit Check + for i, patch in enumerate(patches): + patch_graph = ccr.gridqubits_to_graph_device(patch) + + # If a qubit is isolated, gridqubits_to_graph_device won't include it. + if len(patch_graph.nodes) != len(patch): + missing = set(patch) - set(patch_graph.nodes) + raise ValueError(f"Patch {i} has isolated (dangling) qubits: {missing}") + + # Check for islands + if not nx.is_connected(patch_graph): + raise ValueError(f"Patch {i} is not connected (it is split into islands).") + + self.patches = patches + self.all_qubits = all_qubits + self.depths = depths + self.num_instances = num_instances + self.pattern_name = pattern_name + self.two_qubit_gate = two_qubit_gate + self.seed = seed + + self._rng = random.Random(seed) if seed is not None else random.Random() + self.single_qubit_gates = single_qubit_gates or ( + cirq.X ** 0.5, cirq.Y ** 0.5, cirq.PhasedXPowGate(exponent=0.5, phase_exponent=0.25) + ) + + def run(self, sampler: cirq.Sampler, n_repetitions: int, + characterize: bool = False) -> "RCSresults": + """Executes circuits in parallel using unique keys for patch separation. + + Args: + sampler: Sampler to execute circuits. + n_repetitions: Shots per circuit. + characterize: If True, performs gate characterization before analysis. + + Returns: + An RCSresults object. + """ + + char_data = None + if characterize: + char_data = characterize_pairs( + sampler=sampler, + qubits=self.all_qubits, + gate=self.two_qubit_gate + ) + + zipped_circuits = [] + metadata_flat = [] + all_individual_circuits = [] + + for depth in self.depths: + for instance_idx in range(self.num_instances): + master_instance_seed = self._rng.randint(0, 2 ** 32 - 1) + + circuits_to_zip = [] + + for patch_idx, qubits in enumerate(self.patches): + patch_seed = hash((master_instance_seed, patch_idx)) % (2 ** 32) + + patch_circuit = make_rcs_circuit( + qubits=qubits, + depth=depth, + seed=patch_seed, + pattern_name=self.pattern_name, + single_qubit_gates=self.single_qubit_gates, + two_qubit_op_factory=lambda a, b, _: self.two_qubit_gate(a, b) + ) + + key = f"m_{patch_idx}" + patch_circuit.append(cirq.measure(*sorted(qubits), key=key)) + + circuits_to_zip.append(patch_circuit) + all_individual_circuits.append(patch_circuit) + metadata_flat.append({ + "patch_idx": patch_idx, + "depth": depth, + "instance": instance_idx, + "key": key + }) + + zipped_circuits.append(cirq.Circuit.zip(*circuits_to_zip)) + + print( + f"Running {len(zipped_circuits)} zipped instances on {len(self.patches)} parallel patches...") + batch_results = sampler.run_batch(zipped_circuits, repetitions=n_repetitions) + + measurements_flat = [] + for i, combined_result in enumerate(batch_results): + for patch_idx in range(len(self.patches)): + key = f"m_{patch_idx}" + patch_data = combined_result[0].measurements[key] + measurements_flat.append(patch_data) + + return RCSresults( + circuits=all_individual_circuits, + measurements=measurements_flat, + metadata=metadata_flat, + characterization=char_data + ) + + +class RCSresults: + """Calculates Linear Cross-Entropy Benchmarking (XEB) fidelity. + + Args: + circuits: Unzipped individual circuits for the measurements. + measurements: Measured bitstrings per circuit. + metadata: Tracking info (patch, depth). + characterization: A dictionary mapping qubit pairs to their calibrated PhasedFSimGate. + """ + + def __init__( + self, + circuits: List[cirq.Circuit], + measurements: List[np.ndarray], + metadata: List[Dict], + characterization: Optional[Dict] = None + ): + self.circuits = circuits + self.measurements = measurements + self.metadata = metadata + self.characterization = characterization + self._fidelities_lin: Optional[Dict] = None + + def _analyze(self) -> None: + """Calculates linear xeb by comparing measured counts against ideal probabilities. + + Formula used for linear XEB: F = dimensions * E[P_ideal] - 1 + """ + fidelities_lin = defaultdict(list) + sim = cirq.Simulator() + + for i, circuit_measurements in enumerate(tqdm(self.measurements, desc="XEB Analysis")): + raw_circuit = self.circuits[i] + + sim_circuit = get_calibrated_circuit(raw_circuit, self.characterization) + + program = cirq.Circuit(op for op in sim_circuit if not cirq.is_measurement(op)) + + meta = self.metadata[i] + patch_idx, depth = meta["patch_idx"], meta["depth"] + qubits = sorted(raw_circuit.all_qubits()) + n_qubits = len(qubits) + dim = 2 ** n_qubits + + # Convert bitstrings and calculate observed probabilities + measured_ints = [cirq.big_endian_bits_to_int(m) for m in circuit_measurements] + unique_ints, counts = np.unique(measured_ints, return_counts=True) + measured_probs = counts / len(measured_ints) + amplitudes = sim.compute_amplitudes( + program=program, + bitstrings=unique_ints, + qubit_order=qubits + ) + ideal_probs = np.abs(np.array(amplitudes)) ** 2 + + # Calculate linear fidelity + linear_fidelity = dim * np.sum(measured_probs * ideal_probs) - 1 + fidelities_lin[(patch_idx, depth)].append(linear_fidelity) + + self._fidelities_lin = dict(fidelities_lin) + + @property + def fidelities_lin(self): + """Accesses the Linear XEB fidelities, triggering analysis if needed.""" + if self._fidelities_lin is None: + self._analyze() + return self._fidelities_lin diff --git a/recirq/random_circuit_sampling/rcs_experiment_test.py b/recirq/random_circuit_sampling/rcs_experiment_test.py new file mode 100644 index 00000000..0b0746a1 --- /dev/null +++ b/recirq/random_circuit_sampling/rcs_experiment_test.py @@ -0,0 +1,115 @@ +# Copyright 2026 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +import numpy as np +import cirq +import recirq.random_circuit_sampling.rcs_experiment as rcs + + +def test_rcs_multi_depth_regression(): + """Regression test for multi-depth, multi-instance RCS fidelity.""" + patch_1 = cirq.GridQubit.rect(4, 3, top=0, left=0) + patch_2 = cirq.GridQubit.rect(4, 3, top=0, left=3) + patch_3 = cirq.GridQubit.rect(4, 3, top=4, left=0) + patches = [patch_1, patch_2, patch_3] + + DEPTHS = [30, 50] + NUM_INSTANCES = 3 + N_REPETITIONS = 10000 + FIXED_SEED = 2026 + + # Benchmark values from your deterministic run + expected_benchmarks = { + (0, 30): 0.9846, + (0, 50): 0.9753, + (1, 30): 0.9896, + (1, 50): 0.9967, + (2, 30): 1.0030, + (2, 50): 0.9927, + } + + experiment = rcs.RCSexperiment( + patches=patches, + depths=DEPTHS, + num_instances=NUM_INSTANCES, + pattern_name="staggered", + seed=FIXED_SEED + ) + + simulator = cirq.Simulator(seed=FIXED_SEED) + results = experiment.run(sampler=simulator, n_repetitions=N_REPETITIONS, characterize=False) + + fidelities = results.fidelities_lin + + for (patch_idx, depth), expected_val in expected_benchmarks.items(): + actual_val = np.mean(fidelities[(patch_idx, depth)]) + assert actual_val == pytest.approx(expected_val, abs=1e-4) + + +def test_rcs_validation_logic(): + """Verifies that the experiment catches invalid patch configurations.""" + q0, q1 = cirq.GridQubit(0, 0), cirq.GridQubit(0, 1) + q_far = cirq.GridQubit(5, 5) + + # Test Overlapping Patches + with pytest.raises(ValueError, match="disjoint"): + rcs.RCSexperiment(patches=[[q0, q1], [q1]], depths=[5], num_instances=1) + + # Test Isolated Qubits + with pytest.raises(ValueError, match="isolated"): + rcs.RCSexperiment(patches=[[q0, q1, q_far]], depths=[5], num_instances=1) + + # Test Disconnected Islands + q_far_neighbor = cirq.GridQubit(5, 6) + with pytest.raises(ValueError, match="connected"): + rcs.RCSexperiment(patches=[[q0, q1, q_far, q_far_neighbor]], depths=[5], num_instances=1) + + +def test_rcs_data_consistency(): + """Checks that the internal data structures maintain correct shapes and qubit sets.""" + p1 = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] + p2 = [cirq.GridQubit(1, 0), cirq.GridQubit(1, 1)] + depths = [5, 10, 20] + num_instances = 4 + + exp = rcs.RCSexperiment(patches=[p1, p2], depths=depths, num_instances=num_instances) + results = exp.run(sampler=cirq.Simulator(), n_repetitions=10) + + # Check total result count (Patches * Depths * Instances) + expected_total = 2 * 3 * 4 + assert len(results.circuits) == expected_total + assert len(results.measurements) == expected_total + assert len(results.metadata) == expected_total + + # Check qubit isolation (ensure parallel zipping didn't leak qubits) + for i, meta in enumerate(results.metadata): + patch_idx = meta["patch_idx"] + expected_qubits = set(exp.patches[patch_idx]) + actual_qubits = results.circuits[i].all_qubits() + assert set(actual_qubits) == expected_qubits + + +def test_rcs_analysis_evaluation(): + """Ensures fidelities_lin triggers analysis automatically via property.""" + p = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] + exp = rcs.RCSexperiment(patches=[p], depths=[2], num_instances=1) + results = exp.run(sampler=cirq.Simulator(), n_repetitions=100) + + # Check that analysis hasn't run yet + assert results._fidelities_lin is None + # Accessing the property triggers rcs.RCSresults._analyze() + fids = results.fidelities_lin + assert fids is not None + assert (0, 2) in fids From c73f29cb9573421fed8358bd069478c5470ab65a Mon Sep 17 00:00:00 2001 From: Shashwat Kumar Date: Fri, 27 Feb 2026 23:22:08 +0000 Subject: [PATCH 02/11] typo --- recirq/random_circuit_sampling/rcs_experiment_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recirq/random_circuit_sampling/rcs_experiment_test.py b/recirq/random_circuit_sampling/rcs_experiment_test.py index 0b0746a1..970950df 100644 --- a/recirq/random_circuit_sampling/rcs_experiment_test.py +++ b/recirq/random_circuit_sampling/rcs_experiment_test.py @@ -30,7 +30,7 @@ def test_rcs_multi_depth_regression(): N_REPETITIONS = 10000 FIXED_SEED = 2026 - # Benchmark values from your deterministic run + # Benchmark values from the deterministic run expected_benchmarks = { (0, 30): 0.9846, (0, 50): 0.9753, From 60b82ca0383ae52f85e2a08a3bb31b3033cf0912 Mon Sep 17 00:00:00 2001 From: Shashwat Kumar Date: Fri, 27 Feb 2026 23:59:54 +0000 Subject: [PATCH 03/11] fix docstrings --- .../random_circuit_sampling/rcs_experiment.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/recirq/random_circuit_sampling/rcs_experiment.py b/recirq/random_circuit_sampling/rcs_experiment.py index 2da158c0..b32f36d0 100644 --- a/recirq/random_circuit_sampling/rcs_experiment.py +++ b/recirq/random_circuit_sampling/rcs_experiment.py @@ -51,7 +51,7 @@ def characterize_pairs( sampler: cirq.Sampler, qubits: List[cirq.GridQubit], gate: cirq.Gate -) -> Dict[Tuple[cirq.GridQubit, cirq.GridQubit], cirq.PhasedFSimGate]: +) -> Dict[Tuple[cirq.Qid, cirq.Qid], cirq.PhasedFSimGate]: """Performs XEB characterization for PhasedFSimGate angles. Args: @@ -116,9 +116,10 @@ def make_rcs_circuit( cirq.Y ** 0.5, cirq.PhasedXPowGate(exponent=0.5, phase_exponent=0.25) ), - two_qubit_op_factory: Callable[[cirq.Qid, cirq.Qid, random.Random], cirq.Operation] = ( - lambda a, b, _: cirq_google.SYC(a, b) - ) + two_qubit_op_factory: Callable[ + [cirq.GridQubit, cirq.GridQubit, np.random.RandomState], + cirq.OP_TREE + ] = lambda a, b, _: cirq_google.SYC(a, b) ) -> cirq.Circuit: """Generates a customizable RCS circuit using official Cirq experiment utilities. @@ -187,9 +188,8 @@ def __init__( seed: Master seed for reproducibility. Raises: - ValueError: If patches are not disjoint. - ValueError: If a patch contains isolated qubits. - ValueError: If a patch is split into islands. + ValueError: If patches are not disjoint, contain isolated qubits, + or are split into islands. """ # Basic Disjointness Check @@ -302,11 +302,12 @@ def run(self, sampler: cirq.Sampler, n_repetitions: int, class RCSresults: """Calculates Linear Cross-Entropy Benchmarking (XEB) fidelity. - Args: + Attributes: circuits: Unzipped individual circuits for the measurements. measurements: Measured bitstrings per circuit. - metadata: Tracking info (patch, depth). + metadata: Tracking info (e.g., patch, depth). characterization: A dictionary mapping qubit pairs to their calibrated PhasedFSimGate. + fidelities_lin: A dictionary mapping (patch, depth) to computed linear XEB fidelity values. """ def __init__( @@ -349,7 +350,7 @@ def _analyze(self) -> None: measured_probs = counts / len(measured_ints) amplitudes = sim.compute_amplitudes( program=program, - bitstrings=unique_ints, + bitstrings=list(unique_ints), qubit_order=qubits ) ideal_probs = np.abs(np.array(amplitudes)) ** 2 From c45382dfc0611454b7124c343acfccad330fd700 Mon Sep 17 00:00:00 2001 From: Michael Hucka Date: Wed, 4 Mar 2026 12:06:46 -0800 Subject: [PATCH 04/11] Add Gemini Code Assist config file (#462) This configures Gemini Code Assist to be a little bit less noisy and verbose. Based on experiences with other repos, I find this makes Gemini more useful. --- .gemini/config.yaml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .gemini/config.yaml diff --git a/.gemini/config.yaml b/.gemini/config.yaml new file mode 100644 index 00000000..93694abd --- /dev/null +++ b/.gemini/config.yaml @@ -0,0 +1,28 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Summary: configure Gemini Code Assist (https://codeassist.google/). +# See https://github.com/marketplace/gemini-code-assist for more info. + +have_fun: false +code_review: + disable: false + comment_severity_threshold: HIGH + max_review_comments: -1 + pull_request_opened: + help: false + summary: false + code_review: true + include_drafts: false +ignore_patterns: [] From cbc089c532b2798959ba108cf52c8ef353c27f6a Mon Sep 17 00:00:00 2001 From: Shashwat Kumar Date: Wed, 4 Mar 2026 23:04:47 +0000 Subject: [PATCH 05/11] addressed comments --- recirq/random_circuit_sampling/__init__.py | 21 ++++ .../random_circuit_sampling/rcs_experiment.py | 96 ++++++++++++------- .../rcs_experiment_test.py | 42 ++++++-- 3 files changed, 114 insertions(+), 45 deletions(-) create mode 100644 recirq/random_circuit_sampling/__init__.py diff --git a/recirq/random_circuit_sampling/__init__.py b/recirq/random_circuit_sampling/__init__.py new file mode 100644 index 00000000..8ebc7ce9 --- /dev/null +++ b/recirq/random_circuit_sampling/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2026 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from recirq.random_circuit_sampling.rcs_experiment import ( + characterize_pairs, + make_rcs_circuit, + RCSExperiment, + RCSResults, +) diff --git a/recirq/random_circuit_sampling/rcs_experiment.py b/recirq/random_circuit_sampling/rcs_experiment.py index b32f36d0..da58d092 100644 --- a/recirq/random_circuit_sampling/rcs_experiment.py +++ b/recirq/random_circuit_sampling/rcs_experiment.py @@ -26,9 +26,9 @@ https://www.nature.com/articles/s41586-019-1666-5 Main components: - - RCSexperiment: Manages parallel execution of circuit instances across + - RCSExperiment: Manages parallel execution of circuit instances across disjoint grid patches. - - RCSresults: Performs noiseless simulation and computes Linear XEB fidelity. + - RCSResults: Performs noiseless simulation and computes Linear XEB fidelity. """ import random @@ -46,11 +46,15 @@ from cirq.experiments import xeb_fitting import cirq.experiments.z_phase_calibration as xeb_characterize - def characterize_pairs( sampler: cirq.Sampler, qubits: List[cirq.GridQubit], - gate: cirq.Gate + gate: cirq.Gate, + theta: bool = True, + phi: bool = True, + zeta: bool = True, + chi: bool = True, + gamma: bool = True ) -> Dict[Tuple[cirq.Qid, cirq.Qid], cirq.PhasedFSimGate]: """Performs XEB characterization for PhasedFSimGate angles. @@ -58,20 +62,19 @@ def characterize_pairs( sampler: The cirq.Sampler used for data collection. qubits: All qubits involved in the characterization. gate: The baseline ideal gate to calibrate against, e.g., cirq_google.SYC + theta, phi, zeta, chi, gamma: Toggles for parameters to characterize. Defaults to True + for all. Returns: A dictionary mapping qubit pairs to their calibrated PhasedFSimGate. """ - - base_options = xeb_fitting.XEBPhasedFSimCharacterizationOptions( - characterize_theta=True, - characterize_phi=True, - characterize_zeta=True, - characterize_chi=True, - characterize_gamma=True - ) - - options = base_options.with_defaults_from_gate(gate) + options = xeb_fitting.XEBPhasedFSimCharacterizationOptions( + characterize_theta=theta, + characterize_phi=phi, + characterize_zeta=zeta, + characterize_chi=chi, + characterize_gamma=gamma + ).with_defaults_from_gate(gate) results = xeb_characterize.calibrate_z_phases( sampler=sampler, @@ -82,7 +85,7 @@ def characterize_pairs( return results -def get_calibrated_circuit(circuit: cirq.Circuit, characterization: dict | None) -> cirq.Circuit: +def get_calibrated_circuit(circuit: cirq.Circuit, characterization: Optional[Dict] = None) -> cirq.Circuit: """Replaces ideal 2-qubit gates with calibrated PhasedFSimGate models. Args: @@ -97,7 +100,7 @@ def get_calibrated_circuit(circuit: cirq.Circuit, characterization: dict | None) return circuit def map_func(op: cirq.Operation, _): - if len(op.qubits) == 2: + if len(op.qubits) == 2 and not cirq.is_measurement(op): edge = tuple(op.qubits) gate = characterization.get(edge) or characterization.get(edge[::-1]) if gate: return gate.on(*op.qubits) @@ -163,7 +166,7 @@ def make_rcs_circuit( return circuit -class RCSexperiment: +class RCSExperiment: """Manages generation and parallel execution of RCS experiments.""" def __init__( @@ -223,17 +226,28 @@ def __init__( cirq.X ** 0.5, cirq.Y ** 0.5, cirq.PhasedXPowGate(exponent=0.5, phase_exponent=0.25) ) - def run(self, sampler: cirq.Sampler, n_repetitions: int, - characterize: bool = False) -> "RCSresults": + def run( + self, + sampler: cirq.Sampler, + n_repetitions: int, + characterize: bool = False, + theta: bool = True, + phi: bool = True, + zeta: bool = True, + chi: bool = True, + gamma: bool = True + ) -> "RCSResults": """Executes circuits in parallel using unique keys for patch separation. Args: sampler: Sampler to execute circuits. n_repetitions: Shots per circuit. characterize: If True, performs gate characterization before analysis. + theta, phi, zeta, chi, gamma: Toggles for characterization angles. Default to True + for all. Returns: - An RCSresults object. + An RCSResults object. """ char_data = None @@ -241,7 +255,12 @@ def run(self, sampler: cirq.Sampler, n_repetitions: int, char_data = characterize_pairs( sampler=sampler, qubits=self.all_qubits, - gate=self.two_qubit_gate + gate=self.two_qubit_gate, + theta=theta, + phi=phi, + zeta=zeta, + chi=chi, + gamma=gamma ) zipped_circuits = [] @@ -291,7 +310,7 @@ def run(self, sampler: cirq.Sampler, n_repetitions: int, patch_data = combined_result[0].measurements[key] measurements_flat.append(patch_data) - return RCSresults( + return RCSResults( circuits=all_individual_circuits, measurements=measurements_flat, metadata=metadata_flat, @@ -299,7 +318,7 @@ def run(self, sampler: cirq.Sampler, n_repetitions: int, ) -class RCSresults: +class RCSResults: """Calculates Linear Cross-Entropy Benchmarking (XEB) fidelity. Attributes: @@ -307,7 +326,6 @@ class RCSresults: measurements: Measured bitstrings per circuit. metadata: Tracking info (e.g., patch, depth). characterization: A dictionary mapping qubit pairs to their calibrated PhasedFSimGate. - fidelities_lin: A dictionary mapping (patch, depth) to computed linear XEB fidelity values. """ def __init__( @@ -323,13 +341,23 @@ def __init__( self.characterization = characterization self._fidelities_lin: Optional[Dict] = None - def _analyze(self) -> None: + def fidelities_lin(self, simulator: cirq.SimulatesAmplitudes = cirq.Simulator()) -> Dict: + """Accesses or calculates Linear XEB fidelities. + + Args: + simulator: The simulator to use for computing ideal amplitudes. Defaults to + cirq.Simulator(). + """ + if self._fidelities_lin is None: + self._analyze(simulator=simulator) + return self._fidelities_lin + + def _analyze(self, simulator: cirq.SimulatesAmplitudes) -> None: """Calculates linear xeb by comparing measured counts against ideal probabilities. Formula used for linear XEB: F = dimensions * E[P_ideal] - 1 """ - fidelities_lin = defaultdict(list) - sim = cirq.Simulator() + fidelities = defaultdict(list) for i, circuit_measurements in enumerate(tqdm(self.measurements, desc="XEB Analysis")): raw_circuit = self.circuits[i] @@ -348,7 +376,7 @@ def _analyze(self) -> None: measured_ints = [cirq.big_endian_bits_to_int(m) for m in circuit_measurements] unique_ints, counts = np.unique(measured_ints, return_counts=True) measured_probs = counts / len(measured_ints) - amplitudes = sim.compute_amplitudes( + amplitudes = simulator.compute_amplitudes( program=program, bitstrings=list(unique_ints), qubit_order=qubits @@ -356,14 +384,8 @@ def _analyze(self) -> None: ideal_probs = np.abs(np.array(amplitudes)) ** 2 # Calculate linear fidelity - linear_fidelity = dim * np.sum(measured_probs * ideal_probs) - 1 - fidelities_lin[(patch_idx, depth)].append(linear_fidelity) + linear_fidelity = dim * (measured_probs @ ideal_probs) - 1 + fidelities[(patch_idx, depth)].append(linear_fidelity) - self._fidelities_lin = dict(fidelities_lin) + self._fidelities_lin = dict(fidelities) - @property - def fidelities_lin(self): - """Accesses the Linear XEB fidelities, triggering analysis if needed.""" - if self._fidelities_lin is None: - self._analyze() - return self._fidelities_lin diff --git a/recirq/random_circuit_sampling/rcs_experiment_test.py b/recirq/random_circuit_sampling/rcs_experiment_test.py index 970950df..378c2b10 100644 --- a/recirq/random_circuit_sampling/rcs_experiment_test.py +++ b/recirq/random_circuit_sampling/rcs_experiment_test.py @@ -40,7 +40,7 @@ def test_rcs_multi_depth_regression(): (2, 50): 0.9927, } - experiment = rcs.RCSexperiment( + experiment = rcs.RCSExperiment( patches=patches, depths=DEPTHS, num_instances=NUM_INSTANCES, @@ -51,7 +51,7 @@ def test_rcs_multi_depth_regression(): simulator = cirq.Simulator(seed=FIXED_SEED) results = experiment.run(sampler=simulator, n_repetitions=N_REPETITIONS, characterize=False) - fidelities = results.fidelities_lin + fidelities = results.fidelities_lin() for (patch_idx, depth), expected_val in expected_benchmarks.items(): actual_val = np.mean(fidelities[(patch_idx, depth)]) @@ -65,16 +65,16 @@ def test_rcs_validation_logic(): # Test Overlapping Patches with pytest.raises(ValueError, match="disjoint"): - rcs.RCSexperiment(patches=[[q0, q1], [q1]], depths=[5], num_instances=1) + rcs.RCSExperiment(patches=[[q0, q1], [q1]], depths=[5], num_instances=1) # Test Isolated Qubits with pytest.raises(ValueError, match="isolated"): - rcs.RCSexperiment(patches=[[q0, q1, q_far]], depths=[5], num_instances=1) + rcs.RCSExperiment(patches=[[q0, q1, q_far]], depths=[5], num_instances=1) # Test Disconnected Islands q_far_neighbor = cirq.GridQubit(5, 6) with pytest.raises(ValueError, match="connected"): - rcs.RCSexperiment(patches=[[q0, q1, q_far, q_far_neighbor]], depths=[5], num_instances=1) + rcs.RCSExperiment(patches=[[q0, q1, q_far, q_far_neighbor]], depths=[5], num_instances=1) def test_rcs_data_consistency(): @@ -84,7 +84,7 @@ def test_rcs_data_consistency(): depths = [5, 10, 20] num_instances = 4 - exp = rcs.RCSexperiment(patches=[p1, p2], depths=depths, num_instances=num_instances) + exp = rcs.RCSExperiment(patches=[p1, p2], depths=depths, num_instances=num_instances) results = exp.run(sampler=cirq.Simulator(), n_repetitions=10) # Check total result count (Patches * Depths * Instances) @@ -104,12 +104,38 @@ def test_rcs_data_consistency(): def test_rcs_analysis_evaluation(): """Ensures fidelities_lin triggers analysis automatically via property.""" p = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] - exp = rcs.RCSexperiment(patches=[p], depths=[2], num_instances=1) + exp = rcs.RCSExperiment(patches=[p], depths=[2], num_instances=1) results = exp.run(sampler=cirq.Simulator(), n_repetitions=100) # Check that analysis hasn't run yet assert results._fidelities_lin is None # Accessing the property triggers rcs.RCSresults._analyze() - fids = results.fidelities_lin + fids = results.fidelities_lin() assert fids is not None assert (0, 2) in fids + + +def test_get_calibrated_circuit(): + """Verifies that 2-qubit measurements are not replaced by calibrated gates.""" + q0, q1 = cirq.GridQubit(0, 0), cirq.GridQubit(0, 1) + + + circuit = cirq.Circuit( + cirq.CZ(q0, q1), + cirq.measure(q0, q1, key='m') + ) + + # Mock characterization + calibrated_gate = cirq.PhasedFSimGate(theta=0.1) + characterization = {(q0, q1): calibrated_gate} + + calibrated_circuit = rcs.get_calibrated_circuit(circuit, characterization) + + + assert any(isinstance(op.gate, cirq.PhasedFSimGate) for op in + calibrated_circuit.all_operations()) + + measurements = [op for op in calibrated_circuit.all_operations() if + cirq.is_measurement(op)] + assert len(measurements) == 1 + assert measurements[0].qubits == (q0, q1) From b21fdb84ef3795a446d228a9a2e66a8f38e5cc19 Mon Sep 17 00:00:00 2001 From: Pavol Juhas Date: Thu, 5 Mar 2026 15:10:44 -0800 Subject: [PATCH 06/11] Fix incompatible return value found by typecheck And make it bit more apparent how is `RCSResults._fidelities_lin` set. Fixes mypy recirq/random_circuit_sampling | grep random_circuit --- recirq/random_circuit_sampling/rcs_experiment.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/recirq/random_circuit_sampling/rcs_experiment.py b/recirq/random_circuit_sampling/rcs_experiment.py index da58d092..17a60d05 100644 --- a/recirq/random_circuit_sampling/rcs_experiment.py +++ b/recirq/random_circuit_sampling/rcs_experiment.py @@ -339,7 +339,7 @@ def __init__( self.measurements = measurements self.metadata = metadata self.characterization = characterization - self._fidelities_lin: Optional[Dict] = None + self._fidelities_lin: Optional[Dict[Tuple[int, int], float]] = None def fidelities_lin(self, simulator: cirq.SimulatesAmplitudes = cirq.Simulator()) -> Dict: """Accesses or calculates Linear XEB fidelities. @@ -349,10 +349,10 @@ def fidelities_lin(self, simulator: cirq.SimulatesAmplitudes = cirq.Simulator()) cirq.Simulator(). """ if self._fidelities_lin is None: - self._analyze(simulator=simulator) + self._fidelities_lin = self._analyze(simulator=simulator) return self._fidelities_lin - def _analyze(self, simulator: cirq.SimulatesAmplitudes) -> None: + def _analyze(self, simulator: cirq.SimulatesAmplitudes) -> Dict[Tuple[int, int], float]: """Calculates linear xeb by comparing measured counts against ideal probabilities. Formula used for linear XEB: F = dimensions * E[P_ideal] - 1 @@ -387,5 +387,4 @@ def _analyze(self, simulator: cirq.SimulatesAmplitudes) -> None: linear_fidelity = dim * (measured_probs @ ideal_probs) - 1 fidelities[(patch_idx, depth)].append(linear_fidelity) - self._fidelities_lin = dict(fidelities) - + return dict(fidelities) From fbb390bcce005a1cad9b6b6987c163455c8d2810 Mon Sep 17 00:00:00 2001 From: Shashwat Kumar <46276730+shashwatk1998@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:15:30 -0800 Subject: [PATCH 07/11] Update recirq/random_circuit_sampling/rcs_experiment.py Co-authored-by: Pavol Juhas --- recirq/random_circuit_sampling/rcs_experiment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/recirq/random_circuit_sampling/rcs_experiment.py b/recirq/random_circuit_sampling/rcs_experiment.py index 17a60d05..f82fc4dc 100644 --- a/recirq/random_circuit_sampling/rcs_experiment.py +++ b/recirq/random_circuit_sampling/rcs_experiment.py @@ -62,8 +62,8 @@ def characterize_pairs( sampler: The cirq.Sampler used for data collection. qubits: All qubits involved in the characterization. gate: The baseline ideal gate to calibrate against, e.g., cirq_google.SYC - theta, phi, zeta, chi, gamma: Toggles for parameters to characterize. Defaults to True - for all. + theta, phi, zeta, chi, gamma: Toggles for parameters to characterize. + Defaults to True for all. Returns: A dictionary mapping qubit pairs to their calibrated PhasedFSimGate. From 5eee62ef91e3fb91c1b8024331c4ded260c7b9b7 Mon Sep 17 00:00:00 2001 From: Shashwat Kumar <46276730+shashwatk1998@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:15:43 -0800 Subject: [PATCH 08/11] Update recirq/random_circuit_sampling/rcs_experiment.py Co-authored-by: Pavol Juhas --- recirq/random_circuit_sampling/rcs_experiment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/recirq/random_circuit_sampling/rcs_experiment.py b/recirq/random_circuit_sampling/rcs_experiment.py index f82fc4dc..2af6cfbb 100644 --- a/recirq/random_circuit_sampling/rcs_experiment.py +++ b/recirq/random_circuit_sampling/rcs_experiment.py @@ -243,8 +243,8 @@ def run( sampler: Sampler to execute circuits. n_repetitions: Shots per circuit. characterize: If True, performs gate characterization before analysis. - theta, phi, zeta, chi, gamma: Toggles for characterization angles. Default to True - for all. + theta, phi, zeta, chi, gamma: Toggles for characterization angles. + Default to True for all. Returns: An RCSResults object. From d49d4652b110886b9018b1c010f27696851b92ae Mon Sep 17 00:00:00 2001 From: Shashwat Kumar <46276730+shashwatk1998@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:16:26 -0800 Subject: [PATCH 09/11] Update recirq/random_circuit_sampling/rcs_experiment.py Co-authored-by: Pavol Juhas --- recirq/random_circuit_sampling/rcs_experiment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recirq/random_circuit_sampling/rcs_experiment.py b/recirq/random_circuit_sampling/rcs_experiment.py index 2af6cfbb..0a48d4c5 100644 --- a/recirq/random_circuit_sampling/rcs_experiment.py +++ b/recirq/random_circuit_sampling/rcs_experiment.py @@ -304,7 +304,7 @@ def run( batch_results = sampler.run_batch(zipped_circuits, repetitions=n_repetitions) measurements_flat = [] - for i, combined_result in enumerate(batch_results): + for combined_result in batch_results: for patch_idx in range(len(self.patches)): key = f"m_{patch_idx}" patch_data = combined_result[0].measurements[key] From d7cb15d7f734262da915e93ce108a0ef68208c3e Mon Sep 17 00:00:00 2001 From: Shashwat Kumar Date: Fri, 6 Mar 2026 22:04:32 +0000 Subject: [PATCH 10/11] addressed Alexis's comments --- .../random_circuit_sampling/rcs_experiment.py | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/recirq/random_circuit_sampling/rcs_experiment.py b/recirq/random_circuit_sampling/rcs_experiment.py index 0a48d4c5..0715e577 100644 --- a/recirq/random_circuit_sampling/rcs_experiment.py +++ b/recirq/random_circuit_sampling/rcs_experiment.py @@ -33,7 +33,7 @@ import random from collections import defaultdict -from typing import Dict, Tuple, List, Callable, Optional +from typing import Dict, Tuple, List, Callable, Optional, Literal import cirq import cirq_google @@ -58,6 +58,10 @@ def characterize_pairs( ) -> Dict[Tuple[cirq.Qid, cirq.Qid], cirq.PhasedFSimGate]: """Performs XEB characterization for PhasedFSimGate angles. + This characterization involves running XEB circuits to extract the + specific unitary parameters (theta, phi, zeta, chi, gamma) for the + PhasedFSim gates on each qubit pair. + Args: sampler: The cirq.Sampler used for data collection. qubits: All qubits involved in the characterization. @@ -167,14 +171,24 @@ def make_rcs_circuit( class RCSExperiment: - """Manages generation and parallel execution of RCS experiments.""" + """Manages generation and parallel execution of RCS experiments. + + This experiment accepts a list of different disjoint subgrids known as + patches and execute random circuits on them simultaneously. Qubit + interactions are defined by a tiling 'pattern' as defined in Section + VII C of https://arxiv.org/pdf/1910.11333: + + - 'staggered': The 8-cycle ABCDCDAB sequence. + - 'half': A 4-cycle ABCD staggered sequence. + - 'aligned': A 4-cycle EFGH non-staggered sequence. + """ def __init__( self, patches: List[List[cirq.GridQubit]], depths: List[int], num_instances: int, - pattern_name: str = "staggered", + pattern_name: Literal["staggered", "half", "aligned"] = "staggered", single_qubit_gates: Optional[Tuple[cirq.Gate, ...]] = None, two_qubit_gate: cirq.Gate = cirq_google.SYC, seed: Optional[int] = None @@ -321,6 +335,15 @@ def run( class RCSResults: """Calculates Linear Cross-Entropy Benchmarking (XEB) fidelity. + This class provides tools to estimate circuit fidelity by comparing + experimental bitstring distributions with ideal probabilities. It uses + the Linear XEB estimator, which is statistically optimal for + low-fidelity regimes observed on noisy quantum processors. + + Logarithmic XEB is an alternative estimator that provides lower + statistical variance in high-fidelity regimes. For a detailed comparison + of these estimators, see Section IV of https://arxiv.org/pdf/1910.11333. + Attributes: circuits: Unzipped individual circuits for the measurements. measurements: Measured bitstrings per circuit. From 541f3496561d02321e453ee1f0a2a4eba6206e82 Mon Sep 17 00:00:00 2001 From: Shashwat Kumar Date: Mon, 30 Mar 2026 22:31:51 +0000 Subject: [PATCH 11/11] docs: add demonstration notebook for RCS and XEB analysis --- .../rcs_experiment_demonstration.ipynb | 226 ++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 recirq/random_circuit_sampling/rcs_experiment_demonstration.ipynb diff --git a/recirq/random_circuit_sampling/rcs_experiment_demonstration.ipynb b/recirq/random_circuit_sampling/rcs_experiment_demonstration.ipynb new file mode 100644 index 00000000..f6234264 --- /dev/null +++ b/recirq/random_circuit_sampling/rcs_experiment_demonstration.ipynb @@ -0,0 +1,226 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Parallel Random Circuit Sampling (RCS) & XEB Analysis\n", + "\n", + "This notebook demonstrates the `RCSExperiment` and `RCSResults` framework for generating, executing, and analyzing Random Circuit Sampling experiments." + ], + "metadata": { + "id": "TKUuvR37up68" + } + }, + { + "cell_type": "markdown", + "source": [ + "#Imports" + ], + "metadata": { + "id": "RMz52FOW-8DO" + } + }, + { + "cell_type": "code", + "source": [ + "import cirq\n", + "import cirq_google\n", + "import qsimcirq\n", + "import recirq.random_circuit_sampling as rcs\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np" + ], + "metadata": { + "id": "dKWboTyH-ssA" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Define the patches\n", + "\n", + "Select sets of connected qubits to serve as patches. These patches must be disjoint and should not share any common qubits." + ], + "metadata": { + "id": "tGtt-E53Acam" + } + }, + { + "cell_type": "code", + "source": [ + "patch_1 = cirq.GridQubit.rect(4, 3, top=0, left=0)\n", + "patch_2 = cirq.GridQubit.rect(4, 3, top=0, left=3)\n", + "patch_3 = cirq.GridQubit.rect(4, 3, top=4, left=0)\n", + "patches = [patch_1, patch_2, patch_3]" + ], + "metadata": { + "id": "kK-PGBsNAcAT" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Set up and run the experiment\n", + "\n", + "Specify the qubit patches and the desired circuit depths for XEB calculation. Additionally, define the number of random circuit instances per configuration and the specific tiling pattern for the experiment. While the framework supports characterizing the $fSim$ gate parameters ($\\theta, \\phi, \\zeta, \\chi, \\gamma$), this step is bypassed here as we are performing an ideal noiseless simulation." + ], + "metadata": { + "id": "c0zvsbl9AnSx" + } + }, + { + "cell_type": "code", + "source": [ + "experiment = rcs.RCSExperiment(\n", + " patches=patches,\n", + " depths=[30, 50, 70, 90],\n", + " num_instances=3,\n", + " pattern_name=\"staggered\",\n", + " seed= 2026\n", + ")" + ], + "metadata": { + "id": "YCL7PrIoAfmW" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "results = experiment.run(sampler=cirq.Simulator(), n_repetitions=10000, characterize=False)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "0yCdLPwQAqut", + "outputId": "427cc8d3-be63-4a56-a391-d434b0c69422" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Running 12 zipped instances on 3 parallel patches...\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Calculate linear XEB fidelities\n", + "\n", + "To calculate fidelities via noiseless simulation, we can employ the standard `cirq.Simulator()`. For experiments involving larger system sizes, the more performance-optimized `qsimcirq.QSimSimulator()` can be used to improve execution efficiency." + ], + "metadata": { + "id": "sgg6y0xU2hZJ" + } + }, + { + "cell_type": "code", + "source": [ + "fidelities = results.fidelities_lin(simulator=qsimcirq.QSimSimulator())" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "lIKch40S1fX9", + "outputId": "898ba6d2-f0aa-4995-8dc0-fe14ab4397f1" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stderr", + "text": [ + "XEB Analysis: 100%|████████████████████████| 36/36 [00:01<00:00, 21.14it/s]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Plot the results\n", + "\n", + "Visualize the XEB results for each patch across the specified circuit depths. The plot reflects the mean fidelity calculated across all random circuit instances for each (patch, depth) configuration." + ], + "metadata": { + "id": "OGrm6xgBAxR4" + } + }, + { + "cell_type": "code", + "source": [ + "fig, ax = plt.subplots(figsize=(8, 6))\n", + "for i in range(len(patches)):\n", + " x = experiment.depths\n", + " y = [np.mean(fidelities[(i, d)]) for d in x]\n", + " ax.plot(x, y, marker='o', markersize=8, linewidth=2, label=f'Patch {i}')\n", + "\n", + "ax.set_ylim(-0.1, 1.1)\n", + "ax.set_xlim(min(experiment.depths) - 2, max(experiment.depths) + 2)\n", + "ax.set_xlabel('Cycle Depth', fontsize=14)\n", + "ax.set_ylabel('Linear XEB Fidelity', fontsize=14)\n", + "ax.set_title('Parallel RCS', fontsize=16, pad=20)\n", + "ax.legend(frameon=False, fontsize=12)\n", + "ax.tick_params(\n", + " axis='both',\n", + " direction='in',\n", + " top=True,\n", + " right=True,\n", + " labelsize=12,\n", + " width=1.2,\n", + " length=6\n", + ")\n", + "for spine in ax.spines.values():\n", + " spine.set_linewidth(1.2)\n", + "plt.tight_layout()\n", + "plt.show()" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 607 + }, + "id": "iJpv_xBQqUwy", + "outputId": "0f0c67e4-b270-4c46-9017-dc22d2d3ea28" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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JN8lW4vKl19+GfReG926ddOCBQsUFRVVZO8lSXFxcfr444+1bds2/e9//9P69ev1/fffa9++fdqxY4eefvppzZs3T2vWrFGlSpWK9L0BoLgRLAAggmW9j0XmQuZrrrlGnTp1ks127q/wnTt3qmfPntq2bVuux8pcNxDMpZdemuu2b7/9Vn379tXevXsLdexQZE7t2rVrV9COflZHjhwJ672yynofi5SUFG3ZskU7duzQ0qVLNWrUKE2cONFft2rVqrJYLPJ6vTp8+HBY79ukSRM1adLE//rnn3/W9OnTNW3aNG3ZskX/93//l+MytwAQ6QgWABDBst/HIje9evXStm3bdPvtt2vEiBG68sorFRsbK7vdrvT09HwXGud2edOUlBT16NFDhw4d0v33369HHnlEDRo0UGxsrKxWq3bs2KFGjRqFvXg7c4rRRRddpNtuuy3PutWqVQvrvbIKdh+L//73vxo2bJief/553XjjjerSpYskyWazqVmzZkpMTNTGjRt1zz33FFk7rrjiCv33v/+VxWLR1KlTtXjxYoIFgDKHYAEAZdz27dv1ww8/qEaNGvroo48CRjIk32hGYX311Vc6dOiQWrZsqbfffjvH9nCOnVWdOnUk+UYFSvuKSY8++qg2bNigd955R48//rg6duzo/53ecccdSkxM1AcffKBJkyYV+MpQ+enYsaOmTp2qo0ePFulxAaAksHgbAMq448ePS/Itps4eKiTpnXfeCfvYmZdKLcpjZ3X11VerWrVq+umnn/KczlVSJk6cKKfTqV9++UVz5871lz/66KOqWLGiDh8+rKeeeirf46xdu9b/cyijOpnTzbhCFICyiGABAGVcw4YNZbVa9eOPP2rNmjUB25YuXaopU6YU+thXXHGFJN9N63766aeAbW+88YY++OCDQh87K7vdrtGjR8s0TfXs2VPr1q3LUcfj8eiLL77Qt99+WyTvmZdatWrp0UcflSSNGzdObrdbkm9EZc6cObJYLHr55Zc1ePDgoOst9u3bp6FDh6pHjx7+sqVLl6pHjx767LPP5PF4cuyzZs0a/5Wx+vXrV/QfCgCKGcECAMq4atWqaejQofJ4PLr55pvVvn173X333WrVqpW6d++e670rQtGiRQvdcccdOnXqlFq0aKHbbrtN/fv31xVXXKGHH35Y//znP4vscwwdOlRPPvmkdu7c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SJLNZlO1atXkdDqLtd0AAADAhSzigsXmzZvVsGFDxcbGBpS3bt1akpSYmJjrvu3bt9e2bds0atQo/frrr9q1a5f+9a9/adOmTRoxYkRxNhsAAAC4oNlKuwHZHThwQHFxcTnKM8v279+f676jRo3S7t27NX78eI0bN06SFBMTo4ULF+qOO+7I83137drlH+oJhmlSAAAAOB/l1QfetWuX6tevH9JxIm7EIjU1NWAqU6bo6Gj/9tw4HA41bNhQvXr10rx58/TOO+/oqquu0sCBA/Xtt98WW5sBAACAC13EjVg4nU65XK4c5Wlpaf7tuRk6dKi+/fZbff/997JYfJmpT58+atKkif7+978rISEh133r16/PqAQAAAAuOHn1gfMazcgu4kYs4uLidODAgRzlmWW1atUKul96erpmzJihrl27+kOFJNntdnXu3FmbNm1Senp68TQaAAAAuMBFXLBo3ry5duzYoeTk5IDyzNGG5s2bB93v2LFjcrvd8ng8ObZlZGTI6/UG3QYAAAAgfBEXLHr16iWPx6M33njDX+ZyuTRz5ky1adNGderUkSTt3btX27dv99epUaOGKlWqpI8++ihgZOL06dNaunSpGjduzCVnAQAAgGIScWss2rRpo969e+uZZ57R4cOH1aBBA82ePVt79uzRjBkz/PUGDRqkL7/8Upn397NarRo+fLhGjhypa665RoMGDZLH49GMGTP0559/6p133imtjwQAAACc9yIuWEjSnDlzNGrUKM2dO1cnTpxQs2bNtGzZMrVr1y7P/f7v//5P9erV08svv6znnntOLpdLzZo104IFC3TXXXeVUOsBAACAC49hZn7lfwEryK3KAQAAgAtFQfrJEbfGAgAAAEDZQ7AAAAAAEDaCBQAAAICwESwAAAAAhI1gAQAAACBsBAsAAAAAYSNYAAAAAAgbwQIAAABA2AgWAAAAAMJGsAAAAAAQNoIFAAAAgLARLAAAAACEjWABAAAAIGwECwAAAABhI1gAAAAACBvBAgAAAEDYCBYAAAAAwkawAAAAABA2ggUAAACAsBEsAAAAAISNYAEAAAAgbAQLAAAAAGErVLBISEgo6nYAAAAAKMMKFSyuvfZa/eUvf9Err7yipKSkIm4SAAAAgLKmUMFi4MCB+vXXXzVs2DDVqlVLgwYN0tq1a4u6bQAAAADKiEIFizlz5mj//v3673//q8aNG+udd95R+/bt1bhxY7344os6evRoUbcTAAAAQAQr9OLtihUr6m9/+5u+//57bdq0SUOGDNGhQ4f05JNP6uKLL1bfvn21atWqomwrAAAAgAhVJFeFatmypV599VXt379fs2bNUrVq1bRgwQLddtttuuyyy/T888/r1KlTRfFWAAAAACJQkV1u9sSJE3rjjTc0adIk7d+/X5J0/fXX69SpU3r66afVqFEjbdy4sajeDgAAAEAECTtYrF69Wnfffbdq166tf/zjHzp8+LCefPJJ7dy5U1999ZX+/PNPTZs2TadOndKjjz5aFG0GAAAAEGFshdnp0KFDmjlzpmbMmKHffvtNpmnqxhtv1MMPP6w777xTdrvdX9fhcOiRRx7Rr7/+qmnTphVZwwEAAABEjkIFi4svvlher1eVK1fWY489piFDhqhRo0Z57lO9enWlp6cXqpEAAAAAIpthmqZZ0J1uuOEGPfzww+rdu7ccDkdxtKtENWnSRJK0bdu2Um4JAAAAEDkK0k8u1IjFunXrCrMbAAAAgPNUoRZvW61W/etf/8qzzvjx42WzFSq3AAAAAChjChUsTNNUKDOoCjHLCgAAAEAZVGT3scjuyJEjcjqdxXV4AAAAABEk5LlKc+bMCXidmJiYo0ySPB6P/vjjD82ZM0fx8fHhtxAAAABAxAv5qlAWi0WGYeRbL/NwTqdTCxcuVKdOncJrYQngqlAAAABATsVyVaiZM2dK8gWHBx54QD169NAdd9yRo57ValWVKlV07bXXqnLlyqEeHgAAAEAZFnKwuPfee/0/f/nll+rZs6e6d+9eLI0CAAAAULYU6nqwmaMXAAAAACAV41WhAAAAAFw4QgoWFotFNptNO3bs8L+2Wq35PrhBHgAAAHBhCKnn365dOxmGoZiYmIDXAAAAACCFGCzWrFmT52sAAAAAFzbWWAAAAAAIG8ECAAAAQNhCmgo1duzYQh3cMAyNGjWqUPsCAAAAKDsM0zTN/CpZLIUb2DAMQx6Pp1D7lqSC3KocAAAAuFAUpJ8c0ojF6tWrw2sRAAAAgPNaSMHixhtvLO52AAAAACjDWLwNAAAAIGxhBYuPPvpIffr0UbNmzdSgQQN/+fbt2/X8889r3759YTcQAAAAQOQLaSpUdl6vV/3799eCBQskSU6nU6mpqf7tlStX1v/93//J4/HomWeeKZqWAgAAAIhYhRqxmDJliubPn6+HHnpIJ06c0PDhwwO216xZU23bttXy5cuLpJEAAAAAIluhgsWsWbN09dVXa/r06YqNjZVhGDnqNGjQQLt37w67gQAAAAAiX6GCxa+//qq2bdvmWadq1ao6duxYoRoFAAAAoGwpVLBwOp06efJknnV+//13VapUqTCHBwAAAFDGFCpYtGjRQitWrFBaWlrQ7cePH9enn36qa665JqzGAQAAACgbChUshg0bpj///FN33XWX/vzzz4Btu3btUs+ePXXy5EkNGzasSBoJAAAAILIV6nKzd9xxh5566ilNnDhRl1xyicqVKydJqlGjho4dOybTNDVq1Ch16NChSBsLAAAAIDIV+gZ5//73v7VixQrdfvvtiomJkdVqldfrVadOnfTJJ5/oueeeK8p2AgAAAIhghRqxyHTrrbfq1ltvLaq2AAAAACijCj1iAQAAAACZQhqx2Lt3b6HfoG7duoXeFwAAAEDZEFKwuPTSS4PeXTs/hmHI7XYXeD+Xy6Vnn31Wc+fO1YkTJ9SsWTONGzcu5GlXH3zwgV566SX98MMPstvtuvLKKzVu3DgWkwMAAADFJKRgMWjQoBzB4rffftPatWtVqVIlNW/eXDVr1tShQ4eUmJiopKQktW3bVpdddlmhGnXfffdpwYIFeuyxx3T55Zdr1qxZ6tKli1avXq0bbrghz33HjBmjsWPHqlevXrrvvvuUkZGhrVu3at++fYVqCwAAAID8GaZpmgXdadu2bbr++us1dOhQPfPMM/7LzUrSmTNnNH78eL366qv6+uuvdeWVVxbo2Bs2bFCbNm00adIkDR8+XJKUlpam+Ph41ahRQ998802u+3777be67rrr9OKLL+of//hHyO/ZpEkT/+cCAAAA4FOQfnKhgkXXrl2VkZGhlStX5lrn1ltvVXR0tJYuXVqgY48YMUKTJ0/W8ePHFRsb6y//97//rX/+85/au3ev6tSpE3Tffv366auvvtKff/4pwzB05swZlS9fPt/3JFgAAAAAORWkn1yoq0J9/fXXat26dZ51WrdurbVr1xb42Js3b1bDhg0DQkXm8SQpMTEx130///xzXX311Zo6daqqV6+uChUqKC4uTq+88kqB2wEAAAAgdIW6j4XX69Wvv/6aZ52dO3eqEIMhOnDggOLi4nKUZ5bt378/6H4n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MTExz2Cxd+9e/ec//9Hbb78tp9NZbO0EAAAAcE6hgkVxOnDggOLi4nKUZ5bt378/z/2feOIJtWjRQv369SvQ++7atcs/1BMM06QAAABwPsqrD7xr1y7Vr18/pONEXLBITU2Vw+HIUR4dHe3fnpvVq1dr4cKFSkhIKLb2AQAAAMip0MHC4/Howw8/1KpVq7R//365XK4cdQzD0Oeff16g4zqdzqDHSktL828Pxu12a9iwYbrnnnt09dVXF+g9Jal+/fqMSgAAAOCCk1cfOK/RjOwKFSzOnDmjjh076ttvv/VfGSrrxaUyXxdmgXdcXJz27duXo/zAgQOSpFq1agXdb86cOfrll1/0+uuva8+ePQHbTp06pT179qhGjRqKiYkpcJsAAAAA5K1Q97EYN26c1q9fr+eee05Hjx6VaZoaM2aMDhw4oA8++ECXXXaZevfuHXTkIT/NmzfXjh07lJycHFCeOb2pefPmQffbu3evMjIydP3116tevXr+h+QLHfXq1dPKlSsL3B4AAAAA+StUsFi0aJGuueYajRw5UlWqVPGX16xZU71799bq1au1atUqTZo0qcDH7tWrlzwej9544w1/mcvl0syZM9WmTRv/FaH27t2r7du3++v069dPH330UY6HJHXp0kUfffSR2rRpU5iPCwAAACAfhZoKtXfvXnXt2tX/2mKxBIxOXHzxxeratatmz56tZ555pkDHbtOmjXr37q1nnnlGhw8fVoMGDTR79mzt2bNHM2bM8NcbNGiQvvzyS/8UrMaNG6tx48ZBj1mvXj316NGjQO0AAAAAELpCBYty5crJYjk32FGxYkX/GohMF110kfbu3VuoRs2ZM0ejRo3S3LlzdeLECTVr1kzLli1Tu3btCnU8AAAAAMWrUMHikksuCQgN8fHx+uKLL+RyueRwOGSapj7//POg96MIRXR0tCZNmpTnVKpQb9KXdVE5AAAAgOJRqDUWN998s1avXi232y1Juvfee7V3715de+21evLJJ3XDDTcoMTFRd911V5E2FgAAAEBkKtSIxV//+ldVrVpVR44cUVxcnB544AFt3rxZ06dPV2JioiTprrvu0pgxY4qwqQAAAAAilWEW4VyhI0eO6LffftMll1yiiy66qKgOW+wyb/zBDfIAAACAcwrSTy70nbeDqV69uqpXr16UhwQAAABQBoQVLA4ePKhF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yjbOX9nFuSvbOH9lWyScP4IFAAAAgLARLAAAAACEjWABAAAAIGwECwAAAABhI1gAAAAACBvBAgAAAEDYDNM0zdJuRGmrUKGCMjIyVL9+/SI97q5duySpyI+LksH5K7s4d2Ub56/s4tyVbZy/sq24zt+uXbtkt9t16tSpfOsSLCRddNFFOnPmjOrWrVvaTQEAAAAixt69e1WuXDkdPHgw37oECwAAAABhY40FAAAAgLARLAAAAACEjWABAAAAIGwEizBs27ZNvXv31mWXXaaYmBhVq1ZN7dq109KlS3PU/fnnn9WpUyeVL19eVapU0T333KMjR46UQquRm/Hjx8swDMXHx+fY9s033+iGG25QTEyMLrroIg0bNkynT58uhVZCktasWSPDMII+vv3224C6nLvI9f3336t79+6qUqWKYmJiFB8fr6lTpwbU4fxFlvvuuy/XP3uGYWjfvn3+upy7yLRz507169dPF198sWJiYtS4cWONHTtWKSkpAfU4f5Hnu+++U6dOnRQbG6sKFSqoY8eOSkxMDFq3tM6frdjf4Tz2+++/69SpU7r33ntVq1YtpaSkaOHCherevbtef/11DRkyRJL0559/ql27dqpYsaImTJig06dP64UXXtCPP/6oDRs2KCoqqpQ/Cf78809NmDBB5cqVy7EtMTFRN998s6644gpNnjxZf/75p1544QXt3LlTn3zySSm0FpmGDRumq6++OqCsQYMG/p85d5Fr5cqV6tatm1q0aKFRo0apfPny2rVrl/78809/Hc5f5HnooYd0yy23BJSZpqmHH35Yl156qWrXri2Jcxep/vjjD7Vu3VoVK1bU0KFDVaVKFa1fv16jR4/Wd999pyVLlkji/EWi77//XjfccIPq1Kmj0aNHy+v1avr06brxxhu1YcMGNWrUyF+3VM+fiSLldrvNv/zlL2ajRo38ZY888ojpdDrN33//3V/22WefmZLM119/vTSaiWz69u1rdujQwbzxxhvNJk2aBGzr3LmzGRcXZ548edJf9uabb5qSzBUrVpR0U2Ga5urVq01J5vz58/Osx7mLTCdPnjRr1qxp9uzZ0/R4PLnW4/yVDWvXrjUlmePHj/eXce4i0/jx401J5tatWwPKBw0aZEoyjx8/bpom5y8SdenSxaxcubJ59OhRf9n+/fvN8uXLm3feeWdA3dI8f0yFKmJW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/EWu//73v6Yk89NPPw26nXMXmb788kvTarWaNWrUMMeOHWtOmzbN7Ny5synJHDx4sL8e5y/yfPnll+bNN99sTpw40XzrrbfMwYMHm1ar1ezUqZOZkZERULc0zx/BIgzz5s0zb7nlFrNmzZqmzWYzK1eubN5yyy3mkiVLctTdunWr2bFjRzMmJsasVKmSOWDAAPPgwYOl0GrkJViwME3fnWWvu+46Mzo62qxevbr5t7/9LeCbAJSsl19+2WzdurVZpUoV02azmXFxcebAgQPNnTt35qjLuYtM6enp5pgxY8xLLrnEtNvtZoMGDcwpU6bkqMf5i0zXXHONWaNGDdPtdudah3MXmRISEszOnTubF110kWm3282GDRua48ePz9E55fxFll9//dXs2LGjWa1aNdPhcJiNGzc2//3vf5sulyto/dI6f4ZpmmbxTrYCAAAAcL5j8TYAAACAsBEsAAAAAISNYAEAAAAgbAQLAAAAAGEjWAAAAAAIG8ECAAAAQNgIFgAAAADCRrAAAAAAEDaCBQAAAICwESwAAIW2Zs0aGYahMWPGlHZTIk779u1lGEZpNwMASgzBAgDKqO+++04PPvigLr/8cpUrV05Op1P169fXPffco88++6y0m1ckxowZI8Mw/A+r1apKlSqpYcOG6t27t2bOnKkzZ86USttmzZolwzA0a9asUnl/AIg0ttJuAACgYLxer4YPH64pU6bIZrOpQ4cO6t69u+x2u3777TctX75c77zzjsaOHatRo0aVdnOLxF133aX4+HhJUnJysvbs2aM1a9ZowYIFevbZZzV37ly1b9++dBsJABc4ggUAlDEjR47UlClT1Lx5cy1YsED169cP2J6amqpXXnlFx44dK6UWFr1evXqpX79+AWUul0svvfSS/vnPf+r222/XN998o2bNmpVSCwEATIUCgDLk119/1fPPP6+qVavq008/zREqJMnpdOrJJ5/Uc889J0kaOHCgDMPQhg0bgh7z2WeflWEYmjdvXkD5li1bNGDAAF188cVyOByKi4tTp06dtHTp0pDaevjwYf3jH/9QgwYN5HA4VK1aNd11113aunVrAT91cA6HQ0899ZSeffZZnTlzRk8//XSOOqdOndLo0aPVpEkTOZ1OVapUSbfddpvWrVuXo27mmoi0tDQ9/fTTqlu3rqKjo3XFFVfov//9r0zT9Ne97777dP/990uS7r///oDpWtllZGRozJgxuvTSS+VwONSwYUNNnz69SH4HABBJGLEAgDJk1qxZ8ng8euihh1SzZs086zocDknSQw89pHfffVdvvfWWWrduHVDH4/Fo5syZqlq1qu68805/+cKFC3X33XfLNE1169ZNjRo10uHDh5WQkKAZM2aoW7dueb73rl271L59e/3555/q2LGjevToocOHD2vhwoVasWKFPv/8c7Vp06aQv4VATzzxhJ5//nmtWLFCJ0+eVMWKFSVJx48fV7t27bRt2zZdf/31evjhh5WcnKwlS5bopptu0vz589WjR48cx+vTp482b96su+66y/+7GDZsmPbs2aMXX3xRktSjRw8lJSVpyZIluuOOO9S8efNc29e/f39t2LBBnTt3ltVq1Ycffqi//e1vstvt+utf/1okvwMAiAgmAKDMaN++vSnJXLVqVYH2u/LKK80KFSqYp0+fDihftmyZKcl87LHH/GUHDx40y5UrZ5YrV878/vvvcxzrjz/+8P+8evVqU5I5evTogDrXXXedabVazU8//TSg/JdffjErVKhgNm3aNKR2jx492pRkzps3L896bdu2NSWZn3/+ub/s7rvvNiWZb775ZkDdQ4cOmXXq1DGrV69upqam+stvvPFGU5LZqFEjMykpyV+elJRkNmrUyDQMw9y4caO/fObMmaYkc+bMmUHblHm8Nm3amCdPnvSXb9++3bTZbGajRo1C+h0AQFnBVCgAKEMOHjwoSbr44osLtN9DDz2kU6dO6f333w8of+uttyQp4Jvz2bNn68yZM3riiSfUokWLHMfK7703b96sb775Rvfee69uu+22gG0NGzbUX//6V/34449FNiVKkmrVqiVJOnr0qP/5gw8+UIcOHTR48OCAujVq1NCTTz6pI0eOaNWqVTmONWrUKP+ohyRVrFhRI0eOlGmamj17doHb9u9//1uxsbH+140aNdL111+vX375RadOnSrw8QAgUjEVCgAuAIMGDdLTTz+tN998Uw8++KAk6dChQ1q2bJmuu+46XXnllf66mWsxOnbsWKj3+vbbb/3HD3Z/i+3bt/ufM6/0VNQ2btwoj8cjl8sVtA07d+70t+H2228P2Na2bdsc9TPLNm/eXOC2tGrVKkdZZjhLSkpShQoVCnxMAIhEBAsAKEMuuugibd++Xfv27VOjRo1C3q9SpUrq06ePZs+era1btyo+Pl6zZs2S2+3OMc//5MmTkqTatWsXqo3Hjx+XJC1fvlzLly/PtV5R3n9i//79kqTq1asHtOHrr7/W119/XaA2BFu7klmW+bspiKyjFZlsNt8/vx6Pp8DHA4BIxVQoAChDrr/+eknS559/XuB9H374YUnSm2++KUmaMWOGYmNj1adPn4B6lSpVkiTt27evUG3M7EhnXkkpt8e9995bqONnd/r0aX333XeyWq1q2bJlQBueeOKJPNswevToHMc7dOhQrmVZp0gBAAIRLACgDLnvvvtktVr1xhtv6MiRI3nWdblcAa+vueYaNWvWTO+8845WrlypnTt3asCAAYqJiQmol3nlqJUrVxaqjZlXe1q/fn2h9i+oF198USkpKercubO/43/11VfLMIxCtWHt2rW5lmVdc2K1WiUx6gAAmQgWAFCGNGjQQCNGjNDRo0fVuXNn7d69O0edtLQ0TZ48OejagoceekjHjx/334Mh2OVO7733XpUvX14vvviiEhMTc2zPbySjdevWatOmjebNm6cPPvggx3av16svv/wyz2OEwuVy6fnnn9fYsWNVvnx5/fvf//Zvu+iii9SnTx998803mjRpUsA9KDIlJCQoJSUlR/m//vWvgClPJ0+e1Lhx42QYRsAoS5UqVSRJf/zxR9ifBQDOB6yxAIAyZty4cUpLS9OUKVPUqFEjdejQQfHx8bLb7dq9e7dWrVqlY8eOady4cTn2HThwoEaMGKH9+/erVatWQa/6VKNGDc2ZM0f9+vVT69at1b17dzVq1EhHjx5VQkKCLr30Ui1evDjPNs6bN0833XST+vXrp5deekktW7aU0+nU3r17tX79eh05ckRpaWkhf+YFCxb4F32fPn1au3fv1ldffaWjR4+qTp06euedd3IsBJ8+fbp++eUXjRgxQnPnztW1116rSpUq6Y8//tCmTZu0c+dOHThwIMeITcOGDRUfHx9wH4s///xTjz/+uK666ip/vWuvvVZOp1MvvfSSTpw44V/fMXLkyJA/FwCcV0r+CrcAgKKwceNG84EHHjAbNGhgOp1O0+FwmJdeeql59913m5999lmu+w0cONCUZL722mt5Hn/z5s1mnz59zJo1a5p2u92Mi4szO3fubC5btsxfJ7f7WJimaR4/ftwcOXKkGR8fbzqdTrN8+fLm5Zdfbt59993mokWLQvqMmfexyHxYLBYzNjbWbNCggdmrVy9z5syZ5pkzZ3LdPyUlxXz++efNVq1ameXKlTOdTqdZr149s0ePHuacOXPMjIwMf93M+06kpqaaI0aMMOvUqWNGRUWZjRo1MqdOnWp6vd4cx1++fLl59dVXm06n09/G7McL5t577zUlmbt37w7p9wAAZYFhmkHGhwEA562mTZtq9+7d2r9/f9ArFl2o2rdvry+//DLotCkAQP5YYwEAF5BPPvlEW7du1YABAwgVAIAixRoLALgAvPrqq/rjjz/01ltvKTo6Wk8//XRpNwkAcJ4hWADABWDixIn6888/1ahRI7399tuqV69eaTcJAHCeYY0FAAAAgLCxxgIAAABA2AgWAAAAAMJGsAAAAAAQNoIFAAAAgLARLAAAAACEjWABAAAAIGwECwAAAABhI1gAAAAACBvBAgAAAEDY/j8UP1sX19qpFQAAAABJRU5ErkJggg==" + }, + "metadata": {} + } + ] + } + ] +} \ No newline at end of file