@@ -284,114 +284,92 @@ def get_state_prep_diagnostic_tasks(
284284 sinter .Task (
285285 circuit = noise_model_family (error_rate ).noisy_circuit (diagnostic_circuit ),
286286 postselection_mask = postselection_mask_bit_packed ,
287- json_metadata = {"p" : error_rate },
287+ json_metadata = {"p" : error_rate , "flags" : detector_record . get_events ( "flag" ) },
288288 )
289289 for error_rate in error_rates
290290 ]
291291
292292
293- def get_logical_error_and_discard_rates (
294- code : codes .QuditCode ,
295- state_prep_circuit : stim .Circuit ,
296- error_rates : Sequence [float ] | npt .NDArray [np .floating ],
297- noise_model_family : Callable [[float ], NoiseModel ] = DepolarizingNoiseModel ,
293+ def get_logical_error_and_discard_rate (
294+ circuit_or_dem : stim .Circuit | stim .DetectorErrorModel ,
295+ sinter_decoder : sinter .Decoder ,
298296 * ,
299- sinter_decoder : sinter .Decoder | Sequence [sinter .Decoder ],
300- num_samples : int | Sequence [int ],
301- observables : npt .NDArray [np .int_ ]
302- | Sequence [Sequence [int ]]
303- | Sequence [stim .PauliString ]
304- | None = None ,
305- post_select_on_flags : bool = False ,
306- skip_validation : bool = False ,
307- ) -> tuple [npt .NDArray [np .floating ], npt .NDArray [np .floating ]]:
308- """Compute logical error rates of the provided logical state prep circuit for the provided code.
309-
310- The first len(code) qubits addressed by the circuit must be the data qubits of the code.
297+ num_samples : int ,
298+ flags : Sequence [int ] | None = None ,
299+ ) -> tuple [float , float ]:
300+ """Compute a logical error rate and discard rate from samples of the provided cirucit.
311301
312302 Each logical error rate is a fraction of the (possibly post-selected) shots in which observable
313303 flips are predicted incorrectly by the provided decoder.
314304
315- This method is provided as an alternative to get_state_prep_diagnostic_tasks, which currently
316- cannot support post-selection due to a sinter bug: https://github.com/quantumlib/Stim/pull/844
317- Once this bug is fixed, it is recommended to instead use get_state_prep_diagnostic_tasks.
305+ This method is provided as an alternative to sinter, which currently cannot support post
306+ selection due to an outstanding bug: https://github.com/quantumlib/Stim/pull/844
307+ Once the bug is fixed, it is recommended to instead build a sinter.Task and call sinter.collect.
308+
309+ The sinter.Task would use the post-selection flags as follows:
310+ postselection_mask_bits = np.zeros(circuit_or_dem.num_detectors, dtype=int)
311+ postselection_mask_bits[flags] = 1
312+ postselection_mask = np.packbits(postselection_mask, bitorder="little")
313+ task = sinter.Task(
314+ circuit=circuit,
315+ postselection_mask=postselection_mask_bit_packed,
316+ )
317+ Sampling data would then be collected with:
318+ stats = sinter.collect(
319+ tasks=[task], # or more maybe more tasks
320+ decoders=["custom"],
321+ custom_decoders={"custom": sinter_decoder},
322+ num_shots=num_samples,
323+ # other options such as num_workers=os.cpu_count() or max_errors=100,
324+ )
318325
319326 Args:
320- code: The code whose logical state is prepared by the provided state_prep_circuit.
321- state_prep_circuit: A circuit that prepares a logical state of the provided code.
322- error_rates: The error rates at which to evaluate the provided family of noise models.
323- noise_model_family: A single-parameter family of noise models for adding noise to circuits.
324- Default: qldpc.circuits.DepolarizingNoiseModel.
327+ circuit_or_dem: The circuit or detector error model we wish to sample.
328+ sinter_decoder: The circuit-level decoder used to predict observable flips.
325329
326330 Keyword args:
327- sinter_decoder: The circuit-level decoder used to predict observable flips, or a sequence of
328- circuit-level decoders (one for each error rate).
329- num_samples: The number of times to sample each noisy circuit, or a sequence of sample
330- numbers (one for each error rate).
331- observables: The observables that should stabilize the prepared state, or (by default) None.
332- If not None, the observables should be either a a matrix of symplectic row vectors, with
333- shape (num_observables, 2 * len(code)), or a sequence of Pauli strings supported on the
334- data qubits of the code. If None, observables are determined automatically by finding
335- all logical Pauli operators of the code that stabilize the state prepared by
336- state_prep_circuit.
337- post_select_on_flags: If True, post-select samples on nonzero measurement outcomes in the
338- provided state_prep_circuit. Default: False.
339- skip_validation: If True, skip the check to assert that the provided circuit prepares a
340- logical state fo the provided code.
331+ num_samples: The number of times to the circuit_or_dem.
332+ flags: The detectors in circuit_or_dem to post-select on.
341333
342334 Returns:
343- An array of estimated logical error rates.
344- An array of discard rates, or the fraction of shots (for each simulated error rate) that
345- were discarded due to post-selection on state prep flags. If post_select_on_flags is
346- False, this array contains only zeros.
335+ A fraction of samples in which at least one observable was decoded incorrectly.
336+ A fraction of samples that were discarded due to post-selection.
347337 """
348- diagnostic_circuit , detector_record = get_state_prep_diagnostic_circuit (
349- code , state_prep_circuit , observables = observables
350- )
351- if not isinstance (num_samples , Sequence ):
352- num_samples = [num_samples ] * len (error_rates )
353- if not isinstance (sinter_decoder , Sequence ):
354- sinter_decoder = [sinter_decoder ] * len (error_rates )
355-
356- logical_error_rates = np .zeros (len (error_rates ), dtype = float )
357- discard_rates = np .zeros (len (error_rates ), dtype = float )
358- for pp , error_rate in enumerate (error_rates ):
359- # sample detector and observable flips in the circuit
360- noise_model = noise_model_family (error_rate )
361- noisy_circuit = noise_model .noisy_circuit (diagnostic_circuit )
362- dem_arrays = decoders .DetectorErrorModelArrays (
363- noisy_circuit .detector_error_model (), simplify = True
364- )
365- dem = dem_arrays .to_dem ()
366- sampler = dem .compile_sampler ()
367- det_data , obs_data , err_data = sampler .sample (shots = num_samples [pp ])
368-
369- # if applicable, post-select on flag detectors
370- if post_select_on_flags :
371- # identify shots and detectors to remove
372- flag_dets = detector_record .get_events ("flag" )
373- shot_mask = ~ np .any (det_data [:, flag_dets ], axis = 1 )
374- detector_mask = np .ones (dem .num_detectors , dtype = bool )
375- detector_mask [flag_dets ] = False
376-
377- # post-select simulated data
378- det_data = det_data [shot_mask ][:, detector_mask ]
379- obs_data = obs_data [shot_mask ]
380- dem = dem_arrays .post_selected_on (detector_record .get_events ("flag" )).to_dem ()
381-
382- # record the fraction of shots that were discarded
383- discard_rates [pp ] = 1 - np .sum (shot_mask ) / len (shot_mask )
384-
385- # compile a decoder for this detector error model
386- compiled_sinter_decoder = sinter_decoder [pp ].compile_decoder_for_dem (dem )
387-
388- # decode and compute the logical error rate
389- predicted_flips = compiled_sinter_decoder .decode_shots (det_data )
390- obs_flips = obs_data ^ predicted_flips
391- failures = np .any (obs_flips , axis = 1 )
392- logical_error_rates [pp ] = np .sum (failures ) / len (failures )
393-
394- return logical_error_rates , discard_rates
338+ # build and simplify a detector error model
339+ dem_arrays = decoders .DetectorErrorModelArrays (circuit_or_dem , simplify = True )
340+ dem = dem_arrays .to_dem ()
341+
342+ # sample detector and observable flips in the circuit
343+ sampler = dem .compile_sampler ()
344+ det_data , obs_data , err_data = sampler .sample (shots = num_samples )
345+
346+ # if applicable, post-select on flag detectors
347+ if flags :
348+ # identify shots and detectors to remove
349+ shot_mask = ~ np .any (det_data [:, flags ], axis = 1 )
350+ detector_mask = np .ones (dem .num_detectors , dtype = bool )
351+ detector_mask [flags ] = False
352+
353+ # post-select simulated data
354+ det_data = det_data [shot_mask ][:, detector_mask ]
355+ obs_data = obs_data [shot_mask ]
356+ dem = dem_arrays .post_selected_on (flags ).to_dem ()
357+
358+ # record the fraction of shots that were discarded
359+ discard_rate = 1 - np .sum (shot_mask ) / len (shot_mask )
360+ else : # pragma: no cover
361+ discard_rate = 0
362+
363+ # compile a decoder for this detector error model
364+ compiled_sinter_decoder = sinter_decoder .compile_decoder_for_dem (dem )
365+
366+ # decode and compute the logical error rate
367+ predicted_flips = compiled_sinter_decoder .decode_shots (det_data )
368+ obs_flips = obs_data ^ predicted_flips
369+ failures = np .any (obs_flips , axis = 1 )
370+ logical_error_rate = np .sum (failures ) / len (failures )
371+
372+ return logical_error_rate , discard_rate
395373
396374
397375def _assert_logical_state_preparation (
0 commit comments