Problem
DemSampler exposes two batch-sampling entry points whose names are nearly identical but whose return types are swapped relative to what the names suggest:
DemSampler.sample_batch(num_shots, seed=None) returns raw Python data: a tuple (list[list[bool]], list[list[bool]]) of detection events and observable flips. It does not return a SampleBatch (python/pecos-rslib/src/fault_tolerance_bindings.rs, fn sample_batch).
DemSampler.generate_samples(num_shots, seed=None) does return a SampleBatch, held in Rust memory for feeding multiple decoders without copying shots into Python (fn generate_samples).
The method named after the SampleBatch type is the one that does not produce it. This violates the project naming convention that user-facing names describe the output, and it is an easy trap for anyone reading example code (the docs use generate_samples, docstrings elsewhere use sample_batch).
Root cause
The duplication exists because SampleBatch has no bulk accessors. Its Python surface is per-shot and decoder-oriented (extract_syndrome(shot, buf), extract_obs_mask(shot), extract_obs_mask_wide(shot)), so a user who wants the sampled bits in Python cannot get them out of a SampleBatch. A second sampling entry point that materializes Python lists grew instead of a conversion on the batch object.
Proposal
- Add bulk accessors to
SampleBatch, e.g. detector_events() and observable_flips() (numpy arrays or lists), so any data the batch holds can be extracted when needed. This also gives a natural home for future extractions.
- Make
SampleBatch-returning sampling the single entry point on DemSampler.
- Migrate the raw-list
sample_batch callers (python/quantum-pecos/src/pecos/qec/surface/decode.py, tests/qec/test_dem_equivalence.py, tests/qec/test_dem_sampler.py) to the batch + accessor path, then deprecate/remove DemSampler.sample_batch. The ~40 existing generate_samples call sites are unaffected.
- Decide the final name as part of this work (e.g. keep
generate_samples, or rename so the SampleBatch-returning method is the one called sample_batch), applying the describe-the-output rule.
Related surfaces to keep consistent in the same pass:
DemSampler.sample_batch_with_pauli_masks(...) also returns raw lists; it should either return a SampleBatch or be folded into the consolidated API.
ParsedDem.sample_batch(...) has the same raw-list shape and should follow whatever convention this issue settles on.
Documentation and discoverability
The Rust binding docstrings for SampleBatch are in good shape (class-level purpose and example; per-method Args/Returns; the narrow-vs-wide observable semantics of get_observable_mask vs get_observable_mask_wide are documented and fail loud past 64 observables). The gaps are in discoverability, and any API consolidated here should close them in the same pass:
- Type stub:
python/pecos-rslib/pecos_rslib.pyi contains no entry for SampleBatch — or for DemSampler or DetectorErrorModel; the qec submodule is absent from the stub entirely. There is no autocomplete or type checking for any of this API. New bulk accessors and the consolidated sampling entry point should ship with stub entries (and the broader qec stub gap is worth closing while in the file).
- User guide:
SampleBatch appears only twice in the docs — decode_count in docs/user-guide/dem-from-guppy.md and the constructor in docs/user-guide/qec-guppy.md. The decoder-comparison methods that are the point of the class (decode_each, compare_decoders, decode_stats, decode_count_parallel, decode_stats_parallel, decode_count_batch) are not documented in any docs page. The consolidated API should get a user-guide section covering sampling, bulk extraction, and decoder comparison in one place.
- Docstring rendering: escaped-backtick artifacts (e.g.
`decoder_type`) leak from clippy doc-markdown fixes into the Python help() output; clean these up when touching the docstrings.
Problem
DemSamplerexposes two batch-sampling entry points whose names are nearly identical but whose return types are swapped relative to what the names suggest:DemSampler.sample_batch(num_shots, seed=None)returns raw Python data: a tuple(list[list[bool]], list[list[bool]])of detection events and observable flips. It does not return aSampleBatch(python/pecos-rslib/src/fault_tolerance_bindings.rs,fn sample_batch).DemSampler.generate_samples(num_shots, seed=None)does return aSampleBatch, held in Rust memory for feeding multiple decoders without copying shots into Python (fn generate_samples).The method named after the
SampleBatchtype is the one that does not produce it. This violates the project naming convention that user-facing names describe the output, and it is an easy trap for anyone reading example code (the docs usegenerate_samples, docstrings elsewhere usesample_batch).Root cause
The duplication exists because
SampleBatchhas no bulk accessors. Its Python surface is per-shot and decoder-oriented (extract_syndrome(shot, buf),extract_obs_mask(shot),extract_obs_mask_wide(shot)), so a user who wants the sampled bits in Python cannot get them out of aSampleBatch. A second sampling entry point that materializes Python lists grew instead of a conversion on the batch object.Proposal
SampleBatch, e.g.detector_events()andobservable_flips()(numpy arrays or lists), so any data the batch holds can be extracted when needed. This also gives a natural home for future extractions.SampleBatch-returning sampling the single entry point onDemSampler.sample_batchcallers (python/quantum-pecos/src/pecos/qec/surface/decode.py,tests/qec/test_dem_equivalence.py,tests/qec/test_dem_sampler.py) to the batch + accessor path, then deprecate/removeDemSampler.sample_batch. The ~40 existinggenerate_samplescall sites are unaffected.generate_samples, or rename so theSampleBatch-returning method is the one calledsample_batch), applying the describe-the-output rule.Related surfaces to keep consistent in the same pass:
DemSampler.sample_batch_with_pauli_masks(...)also returns raw lists; it should either return aSampleBatchor be folded into the consolidated API.ParsedDem.sample_batch(...)has the same raw-list shape and should follow whatever convention this issue settles on.Documentation and discoverability
The Rust binding docstrings for
SampleBatchare in good shape (class-level purpose and example; per-method Args/Returns; the narrow-vs-wide observable semantics ofget_observable_maskvsget_observable_mask_wideare documented and fail loud past 64 observables). The gaps are in discoverability, and any API consolidated here should close them in the same pass:python/pecos-rslib/pecos_rslib.pyicontains no entry forSampleBatch— or forDemSamplerorDetectorErrorModel; theqecsubmodule is absent from the stub entirely. There is no autocomplete or type checking for any of this API. New bulk accessors and the consolidated sampling entry point should ship with stub entries (and the broaderqecstub gap is worth closing while in the file).SampleBatchappears only twice in the docs —decode_countindocs/user-guide/dem-from-guppy.mdand the constructor indocs/user-guide/qec-guppy.md. The decoder-comparison methods that are the point of the class (decode_each,compare_decoders,decode_stats,decode_count_parallel,decode_stats_parallel,decode_count_batch) are not documented in any docs page. The consolidated API should get a user-guide section covering sampling, bulk extraction, and decoder comparison in one place.`decoder_type`) leak from clippy doc-markdown fixes into the Pythonhelp()output; clean these up when touching the docstrings.