From ec8c0ec6494e92f878547081f663f6e95aa778eb Mon Sep 17 00:00:00 2001 From: Rafael Haenel Date: Wed, 4 Nov 2026 11:13:17 +0000 Subject: [PATCH 1/5] Define the public API and qualify seed reproducibility The public API is tsim.Circuit, tsim.CompiledMeasurementSampler and tsim.CompiledDetectorSampler (tsim.__all__). CompiledStateProbs, SteaneEncoder and ColorEncoder5 are re-exported from tsim.experimental, which may change in any release; every other module is internal. The API reference documents only these objects, and the tutorials import from tsim.experimental. Seed docstrings say a seed reproduces its samples only with the same Tsim version and backend (CPU or GPU). Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 7 +++ README.md | 8 +++ docs/demos/magic_state_distillation.ipynb | 4 +- docs/demos/overview.ipynb | 4 +- docs/index.md | 7 +++ docs/scripts/gen_ref_nav.py | 74 +++++++++-------------- src/tsim/__init__.py | 14 ++--- src/tsim/circuit.py | 25 ++++---- src/tsim/experimental.py | 18 ++++++ src/tsim/sampler.py | 42 +++++++------ test/unit/test_public_api.py | 25 ++++++++ 11 files changed, 140 insertions(+), 88 deletions(-) create mode 100644 src/tsim/experimental.py create mode 100644 test/unit/test_public_api.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fcaea98..1d13f0a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 with a stabilizer-rank strategy. Without a mask, exactly `shots` rows are returned. `sample` warns when a masked detector is a constant 1, which discards every shot. (#252) +- The public API is `tsim.Circuit`, `tsim.CompiledMeasurementSampler` and + `tsim.CompiledDetectorSampler`, which follow semantic versioning. Every other + module is internal and may change without notice, and the API reference + documents only the public and experimental objects. `CompiledStateProbs`, + `SteaneEncoder` and `ColorEncoder5` are importable from `tsim.experimental`, + which may change in any release. A seed reproduces its samples only with the + same Tsim version and backend (CPU or GPU). ### Added - `CompiledMeasurementSampler.sample(bit_packed=True)`, as in Stim. diff --git a/README.md b/README.md index 6e011629..44d18554 100644 --- a/README.md +++ b/README.md @@ -144,12 +144,20 @@ CCX 0 1 2 # Toffoli with controls 0, 1 and target 2 ``` +## API stability + +Tsim follows [semantic versioning](https://semver.org/) for its public API: `tsim.Circuit`, `tsim.CompiledMeasurementSampler` and `tsim.CompiledDetectorSampler`. `tsim.experimental` holds helpers that may change in any release. Every other module is internal, even where it is importable. + +A seed reproduces its samples only with the same Tsim version and backend (CPU or GPU); new versions may change the samples of a seed. + + ## Upgrading from 0.1 Tsim 1.0 changes some defaults and APIs. See the [changelog](CHANGELOG.md) for the full list. - Samplers default to `strategy="active_statevector"`. Pass `strategy="cat5"` for the previous behavior. - Samples for a fixed seed differ from earlier versions. +- The public API is `tsim.Circuit`, `tsim.CompiledMeasurementSampler` and `tsim.CompiledDetectorSampler`. `CompiledStateProbs`, `SteaneEncoder` and `ColorEncoder5` are experimental, in `tsim.experimental`; other modules are internal. See [API stability](#api-stability). - `Circuit.get_graph()` and `Circuit.get_sampling_graph()` return `zxparam.Graph`; Tsim no longer depends on `pyzx-param`. - `Circuit.to_matrix()` is removed. `Circuit.to_tensor()` returns the circuit's channel and needs `zxparam[quimb]`. - The `"pyzx"`, `"pyzx-dets"` and `"pyzx-meas"` diagram types are deprecated in favor of `"zx"`, `"zx-dets"` and `"zx-meas"`. diff --git a/docs/demos/magic_state_distillation.ipynb b/docs/demos/magic_state_distillation.ipynb index fe956691..8acf77bb 100644 --- a/docs/demos/magic_state_distillation.ipynb +++ b/docs/demos/magic_state_distillation.ipynb @@ -19,7 +19,7 @@ "import numpy as np\n", "from typing import Literal\n", "import matplotlib.pyplot as plt\n", - "from tsim.utils.encoder import ColorEncoder5" + "from tsim.experimental import ColorEncoder5" ] }, { @@ -338,7 +338,7 @@ "# Encoding the distillation circuit\n", "\n", "To address this issue, we need to make our distillation circuit resilient to noise.\n", - "We do this by encoding it using a quantum error correction code, specifically the [17,1,5] color code. The helper class `ColorEncoder5` provides a convenient way to encode the distillation circuit. Each qubit will be encoded into 17 physical qubits using an encoding circuit. After that, each gate of the distillation circuit will be applied transversally to the encoded qubits." + "We do this by encoding it using a quantum error correction code, specifically the [17,1,5] color code. The experimental helper class `tsim.experimental.ColorEncoder5` provides a convenient way to encode the distillation circuit. Each qubit will be encoded into 17 physical qubits using an encoding circuit. After that, each gate of the distillation circuit will be applied transversally to the encoded qubits." ] }, { diff --git a/docs/demos/overview.ipynb b/docs/demos/overview.ipynb index c33591a4..871f7c0b 100644 --- a/docs/demos/overview.ipynb +++ b/docs/demos/overview.ipynb @@ -634,7 +634,7 @@ "id": "54", "metadata": {}, "source": [ - "Finally, Tsim allows to compute probability values for target states via the `CompiledStateProbs` sampler." + "Finally, Tsim allows to compute probability values for target states via the experimental `tsim.experimental.CompiledStateProbs` sampler, which may change in any release." ] }, { @@ -645,7 +645,7 @@ "outputs": [], "source": [ "import numpy as np\n", - "from tsim.sampler import CompiledStateProbs\n", + "from tsim.experimental import CompiledStateProbs\n", "\n", "sampler = CompiledStateProbs(c)" ] diff --git a/docs/index.md b/docs/index.md index 698c42a9..7e34c964 100644 --- a/docs/index.md +++ b/docs/index.md @@ -170,6 +170,13 @@ the noiseless reference sample, and otherwise when their raw parities are 0. A masked detector that is a constant 1, such as one of a noiseless circuit whose measurements have parity 1, discards every shot; `sample` warns about it. +## API stability + +Tsim follows [semantic versioning](https://semver.org/) for its public API: `tsim.Circuit`, `tsim.CompiledMeasurementSampler` and `tsim.CompiledDetectorSampler`. `tsim.experimental` holds helpers that may change in any release. Every other module is internal, even where it is importable. + +A seed reproduces its samples only with the same Tsim version and backend (CPU or GPU); new versions may change the samples of a seed. + + ## Citing Tsim If you use Tsim, please consider citing the paper describing the core simulation approach: ``` diff --git a/docs/scripts/gen_ref_nav.py b/docs/scripts/gen_ref_nav.py index 58a711e3..f4d7f29a 100644 --- a/docs/scripts/gen_ref_nav.py +++ b/docs/scripts/gen_ref_nav.py @@ -1,55 +1,37 @@ # type: ignore -"""Generate the code reference pages and navigation.""" - -from pathlib import Path +"""Generate the API reference pages for Tsim's public and experimental API.""" import mkdocs_gen_files -SRC_PATH = "src" - -skip_keywords = [] +PAGES = { + "index.md": ( + "Tsim API", + "The public API, which follows semantic versioning. Other modules are " + "internal and may change without notice.", + [ + "tsim.Circuit", + "tsim.CompiledMeasurementSampler", + "tsim.CompiledDetectorSampler", + ], + ), + "experimental.md": ( + "Experimental", + "Helpers outside of the stable API. They may change or be removed in any " + "release.", + [ + "tsim.experimental.CompiledStateProbs", + "tsim.experimental.SteaneEncoder", + "tsim.experimental.ColorEncoder5", + ], + ), +} nav = mkdocs_gen_files.Nav() -for path in sorted(Path(SRC_PATH).rglob("*.py")): - module_path = path.relative_to(SRC_PATH).with_suffix("") - doc_path = path.relative_to(SRC_PATH).with_suffix(".md") - full_doc_path = Path("reference", doc_path) - - iskip = False - - # Skip anything in the external folder - if "external" in str(doc_path): - iskip = True - - for kwrd in skip_keywords: - if kwrd in str(doc_path): - iskip = True - break - if iskip: - print("[Ignore]", str(doc_path)) - continue - - print("[>]", str(doc_path)) - - parts = tuple(module_path.parts) - - if parts[-1] == "__init__": - parts = parts[:-1] - doc_path = doc_path.with_name("index.md") - full_doc_path = full_doc_path.with_name("index.md") - - # [note] if selectionally ignore some files - # elif parts[-1].startswith("_wrapper"): - # pass - # else: - # continue - - nav[parts] = doc_path.as_posix() - with mkdocs_gen_files.open(full_doc_path, "w") as fd: - ident = ".".join(parts) - fd.write(f"::: {ident}") - - mkdocs_gen_files.set_edit_path(full_doc_path, ".." / path) +for doc_path, (title, intro, objects) in PAGES.items(): + nav[title] = doc_path + with mkdocs_gen_files.open(f"reference/{doc_path}", "w") as fd: + fd.write(f"# {title}\n\n{intro}\n\n") + fd.writelines(f"::: {obj}\n\n" for obj in objects) with mkdocs_gen_files.open("reference/SUMMARY.txt", "w") as nav_file: nav_file.writelines(nav.build_literate_nav()) diff --git a/src/tsim/__init__.py b/src/tsim/__init__.py index 531a858f..9453b7d1 100644 --- a/src/tsim/__init__.py +++ b/src/tsim/__init__.py @@ -4,15 +4,9 @@ It exposes `Circuit`, `CompiledDetectorSampler`, and `CompiledMeasurementSampler` objects, which follow the Stim API. -The package is organized as follows: - -- `circuit.py`: The thin `Circuit` wrapper around `stim.Circuit`. -- `sampler.py`: Orchestrates compilation and evaluation to sample measurements or detectors. -- `core/`: Builds quantum/classical ZX graphs with zxparam; parsing and data types. -- `compile/`: Compiles zxparam's stabilizer-rank decompositions to JAX. -- `statevector/`: Samples non-direct components as Pauli rotations and measurements on a dense state vector (`strategy="active_statevector"`). -- `noise/`: Pauli noise channels and detector error models. -- `utils/`: Utility functions for visualization and linear algebra. +These three objects are Tsim's public API, which follows semantic versioning. +`tsim.experimental` holds helpers that may change in any release. Every other +module is internal and may change without notice, even where it is importable. """ __version__ = "0.1.5" @@ -22,3 +16,5 @@ CompiledDetectorSampler as CompiledDetectorSampler, CompiledMeasurementSampler as CompiledMeasurementSampler, ) + +__all__ = ["Circuit", "CompiledDetectorSampler", "CompiledMeasurementSampler"] diff --git a/src/tsim/circuit.py b/src/tsim/circuit.py index ddb0fe53..b1e1be5e 100644 --- a/src/tsim/circuit.py +++ b/src/tsim/circuit.py @@ -954,11 +954,13 @@ def compile_sampler( qubits, or a stabilizer rank decomposition strategy ("cat5", "bss", "cutting"). "active_statevector" samples on the CPU, on ``numba.get_num_threads()`` threads. - seed: Random seed for the sampler. If None, a random seed is drawn. With - "active_statevector", and under any strategy for a circuit without - non-direct components, the samples of a seed depend on neither the batch - size nor the thread count; with a stabilizer-rank strategy they are only - deterministic for a fixed batch size. + seed: Random seed for the sampler. If None, a random seed is drawn. A seed + reproduces its samples only with the same Tsim version and backend (CPU + or GPU); other versions may change them. With "active_statevector", and + under any strategy for a circuit without non-direct components, the + samples of a seed depend on neither the batch size nor the thread count; + with a stabilizer-rank strategy they are only deterministic for a fixed + batch size. Returns: A CompiledMeasurementSampler that can be used to sample measurements. @@ -987,12 +989,13 @@ def compile_detector_sampler( qubits, or a stabilizer rank decomposition strategy ("cat5", "bss", "cutting"). "active_statevector" samples on the CPU, on ``numba.get_num_threads()`` threads. - seed: Random seed for the sampler. If None, a random seed is drawn. With - "active_statevector", and under any strategy for a circuit without - non-direct components, the samples of a seed depend on neither the batch - size nor the thread count; with a stabilizer-rank strategy they are only - deterministic for a fixed batch size and fixed reference sample - settings. + seed: Random seed for the sampler. If None, a random seed is drawn. A seed + reproduces its samples only with the same Tsim version and backend (CPU + or GPU); other versions may change them. With "active_statevector", and + under any strategy for a circuit without non-direct components, the + samples of a seed depend on neither the batch size nor the thread count; + with a stabilizer-rank strategy they are only deterministic for a fixed + batch size and fixed reference sample settings. Returns: A CompiledDetectorSampler that can be used to sample detectors and observables. diff --git a/src/tsim/experimental.py b/src/tsim/experimental.py new file mode 100644 index 00000000..e1941e3e --- /dev/null +++ b/src/tsim/experimental.py @@ -0,0 +1,18 @@ +"""Experimental helpers, outside of Tsim's stable API. + +The objects here may change or be removed in any release, including patch +releases. + +- `CompiledStateProbs` computes the probabilities of given measurement or + detector outcomes with a stabilizer rank decomposition. +- `SteaneEncoder` and `ColorEncoder5` encode a circuit transversally into the + [[7,1,3]] Steane code and the [[17,1,5]] color code. +""" + +from tsim.sampler import CompiledStateProbs as CompiledStateProbs +from tsim.utils.encoder import ( + ColorEncoder5 as ColorEncoder5, + SteaneEncoder as SteaneEncoder, +) + +__all__ = ["ColorEncoder5", "CompiledStateProbs", "SteaneEncoder"] diff --git a/src/tsim/sampler.py b/src/tsim/sampler.py index 55d9b0ed..adaa6e21 100644 --- a/src/tsim/sampler.py +++ b/src/tsim/sampler.py @@ -314,11 +314,13 @@ def __init__( qubits, or a stabilizer rank decomposition strategy ("cat5", "bss", "cutting"). "active_statevector" samples on the CPU, on ``numba.get_num_threads()`` threads. - seed: Random seed for the sampler. If None, a random seed is drawn. With - "active_statevector", and under any strategy for a circuit without - non-direct components, the samples of a seed depend on neither the batch - size nor the thread count; with a stabilizer-rank strategy they are only - deterministic for a fixed batch size. + seed: Random seed for the sampler. If None, a random seed is drawn. A seed + reproduces its samples only with the same Tsim version and backend (CPU + or GPU); other versions may change them. With "active_statevector", and + under any strategy for a circuit without non-direct components, the + samples of a seed depend on neither the batch size nor the thread count; + with a stabilizer-rank strategy they are only deterministic for a fixed + batch size. """ if seed is None: @@ -768,11 +770,13 @@ def __init__( circuit: The quantum circuit to compile. strategy: "active_statevector" (the default), or a stabilizer rank decomposition strategy ("cat5", "bss", "cutting"). - seed: Random seed for the sampler. If None, a random seed is drawn. With - "active_statevector", and under any strategy for a circuit without - non-direct components, the samples of a seed depend on neither the batch - size nor the thread count; with a stabilizer-rank strategy they are only - deterministic for a fixed batch size. + seed: Random seed for the sampler. If None, a random seed is drawn. A seed + reproduces its samples only with the same Tsim version and backend (CPU + or GPU); other versions may change them. With "active_statevector", and + under any strategy for a circuit without non-direct components, the + samples of a seed depend on neither the batch size nor the thread count; + with a stabilizer-rank strategy they are only deterministic for a fixed + batch size. """ super().__init__( @@ -864,12 +868,13 @@ def __init__( circuit: The quantum circuit to compile. strategy: "active_statevector" (the default), or a stabilizer rank decomposition strategy ("cat5", "bss", "cutting"). - seed: Random seed for the sampler. If None, a random seed is drawn. With - "active_statevector", and under any strategy for a circuit without - non-direct components, the samples of a seed depend on neither the batch - size nor the thread count; with a stabilizer-rank strategy they are only - deterministic for a fixed batch size and fixed reference sample - settings. + seed: Random seed for the sampler. If None, a random seed is drawn. A seed + reproduces its samples only with the same Tsim version and backend (CPU + or GPU); other versions may change them. With "active_statevector", and + under any strategy for a circuit without non-direct components, the + samples of a seed depend on neither the batch size nor the thread count; + with a stabilizer-rank strategy they are only deterministic for a fixed + batch size and fixed reference sample settings. """ super().__init__( @@ -1062,8 +1067,9 @@ def __init__( sample_detectors: If True, compute detector/observable probabilities. strategy: Stabilizer rank decomposition strategy ("cat5", "bss", "cutting"); "active_statevector" cannot compute probabilities. - seed: Random seed. If None, a random seed is generated. Note that - deterministic results are only guaranteed for a fixed batch size. + seed: Random seed. If None, a random seed is generated. Results are + only deterministic for a fixed batch size, Tsim version and + backend (CPU or GPU). """ super().__init__( diff --git a/test/unit/test_public_api.py b/test/unit/test_public_api.py new file mode 100644 index 00000000..2f86dd3c --- /dev/null +++ b/test/unit/test_public_api.py @@ -0,0 +1,25 @@ +import tsim +import tsim.experimental +from tsim.sampler import CompiledStateProbs +from tsim.utils.encoder import ColorEncoder5, SteaneEncoder + + +def test_public_api(): + assert sorted(tsim.__all__) == [ + "Circuit", + "CompiledDetectorSampler", + "CompiledMeasurementSampler", + ] + for name in tsim.__all__: + assert getattr(tsim, name).__module__.startswith("tsim.") + + +def test_experimental_api(): + assert tsim.experimental.CompiledStateProbs is CompiledStateProbs + assert tsim.experimental.ColorEncoder5 is ColorEncoder5 + assert tsim.experimental.SteaneEncoder is SteaneEncoder + assert sorted(tsim.experimental.__all__) == [ + "ColorEncoder5", + "CompiledStateProbs", + "SteaneEncoder", + ] From ca3e608513b1822279a7140cbe88f2fc59ba1af2 Mon Sep 17 00:00:00 2001 From: Rafael Haenel Date: Wed, 4 Nov 2026 12:29:25 +0000 Subject: [PATCH 2/5] Revert README changes Co-Authored-By: Claude Opus 5.5 --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index 44d18554..6e011629 100644 --- a/README.md +++ b/README.md @@ -144,20 +144,12 @@ CCX 0 1 2 # Toffoli with controls 0, 1 and target 2 ``` -## API stability - -Tsim follows [semantic versioning](https://semver.org/) for its public API: `tsim.Circuit`, `tsim.CompiledMeasurementSampler` and `tsim.CompiledDetectorSampler`. `tsim.experimental` holds helpers that may change in any release. Every other module is internal, even where it is importable. - -A seed reproduces its samples only with the same Tsim version and backend (CPU or GPU); new versions may change the samples of a seed. - - ## Upgrading from 0.1 Tsim 1.0 changes some defaults and APIs. See the [changelog](CHANGELOG.md) for the full list. - Samplers default to `strategy="active_statevector"`. Pass `strategy="cat5"` for the previous behavior. - Samples for a fixed seed differ from earlier versions. -- The public API is `tsim.Circuit`, `tsim.CompiledMeasurementSampler` and `tsim.CompiledDetectorSampler`. `CompiledStateProbs`, `SteaneEncoder` and `ColorEncoder5` are experimental, in `tsim.experimental`; other modules are internal. See [API stability](#api-stability). - `Circuit.get_graph()` and `Circuit.get_sampling_graph()` return `zxparam.Graph`; Tsim no longer depends on `pyzx-param`. - `Circuit.to_matrix()` is removed. `Circuit.to_tensor()` returns the circuit's channel and needs `zxparam[quimb]`. - The `"pyzx"`, `"pyzx-dets"` and `"pyzx-meas"` diagram types are deprecated in favor of `"zx"`, `"zx-dets"` and `"zx-meas"`. From 1c687e08d4d3a026dd6822368a6b37a17bf71dca Mon Sep 17 00:00:00 2001 From: Rafael Haenel Date: Wed, 4 Nov 2026 12:39:48 +0000 Subject: [PATCH 3/5] Address review: seed conditions, reference rendering, test - Seed docs name dependency versions and, for stabilizer-rank strategies, an explicit batch_size (the default depends on the free memory). - The API reference shows inherited members and hides bases and inheritance diagrams, so the encoders list their methods and no internal class shows. - docs/index.md links to the API reference; the public API test checks that every name in `tsim.__all__` resolves. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 3 ++- docs/index.md | 5 ++--- docs/scripts/gen_ref_nav.py | 10 ++++++++- src/tsim/circuit.py | 26 +++++++++++----------- src/tsim/sampler.py | 42 ++++++++++++++++++++---------------- test/unit/test_public_api.py | 3 +-- 6 files changed, 51 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d13f0a7..1a9cd459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 documents only the public and experimental objects. `CompiledStateProbs`, `SteaneEncoder` and `ColorEncoder5` are importable from `tsim.experimental`, which may change in any release. A seed reproduces its samples only with the - same Tsim version and backend (CPU or GPU). + same versions of Tsim and its dependencies and the same backend (CPU or GPU), + and, with a stabilizer-rank strategy, the same explicit `batch_size`. ### Added - `CompiledMeasurementSampler.sample(bit_packed=True)`, as in Stim. diff --git a/docs/index.md b/docs/index.md index 7e34c964..0f63ffb1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -172,10 +172,9 @@ measurements have parity 1, discards every shot; `sample` warns about it. ## API stability -Tsim follows [semantic versioning](https://semver.org/) for its public API: `tsim.Circuit`, `tsim.CompiledMeasurementSampler` and `tsim.CompiledDetectorSampler`. `tsim.experimental` holds helpers that may change in any release. Every other module is internal, even where it is importable. - -A seed reproduces its samples only with the same Tsim version and backend (CPU or GPU); new versions may change the samples of a seed. +Tsim follows [semantic versioning](https://semver.org/) for its public API: `tsim.Circuit`, `tsim.CompiledMeasurementSampler` and `tsim.CompiledDetectorSampler`. `tsim.experimental` holds helpers that may change in any release. Every other module is internal, even where it is importable. See the [API reference](reference/index.md). +A seed reproduces its samples only with the same versions of Tsim and its dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). With a stabilizer-rank strategy, pass the same explicit `batch_size` as well, since the default batch size depends on the free memory. ## Citing Tsim If you use Tsim, please consider citing the paper describing the core simulation approach: diff --git a/docs/scripts/gen_ref_nav.py b/docs/scripts/gen_ref_nav.py index f4d7f29a..7af89180 100644 --- a/docs/scripts/gen_ref_nav.py +++ b/docs/scripts/gen_ref_nav.py @@ -26,12 +26,20 @@ ), } +# NOTE: the encoders' methods are inherited from an internal base class, and the +# bases would name internal classes. +OPTIONS = """ options: + inherited_members: true + show_bases: false + show_inheritance_diagram: false +""" + nav = mkdocs_gen_files.Nav() for doc_path, (title, intro, objects) in PAGES.items(): nav[title] = doc_path with mkdocs_gen_files.open(f"reference/{doc_path}", "w") as fd: fd.write(f"# {title}\n\n{intro}\n\n") - fd.writelines(f"::: {obj}\n\n" for obj in objects) + fd.writelines(f"::: {obj}\n{OPTIONS}\n" for obj in objects) with mkdocs_gen_files.open("reference/SUMMARY.txt", "w") as nav_file: nav_file.writelines(nav.build_literate_nav()) diff --git a/src/tsim/circuit.py b/src/tsim/circuit.py index b1e1be5e..c8e446cd 100644 --- a/src/tsim/circuit.py +++ b/src/tsim/circuit.py @@ -955,12 +955,13 @@ def compile_sampler( "bss", "cutting"). "active_statevector" samples on the CPU, on ``numba.get_num_threads()`` threads. seed: Random seed for the sampler. If None, a random seed is drawn. A seed - reproduces its samples only with the same Tsim version and backend (CPU - or GPU); other versions may change them. With "active_statevector", and - under any strategy for a circuit without non-direct components, the - samples of a seed depend on neither the batch size nor the thread count; - with a stabilizer-rank strategy they are only deterministic for a fixed - batch size. + reproduces its samples only with the same versions of Tsim and its + dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). + With "active_statevector", and under any strategy for a circuit without + non-direct components, the samples of a seed depend on neither the batch + size nor the thread count. With a stabilizer-rank strategy they also + need the same explicit ``batch_size``, since the default batch size + depends on the free memory. Returns: A CompiledMeasurementSampler that can be used to sample measurements. @@ -990,12 +991,13 @@ def compile_detector_sampler( "bss", "cutting"). "active_statevector" samples on the CPU, on ``numba.get_num_threads()`` threads. seed: Random seed for the sampler. If None, a random seed is drawn. A seed - reproduces its samples only with the same Tsim version and backend (CPU - or GPU); other versions may change them. With "active_statevector", and - under any strategy for a circuit without non-direct components, the - samples of a seed depend on neither the batch size nor the thread count; - with a stabilizer-rank strategy they are only deterministic for a fixed - batch size and fixed reference sample settings. + reproduces its samples only with the same versions of Tsim and its + dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). + With "active_statevector", and under any strategy for a circuit without + non-direct components, the samples of a seed depend on neither the batch + size nor the thread count. With a stabilizer-rank strategy they also + need the same explicit ``batch_size`` and the same reference sample + settings, since the default batch size depends on the free memory. Returns: A CompiledDetectorSampler that can be used to sample detectors and observables. diff --git a/src/tsim/sampler.py b/src/tsim/sampler.py index adaa6e21..77d2976a 100644 --- a/src/tsim/sampler.py +++ b/src/tsim/sampler.py @@ -315,12 +315,13 @@ def __init__( "bss", "cutting"). "active_statevector" samples on the CPU, on ``numba.get_num_threads()`` threads. seed: Random seed for the sampler. If None, a random seed is drawn. A seed - reproduces its samples only with the same Tsim version and backend (CPU - or GPU); other versions may change them. With "active_statevector", and - under any strategy for a circuit without non-direct components, the - samples of a seed depend on neither the batch size nor the thread count; - with a stabilizer-rank strategy they are only deterministic for a fixed - batch size. + reproduces its samples only with the same versions of Tsim and its + dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). + With "active_statevector", and under any strategy for a circuit without + non-direct components, the samples of a seed depend on neither the batch + size nor the thread count. With a stabilizer-rank strategy they also + need the same explicit ``batch_size``, since the default batch size + depends on the free memory. """ if seed is None: @@ -771,12 +772,13 @@ def __init__( strategy: "active_statevector" (the default), or a stabilizer rank decomposition strategy ("cat5", "bss", "cutting"). seed: Random seed for the sampler. If None, a random seed is drawn. A seed - reproduces its samples only with the same Tsim version and backend (CPU - or GPU); other versions may change them. With "active_statevector", and - under any strategy for a circuit without non-direct components, the - samples of a seed depend on neither the batch size nor the thread count; - with a stabilizer-rank strategy they are only deterministic for a fixed - batch size. + reproduces its samples only with the same versions of Tsim and its + dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). + With "active_statevector", and under any strategy for a circuit without + non-direct components, the samples of a seed depend on neither the batch + size nor the thread count. With a stabilizer-rank strategy they also + need the same explicit ``batch_size``, since the default batch size + depends on the free memory. """ super().__init__( @@ -869,12 +871,13 @@ def __init__( strategy: "active_statevector" (the default), or a stabilizer rank decomposition strategy ("cat5", "bss", "cutting"). seed: Random seed for the sampler. If None, a random seed is drawn. A seed - reproduces its samples only with the same Tsim version and backend (CPU - or GPU); other versions may change them. With "active_statevector", and - under any strategy for a circuit without non-direct components, the - samples of a seed depend on neither the batch size nor the thread count; - with a stabilizer-rank strategy they are only deterministic for a fixed - batch size and fixed reference sample settings. + reproduces its samples only with the same versions of Tsim and its + dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). + With "active_statevector", and under any strategy for a circuit without + non-direct components, the samples of a seed depend on neither the batch + size nor the thread count. With a stabilizer-rank strategy they also + need the same explicit ``batch_size`` and the same reference sample + settings, since the default batch size depends on the free memory. """ super().__init__( @@ -1068,7 +1071,8 @@ def __init__( strategy: Stabilizer rank decomposition strategy ("cat5", "bss", "cutting"); "active_statevector" cannot compute probabilities. seed: Random seed. If None, a random seed is generated. Results are - only deterministic for a fixed batch size, Tsim version and + only deterministic for a fixed batch size, the same versions of + Tsim and its dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). """ diff --git a/test/unit/test_public_api.py b/test/unit/test_public_api.py index 2f86dd3c..8c4eb956 100644 --- a/test/unit/test_public_api.py +++ b/test/unit/test_public_api.py @@ -10,8 +10,7 @@ def test_public_api(): "CompiledDetectorSampler", "CompiledMeasurementSampler", ] - for name in tsim.__all__: - assert getattr(tsim, name).__module__.startswith("tsim.") + assert all(hasattr(tsim, name) for name in tsim.__all__) def test_experimental_api(): From 2fd8e03346f7491836e4fee34bd66bf6231b92b2 Mon Sep 17 00:00:00 2001 From: Rafael Haenel Date: Wed, 4 Nov 2026 12:42:40 +0000 Subject: [PATCH 4/5] Document the stabilizer rank strategies as experimental Only strategy="active_statevector" is stable; "cat5", "bss" and "cutting" may change or be removed in any release. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 9 ++++++--- docs/index.md | 4 +++- src/tsim/circuit.py | 8 ++++++-- src/tsim/sampler.py | 12 +++++++++--- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a9cd459..877d9207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,9 +26,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 module is internal and may change without notice, and the API reference documents only the public and experimental objects. `CompiledStateProbs`, `SteaneEncoder` and `ColorEncoder5` are importable from `tsim.experimental`, - which may change in any release. A seed reproduces its samples only with the - same versions of Tsim and its dependencies and the same backend (CPU or GPU), - and, with a stabilizer-rank strategy, the same explicit `batch_size`. + which may change in any release. Of the sampling strategies, only + `"active_statevector"` is stable; `"cat5"`, `"bss"` and `"cutting"` are + experimental and may change or be removed in any release. A seed reproduces + its samples only with the same versions of Tsim and its dependencies and the + same backend (CPU or GPU), and, with a stabilizer-rank strategy, the same + explicit `batch_size`. ### Added - `CompiledMeasurementSampler.sample(bit_packed=True)`, as in Stim. diff --git a/docs/index.md b/docs/index.md index 0f63ffb1..91cba68d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -60,6 +60,8 @@ Choose a strategy with the `strategy` argument of `compile_sampler` and `compile sampler = c.compile_detector_sampler(strategy="cat5") ``` +Only `"active_statevector"` is part of the stable API. The stabilizer rank strategies are experimental and may change or be removed in any release. + The [Tsim 1.0 blog post](https://bloqade.quera.com/blog/introducing-tsim-1-0-0/) introduces the active state vector strategy and compares its performance with other simulators. ## Architecture @@ -172,7 +174,7 @@ measurements have parity 1, discards every shot; `sample` warns about it. ## API stability -Tsim follows [semantic versioning](https://semver.org/) for its public API: `tsim.Circuit`, `tsim.CompiledMeasurementSampler` and `tsim.CompiledDetectorSampler`. `tsim.experimental` holds helpers that may change in any release. Every other module is internal, even where it is importable. See the [API reference](reference/index.md). +Tsim follows [semantic versioning](https://semver.org/) for its public API: `tsim.Circuit`, `tsim.CompiledMeasurementSampler` and `tsim.CompiledDetectorSampler`. `tsim.experimental` holds helpers that may change in any release. Every other module is internal, even where it is importable. Of the [sampling strategies](#sampling-strategies), only `"active_statevector"` is stable; the stabilizer rank strategies (`"cat5"`, `"bss"`, `"cutting"`) are experimental. See the [API reference](reference/index.md). A seed reproduces its samples only with the same versions of Tsim and its dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). With a stabilizer-rank strategy, pass the same explicit `batch_size` as well, since the default batch size depends on the free memory. diff --git a/src/tsim/circuit.py b/src/tsim/circuit.py index c8e446cd..1ee2cdbc 100644 --- a/src/tsim/circuit.py +++ b/src/tsim/circuit.py @@ -953,7 +953,9 @@ def compile_sampler( non-direct component on a dense state vector over its active qubits, or a stabilizer rank decomposition strategy ("cat5", "bss", "cutting"). "active_statevector" samples on the CPU, on - ``numba.get_num_threads()`` threads. + ``numba.get_num_threads()`` threads. Only "active_statevector" + is stable; the stabilizer rank strategies are experimental and + may change or be removed in any release. seed: Random seed for the sampler. If None, a random seed is drawn. A seed reproduces its samples only with the same versions of Tsim and its dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). @@ -989,7 +991,9 @@ def compile_detector_sampler( non-direct component on a dense state vector over its active qubits, or a stabilizer rank decomposition strategy ("cat5", "bss", "cutting"). "active_statevector" samples on the CPU, on - ``numba.get_num_threads()`` threads. + ``numba.get_num_threads()`` threads. Only "active_statevector" + is stable; the stabilizer rank strategies are experimental and + may change or be removed in any release. seed: Random seed for the sampler. If None, a random seed is drawn. A seed reproduces its samples only with the same versions of Tsim and its dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). diff --git a/src/tsim/sampler.py b/src/tsim/sampler.py index 77d2976a..dcabafd8 100644 --- a/src/tsim/sampler.py +++ b/src/tsim/sampler.py @@ -313,7 +313,9 @@ def __init__( non-direct component on a dense state vector over its active qubits, or a stabilizer rank decomposition strategy ("cat5", "bss", "cutting"). "active_statevector" samples on the CPU, on - ``numba.get_num_threads()`` threads. + ``numba.get_num_threads()`` threads. Only "active_statevector" + is stable; the stabilizer rank strategies are experimental and + may change or be removed in any release. seed: Random seed for the sampler. If None, a random seed is drawn. A seed reproduces its samples only with the same versions of Tsim and its dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). @@ -770,7 +772,9 @@ def __init__( Args: circuit: The quantum circuit to compile. strategy: "active_statevector" (the default), or a stabilizer rank - decomposition strategy ("cat5", "bss", "cutting"). + decomposition strategy ("cat5", "bss", "cutting"). Only + "active_statevector" is stable; the stabilizer rank strategies are + experimental and may change or be removed in any release. seed: Random seed for the sampler. If None, a random seed is drawn. A seed reproduces its samples only with the same versions of Tsim and its dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). @@ -869,7 +873,9 @@ def __init__( Args: circuit: The quantum circuit to compile. strategy: "active_statevector" (the default), or a stabilizer rank - decomposition strategy ("cat5", "bss", "cutting"). + decomposition strategy ("cat5", "bss", "cutting"). Only + "active_statevector" is stable; the stabilizer rank strategies are + experimental and may change or be removed in any release. seed: Random seed for the sampler. If None, a random seed is drawn. A seed reproduces its samples only with the same versions of Tsim and its dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). From d8b01f028d7a07b30a82b4586647e3b093626047 Mon Sep 17 00:00:00 2001 From: Rafael Haenel Date: Wed, 4 Nov 2026 13:20:15 +0000 Subject: [PATCH 5/5] Call the stabilizer rank strategies provisional They may be removed in a minor release, after at least one minor release in which they emit a DeprecationWarning. Docstrings say only that they are provisional; docs/index.md and the changelog state the policy. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 12 ++++++------ docs/index.md | 4 ++-- src/tsim/circuit.py | 10 ++++------ src/tsim/sampler.py | 15 ++++++--------- 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 877d9207..598369fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,12 +26,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 module is internal and may change without notice, and the API reference documents only the public and experimental objects. `CompiledStateProbs`, `SteaneEncoder` and `ColorEncoder5` are importable from `tsim.experimental`, - which may change in any release. Of the sampling strategies, only - `"active_statevector"` is stable; `"cat5"`, `"bss"` and `"cutting"` are - experimental and may change or be removed in any release. A seed reproduces - its samples only with the same versions of Tsim and its dependencies and the - same backend (CPU or GPU), and, with a stabilizer-rank strategy, the same - explicit `batch_size`. + which may change in any release. The stabilizer rank strategies (`"cat5"`, + `"bss"`, `"cutting"`) are provisional: they may be removed in a minor + release, after at least one minor release in which they emit a + `DeprecationWarning`. A seed reproduces its samples only with the same + versions of Tsim and its dependencies and the same backend (CPU or GPU), and, + with a stabilizer-rank strategy, the same explicit `batch_size`. ### Added - `CompiledMeasurementSampler.sample(bit_packed=True)`, as in Stim. diff --git a/docs/index.md b/docs/index.md index 91cba68d..67cabeb3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -60,7 +60,7 @@ Choose a strategy with the `strategy` argument of `compile_sampler` and `compile sampler = c.compile_detector_sampler(strategy="cat5") ``` -Only `"active_statevector"` is part of the stable API. The stabilizer rank strategies are experimental and may change or be removed in any release. +The stabilizer rank strategies are provisional; see [API stability](#api-stability). The [Tsim 1.0 blog post](https://bloqade.quera.com/blog/introducing-tsim-1-0-0/) introduces the active state vector strategy and compares its performance with other simulators. @@ -174,7 +174,7 @@ measurements have parity 1, discards every shot; `sample` warns about it. ## API stability -Tsim follows [semantic versioning](https://semver.org/) for its public API: `tsim.Circuit`, `tsim.CompiledMeasurementSampler` and `tsim.CompiledDetectorSampler`. `tsim.experimental` holds helpers that may change in any release. Every other module is internal, even where it is importable. Of the [sampling strategies](#sampling-strategies), only `"active_statevector"` is stable; the stabilizer rank strategies (`"cat5"`, `"bss"`, `"cutting"`) are experimental. See the [API reference](reference/index.md). +Tsim follows [semantic versioning](https://semver.org/) for its public API: `tsim.Circuit`, `tsim.CompiledMeasurementSampler` and `tsim.CompiledDetectorSampler`. `tsim.experimental` holds helpers that may change in any release. Every other module is internal, even where it is importable. The stabilizer rank [sampling strategies](#sampling-strategies) (`"cat5"`, `"bss"`, `"cutting"`) are provisional: they may be removed in a minor release, after at least one minor release in which they emit a `DeprecationWarning`. See the [API reference](reference/index.md). A seed reproduces its samples only with the same versions of Tsim and its dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). With a stabilizer-rank strategy, pass the same explicit `batch_size` as well, since the default batch size depends on the free memory. diff --git a/src/tsim/circuit.py b/src/tsim/circuit.py index 1ee2cdbc..f4e25210 100644 --- a/src/tsim/circuit.py +++ b/src/tsim/circuit.py @@ -953,9 +953,8 @@ def compile_sampler( non-direct component on a dense state vector over its active qubits, or a stabilizer rank decomposition strategy ("cat5", "bss", "cutting"). "active_statevector" samples on the CPU, on - ``numba.get_num_threads()`` threads. Only "active_statevector" - is stable; the stabilizer rank strategies are experimental and - may change or be removed in any release. + ``numba.get_num_threads()`` threads. The stabilizer rank + strategies are provisional. seed: Random seed for the sampler. If None, a random seed is drawn. A seed reproduces its samples only with the same versions of Tsim and its dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). @@ -991,9 +990,8 @@ def compile_detector_sampler( non-direct component on a dense state vector over its active qubits, or a stabilizer rank decomposition strategy ("cat5", "bss", "cutting"). "active_statevector" samples on the CPU, on - ``numba.get_num_threads()`` threads. Only "active_statevector" - is stable; the stabilizer rank strategies are experimental and - may change or be removed in any release. + ``numba.get_num_threads()`` threads. The stabilizer rank + strategies are provisional. seed: Random seed for the sampler. If None, a random seed is drawn. A seed reproduces its samples only with the same versions of Tsim and its dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). diff --git a/src/tsim/sampler.py b/src/tsim/sampler.py index dcabafd8..c9183d72 100644 --- a/src/tsim/sampler.py +++ b/src/tsim/sampler.py @@ -313,9 +313,8 @@ def __init__( non-direct component on a dense state vector over its active qubits, or a stabilizer rank decomposition strategy ("cat5", "bss", "cutting"). "active_statevector" samples on the CPU, on - ``numba.get_num_threads()`` threads. Only "active_statevector" - is stable; the stabilizer rank strategies are experimental and - may change or be removed in any release. + ``numba.get_num_threads()`` threads. The stabilizer rank + strategies are provisional. seed: Random seed for the sampler. If None, a random seed is drawn. A seed reproduces its samples only with the same versions of Tsim and its dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). @@ -772,9 +771,8 @@ def __init__( Args: circuit: The quantum circuit to compile. strategy: "active_statevector" (the default), or a stabilizer rank - decomposition strategy ("cat5", "bss", "cutting"). Only - "active_statevector" is stable; the stabilizer rank strategies are - experimental and may change or be removed in any release. + decomposition strategy ("cat5", "bss", "cutting"). The + stabilizer rank strategies are provisional. seed: Random seed for the sampler. If None, a random seed is drawn. A seed reproduces its samples only with the same versions of Tsim and its dependencies (such as NumPy and JAX) and the same backend (CPU or GPU). @@ -873,9 +871,8 @@ def __init__( Args: circuit: The quantum circuit to compile. strategy: "active_statevector" (the default), or a stabilizer rank - decomposition strategy ("cat5", "bss", "cutting"). Only - "active_statevector" is stable; the stabilizer rank strategies are - experimental and may change or be removed in any release. + decomposition strategy ("cat5", "bss", "cutting"). The + stabilizer rank strategies are provisional. seed: Random seed for the sampler. If None, a random seed is drawn. A seed reproduces its samples only with the same versions of Tsim and its dependencies (such as NumPy and JAX) and the same backend (CPU or GPU).