Skip to content

Commit a1ebe5d

Browse files
arshpreetmaanaria-googler
authored andcommitted
Add GARI and generic detector layout support to decoder CLIs
Rebase the final PR #277 feature onto detector traversal semantics from PR #306. Remove the obsolete GARI order inversion so generated physical detector prefixes use detector_at_position directly. Co-authored-by: Aria Shahingohar <ariash@google.com>
1 parent bb26f60 commit a1ebe5d

9 files changed

Lines changed: 368 additions & 115 deletions

File tree

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,78 @@ Using a Detection Event File and Observable Flips:
144144
./tesseract --in events.01 --in-format 01 --obs_in obs.01 --obs-in-format 01 --dem surface_code.dem --out decoded.txt
145145
```
146146

147+
Tesseract also accepts explicit detector traversal orders in a JSON file. The
148+
file has the same list-of-lists form as Python's `TesseractConfig.det_orders`,
149+
and every inner list must be a complete permutation of the DEM detector IDs:
150+
151+
```json
152+
[[0, 2, 1, 3], [3, 1, 2, 0]]
153+
```
154+
155+
Pass it with `--detector-orders orders.json`. This replaces the orders normally
156+
generated by `--num-det-orders`, `--det-order-seed`, and the `--det-order-*`
157+
method flags.
158+
147159
Tesseract supports reading and writing from all of Stim's standard [output
148160
formats](https://github.com/quantumlib/Stim/blob/main/doc/result_formats.md).
149161

162+
### Decoding with GARI
163+
164+
Generate a GARI matrix DEM from a source circuit:
165+
166+
```bash
167+
python src/py/_tesseract_py_util/gari.py \
168+
--circuit circuit_file.stim \
169+
--prior xor \
170+
--out-dir gari_output
171+
```
172+
173+
This writes `gari_output/circuit_file_gari_xor.dem`. Its physical detector rows preserve the
174+
source detector IDs, and its added virtual rows form a suffix. GARI-aware detector orders can be
175+
written in the generic CLI format when reordering is wanted:
176+
177+
```python
178+
import json
179+
import stim
180+
from tesseract_decoder import demutil
181+
182+
circuit = stim.Circuit.from_file("circuit_file.stim")
183+
gari_dem = stim.DetectorErrorModel.from_file("gari_output/circuit_file_gari_xor.dem")
184+
orders = demutil.gari.build_detector_orders(circuit, gari_dem, num_det_orders=5)
185+
with open("gari_orders.json", "w") as f:
186+
json.dump(orders, f)
187+
```
188+
189+
Sample from the source circuit and decode with the generated DEM:
190+
191+
```bash
192+
./bazel-bin/src/tesseract \
193+
--circuit circuit_file.stim \
194+
--dem gari_output/circuit_file_gari_xor.dem \
195+
--detector-orders gari_orders.json \
196+
--sample-num-shots 100 \
197+
--sample-seed 1234 \
198+
--threads 1 \
199+
--pqlimit 1000000 \
200+
--beam 5 \
201+
--beam-climbing \
202+
--no-revisit-dets \
203+
--print-stats \
204+
--stats-out gari-stats.json
205+
```
206+
207+
The GARI matrix DEM is a decoding representation and must not be sampled. When a circuit and a
208+
larger DEM are supplied together, both command-line decoders read or sample the circuit's detector
209+
prefix and leave the DEM's virtual suffix zero. Their observable counts must agree. When reading a
210+
source-width event file, pass both `--circuit` and `--dem`; with `--dem` alone the input width is the
211+
full DEM width.
212+
213+
For matrix analysis, `gari.py --row-order block` writes a `_block.dem` file in the internal
214+
physical-X, physical-Z, virtual-Z, virtual-X row order. This research form does not accept source
215+
syndromes as a direct prefix. See
216+
[GARI transformed matrices](src/py/README.md#gari-transformed-matrices) for supported circuit
217+
conventions and Python API details.
218+
150219
### Performance Optimization
151220

152221
Here are some tips for improving performance:

docs/tutorial.ipynb

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -935,7 +935,7 @@
935935
"source": [
936936
"gari = tesseract_decoder.demutil.gari\n",
937937
"\n",
938-
"gari_dem, gari_layout = gari.circuit_to_gari(\n",
938+
"gari_dem = gari.circuit_to_gari(\n",
939939
" circuit,\n",
940940
" prior_function=gari.tesseract_xor_prior_probabilities,\n",
941941
")"
@@ -950,7 +950,8 @@
950950
"source": [
951951
"Sample detection events only from the original circuit. The GARI matrix DEM\n",
952952
"stores the transformed matrices for decoding and is not sampled. Copy the\n",
953-
"source syndrome into its physical rows; the added virtual entries stay zero."
953+
"source syndrome into the detector prefix; the added virtual entries stay\n",
954+
"zero."
954955
]
955956
},
956957
{
@@ -964,34 +965,26 @@
964965
"source": [
965966
"num_shots = 10\n",
966967
"gari_dets = np.zeros((num_shots, gari_dem.num_detectors), dtype=bool)\n",
967-
"gari_dets[:, gari_layout[\"source_to_gari\"]] = dets[:num_shots]"
968-
]
969-
},
970-
{
971-
"cell_type": "markdown",
972-
"id": "abc39564",
973-
"metadata": {
974-
"id": "gari-detector-order"
975-
},
976-
"source": [
977-
"The layout is physical-then-virtual. Setting `num_det_orders=0` selects one\n",
978-
"ascending detector order, so Tesseract processes the rows in that order."
968+
"gari_dets[:, :dets.shape[1]] = dets[:num_shots]"
979969
]
980970
},
981971
{
982972
"cell_type": "code",
983973
"execution_count": null,
984-
"id": "bc0c802c",
974+
"id": "3e397ee8",
985975
"metadata": {
986976
"id": "gari-decode-example"
987977
},
988978
"outputs": [],
989979
"source": [
990-
"short_beam = tesseract_decoder.make_tesseract_sinter_decoders_dict()[\n",
991-
" \"tesseract-short-beam\"\n",
992-
"]\n",
993-
"short_beam.num_det_orders = 0\n",
994-
"gari_decoder = short_beam.compile_decoder_for_dem(dem=gari_dem).decoder\n",
980+
"gari_config = tesseract.TesseractConfig(\n",
981+
" dem=gari_dem,\n",
982+
" det_beam=15,\n",
983+
" beam_climbing=True,\n",
984+
" no_revisit_dets=True,\n",
985+
" pqlimit=200_000,\n",
986+
")\n",
987+
"gari_decoder = gari_config.compile_decoder()\n",
995988
"predicted_observables = gari_decoder.decode_batch(gari_dets)\n",
996989
"logical_failures = np.count_nonzero(\n",
997990
" np.any(predicted_observables != obs[:num_shots], axis=1)\n",

docs/tutorial.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -358,31 +358,31 @@ def run_tesseract_decoder(decoder, dets, obs):
358358
# %% id="gari-transform-example"
359359
gari = tesseract_decoder.demutil.gari
360360

361-
gari_dem, gari_layout = gari.circuit_to_gari(
361+
gari_dem = gari.circuit_to_gari(
362362
circuit,
363363
prior_function=gari.tesseract_xor_prior_probabilities,
364364
)
365365

366366
# %% [markdown] id="gari-syndrome-layout"
367367
# Sample detection events only from the original circuit. The GARI matrix DEM
368368
# stores the transformed matrices for decoding and is not sampled. Copy the
369-
# source syndrome into its physical rows; the added virtual entries stay zero.
369+
# source syndrome into the detector prefix; the added virtual entries stay
370+
# zero.
370371

371372
# %% id="gari-sample-example"
372373
num_shots = 10
373374
gari_dets = np.zeros((num_shots, gari_dem.num_detectors), dtype=bool)
374-
gari_dets[:, gari_layout["source_to_gari"]] = dets[:num_shots]
375-
376-
# %% [markdown] id="gari-detector-order"
377-
# The layout is physical-then-virtual. Setting `num_det_orders=0` selects one
378-
# ascending detector order, so Tesseract processes the rows in that order.
375+
gari_dets[:, :dets.shape[1]] = dets[:num_shots]
379376

380377
# %% id="gari-decode-example"
381-
short_beam = tesseract_decoder.make_tesseract_sinter_decoders_dict()[
382-
"tesseract-short-beam"
383-
]
384-
short_beam.num_det_orders = 0
385-
gari_decoder = short_beam.compile_decoder_for_dem(dem=gari_dem).decoder
378+
gari_config = tesseract.TesseractConfig(
379+
dem=gari_dem,
380+
det_beam=15,
381+
beam_climbing=True,
382+
no_revisit_dets=True,
383+
pqlimit=200_000,
384+
)
385+
gari_decoder = gari_config.compile_decoder()
386386
predicted_observables = gari_decoder.decode_batch(gari_dets)
387387
logical_failures = np.count_nonzero(
388388
np.any(predicted_observables != obs[:num_shots], axis=1)

src/py/README.md

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -707,29 +707,34 @@ nice_calibrated_dem = demutil.regeneralize_spatial_dem(
707707
#### GARI transformed matrices
708708

709709
`demutil.gari.circuit_to_gari` converts a supported correlated CSS Stim
710-
circuit into a GARI matrix DEM and companion layout for Tesseract. It
711-
generates a flattened source DEM with `decompose_errors=False`. Detectors must
712-
follow the repository's fourth-coordinate convention: values `0``2` identify
713-
X detectors and `3``5` identify Z detectors.
710+
circuit into a GARI matrix DEM. It generates a flattened source DEM with
711+
`decompose_errors=False`. Detectors must follow the repository's
712+
fourth-coordinate convention: values `0``2` identify X detectors and `3``5`
713+
identify Z detectors.
714714

715715
```python
716716
import stim
717717
from tesseract_decoder import demutil
718718

719719
circuit = stim.Circuit.from_file("circuitFile.stim")
720-
gari_dem, gari_layout = demutil.gari.circuit_to_gari(
720+
gari_dem = demutil.gari.circuit_to_gari(
721721
circuit,
722722
prior_function=demutil.gari.tesseract_xor_prior_probabilities,
723723
)
724724
```
725725

726-
`circuit_to_gari` returns:
726+
The returned DEM preserves the source detector IDs as a prefix and appends the
727+
virtual detector rows. For matrix analysis,
728+
`circuit_to_gari(..., row_order="block")` instead emits the internal
729+
`[physical X, physical Z, virtual Z, virtual X]` row order. This research form
730+
does not accept source syndromes as a direct prefix.
727731

728-
* `gari_dem`: the augmented detector and logical matrices stored using Stim
729-
DEM syntax.
730-
* `gari_layout`: a `tesseract.gari_layout.v1` dictionary containing the source
731-
and GARI detector counts, the `source_to_gari` detector mapping, and the
732-
`physical_then_virtual` detector order.
732+
`demutil.gari.build_detector_orders(circuit, gari_dem, num_det_orders, ...)`
733+
uses the source circuit to build BFS, coordinate, or index orders and then
734+
appends the virtual detector IDs. The resulting list has the same format as
735+
`TesseractConfig.det_orders` and the Tesseract CLI's `--detector-orders` JSON
736+
file. It applies to the default source-aligned GARI DEM, not the research-only
737+
block form.
733738

734739
Related public APIs:
735740

@@ -738,13 +743,14 @@ Related public APIs:
738743
* `demutil.gari.GariTransform` is passed to prior-policy callbacks. It exposes
739744
the transformed detector and logical matrices, the `U` and `V` projection
740745
matrices, the source `e_Z`, `e_X`, and `e_Y` column indices, and the source
741-
detector mapping.
746+
detector mapping into the internal block rows.
742747
* `paper_prior_probabilities`, `tesseract_xor_prior_probabilities`, and
743748
`tesseract_lp_max_barred_cost_prior_probabilities` return one probability for
744749
each transformed GARI column. A user-defined prior can follow the same
745750
callable interface.
746751

747752
The returned GARI matrix DEM stores transformed matrices for decoding and must
748-
not be sampled. Sample from the original circuit and use the companion layout
749-
to place its physical syndrome. See the
753+
not be sampled. Sample from the original circuit, copy its syndrome into the
754+
beginning of a zero-filled GARI syndrome, and leave the virtual suffix zero.
755+
See the
750756
[GARI tutorial](../../docs/tutorial.ipynb) for a complete decoding example.

0 commit comments

Comments
 (0)