Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions CITATION.cff
Original file line number Diff line number Diff line change
@@ -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"
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -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.
48 changes: 35 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
38 changes: 38 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions plots/ec_experiment_3_H2_params/experiment_metadata.txt
Original file line number Diff line number Diff line change
@@ -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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 3 additions & 6 deletions tesseract_sim/error_correction/decoder_manual.py
Original file line number Diff line number Diff line change
@@ -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):
"""
Expand Down
2 changes: 2 additions & 0 deletions tesseract_sim/noise/noise_cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 7 additions & 3 deletions tesseract_sim/noise/noise_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -27,15 +27,19 @@ 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:
circuit.append(op1, targets, rate_1q)
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)
92 changes: 85 additions & 7 deletions tesseract_sim/plotting/plot_acceptance_rates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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()
Expand All @@ -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
)
Comment thread
DeDuckProject marked this conversation as resolved.

raw_results = sweep_results(
run_simulation_ec_experiment,
Expand Down Expand Up @@ -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}")
Expand Down Expand Up @@ -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
Expand All @@ -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()