diff --git a/CHANGELOG.md b/CHANGELOG.md index 598369fa..f082f912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,20 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.0.0] - 2026-11-04 + +Tsim 1.0 adds a new default sampling strategy, `"active_statevector"`, which +simulates each non-Clifford component on a dense state vector over its active +qubits, building on the ideas of [Clifft](https://arxiv.org/abs/2604.27058) and +[SymFT](https://arxiv.org/abs/2607.28600). Tsim now uses +[zxparam](https://pypi.org/project/zxparam/) for its ZX diagrams instead of +pyzx-param. See the +[blog post](https://bloqade.quera.com/blog/introducing-tsim-1-0-0/) for an +overview and benchmarks. + ### Breaking +- The samplers default to `strategy="active_statevector"`. Pass + `strategy="cat5"` for the previous stabilizer-rank default. + `CompiledStateProbs` keeps `"cat5"`. (#203) - `CompiledDetectorSampler.sample(postselection_mask=...)` returns only the shots that no masked detector discards, so it can return fewer than `shots` - rows, possibly none, and `len(result) / shots` is the acceptance rate. It used - to return all `shots` rows, with the discarded ones partly filled with - `False`, for the caller to filter. Filtering the result with the mask again - still works but no longer does anything. Code that assumes - `len(result) == shots` must change, and code that computes the acceptance - rate as the mean of the re-applied mask now silently gets 1.0; use - `len(result) / shots` instead. The - rule is now the same for every strategy, including masked component detectors - 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) + rows, possibly none. The acceptance rate is the number of returned rows + divided by `shots`: `len(result) / shots`, or `len(result[0]) / shots` with + `separate_observables=True`. It used to return all `shots` rows, with the + discarded ones partly filled with `False`, for the caller to filter. + Filtering the result with the mask again still works but no longer does + anything. Code that assumes `len(result) == shots` must change, and code + that computes the acceptance rate as the mean of the re-applied mask now + silently gets 1.0. The rule is the same for every strategy. Without a mask, + exactly `shots` rows are returned. `sample` warns when a masked detector is + a constant 1, which discards every shot. (#222, #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 @@ -31,161 +44,132 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 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`. + with a stabilizer-rank strategy, the same explicit `batch_size`. (#268) +- Samples for a fixed seed differ from earlier versions under every + strategy: noise sampling runs in numba kernels, and the noise, the components + and the reference samples each draw from their own child of + `np.random.SeedSequence(seed)`. (#199, #201, #211, #263) +- Python 3.10 is no longer supported; Tsim supports Python 3.11 to 3.14. (#216) +- Tsim requires `stim>=1.15.0`, the first release with instruction tags, which + Tsim uses to store its gates. It used to declare `stim>=1.0.0`. (#255) +- Tsim requires `numpy>=2.0` and `equinox>=0.11.5`, the oldest versions it + works with; the `cuda13` extra requires `jax>=0.7.0`, the first release with + a `cuda13` extra. +- `Circuit.cast_to_stim()` is removed. It only cast the type and returned the + same `tsim.Circuit`; pass the circuit directly. (#266) +- Tsim uses zxparam instead of pyzx-param (#194, #195, #205): + - `Circuit.get_graph()` and `Circuit.get_sampling_graph()` return + `zxparam.Graph`. Circuits are built directly as quantum/classical ZX + diagrams, without composing with the adjoint. + - In `Circuit.get_graph()` (and the `"zx"` diagram), every detector and + observable has a classical output boundary, with ports after the qubit + outputs, so the graph's tensor is the circuit's channel together with the + detector and observable bits. + - `Circuit.get_sampling_graph()` is no longer doubled: qubit wires are + quantum and measurement records are classical spiders. Its `to_tensor()` + raises on unbound error parameters (pyzx treated them as 0); bind them by + error index. + - `Circuit.to_tensor()` returns the doubled tensor (the circuit's channel, + `U ⊗ Ū` for unitaries), with error parameters set to 0 unless bound. It + needs quimb, which is not a Tsim runtime dependency: install + `zxparam[quimb]`, otherwise it raises `ImportError`. (#246) + - `Circuit.to_matrix()` is removed; compare `to_tensor()` with `U ⊗ Ū` + instead. + - The ZX diagram types `"pyzx"`, `"pyzx-dets"` and `"pyzx-meas"` are now + `"zx"`, `"zx-dets"` and `"zx-meas"`, and return zxparam's HTML drawing + object instead of a graph. The old names raise `ValueError`. +- `Circuit.append` raises `ValueError` when `targets`, `arg` or `tag` are + passed with a `stim.CircuitInstruction`, `stim.CircuitRepeatBlock` or + `stim.Circuit`. They used to be silently ignored. (#254) ### Added -- `CompiledMeasurementSampler.sample(bit_packed=True)`, as in Stim. -- `Circuit.detector_error_model(decompose_errors=True)` is supported. The - argument is passed on to Stim, with the logical observables kept as - observables, so the observables must be deterministic. The docstring now warns - that the default, undecomposed model loses every error mechanism that flips - more than two detectors when it is given to a matching decoder such as - PyMatching. (#232) +- `strategy="active_statevector"` for `compile_sampler`, + `compile_detector_sampler` and the sampler classes. Each non-direct + component is read off the reduced ZX diagram, without circuit extraction, as + Pauli rotations and measurements on `|0…0⟩`, and simulated on a dense state + vector over its active qubits, so the cost grows as `2^W` in the active width + `W`. It runs on the CPU in multi-threaded numba kernels, without JAX. It only + samples: it cannot compute probabilities, and it raises `ValueError` for + components it cannot read, for components wider than 30 active qubits, and + when one shot needs more than half of the available memory. (#203, #199, + #212, #213, #223, #224) + - Shots with equal histories share one state vector, which splits at + measurements, and a shot that a postselected detector discards stops being + simulated there. (#198, #250) + - The samples of a seed depend on neither `batch_size` nor the thread + count: the noise and every measurement outcome are drawn from + counter-based streams by shot index. (#199, #207, #212) + - With `bit_packed=True`, samples usually come straight from the packed + words the sampler writes, without building a boolean array. (#211, #212) - `"timeline-text"` diagram type. Renders Tsim's custom gates under their logical names instead of the Clifford placeholders used to store them, so `T` prints as `T` rather than `S`. Covers `T`, `T_DAG`, `TPP`, `TPP_DAG`, `R_X`, `R_Y`, `R_Z`, and `U3`. `R_XX`, `R_YY`, and `R_ZZ` share `R_PAULI`'s metadata tag and render as `R_PAULI(alpha)`. Rendering is provided by the - `stim-timeline-text` package. (#171) -- `strategy="active_statevector"` for `compile_sampler`, `compile_detector_sampler` - and the sampler classes. Each non-direct component is read off the reduced ZX - diagram, without circuit extraction, as Pauli rotations and measurements on - `|0…0⟩`, and simulated on a dense state vector over its active qubits, so the - cost grows as `2^W` in the active width `W`. It only samples: it cannot compute - probabilities, and it raises `ValueError` for components it cannot read, for - components wider than 30 active qubits, and when one shot needs more than half - of the available memory. (#203) -- Trajectory sharing for `strategy="active_statevector"`: shots with - equal histories share one state vector, which splits at measurements, and - shots that a postselected component detector discards stop being simulated - there. It is always on and has no option. (#198) -- Parametric tags accept positional angles in units of pi, as in the shorthand: - `I[R_X(0.1)] 0` is `R_X(0.1) 0`, `I[U3(0.3, 0.24, 0.49)] 0` is + `stim-timeline-text` package. (#186 by @shalini9894; reported in #171 by + @egcarnio) +- `Circuit.detector_error_model(decompose_errors=True)`. The argument is passed + on to Stim, with the logical observables kept as observables, so the + observables must be deterministic. The docstring warns that the default, + undecomposed model loses every error mechanism that flips more than two + detectors when it is given to a matching decoder such as PyMatching. (#234 + by @mamuncseru, #232) +- `CompiledMeasurementSampler.sample(bit_packed=True)`, as in Stim. (#253) +- Parametric tags accept positional angles in units of pi, as in the + shorthand: `I[R_X(0.1)] 0` is `R_X(0.1) 0`, `I[U3(0.3, 0.24, 0.49)] 0` is `U3(0.3, 0.24, 0.49) 0`, and `SPP[R_PAULI(0.1)] X0*Y1` is `R_PAULI(0.1) X0*Y1`. The keyword form (`R_X(theta=0.1*pi)`) still works; program text is canonicalized to it. Empty angle fields, as in - `R_X(theta=0.1*pi,)`, are now rejected in both forms. (#202) + `R_X(theta=0.1*pi,)`, are now rejected in both forms. (#218; reported in + #202 by @dobbse42) +- Tsim gate names are case-insensitive, like Stim's: `t 0`, `ccz 0 1 2`, + `r_x(0.1) 0` and `Circuit.append("tpp", ...)` used to fail with + `Gate not found`. (#253) ### Changed -- `Circuit.append` raises `ValueError` when `targets`, `arg` or `tag` are - passed with a `stim.CircuitInstruction`, `stim.CircuitRepeatBlock` or - `stim.Circuit`. They used to be silently ignored. -- Tsim requires `stim>=1.15.0`, the first release with instruction tags, - which Tsim uses to store its gates. It used to declare `stim>=1.0.0`. -- The samplers now default to `strategy="active_statevector"`; pass - `strategy="cat5"` for the previous stabilizer-rank default. - `CompiledStateProbs` keeps `"cat5"`. (#203) -- `strategy="active_statevector"` no longer uses JAX: it samples on the CPU - with numba kernels, also when JAX has a GPU. Amplitudes are complex128. Postselected - detectors of a component prune its shots with sharing off as well, and also - when no direct detector is postselected. (#199) -- A statevector call samples its noise, then runs its components on one chunk - of shots per numba thread, each of at least 256 and at most 2^16 shots, or - `batch_size`. The samples of a seed depend on neither `batch_size`, the - thread count nor trajectory sharing: the noise and every measurement - outcome are drawn from counter-based streams by shot index. Seeded samples - differ from earlier versions. (#199, #207, #212) -- Noise sampling runs in numba kernels, so the samples for a given seed - differ from earlier versions; `numba>=0.61` is now a dependency. (#199) -- Constant outputs now use the existing direct-output path, skipping component - compilation and sampling. Postselection treats them as direct detectors. - Samples for a fixed seed can differ from earlier versions. (#201) -- Compiling a sampler does less work in channel validation and - simplification, the noise sampling tables, the GF(2) error basis - (`find_basis`), detector placement while parsing, and, for - `strategy="active_statevector"`, graph-state stabilizers and op scheduling; - `cultivation_d5_end2end` compiles about 2.5x faster. Compiled programs are - unchanged. Postselection also writes direct detectors faster. (#207) -- The noise sampler picks each fault's outcome from an alias table and takes - the logarithm of its geometric skips inline instead of calling libm, so it - draws faults faster; the sampled distribution is unchanged, but seeded - samples differ from earlier versions. (#211) -- `CompiledDetectorSampler.sample(bit_packed=True)` packs the outputs straight - from the noise sampler when every output is a noise bit, as in typical - surface-code memory circuits, instead of packing a boolean array. (#211) -- `strategy="active_statevector"` samples into *shot words*: the noise sampler - XORs each fault into the packed bits of its shot's outputs and component - keys, and the components add their branch parts in place, so no f-parameter - matrix is built and `bit_packed=True` returns the words' bytes for any circuit - whose outputs are in order; circuits of noise bits alone still get boolean - samples straight from the noise sampler. Each component runs in one numba kernel per - chunk, on parallel amplitude loops when a call is one chunk; single-component - circuits such as distillation and cultivation sample about 3-4x faster on - one thread. (#212) -- The statevector rotation and measurement kernels take each amplitude pair's - parity with one popcount and mix pairs with real products (the coupling of - a Pauli rotation is real or imaginary), so wide components such as - `cultivation_d5` without postselection and `coherent_d5_r5` sample about - 1.6x faster. Samples are unchanged. (#213) -- Tsim now uses zxparam instead of pyzx-param. Circuits are built directly as quantum/classical ZX diagrams (no more composing with the adjoint), and reduction and stabilizer-rank decomposition run in zxparam. -- `Circuit.get_graph()` and `Circuit.get_sampling_graph()` return `zxparam.Graph`. -- In `Circuit.get_graph()` (and the `"zx"` diagram), every detector and observable has a classical output boundary, with ports after the qubit outputs, so the graph's tensor is the circuit's channel together with the detector and observable bits. -- `Circuit.to_tensor()` returns the doubled tensor (the circuit's channel, `U ⊗ Ū` for unitaries), with error parameters set to 0 unless bound. -- `Circuit.to_tensor()` needs quimb, which is not a Tsim runtime dependency: install `zxparam[quimb]`, otherwise it raises `ImportError`. -- `Circuit.get_sampling_graph()` is no longer doubled: qubit wires are quantum and measurement records are classical spiders. Its `to_tensor()` raises on unbound error parameters (pyzx treated them as 0); bind them by error index. With all errors bound, the tensor is the joint distribution of the outputs. -- `repr()` of a sampler shows only the statistics of the backends it uses: with `strategy="active_statevector"` it drops the graph, parameter and A–E term counts, which were always zero. -- The ZX diagram types are now `"zx"`, `"zx-dets"` and `"zx-meas"`. They return zxparam's HTML drawing object instead of a graph. -- Internals: `GraphRepresentation` (`tsim.core.instructions`) is now `GraphBuilder` in the new `tsim.core.builder`, and `parse_stim_circuit` returns it. Its bookkeeping functions became methods: `detector` is now `add_detector` and `observable_include` is now `include_observable`; `tick`, `correlated_error` and `finalize_correlated_error` keep their names; `ensure_lane`, `add_lane`, `add_quantum_boundary`, `last_col` and `last_edge` are now private. Its fields are private behind read-only properties (`rec` is now `records`). `ANNOTATION_ROW`, `HELPER_LANE` and `_error_phase` (now `error_phase`) moved to `tsim.core.builder`. `build_sampling_graph` (`tsim.core.graph`) is replaced by `GraphBuilder.build(closed=..., outputs=...)`. -- Internals moved or removed: `classify_direct` moved from `tsim.core.graph` to `tsim.compile.pipeline`; `tsim.utils.diagram.render_pyzx_d3`, `squash_graph` and `scale_horizontally` are gone, since ZX diagrams use zxparam's `draw` layout and scale options. -- Internals of `strategy="active_statevector"`: a component's tape keeps a - boolean column per symbol instead of uint32 words, `run_lanes` writes a pruned - shot's outputs when its lane dies instead of keeping graves, and both samplers - pick their backend in one `_sample_outputs` method. Samples are unchanged. - (#223) -- With `strategy="active_statevector"` and a postselection mask, the shots - that pass are gathered by numba kernels in the threads that sampled them, - instead of by NumPy on one thread. Discarded shots are no longer finished: - a pruned shot draws no coins, and its outputs are neither completed nor - cleared. A program without components screens each noise block while it is - in cache, and reuses its noise words across calls: a sampler keeps up to - 16 MiB of them. Samples are unchanged. +- Compiling a sampler is faster: ZX reduction and stabilizer-rank + decomposition run in zxparam's Rust core (#205), and channel validation, + the noise sampling tables, the GF(2) error basis and detector placement do + less work. (#207) +- Noise sampling runs in numba kernels and draws each fault's outcome from an + alias table; `numba>=0.61` is now a dependency. (#199, #211) +- Outputs that are constant now take the direct-output path, skipping + component compilation and sampling. Postselection treats them as direct + detectors. (#201) - Components that feed no sampled output, such as a non-Clifford block cut off by an `MR` that no detector reads or by a mid-circuit `R`, are no longer - compiled or sampled. With `strategy="active_statevector"`, such a block - without noise could raise `IndexError`. Samples for a fixed seed can differ - from earlier versions. (#262) - -### Deprecated -- The `"pyzx"`, `"pyzx-dets"` and `"pyzx-meas"` diagram types. They still work as aliases for `"zx"`, `"zx-dets"` and `"zx-meas"` but emit a `DeprecationWarning`. + compiled or sampled. (#262) +- With `strategy="active_statevector"` and a postselection mask, the surviving + shots are gathered in parallel, and discarded shots are no longer finished. + (#259) +- `repr()` of a sampler shows only the statistics of the backends it uses. + (#251) ### Removed -- `Circuit.to_matrix()` (compare `to_tensor()` with `U ⊗ Ū` instead). -- The `pyzx-param` dependency. -- Internals: the `track_classical_wires` argument of `parse_stim_circuit`, the `tsim.compile.stabrank` module, and `ConnectedComponent`, `connected_components`, `get_params` and `evaluate_graph` from `tsim.core.graph`. +- The `pyzx-param` dependency. (#194) ### Fixed +- Noise channels whose total probability is positive but at most `1e-15` are + sampled instead of silently dropped. (#267) - Different seeds no longer give the same samples. The noise was seeded from a 30-bit number drawn from the seed, so seeds shared it in pairs: seeds 0 to 300,000 hold 46 such pairs, such as 37430 and 43909, and a circuit of noise bits alone gave identical samples for both. The stabilizer-rank strategies seeded JAX with `jax.random.key(seed)`, which keeps only the low 32 bits of a seed, so `seed` and `seed + 2**32` gave the same component samples, and seeds - from 2^63 on raised `OverflowError`. The noise, the components and the - reference samples now each draw from their own child of - `np.random.SeedSequence(seed)`, and `seed=None` draws 128 bits instead of 30. - Seeded samples differ from earlier versions under every strategy. (#263) -- Tsim gate names are case-insensitive, like Stim's: `t 0`, `ccz 0 1 2`, - `r_x(0.1) 0` and `Circuit.append("tpp", ...)` used to fail with - `Gate not found`. -- `Circuit.is_clifford` returns False for an `SPP`/`SPP_DAG` tagged with a - parametric gate other than `R_PAULI`, such as `SPP[R_X(0.5)] X0`, which - can't be compiled. It used to return True. -- An `E`/`ELSE_CORRELATED_ERROR` chain whose links repeat Pauli components now - compiles to one error bit per component (X or Z on a qubit) instead of one - per link, so a general 3-qubit Pauli channel, a 63-link chain, is a 6-bit - channel instead of failing with `Maximum allowed dimension exceeded`. Links - share a component while nothing else acts on its wire between them (a - `SWAP` moves the wire to the other qubit). Chains with fewer components than - links get fewer error bits, numbered by component, so - `Circuit.to_tensor(bindings)` and `get_graph` change for them, and their - seeded samples can differ. A chain builds the same graph as before if all its - links have targets and it never flips a component of a wire twice, also not - within one link: `E X0 Z1; ELSE_CORRELATED_ERROR Y2` does, but `E X0 X0` and - `E X0; SWAP 0 1; ELSE_CORRELATED_ERROR X1` do not. A chain that would need - more than 2^20 outcomes raises `ValueError`. (#248) + from 2^63 on raised `OverflowError`. `seed=None` now draws 128 bits instead + of 30. (#263) +- `Circuit.stim_circuit` keeps the tag of a `stim.CircuitRepeatBlock`. (#183 by + @perlinm) +- `Circuit.stim_circuit` no longer reorders merged Clifford-angle rotations + such as `SPP[R_PAULI(theta=1.0*pi)] X0 Z0` or `I[U3(...)] 0 0`, which + produced a different circuit although `is_clifford` was True. (#242, #225) +- The 64-qubit limit of `R_PAULI` applies to each Pauli product, not to the + whole instruction, so 33 adjacent `R_ZZ` rotations, which Stim merges into + one instruction, are no longer rejected. (#241 by @Mohith26, #239) - `Circuit.append("R_PAULI", pauli_string, angle)` no longer drops the sign of a `stim.PauliString`: a `-1` sign now reverses the rotation, and a sign of `±i` - raises `ValueError`. (#228) + raises `ValueError`. (#237 by @Shriraj1901, #228) - `Circuit.append` no longer drops the sign of a `stim.PauliString` target of `MPP`, `SPP`, `SPP_DAG`, `OBSERVABLE_INCLUDE`, `TPP` or `TPP_DAG` either, so `MPP` about `-Z` flips the result and `TPP` about `-Z` is `T_DAG`. Stim itself @@ -196,23 +180,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `R_PAULI` program text accepts inverted targets such as `R_PAULI(0.3) !X0*Y1`, and circuits print them in that form instead of `SPP[R_PAULI(theta=0.3*pi)] !X0*Y1`. (#244) -- `CompiledDetectorSampler.sample` ignored `postselection_mask` for circuits - without non-direct components, so their discarded rows kept their direct - observables. A discarded row now keeps its direct detectors and reads `False` - elsewhere for every circuit. With `strategy="active_statevector"`, a shot that a - component's postselected detector discards is no longer simulated by the - other components, and reads `False` in their outputs and in the direct - observables, whatever the order of the components. (#222) -- With a `postselection_mask` and no `batch_size`, the last survivors of a call, - split into a batch per thread, ran one batch at a time for components so wide - that only one of the largest batches fits in memory. They now run on as many - threads as batches of their size allow. (#207) +- Long `E`/`ELSE_CORRELATED_ERROR` chains whose links share Pauli components, + such as a general 3-qubit Pauli channel, compile instead of failing with + `Maximum allowed dimension exceeded`. A chain that would need more than 2^20 + outcomes raises `ValueError`. (#248) - Program text no longer rewrites shorthand inside tags or comments, so tags such as `I[R_X(0.1)]`, `X[U3(0.1, 0.2, 0.3)]` or `H[my T tag]` no longer - fail to parse or change. (#202) + fail to parse or change. (#218, #202) - `SPP` and `SPP_DAG` with a parametric tag other than `R_PAULI` (e.g. - `SPP[R_X(theta=0.1*pi)] X0`) now raise instead of acting as a plain `SPP`. - (#202) + `SPP[R_X(theta=0.1*pi)] X0`) raise instead of acting as a plain `SPP`, and + `Circuit.is_clifford` returns False for them. (#218, #253) +- The README's quick start links to the tutorial notebook instead of a retired + docs page. (#238 by @Vamsi-2581; reported in #235 by @cduck) ## [0.1.5] - 2026-07-21 diff --git a/README.md b/README.md index 6e011629..26f490d1 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ pip install "bloqade-tsim[cuda13]" # or bloqade-tsim[cuda12] ## Quick Start An introductory tutorial is available [here](docs/demos/encoding_demo.ipynb). Please also refer to the [documentation](https://tsim.mintlify.app/). -For many existing scripts, replacing `stim` with `tsim` should just work. Tsim mirrors the Stim API and supports all [Stim instructions](https://github.com/quantumlib/Stim/wiki/Stim-v1.13-Gate-Reference), plus the [non-Clifford instructions](#supported-instructions) listed below. +For many existing scripts, replacing `stim` with `tsim` should just work. Tsim mirrors the Stim API and supports all [Stim instructions](https://github.com/quantumlib/Stim/blob/v1.16.0/doc/gates.md), plus the [non-Clifford instructions](#supported-instructions) listed below. ```python import tsim @@ -121,7 +121,7 @@ Note that this feature should be used carefully. If detectors or observables are ## Supported Instructions -Tsim supports all [Stim instructions](https://github.com/quantumlib/Stim/wiki/Stim-v1.13-Gate-Reference). In addition, Tsim defines the following non-Clifford instructions. Angles are given in units of π (half-turns). +Tsim supports all [Stim instructions](https://github.com/quantumlib/Stim/blob/v1.16.0/doc/gates.md). In addition, Tsim defines the following non-Clifford instructions. Angles are given in units of π (half-turns). | Instruction | Targets | Action | |---|---|---| @@ -149,10 +149,12 @@ CCX 0 1 2 # Toffoli with controls 0, 1 and target 2 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. +- With a `postselection_mask`, `CompiledDetectorSampler.sample` returns only the surviving shots, so the acceptance rate is the number of returned rows divided by `shots` (`len(result[0]) / shots` with `separate_observables=True`). - Samples for a fixed seed differ from earlier versions. +- Tsim requires Python 3.11 or later and `stim>=1.15.0`. - `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"`. +- The `"pyzx"`, `"pyzx-dets"` and `"pyzx-meas"` diagram types are now `"zx"`, `"zx-dets"` and `"zx-meas"`. ## Publications Using Tsim diff --git a/docs/demos/overview.ipynb b/docs/demos/overview.ipynb index 871f7c0b..1a57730a 100644 --- a/docs/demos/overview.ipynb +++ b/docs/demos/overview.ipynb @@ -28,7 +28,7 @@ "source": [ "### Clifford Instructions\n", "\n", - "Tsim supports all instructions [supported by Stim](https://github.com/quantumlib/Stim/wiki/Stim-v1.13-Gate-Reference). Below, we show the standard generating set of Clifford gates:" + "Tsim supports all instructions [supported by Stim](https://github.com/quantumlib/Stim/blob/v1.16.0/doc/gates.md). Below, we show the standard generating set of Clifford gates:" ] }, { @@ -204,7 +204,7 @@ "source": [ "### Measurement and Reset instructions\n", "\n", - "Tsim supports all collapsing gates [supported by Stim](https://github.com/quantumlib/Stim/wiki/Stim-v1.9-Gate-Reference#collapsing-gates)." + "Tsim supports all collapsing gates [supported by Stim](https://github.com/quantumlib/Stim/blob/v1.16.0/doc/gates.md#collapsing-gates)." ] }, { @@ -369,7 +369,7 @@ "source": [ "### Noise Channels\n", "\n", - "Tsim supports all noise channels [supported by Stim](https://github.com/quantumlib/Stim/wiki/Stim-v1.9-Gate-Reference#noise-channels).\n", + "Tsim supports all noise channels [supported by Stim](https://github.com/quantumlib/Stim/blob/v1.16.0/doc/gates.md#noise-channels).\n", "\n", "The `X_ERROR(p)` instruction is a `X` instruction that is applied with probability `p`." ] diff --git a/docs/index.md b/docs/index.md index 67cabeb3..94d6fd9d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,7 +8,7 @@ A detailed description of Tsim is given in [arXiv:2604.01059](https://arxiv.org/ ## Quick Start -An introductory tutorial is available [here](demos/encoding_demo.ipynb). For many existing scripts, replacing `stim` with `tsim` should just work. Tsim mirrors the Stim API and supports all [Stim instructions](https://github.com/quantumlib/Stim/wiki/Stim-v1.13-Gate-Reference), plus the [non-Clifford instructions](#supported-instructions) listed below. +An introductory tutorial is available [here](demos/encoding_demo.ipynb). For many existing scripts, replacing `stim` with `tsim` should just work. Tsim mirrors the Stim API and supports all [Stim instructions](https://github.com/quantumlib/Stim/blob/v1.16.0/doc/gates.md), plus the [non-Clifford instructions](#supported-instructions) listed below. ```python import tsim @@ -106,7 +106,7 @@ Note that this feature should be used carefully. If detectors or observables are ## Supported Instructions -Tsim supports all [Stim instructions](https://github.com/quantumlib/Stim/wiki/Stim-v1.13-Gate-Reference). In addition, Tsim defines the following non-Clifford instructions. Angles are given in units of π (half-turns). +Tsim supports all [Stim instructions](https://github.com/quantumlib/Stim/blob/v1.16.0/doc/gates.md). In addition, Tsim defines the following non-Clifford instructions. Angles are given in units of π (half-turns). | Instruction | Targets | Action | |---|---|---| diff --git a/pyproject.toml b/pyproject.toml index 7b2d77b2..6aec5e51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "bloqade-tsim" -version = "0.1.5" -description = "A low-magic universal quantum circuit sampler based on ZX stabilizer rank decomposition" +version = "1.0.0" +description = "A fast sampler for noisy quantum circuits with non-Clifford gates, built on the ZX-calculus" authors = [ { name = "Rafael Haenel", email = "rhaenel@quera.com" }, { name = "Chen Zhao", email = "czhao@quera.com" }, @@ -19,7 +19,7 @@ keywords = [ "quantum-error-correction", ] classifiers = [ - "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", @@ -33,12 +33,12 @@ classifiers = [ ] dependencies = [ - "equinox>=0.11.0", + "equinox>=0.11.5", "ipywidgets>=8.0.0", "jax>=0.4.38", "lxml>=5.0.0", "numba>=0.61", - "numpy>=1.25.0", + "numpy>=2.0", "psutil>=5.9.0", "stim>=1.15.0", "stim-timeline-text>=0.1.0", @@ -47,13 +47,13 @@ dependencies = [ [project.urls] Homepage = "https://github.com/QuEraComputing/tsim" -Documentation = "https://queracomputing.github.io/tsim/latest" +Documentation = "https://tsim.mintlify.app/" Repository = "https://github.com/QuEraComputing/tsim" Issues = "https://github.com/QuEraComputing/tsim/issues" [project.optional-dependencies] cuda12 = ["jax[cuda12]>=0.4.38"] -cuda13 = ["jax[cuda13]>=0.6.0"] +cuda13 = ["jax[cuda13]>=0.7.0"] [build-system] requires = ["hatchling"] diff --git a/src/tsim/__init__.py b/src/tsim/__init__.py index 9453b7d1..590397c9 100644 --- a/src/tsim/__init__.py +++ b/src/tsim/__init__.py @@ -9,7 +9,7 @@ module is internal and may change without notice, even where it is importable. """ -__version__ = "0.1.5" +__version__ = "1.0.0" from tsim.circuit import Circuit as Circuit from tsim.sampler import ( diff --git a/src/tsim/circuit.py b/src/tsim/circuit.py index f4e25210..712626f3 100644 --- a/src/tsim/circuit.py +++ b/src/tsim/circuit.py @@ -2,7 +2,6 @@ from __future__ import annotations -import warnings from collections.abc import Mapping from typing import ( TYPE_CHECKING, @@ -42,7 +41,7 @@ {"MPP", "SPP", "SPP_DAG", "OBSERVABLE_INCLUDE", "TPP", "TPP_DAG", "R_PAULI"} ) -_DEPRECATED_DIAGRAM_TYPES: dict[str, Literal["zx", "zx-dets", "zx-meas"]] = { +_REMOVED_DIAGRAM_TYPES = { "pyzx": "zx", "pyzx-dets": "zx-dets", "pyzx-meas": "zx-meas", @@ -741,9 +740,6 @@ def diagram( "zx", "zx-dets", "zx-meas", - "pyzx", - "pyzx-dets", - "pyzx-meas", "timeline-svg", "timeslice-svg", "timeline-text", @@ -766,8 +762,6 @@ def diagram( used to compute probabilities of detectors and observables. "zx-meas": A zxparam D3 drawing of the reduced ZX graph that is used to compute probabilities of measurements. - "pyzx", "pyzx-dets", "pyzx-meas": Deprecated aliases for - "zx", "zx-dets" and "zx-meas". "timeline-svg": An SVG image of the operations applied by the circuit over time. Includes annotations showing the measurement record index that each measurement writes @@ -824,14 +818,11 @@ def diagram( viewer instead of as raw text. """ - if type in _DEPRECATED_DIAGRAM_TYPES: - replacement = _DEPRECATED_DIAGRAM_TYPES[type] - warnings.warn( - f"Diagram type {type!r} is deprecated; use {replacement!r} instead.", - DeprecationWarning, - stacklevel=2, + if type in _REMOVED_DIAGRAM_TYPES: + replacement = _REMOVED_DIAGRAM_TYPES[type] + raise ValueError( + f"Diagram type {type!r} was removed in Tsim 1.0; use {replacement!r}." ) - type = replacement if type in [ "timeline-svg", "timeslice-svg", diff --git a/test/unit/test_circuit.py b/test/unit/test_circuit.py index 954c3bc1..56f56313 100644 --- a/test/unit/test_circuit.py +++ b/test/unit/test_circuit.py @@ -1095,15 +1095,10 @@ def test_diagram_zx_with_zero_scale_draws(type: Literal["zx", "zx-meas", "zx-det @pytest.mark.parametrize("type", ["pyzx", "pyzx-dets", "pyzx-meas"]) -def test_pyzx_diagram_types_are_deprecated_aliases( - type: Literal["pyzx", "pyzx-dets", "pyzx-meas"], -): +def test_removed_pyzx_diagram_types_raise(type: str): replacement = type.replace("pyzx", "zx") - with pytest.warns( - DeprecationWarning, match=re.escape(f"use {replacement!r} instead") - ): - html = Circuit("H 0\nM 0").diagram(type=type) - assert "renderZx" in html._repr_html_() + with pytest.raises(ValueError, match=re.escape(f"use {replacement!r}")): + Circuit("H 0\nM 0").diagram(type=type) # type: ignore[arg-type] def test_append(): diff --git a/uv.lock b/uv.lock index 568762d2..ea706077 100644 --- a/uv.lock +++ b/uv.lock @@ -143,7 +143,7 @@ css = [ [[package]] name = "bloqade-tsim" -version = "0.1.5" +version = "1.0.0" source = { editable = "." } dependencies = [ { name = "equinox" }, @@ -209,14 +209,14 @@ doc = [ [package.metadata] requires-dist = [ - { name = "equinox", specifier = ">=0.11.0" }, + { name = "equinox", specifier = ">=0.11.5" }, { name = "ipywidgets", specifier = ">=8.0.0" }, { name = "jax", specifier = ">=0.4.38" }, { name = "jax", extras = ["cuda12"], marker = "extra == 'cuda12'", specifier = ">=0.4.38" }, - { name = "jax", extras = ["cuda13"], marker = "extra == 'cuda13'", specifier = ">=0.6.0" }, + { name = "jax", extras = ["cuda13"], marker = "extra == 'cuda13'", specifier = ">=0.7.0" }, { name = "lxml", specifier = ">=5.0.0" }, { name = "numba", specifier = ">=0.61" }, - { name = "numpy", specifier = ">=1.25.0" }, + { name = "numpy", specifier = ">=2.0" }, { name = "psutil", specifier = ">=5.9.0" }, { name = "stim", specifier = ">=1.15.0" }, { name = "stim-timeline-text", specifier = ">=0.1.0" },