diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..6e291fd --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,38 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +type: software +title: "Stim Implementation of the [[16,4,4]] Tesseract Code" +abstract: "A complete Stim-based simulation of the Tesseract quantum error correction code with gate-level encoding, error correction rounds, and manual decoding." +authors: + - family-names: "Yakar" + given-names: "Iftach" + orcid: "https://orcid.org/0009-0003-8795-3794" +repository-code: "https://github.com/DeDuckProject/tesseract-code-stim" +url: "https://github.com/DeDuckProject/tesseract-code-stim" +doi: "10.5281/zenodo.17137851" +license: MIT +date-released: 2024-09-16 +version: "0.1.1" +keywords: + - "quantum error correction" + - "tesseract code" + - "stim" + - "quantum computing" + - "fault tolerance" +references: + - type: article + title: "Demonstration of quantum computation and error correction with a tesseract code" + authors: + - family-names: "Reichardt" + given-names: "Ben W." + year: 2024 + url: "https://arxiv.org/abs/2409.04628" + notes: "arXiv:2409.04628" + - type: software + title: "Stim: a fast stabilizer circuit simulator" + authors: + - family-names: "Gidney" + given-names: "Craig" + year: 2021 + url: "https://github.com/quantumlib/Stim" + doi: "10.22331/q-2021-07-06-497" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d4a7563 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Tesseract Code Stim Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index fd0bcda..9c36fa7 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,53 @@ -# Stim implementation of the [[16,4,4]] Tesseract Code +# Stim Implementation of the [[16,4,4]] Tesseract Code -This repository contains implementations and simulations of the Tesseract quantum error correction code [[1]](#references) using Stim [[3]](#references). +[![DOI](https://zenodo.org/badge/1014457478.svg)](https://doi.org/10.5281/zenodo.17137850) ## Overview -### Motivation -The 16-qubit tesseract subsystem color code offers a useful comprosmise between protection against errors and overhead. It encodes 4 logical qubits with a code rate of 1/4. By reducing 2 logical qubit from the original [[16,6,4]] code, this code achieves single-shot error correction, with only 2 ancilla qubits. This makes this code practical for current trapped-ion platforms. Recent experiments on Quantinuum hardware[[1]](#references) demonstrated preparing high-fidelity encoded graph states of up to 12 logical qubits, as well as protecting encoded states through 5 rounds of error correction. This repository reproduces these results in simulation, providing modular Stim circuits, noise modelling, and verification tests to support further research on low-overhead fault tolerance. +• **What it is**: A complete Stim-based [[3]](#references) simulation of the Tesseract quantum error correction code [[1]](#references) with gate-level encoding, error correction rounds, and manual decoding. + +• **Why it matters**: The [[16,4,4]] Tesseract subsystem color code offers practical fault tolerance for NISQ devices; encoding 4 logical qubits with single-shot error correction using only 2 ancilla qubits. This makes it viable for current trapped-ion platforms and a stepping stone to larger codes. + +• **What's implemented**: Full error correction pipeline including two encoding modes, configurable noise, stabilizer measurement rounds, Pauli frame tracking, and acceptance/fidelity analysis. + +• **Validation status**: Error correction improves fidelity as expected, but quantitative reproduction of paper results is work-in-progress. Acceptance rates show correct trend but offset (~1.0 vs ~0.85 baseline); logical error rates have correct shape but ~10× scaling difference. ![Figure 1: Tesseract code structure (from [1])](docs/images/fig1_tesseract_code.png) -### Project status -This codebase is an active work-in-progress. -• All building blocks (encoding, noise, measurement, decoder) are implemented. -• The end-to-end error-correction success rate is **not yet at the target level**. -Community testing, bug-fixes, and feature PRs are highly appreciated! +## Reproduce the Paper + +To regenerate the main results with paper parameters: + +```bash +python tesseract_sim/plotting/plot_acceptance_rates.py \ + --shots 10000 \ + --apply_pauli_frame true \ + --encoding-mode 9a \ + --ec-rate-1q 2.9e-5 \ + --ec-rate-2q 1.15e-3 \ + --meas-error-rate 1.47e-3 \ + --rounds 0 1 2 3 4 5 6 7 8 9 10 15 20 25 30 35 40 45 50 +``` + +**Requirements**: Python 3.8+, Stim 1.11+. Install via `pip install -r requirements.txt`. + +**Known differences from paper**: +- Depolarizing noise model (vs. experimental noise) +- Pauli frame correction applied post-measurement (vs. pre-measurement) +- No memory decoherence during idle periods +- Noiseless encoding to avoid preselection +- Only two logical Z measurements (due to |++0000⟩ encoding split into [[8,3,2]] codes) -**Disclaimer:** This repository is an independent, community-driven implementation. -It is **not** affiliated with Microsoft, Quantinuum, nor the authors of the original tesseract-code paper. +**Disclaimer**: Independent implementation, not affiliated with Microsoft/Quantinuum/original authors. ### Features -- Circuit implementation of the [[16,4,4]] Tesseract subsystem color code [[2]](#references) in Stim, including encoding, error correction rounds and final measurements. +- Circuit implementation of the [[16,4,4]] Tesseract subsystem color code [[1]](#references) in Stim, including encoding, error correction rounds and final measurements. - Simulation of an error correction experiment with configurable noise setting, rounds, shot and more. - Plotting: sweeping of different parameters and obtaining acceptance rate and logical success rate. ### Implementation details -In order to mimic the original paper's error correction, the different parts of the experiment are implemented in the gate level, and not using Stim's `MPP` stabilizer measurements, or detectors, for example. +In order to mimic the original paper's error correction, the different parts of the experiment are implemented at the gate level. The experiment implemented here is an error correction experiment based on Microsoft's paper[[1]](#references), and resembles the experimental setup shown in Figure 8 below. ![Figure 8: Error correction experiment (from [1])](docs/images/fig8_error_correction_experiment.png) diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..041be31 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,38 @@ +# Roadmap + +This document outlines the next major features and improvements planned for the Tesseract Code Stim implementation. + +## Upcoming Features + +### 1. Pre-measurement Pauli Frame Correction +**GitHub Issue**: [#6](https://github.com/DeDuckProject/tesseract-code-stim/issues/6) + +Currently, Pauli frame corrections are applied after measurement, which differs from the original paper's approach. This feature will: +- Modify the circuit flow to apply Pauli frame corrections before logical measurements +- Align the implementation more closely with the experimental procedure described in the paper +- Potentially improve quantitative reproduction of paper results + +### 2. Logical vs Unencoded Qubit Benchmarking + +Implement comprehensive benchmarking to compare the performance of encoded logical qubits against unencoded physical qubits under realistic conditions: +- Add memory decoherence modeling for idle qubits (currently missing) +- Implement realistic T1/T2 coherence times based on trapped-ion parameters +- Generate comparative plots showing logical vs physical qubit fidelity over time + +### 3. Alternative Noise Models + +Extend the `noise_cfg.py` API to support custom and alternative noise models beyond depolarizing noise: +- Implement amplitude damping and phase damping channels +- Add support for correlated noise models +- Enable user-defined custom noise channels via configuration +- Add noise model parameter sweeps for comparative analysis +- Support for experimental noise characterization data import + +## Contributing + +We welcome contributions to any of these roadmap items! Please: +1. Check existing GitHub issues for related discussions +2. Open a new issue to discuss your proposed implementation approach +3. Submit pull requests with tests and documentation + +For questions or suggestions about the roadmap, please open an issue with the `roadmap` label. diff --git a/plots/ec_experiment_3_H2_params/acceptance_rates_ec_experiment.png b/plots/ec_experiment_3_H2_params/acceptance_rates_ec_experiment.png new file mode 100644 index 0000000..0e772aa Binary files /dev/null and b/plots/ec_experiment_3_H2_params/acceptance_rates_ec_experiment.png differ diff --git a/plots/ec_experiment_3_H2_params/experiment_metadata.txt b/plots/ec_experiment_3_H2_params/experiment_metadata.txt new file mode 100644 index 0000000..1c6c1ba --- /dev/null +++ b/plots/ec_experiment_3_H2_params/experiment_metadata.txt @@ -0,0 +1,24 @@ +Tesseract EC Experiment Metadata +=================================== + +Timestamp: 2025-09-16 22:48:05 +Total runtime: 00:00:27.687 (27.687 seconds) + +Experiment Parameters: +-------------------- +Rounds: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 35, 40, 45, 50] +Noise levels: [0.00115] +Shots per data point: 10000 +Apply Pauli frame correction: True +Encoding mode: 9a +Sweep channel noise: False +Measurement error rate: 0.00147 +Mode: Fixed noise rates +EC 1Q rate (fixed): 2.9e-05 +EC 2Q rate (fixed): 0.00115 +Noise configuration: Sweeping EC/decoding noise + - EC noise applied: During error correction rounds and decoding + - EC 1Q rate: Swept parameter + - EC 2Q rate: Swept parameter (same as 1Q) + - Channel noise: None (0.0) + - Encoding: Noiseless diff --git a/plots/ec_experiment_3_H2_params/fidelity_rates_ec_experiment.png b/plots/ec_experiment_3_H2_params/fidelity_rates_ec_experiment.png new file mode 100644 index 0000000..2324f2a Binary files /dev/null and b/plots/ec_experiment_3_H2_params/fidelity_rates_ec_experiment.png differ diff --git a/plots/ec_experiment_3_H2_params/logical_rates_ec_experiment.png b/plots/ec_experiment_3_H2_params/logical_rates_ec_experiment.png new file mode 100644 index 0000000..4a5146a Binary files /dev/null and b/plots/ec_experiment_3_H2_params/logical_rates_ec_experiment.png differ diff --git a/plots/ec_experiment_20250916_161024_with_correction/acceptance_rates_ec_experiment.png b/plots/ec_experiment_with_correction/acceptance_rates_ec_experiment.png similarity index 100% rename from plots/ec_experiment_20250916_161024_with_correction/acceptance_rates_ec_experiment.png rename to plots/ec_experiment_with_correction/acceptance_rates_ec_experiment.png diff --git a/plots/ec_experiment_20250916_161024_with_correction/experiment_metadata.txt b/plots/ec_experiment_with_correction/experiment_metadata.txt similarity index 100% rename from plots/ec_experiment_20250916_161024_with_correction/experiment_metadata.txt rename to plots/ec_experiment_with_correction/experiment_metadata.txt diff --git a/plots/ec_experiment_20250916_161024_with_correction/fidelity_rates_ec_experiment.png b/plots/ec_experiment_with_correction/fidelity_rates_ec_experiment.png similarity index 100% rename from plots/ec_experiment_20250916_161024_with_correction/fidelity_rates_ec_experiment.png rename to plots/ec_experiment_with_correction/fidelity_rates_ec_experiment.png diff --git a/plots/ec_experiment_20250916_161024_with_correction/logical_rates_ec_experiment.png b/plots/ec_experiment_with_correction/logical_rates_ec_experiment.png similarity index 100% rename from plots/ec_experiment_20250916_161024_with_correction/logical_rates_ec_experiment.png rename to plots/ec_experiment_with_correction/logical_rates_ec_experiment.png diff --git a/plots/ec_experiment_20250916_165427_without_correction/acceptance_rates_ec_experiment.png b/plots/ec_experiment_without_correction/acceptance_rates_ec_experiment.png similarity index 100% rename from plots/ec_experiment_20250916_165427_without_correction/acceptance_rates_ec_experiment.png rename to plots/ec_experiment_without_correction/acceptance_rates_ec_experiment.png diff --git a/plots/ec_experiment_20250916_165427_without_correction/experiment_metadata.txt b/plots/ec_experiment_without_correction/experiment_metadata.txt similarity index 100% rename from plots/ec_experiment_20250916_165427_without_correction/experiment_metadata.txt rename to plots/ec_experiment_without_correction/experiment_metadata.txt diff --git a/plots/ec_experiment_20250916_165427_without_correction/fidelity_rates_ec_experiment.png b/plots/ec_experiment_without_correction/fidelity_rates_ec_experiment.png similarity index 100% rename from plots/ec_experiment_20250916_165427_without_correction/fidelity_rates_ec_experiment.png rename to plots/ec_experiment_without_correction/fidelity_rates_ec_experiment.png diff --git a/plots/ec_experiment_20250916_165427_without_correction/logical_rates_ec_experiment.png b/plots/ec_experiment_without_correction/logical_rates_ec_experiment.png similarity index 100% rename from plots/ec_experiment_20250916_165427_without_correction/logical_rates_ec_experiment.png rename to plots/ec_experiment_without_correction/logical_rates_ec_experiment.png diff --git a/tesseract_sim/error_correction/decoder_manual.py b/tesseract_sim/error_correction/decoder_manual.py index b13a3b4..f073573 100644 --- a/tesseract_sim/error_correction/decoder_manual.py +++ b/tesseract_sim/error_correction/decoder_manual.py @@ -1,11 +1,8 @@ -import stim import numpy as np -from tesseract_sim.error_correction.correction_rules import correct_row_Z, correct_row_X, correct_column_Z, correct_column_X -def append_detector_on_last_n_measurements(circuit, num_measurements=4): - circuit.append("DETECTOR", [ - stim.target_rec(-i) for i in range(1,num_measurements+1) - ]) +from tesseract_sim.error_correction.correction_rules import correct_row_Z, correct_row_X, correct_column_Z, \ + correct_column_X + def process_shot(shot_data, rounds, measurement_offset=0): """ diff --git a/tesseract_sim/noise/noise_cfg.py b/tesseract_sim/noise/noise_cfg.py index baca267..8549ac6 100644 --- a/tesseract_sim/noise/noise_cfg.py +++ b/tesseract_sim/noise/noise_cfg.py @@ -13,6 +13,8 @@ class NoiseCfg: op2: str = "DEPOLARIZE2" channel_noise_level: float = 0.0 # channel noise between encoding and EC channel_noise_type: str = "DEPOLARIZE1" + meas_active: bool = False # apply to measurements (SPAM error)? + meas_error_rate: float = 0.0 # measurement error rate # TODO add noise on 'meas' phase as well diff --git a/tesseract_sim/noise/noise_utils.py b/tesseract_sim/noise/noise_utils.py index 2be0f15..99218a7 100644 --- a/tesseract_sim/noise/noise_utils.py +++ b/tesseract_sim/noise/noise_utils.py @@ -8,7 +8,7 @@ def append_op( circuit: stim.Circuit, opname: str, targets: Sequence[int], - phase: Literal['enc', 'ec'], + phase: Literal['enc', 'ec', 'meas'], cfg: NoiseCfg = CURRENT_NOISE_CFG ): circuit.append(opname, targets) @@ -27,6 +27,10 @@ def append_op( active_noise = True rate_1q = cfg.ec_rate_1q rate_2q = cfg.ec_rate_2q + elif phase == 'meas' and cfg.meas_active: + active_noise = True + rate_1q = cfg.meas_error_rate + rate_2q = cfg.meas_error_rate # Use same rate for consistency if active_noise: if len(targets) == 1 and rate_1q > 0: @@ -34,8 +38,8 @@ def append_op( elif len(targets) > 1 and rate_2q > 0: circuit.append(op2, targets, rate_2q) -def append_1q(circuit: stim.Circuit, opname: str, target: int, phase: Literal['enc', 'ec'], cfg: NoiseCfg = CURRENT_NOISE_CFG): +def append_1q(circuit: stim.Circuit, opname: str, target: int, phase: Literal['enc', 'ec', 'meas'], cfg: NoiseCfg = CURRENT_NOISE_CFG): append_op(circuit, opname, [target], phase, cfg) -def append_2q(circuit: stim.Circuit, opname: str, target1: int, target2: int, phase: Literal['enc', 'ec'], cfg: NoiseCfg = CURRENT_NOISE_CFG): +def append_2q(circuit: stim.Circuit, opname: str, target1: int, target2: int, phase: Literal['enc', 'ec', 'meas'], cfg: NoiseCfg = CURRENT_NOISE_CFG): append_op(circuit, opname, [target1, target2], phase, cfg) \ No newline at end of file diff --git a/tesseract_sim/plotting/plot_acceptance_rates.py b/tesseract_sim/plotting/plot_acceptance_rates.py index b562016..0a7fedd 100644 --- a/tesseract_sim/plotting/plot_acceptance_rates.py +++ b/tesseract_sim/plotting/plot_acceptance_rates.py @@ -118,7 +118,11 @@ def write_experiment_metadata( apply_pauli_frame: bool, encoding_mode: str, sweep_channel_noise: bool, - runtime_seconds: float = None + runtime_seconds: float = None, + ec_rate_1q: float = None, + ec_rate_2q: float = None, + meas_error_rate: float = 0.0, + channel_noise_rate: float = None ) -> None: """Write experiment metadata to a text file.""" metadata_path = os.path.join(out_dir, "experiment_metadata.txt") @@ -142,6 +146,20 @@ def write_experiment_metadata( f.write(f"Apply Pauli frame correction: {apply_pauli_frame}\n") f.write(f"Encoding mode: {encoding_mode}\n") f.write(f"Sweep channel noise: {sweep_channel_noise}\n") + f.write(f"Measurement error rate: {meas_error_rate}\n") + + # Report if using fixed rates vs sweep + use_fixed_rates = (ec_rate_1q is not None and ec_rate_2q is not None) or channel_noise_rate is not None + if use_fixed_rates: + f.write(f"Mode: Fixed noise rates\n") + if ec_rate_1q is not None: + f.write(f"EC 1Q rate (fixed): {ec_rate_1q}\n") + if ec_rate_2q is not None: + f.write(f"EC 2Q rate (fixed): {ec_rate_2q}\n") + if channel_noise_rate is not None: + f.write(f"Channel noise rate (fixed): {channel_noise_rate}\n") + else: + f.write(f"Mode: Sweeping noise rates\n") if sweep_channel_noise: f.write(f"Noise configuration: Sweeping channel noise only\n") @@ -166,7 +184,11 @@ def plot_ec_experiment( base_out_dir: str, apply_pauli_frame: bool = True, encoding_mode: Literal['9a', '9b'] = '9b', - sweep_channel_noise: bool = False + sweep_channel_noise: bool = False, + ec_rate_1q: float = None, + ec_rate_2q: float = None, + meas_error_rate: float = 0.0, + channel_noise_rate: float = None ) -> None: """Plots both EC acceptance and logical check rates for the EC experiment.""" start_time = time.time() @@ -177,10 +199,52 @@ def plot_ec_experiment( os.makedirs(out_dir, exist_ok=True) # One sweep collecting full results - if sweep_channel_noise: - cfg_builder = lambda noise: NoiseCfg(ec_active=False, channel_noise_level=noise, channel_noise_type="DEPOLARIZE1") + # Determine if we're using fixed rates or sweeping + use_fixed_rates = (ec_rate_1q is not None and ec_rate_2q is not None) or channel_noise_rate is not None + + if use_fixed_rates: + # Use fixed rates - ignore noise_levels sweep + if sweep_channel_noise: + fixed_channel_rate = channel_noise_rate if channel_noise_rate is not None else 0.0 + cfg_builder = lambda _: NoiseCfg( + ec_active=False, + channel_noise_level=fixed_channel_rate, + channel_noise_type="DEPOLARIZE1", + meas_active=meas_error_rate > 0, + meas_error_rate=meas_error_rate + ) + noise_levels = [fixed_channel_rate] # Single point for fixed rate + else: + fixed_1q = ec_rate_1q if ec_rate_1q is not None else 0.0 + fixed_2q = ec_rate_2q if ec_rate_2q is not None else 0.0 + cfg_builder = lambda _: NoiseCfg( + ec_active=True, + ec_rate_1q=fixed_1q, + ec_rate_2q=fixed_2q, + channel_noise_level=0.0, + meas_active=meas_error_rate > 0, + meas_error_rate=meas_error_rate + ) + noise_levels = [max(fixed_1q, fixed_2q)] # Single point for plotting else: - cfg_builder = lambda noise: NoiseCfg(ec_active=True, ec_rate_1q=noise, ec_rate_2q=noise, channel_noise_level=0.0) + # Use original sweep behavior + if sweep_channel_noise: + cfg_builder = lambda noise: NoiseCfg( + ec_active=False, + channel_noise_level=noise, + channel_noise_type="DEPOLARIZE1", + meas_active=meas_error_rate > 0, + meas_error_rate=meas_error_rate + ) + else: + cfg_builder = lambda noise: NoiseCfg( + ec_active=True, + ec_rate_1q=noise, + ec_rate_2q=noise, + channel_noise_level=0.0, + meas_active=meas_error_rate > 0, + meas_error_rate=meas_error_rate + ) raw_results = sweep_results( run_simulation_ec_experiment, @@ -242,7 +306,9 @@ def plot_ec_experiment( write_experiment_metadata( out_dir, rounds, noise_levels, shots, apply_pauli_frame, encoding_mode, sweep_channel_noise, - runtime_seconds=runtime_seconds + runtime_seconds=runtime_seconds, + ec_rate_1q=ec_rate_1q, ec_rate_2q=ec_rate_2q, + meas_error_rate=meas_error_rate, channel_noise_rate=channel_noise_rate ) print(f"All experiment files saved to: {out_dir}") @@ -278,6 +344,14 @@ def main(): help=f'List of EC rounds to sweep (e.g. 1 10 20 30). Default: {default_rounds}') parser.add_argument('--noise-levels', type=float, nargs='+', default=default_noise_levels, help=f'List of noise rates to sweep (e.g. 0.05 0.1 0.2). Default: 10 points from 0.0 to 0.01') + parser.add_argument('--ec-rate-1q', type=float, default=None, + help='Single-qubit gate error rate for EC operations (overrides noise-levels sweep)') + parser.add_argument('--ec-rate-2q', type=float, default=None, + help='Two-qubit gate error rate for EC operations (overrides noise-levels sweep)') + parser.add_argument('--meas-error-rate', type=float, default=0.0, + help='Measurement error rate (SPAM error)') + parser.add_argument('--channel-noise-rate', type=float, default=None, + help='Channel noise rate (overrides noise-levels sweep when using --sweep-channel-noise)') args = parser.parse_args() # Use configurable values @@ -288,7 +362,11 @@ def main(): print(args.experiments) if 2 in args.experiments: - plot_ec_experiment(rounds, noise_levels, args.shots, args.out_dir, args.apply_pauli_frame, args.encoding_mode, args.sweep_channel_noise) + plot_ec_experiment( + rounds, noise_levels, args.shots, args.out_dir, + args.apply_pauli_frame, args.encoding_mode, args.sweep_channel_noise, + args.ec_rate_1q, args.ec_rate_2q, args.meas_error_rate, args.channel_noise_rate + ) if __name__ == "__main__": main() \ No newline at end of file