Skip to content
Closed
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
13 changes: 8 additions & 5 deletions docs/user-guide/dem-from-guppy.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,13 @@ different DEM.
## Grouping Noise Parameters

Both Guppy DEM entry points accept either the existing flat noise keywords or
one `NoiseModel` containing the complete noise configuration. The forms below
are equivalent. Do not mix them in one call: even an explicitly passed flat
one `NoiseParameters` instance containing the complete noise configuration.
`NoiseParameters` is available from the `pecos` top level, and supports both
its original dataclass constructor and immutable `with_<field_name>` chaining.
The grouped and flat forms below are equivalent. Do not mix them in one call:
even an explicitly passed flat
default conflicts with `noise`. When `noise` is present, its defaults fully
replace the entry point's defaults; for example, `NoiseModel().p1` is `0.0`,
replace the entry point's defaults; for example, `NoiseParameters().p1` is `0.0`,
not the flat `p1=0.001` default.

<!--test-name: dem_from_guppy_grouped_noise-->
Expand All @@ -210,8 +213,8 @@ from guppylang import guppy
from guppylang.std.builtins import result
from guppylang.std.quantum import cx, measure, qubit

from pecos import NoiseParameters
from pecos.qec import DetectorErrorModel
from pecos.qec.surface import NoiseModel


@guppy
Expand All @@ -228,7 +231,7 @@ common = {
"observables_json": '[{"id": "L0", "result_tags": ["m1"]}]',
"seed": 0,
}
noise = NoiseModel(p1=0.002, p2=0.004, p_meas=0.006, p_prep=0.008)
noise = NoiseParameters().with_p1(0.002).with_p2(0.004).with_p_meas(0.006).with_p_prep(0.008)

grouped = DetectorErrorModel.from_guppy(noisy_pair, noise=noise, **common)
flat = DetectorErrorModel.from_guppy(
Expand Down
8 changes: 4 additions & 4 deletions docs/workflows/guppy-dem-decoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,19 +123,19 @@ model semantics.

`DetectorErrorModel.builder()` configures the run through chained setters and
returns the DEM together with the audit trail and the result-column evaluator
used in stage 4b. A `NoiseModel` carries the entire noise configuration as one
argument.
used in stage 4b. A `NoiseParameters` instance carries the entire noise
configuration as one argument.

The one-call forms `DetectorErrorModel.from_guppy(...)` and
`build_dem_from_guppy(...)` remain available and run this same pipeline; they
take the noise settings as individual keyword arguments instead.

<!--continuation-->
```python
from pecos import NoiseParameters
from pecos.qec import DetectorErrorModel
from pecos.qec.surface import NoiseModel

noise = NoiseModel(
noise = NoiseParameters(
p1=0.002,
p2=0.02,
p_meas=0.02,
Expand Down
8 changes: 4 additions & 4 deletions examples/surface/decoder_comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from pecos.qec.surface import NoiseModel
from pecos.qec.surface import NoiseParameters


@dataclass
Expand Down Expand Up @@ -69,7 +69,7 @@ class ComparisonPoint:
def _build_sampler(
distance: int,
num_rounds: int,
noise: NoiseModel,
noise: NoiseParameters,
basis: str,
circuit_source: str,
) -> tuple:
Expand Down Expand Up @@ -172,7 +172,7 @@ def run_comparison(
p_prep_scale: float,
) -> list[ComparisonPoint]:
"""Run the full comparison and return results."""
from pecos.qec.surface import NoiseModel
from pecos.qec.surface import NoiseParameters

points: list[ComparisonPoint] = []
total_configs = len(distances) * len(error_rates)
Expand All @@ -182,7 +182,7 @@ def run_comparison(
num_rounds = 2 * distance
for p in error_rates:
config_idx += 1
noise = NoiseModel(
noise = NoiseParameters(
p1=p * p1_scale,
p2=p,
p_meas=p * p_meas_scale,
Expand Down
4 changes: 2 additions & 2 deletions examples/surface/dem_decomposition_diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ def run_case(
pair_analysis_max_effects: int,
) -> CaseResult:
from pecos._traced_circuit import normalize_traced_tick_circuit
from pecos.qec.surface import NoiseModel, SurfacePatch, build_native_sampler
from pecos.qec.surface import NoiseParameters, SurfacePatch, build_native_sampler
from pecos.qec.surface.circuit_builder import (
generate_dem_from_tick_circuit_via_stim,
)
Expand All @@ -542,7 +542,7 @@ def run_case(
)

patch = SurfacePatch.create(distance=distance)
noise = NoiseModel(p1=p / 30.0, p2=p, p_meas=p / 3.0, p_prep=p / 3.0)
noise = NoiseParameters(p1=p / 30.0, p2=p, p_meas=p / 3.0, p_prep=p / 3.0)
noise_args = {
"p1": noise.p1,
"p1_gate_rates": SZZ_Z_FRAME_P1_GATE_RATES if interaction_basis == "szz" else None,
Expand Down
4 changes: 2 additions & 2 deletions examples/surface/dem_method_ler_comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,12 @@ def generate_dems(
Returns list of (method_name, raw_dem, decomposed_dem_or_None).
decomposed_dem is None when the method cannot produce a graphlike DEM.
"""
from pecos.qec.surface import NoiseModel
from pecos.qec.surface import NoiseParameters
from pecos.qec.surface.decode import generate_circuit_level_dem_from_builder

results = []

noise = NoiseModel(
noise = NoiseParameters(
p1=noise_params.get("p1", 0.0),
p2=noise_params.get("p2", 0.0),
p_meas=noise_params.get("p_meas", 0.0),
Expand Down
8 changes: 4 additions & 4 deletions examples/surface/generate_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from pecos.qec.surface import NoiseModel
from pecos.qec.surface import NoiseParameters

# -- Data model ---------------------------------------------------------------

Expand Down Expand Up @@ -97,7 +97,7 @@ def _decoder_base_name(name: str) -> str:
def _build_sampler(
distance: int,
num_rounds: int,
noise: NoiseModel,
noise: NoiseParameters,
basis: str,
circuit_source: str,
) -> tuple:
Expand Down Expand Up @@ -159,7 +159,7 @@ def generate(
duration_multipliers: list[float],
) -> DataShard:
"""Run the full data generation and return a shard."""
from pecos.qec.surface import NoiseModel
from pecos.qec.surface import NoiseParameters

config = {
"distances": distances,
Expand Down Expand Up @@ -200,7 +200,7 @@ def generate(

for d in distances:
for p in error_rates:
noise = NoiseModel(
noise = NoiseParameters(
p1=p * p1_scale,
p2=p,
p_meas=p * p_meas_scale,
Expand Down
4 changes: 2 additions & 2 deletions examples/surface/graphlike_dem_projection_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def build_case(
variants: list[str],
) -> BenchmarkResult:
from pecos._traced_circuit import normalize_traced_tick_circuit
from pecos.qec.surface import NoiseModel, SurfacePatch, build_native_sampler
from pecos.qec.surface import NoiseParameters, SurfacePatch, build_native_sampler
from pecos.qec.surface.circuit_builder import (
generate_dem_from_tick_circuit_via_stim,
)
Expand All @@ -118,7 +118,7 @@ def build_case(
)
setup_timings: list[TimedValue] = []
patch = SurfacePatch.create(distance=distance)
noise = NoiseModel(p1=p / 30.0, p2=p, p_meas=p / 3.0, p_prep=p / 3.0)
noise = NoiseParameters(p1=p / 30.0, p2=p, p_meas=p / 3.0, p_prep=p / 3.0)
noise_args = {
"p1": noise.p1,
"p1_gate_rates": SZZ_Z_FRAME_P1_GATE_RATES if interaction_basis == "szz" else None,
Expand Down
4 changes: 2 additions & 2 deletions examples/surface/ml_lookup_decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,10 @@ def main():
ler_lookup = errors_lookup / n

# Compare with pymatching
from pecos.qec.surface import NoiseModel
from pecos.qec.surface import NoiseParameters
from pecos.qec.surface.decode import generate_circuit_level_dem_from_builder

noise_obj = NoiseModel(
noise_obj = NoiseParameters(
p1=noise_params["p1"],
p2=noise_params["p2"],
p_meas=noise_params["p_meas"],
Expand Down
4 changes: 2 additions & 2 deletions examples/surface/native_dem_threshold_sweep.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,11 +669,11 @@ def _decoder_runtime(
p_prep_scale: float = 0.5,
) -> _DecoderRuntime:
"""Build and cache the expensive native decoder-side objects once."""
from pecos.qec.surface import NoiseModel, SurfaceDecoder
from pecos.qec.surface import NoiseParameters, SurfaceDecoder

basis = basis.upper()
patch = _surface_patch(distance)
noise = NoiseModel(
noise = NoiseParameters(
p1=physical_error_rate * p1_scale,
p2=physical_error_rate,
p_meas=physical_error_rate * p_meas_scale,
Expand Down
4 changes: 2 additions & 2 deletions examples/surface/szz_circuit_quality_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from dem_decomposition_diagnostics import compare_raw_dems, dem_stats
from pecos._traced_circuit import normalize_traced_tick_circuit
from pecos.qec.surface import NoiseModel, OpType, SurfacePatch, build_surface_code_circuit
from pecos.qec.surface import NoiseParameters, OpType, SurfacePatch, build_surface_code_circuit
from pecos.qec.surface.circuit_builder import (
_analyze_szz_forward_flow,
generate_dem_from_tick_circuit_via_stim,
Expand Down Expand Up @@ -315,7 +315,7 @@ def _dem_report(
p: float,
p1_ratio: float,
) -> DemReport:
noise = NoiseModel(p1=p / p1_ratio, p2=p, p_prep=p / 3.0, p_meas=p / 3.0)
noise = NoiseParameters(p1=p / p1_ratio, p2=p, p_prep=p / 3.0, p_meas=p / 3.0)
noise_args = {
"p1": noise.p1,
"p1_gate_rates": SZZ_Z_FRAME_P1_GATE_RATES if interaction_basis == "szz" else None,
Expand Down
6 changes: 3 additions & 3 deletions examples/surface_code_experiments.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"import numpy as np\n",
"from pecos.compilation_pipeline import compile_guppy_to_hugr\n",
"from pecos.guppy.surface import get_num_qubits, make_surface_code\n",
"from pecos.qec.surface import NoiseModel, SurfaceDecoder, SurfacePatch, plot_surface_code\n",
"from pecos.qec.surface import NoiseParameters, SurfaceDecoder, SurfacePatch, plot_surface_code\n",
"from selene_sim import DepolarizingErrorModel, IdealErrorModel, SimpleRuntime, Stim, build"
]
},
Expand Down Expand Up @@ -542,7 +542,7 @@
"\n",
" for p in ERROR_RATES:\n",
" error_model = DepolarizingErrorModel(p_1q=p, p_2q=p, p_meas=p, p_init=p)\n",
" noise = NoiseModel(p1=p, p2=p, p_meas=p, p_prep=p)\n",
" noise = NoiseParameters(p1=p, p2=p, p_meas=p, p_prep=p)\n",
"\n",
" # Simulate once, decode with all decoders\n",
" shots = run_shots(instance, nq, NUM_SHOTS, error_model)\n",
Expand Down Expand Up @@ -744,7 +744,7 @@
"\n",
" for p in ERROR_RATES:\n",
" error_model = DepolarizingErrorModel(p_1q=p, p_2q=p, p_meas=p, p_init=p)\n",
" noise = NoiseModel(p1=p, p2=p, p_meas=p, p_prep=p)\n",
" noise = NoiseParameters(p1=p, p2=p, p_meas=p, p_prep=p)\n",
"\n",
" shots = run_shots(instance, nq, NUM_SHOTS, error_model)\n",
"\n",
Expand Down
8 changes: 4 additions & 4 deletions examples/surface_code_noisy_decoding.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"from pecos.compilation_pipeline import compile_guppy_to_hugr\n",
"from pecos.guppy.surface import get_num_qubits, make_surface_code\n",
"from pecos.qec.surface import (\n",
" NoiseModel,\n",
" NoiseParameters,\n",
" SurfaceDecoder,\n",
" SurfacePatch,\n",
" plot_surface_code,\n",
Expand Down Expand Up @@ -101,7 +101,7 @@
}
},
"outputs": [],
"source": "from typing import Any\n\n\ndef get_logical_qubits(distance: int, basis: str) -> tuple:\n \"\"\"Get qubits in the logical operator.\"\"\"\n patch = SurfacePatch.create(distance=distance)\n if basis == \"Z\":\n return patch.geometry.logical_z.data_qubits\n return patch.geometry.logical_x.data_qubits\n\n\ndef run_memory_experiment(\n distance: int,\n num_rounds: int,\n num_shots: int,\n basis: str,\n error_model: Any,\n *,\n decode: bool = False,\n decoder_type: str = \"pymatching\",\n) -> dict:\n \"\"\"Run memory experiment and compute logical error rate.\n\n For Z-basis: prepare |0_L>, measure in Z basis, check logical Z parity.\n For X-basis: prepare |+_L>, measure in X basis, check logical X parity.\n\n Args:\n distance: Code distance\n num_rounds: Number of syndrome extraction rounds\n num_shots: Number of shots to run\n basis: 'Z' or 'X' basis\n error_model: Selene error model (IdealErrorModel or DepolarizingErrorModel)\n decode: If True, use decoding to correct errors\n decoder_type: Decoder backend ('pymatching', 'fusion_blossom', 'bp_osd', 'bp_lsd', 'union_find', 'tesseract')\n\n Returns:\n Dictionary with experiment results\n \"\"\"\n patch = SurfacePatch.create(distance=distance)\n logical_qubits = get_logical_qubits(distance, basis)\n\n # Create decoder if needed\n decoder = None\n if decode:\n # Extract noise parameters from error model\n noise = NoiseModel(\n p1=getattr(error_model, \"p_1q\", 0.01),\n p2=getattr(error_model, \"p_2q\", 0.01),\n p_meas=getattr(error_model, \"p_meas\", 0.01),\n p_prep=getattr(error_model, \"p_init\", 0.01),\n )\n decoder = SurfaceDecoder(patch, num_rounds=num_rounds, noise=noise, decoder_type=decoder_type)\n\n # Build circuit\n num_qubits = get_num_qubits(distance)\n prog = make_surface_code(distance=distance, num_rounds=num_rounds, basis=basis)\n hugr_bytes = compile_guppy_to_hugr(prog)\n instance = build(hugr_bytes, name=f\"surface_d{distance}\")\n\n # Run\n num_logical_errors = 0\n num_raw_errors = 0\n\n for shot_results in instance.run_shots(\n simulator=Stim(),\n n_qubits=num_qubits,\n n_shots=num_shots,\n error_model=error_model,\n runtime=SimpleRuntime(),\n n_processes=1,\n ):\n # Collect all syndromes properly (multiple entries per key)\n synx_list = []\n synz_list = []\n final = None\n\n for name, values in shot_results:\n vals = list(values)\n if name == \"synx\":\n synx_list.append(np.array(vals, dtype=np.uint8))\n elif name == \"synz\":\n synz_list.append(np.array(vals, dtype=np.uint8))\n elif name == \"final\":\n final = vals\n\n if final is None:\n continue\n\n # Raw parity check (no decoding)\n raw_parity = sum(final[q] for q in logical_qubits) % 2\n if raw_parity != 0:\n num_raw_errors += 1\n\n if decode and decoder is not None:\n final_arr = np.array(final, dtype=np.uint8)\n\n # Decode based on basis\n if basis == \"Z\":\n is_error, _ = decoder.decode_memory_z(synx_list, synz_list, final_arr)\n else:\n is_error, _ = decoder.decode_memory_x(synx_list, synz_list, final_arr)\n\n if is_error:\n num_logical_errors += 1\n else:\n # No decoding - use raw parity\n if raw_parity != 0:\n num_logical_errors += 1\n\n return {\n \"distance\": distance,\n \"num_shots\": num_shots,\n \"num_logical_errors\": num_logical_errors,\n \"num_raw_errors\": num_raw_errors,\n \"logical_error_rate\": num_logical_errors / num_shots,\n \"raw_error_rate\": num_raw_errors / num_shots,\n \"decoded\": decode,\n \"decoder_type\": decoder_type if decode else None,\n }"
"source": "from typing import Any\n\n\ndef get_logical_qubits(distance: int, basis: str) -> tuple:\n \"\"\"Get qubits in the logical operator.\"\"\"\n patch = SurfacePatch.create(distance=distance)\n if basis == \"Z\":\n return patch.geometry.logical_z.data_qubits\n return patch.geometry.logical_x.data_qubits\n\n\ndef run_memory_experiment(\n distance: int,\n num_rounds: int,\n num_shots: int,\n basis: str,\n error_model: Any,\n *,\n decode: bool = False,\n decoder_type: str = \"pymatching\",\n) -> dict:\n \"\"\"Run memory experiment and compute logical error rate.\n\n For Z-basis: prepare |0_L>, measure in Z basis, check logical Z parity.\n For X-basis: prepare |+_L>, measure in X basis, check logical X parity.\n\n Args:\n distance: Code distance\n num_rounds: Number of syndrome extraction rounds\n num_shots: Number of shots to run\n basis: 'Z' or 'X' basis\n error_model: Selene error model (IdealErrorModel or DepolarizingErrorModel)\n decode: If True, use decoding to correct errors\n decoder_type: Decoder backend ('pymatching', 'fusion_blossom', 'bp_osd', 'bp_lsd', 'union_find', 'tesseract')\n\n Returns:\n Dictionary with experiment results\n \"\"\"\n patch = SurfacePatch.create(distance=distance)\n logical_qubits = get_logical_qubits(distance, basis)\n\n # Create decoder if needed\n decoder = None\n if decode:\n # Extract noise parameters from error model\n noise = NoiseParameters(\n p1=getattr(error_model, \"p_1q\", 0.01),\n p2=getattr(error_model, \"p_2q\", 0.01),\n p_meas=getattr(error_model, \"p_meas\", 0.01),\n p_prep=getattr(error_model, \"p_init\", 0.01),\n )\n decoder = SurfaceDecoder(patch, num_rounds=num_rounds, noise=noise, decoder_type=decoder_type)\n\n # Build circuit\n num_qubits = get_num_qubits(distance)\n prog = make_surface_code(distance=distance, num_rounds=num_rounds, basis=basis)\n hugr_bytes = compile_guppy_to_hugr(prog)\n instance = build(hugr_bytes, name=f\"surface_d{distance}\")\n\n # Run\n num_logical_errors = 0\n num_raw_errors = 0\n\n for shot_results in instance.run_shots(\n simulator=Stim(),\n n_qubits=num_qubits,\n n_shots=num_shots,\n error_model=error_model,\n runtime=SimpleRuntime(),\n n_processes=1,\n ):\n # Collect all syndromes properly (multiple entries per key)\n synx_list = []\n synz_list = []\n final = None\n\n for name, values in shot_results:\n vals = list(values)\n if name == \"synx\":\n synx_list.append(np.array(vals, dtype=np.uint8))\n elif name == \"synz\":\n synz_list.append(np.array(vals, dtype=np.uint8))\n elif name == \"final\":\n final = vals\n\n if final is None:\n continue\n\n # Raw parity check (no decoding)\n raw_parity = sum(final[q] for q in logical_qubits) % 2\n if raw_parity != 0:\n num_raw_errors += 1\n\n if decode and decoder is not None:\n final_arr = np.array(final, dtype=np.uint8)\n\n # Decode based on basis\n if basis == \"Z\":\n is_error, _ = decoder.decode_memory_z(synx_list, synz_list, final_arr)\n else:\n is_error, _ = decoder.decode_memory_x(synx_list, synz_list, final_arr)\n\n if is_error:\n num_logical_errors += 1\n else:\n # No decoding - use raw parity\n if raw_parity != 0:\n num_logical_errors += 1\n\n return {\n \"distance\": distance,\n \"num_shots\": num_shots,\n \"num_logical_errors\": num_logical_errors,\n \"num_raw_errors\": num_raw_errors,\n \"logical_error_rate\": num_logical_errors / num_shots,\n \"raw_error_rate\": num_raw_errors / num_shots,\n \"decoded\": decode,\n \"decoder_type\": decoder_type if decode else None,\n }"
},
{
"cell_type": "markdown",
Expand Down Expand Up @@ -1094,7 +1094,7 @@
"\n",
"# Create a decoder configuration\n",
"patch = SurfacePatch.create(distance=3)\n",
"noise = NoiseModel(p1=0.001, p2=0.01, p_meas=0.01, p_prep=0.001)\n",
"noise = NoiseParameters(p1=0.001, p2=0.01, p_meas=0.01, p_prep=0.001)\n",
"\n",
"# Generate DEM using PECOS native pipeline\n",
"tc = generate_tick_circuit_from_patch(patch, num_rounds=3, basis=\"Z\")\n",
Expand Down Expand Up @@ -1646,7 +1646,7 @@
"\n",
"**Noise model**:\n",
"```python\n",
"noise = NoiseModel(\n",
"noise = NoiseParameters(\n",
" p1=0.001, # Single-qubit gate error rate\n",
" p2=0.01, # Two-qubit gate error rate\n",
" p_meas=0.01, # Measurement error rate\n",
Expand Down
Loading