Skip to content

sampler

Compiled samplers for measurements and detectors.

CompiledDetectorSampler

CompiledDetectorSampler(
    circuit: Circuit,
    *,
    strategy: DecompositionStrategy = "cat5",
    seed: int | None = None
)

Bases: _CompiledSamplerBase


              flowchart TD
              tsim.sampler.CompiledDetectorSampler[CompiledDetectorSampler]
              tsim.sampler._CompiledSamplerBase[_CompiledSamplerBase]

                              tsim.sampler._CompiledSamplerBase --> tsim.sampler.CompiledDetectorSampler
                


              click tsim.sampler.CompiledDetectorSampler href "" "tsim.sampler.CompiledDetectorSampler"
              click tsim.sampler._CompiledSamplerBase href "" "tsim.sampler._CompiledSamplerBase"
            

Samples detector and observable outcomes from a quantum circuit.

Parameters:

Name Type Description Default
circuit Circuit

The quantum circuit to compile.

required
strategy DecompositionStrategy

Stabilizer rank decomposition strategy. Must be one of "cat5", "bss", "cutting".

'cat5'
seed int | None

Random seed for the sampler. IMPORTANT: Currently, the sampler will only produce deterministic samples for fixed batch size and fixed reference sample settings. If deterministic samples are needed, the batch size should be set manually.

None
Source code in src/tsim/sampler.py
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
def __init__(
    self,
    circuit: Circuit,
    *,
    strategy: DecompositionStrategy = "cat5",
    seed: int | None = None,
):
    """Create a detector sampler.

    Args:
        circuit: The quantum circuit to compile.
        strategy: Stabilizer rank decomposition strategy.
            Must be one of "cat5", "bss", "cutting".
        seed: Random seed for the sampler. IMPORTANT: Currently, the sampler
            will only produce deterministic samples for fixed batch size and
            fixed reference sample settings. If deterministic samples are
            needed, the batch size should be set manually.

    """
    super().__init__(
        circuit,
        sample_detectors=True,
        mode="sequential",
        seed=seed,
        strategy=strategy,
    )

sample

sample(
    shots: int,
    *,
    batch_size: int | None = None,
    prepend_observables: bool = False,
    append_observables: bool = False,
    separate_observables: Literal[True],
    bit_packed: bool = False,
    use_detector_reference_sample: bool = False,
    use_observable_reference_sample: bool = False
) -> tuple[np.ndarray, np.ndarray]
sample(
    shots: int,
    *,
    batch_size: int | None = None,
    prepend_observables: bool = False,
    append_observables: bool = False,
    separate_observables: Literal[False] = False,
    bit_packed: bool = False,
    use_detector_reference_sample: bool = False,
    use_observable_reference_sample: bool = False
) -> np.ndarray
sample(
    shots: int,
    *,
    batch_size: int | None = None,
    prepend_observables: bool = False,
    append_observables: bool = False,
    separate_observables: bool = False,
    bit_packed: bool = False,
    use_detector_reference_sample: bool = False,
    use_observable_reference_sample: bool = False
) -> np.ndarray | tuple[np.ndarray, np.ndarray]

Return detector samples from the circuit.

The circuit must define the detectors using DETECTOR instructions. Observables defined by OBSERVABLE_INCLUDE instructions can also be included in the results as honorary detectors.

Parameters:

Name Type Description Default
shots int

The number of times to sample every detector in the circuit.

required
batch_size int | None

The number of samples to process in each batch. Defaults to None, which automatically chooses a batch size based on available memory. When using a GPU, setting this explicitly can help fully utilize VRAM for maximum performance. NOTE: Changing the batch size will affect reproducibility even with a fixed seed.

None
separate_observables bool

Defaults to False. When set to True, the return value is a (detection_events, observable_flips) tuple instead of a flat detection_events array.

False
prepend_observables bool

Defaults to false. When set, observables are included with the detectors and are placed at the start of the results.

False
append_observables bool

Defaults to false. When set, observables are included with the detectors and are placed at the end of the results.

False
bit_packed bool

Defaults to false. When set, results are bit-packed.

False
use_detector_reference_sample bool

Defaults to False. When True, a noiseless reference sample is computed and XORed with detector outcomes so that results represent deviations from the noiseless baseline. This should only be used when detectors are deterministic. Otherwise, it can unpredictably change the results.

False
use_observable_reference_sample bool

Defaults to False. When True, a noiseless reference sample is computed and XORed with observable outcomes so that results represent deviations from the noiseless baseline. This should only be used when observables are deterministic. Otherwise, it can unpredictably change the results.

False

Returns:

Type Description
ndarray | tuple[ndarray, ndarray]

A numpy array or tuple of numpy arrays containing the samples.

Raises:

Type Description
ValueError

If separate_observables is combined with prepend_observables or append_observables.

Source code in src/tsim/sampler.py
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
def sample(
    self,
    shots: int,
    *,
    batch_size: int | None = None,
    prepend_observables: bool = False,
    append_observables: bool = False,
    separate_observables: bool = False,
    bit_packed: bool = False,
    use_detector_reference_sample: bool = False,
    use_observable_reference_sample: bool = False,
) -> np.ndarray | tuple[np.ndarray, np.ndarray]:
    """Return detector samples from the circuit.

    The circuit must define the detectors using DETECTOR instructions. Observables
    defined by OBSERVABLE_INCLUDE instructions can also be included in the results
    as honorary detectors.

    Args:
        shots: The number of times to sample every detector in the circuit.
        batch_size: The number of samples to process in each batch. Defaults to
            None, which automatically chooses a batch size based on available
            memory. When using a GPU, setting this explicitly can help fully
            utilize VRAM for maximum performance. NOTE: Changing the batch size
            will affect reproducibility even with a fixed seed.
        separate_observables: Defaults to False. When set to True, the return value
            is a (detection_events, observable_flips) tuple instead of a flat
            detection_events array.
        prepend_observables: Defaults to false. When set, observables are included
            with the detectors and are placed at the start of the results.
        append_observables: Defaults to false. When set, observables are included
            with the detectors and are placed at the end of the results.
        bit_packed: Defaults to false. When set, results are bit-packed.
        use_detector_reference_sample: Defaults to False. When True, a noiseless
            reference sample is computed and XORed with detector outcomes so that
            results represent deviations from the noiseless baseline. This should
            only be used when detectors are deterministic. Otherwise, it can
            unpredictably change the results.
        use_observable_reference_sample: Defaults to False. When True, a noiseless
            reference sample is computed and XORed with observable outcomes so that
            results represent deviations from the noiseless baseline. This should
            only be used when observables are deterministic. Otherwise, it can
            unpredictably change the results.

    Returns:
        A numpy array or tuple of numpy arrays containing the samples.

    Raises:
        ValueError: If ``separate_observables`` is combined with
            ``prepend_observables`` or ``append_observables``.

    """
    if separate_observables and (prepend_observables or append_observables):
        raise ValueError(
            "Can't specify separate_observables=True with "
            "append_observables=True or prepend_observables=True"
        )

    compute_reference = (
        use_detector_reference_sample or use_observable_reference_sample
    )

    if compute_reference:
        samples, reference = self._sample_batches(
            shots, batch_size, compute_reference=True
        )
        num_detectors = self._num_detectors
        if use_detector_reference_sample:
            samples[:, :num_detectors] ^= reference[:num_detectors]
        if use_observable_reference_sample:
            samples[:, num_detectors:] ^= reference[num_detectors:]
    else:
        samples = self._sample_batches(shots, batch_size)

    num_detectors = self._num_detectors
    det_samples = samples[:, :num_detectors]
    obs_samples = samples[:, num_detectors:]

    if prepend_observables and append_observables:
        combined = np.concatenate([obs_samples, det_samples, obs_samples], axis=1)
        return _maybe_bit_pack(combined, bit_packed=bit_packed)
    if append_observables:
        return _maybe_bit_pack(samples, bit_packed=bit_packed)
    if prepend_observables:
        combined = np.concatenate([obs_samples, det_samples], axis=1)
        return _maybe_bit_pack(combined, bit_packed=bit_packed)
    if separate_observables:
        return (
            _maybe_bit_pack(det_samples, bit_packed=bit_packed),
            _maybe_bit_pack(obs_samples, bit_packed=bit_packed),
        )

    return _maybe_bit_pack(det_samples, bit_packed=bit_packed)

CompiledMeasurementSampler

CompiledMeasurementSampler(
    circuit: Circuit,
    *,
    strategy: DecompositionStrategy = "cat5",
    seed: int | None = None
)

Bases: _CompiledSamplerBase


              flowchart TD
              tsim.sampler.CompiledMeasurementSampler[CompiledMeasurementSampler]
              tsim.sampler._CompiledSamplerBase[_CompiledSamplerBase]

                              tsim.sampler._CompiledSamplerBase --> tsim.sampler.CompiledMeasurementSampler
                


              click tsim.sampler.CompiledMeasurementSampler href "" "tsim.sampler.CompiledMeasurementSampler"
              click tsim.sampler._CompiledSamplerBase href "" "tsim.sampler._CompiledSamplerBase"
            

Samples measurement outcomes from a quantum circuit.

Uses sequential decomposition [0, 1, 2, ..., n] where: - compiled_scalar_graphs[0]: normalization (0 outputs plugged) - compiled_scalar_graphs[i]: cumulative probability up to bit i

Parameters:

Name Type Description Default
circuit Circuit

The quantum circuit to compile.

required
strategy DecompositionStrategy

Stabilizer rank decomposition strategy. Must be one of "cat5", "bss", "cutting".

'cat5'
seed int | None

Random seed for the sampler. IMPORTANT: Currently, the sampler will only produce deterministic samples for fixed batch size. If deterministic samples are needed, the batch size should be set manually.

None
Source code in src/tsim/sampler.py
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
def __init__(
    self,
    circuit: Circuit,
    *,
    strategy: DecompositionStrategy = "cat5",
    seed: int | None = None,
):
    """Create a measurement sampler.

    Args:
        circuit: The quantum circuit to compile.
        strategy: Stabilizer rank decomposition strategy.
            Must be one of "cat5", "bss", "cutting".
        seed: Random seed for the sampler. IMPORTANT: Currently, the sampler
            will only produce deterministic samples for fixed batch size. If
            deterministic samples are needed, the batch size should be set
            manually.

    """
    super().__init__(
        circuit,
        sample_detectors=False,
        mode="sequential",
        seed=seed,
        strategy=strategy,
    )

sample

sample(
    shots: int, *, batch_size: int | None = None
) -> np.ndarray

Sample measurement outcomes from the circuit.

Parameters:

Name Type Description Default
shots int

The number of times to sample every measurement in the circuit.

required
batch_size int | None

The number of samples to process in each batch. Defaults to None, which automatically chooses a batch size based on available memory. When using a GPU, setting this explicitly can help fully utilize VRAM for maximum performance. NOTE: Changing the batch size will affect reproducibility even with a fixed seed.

None

Returns:

Type Description
ndarray

A numpy array containing the measurement samples.

Source code in src/tsim/sampler.py
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
def sample(self, shots: int, *, batch_size: int | None = None) -> np.ndarray:
    """Sample measurement outcomes from the circuit.

    Args:
        shots: The number of times to sample every measurement in the circuit.
        batch_size: The number of samples to process in each batch. Defaults to
            None, which automatically chooses a batch size based on available
            memory. When using a GPU, setting this explicitly can help fully
            utilize VRAM for maximum performance. NOTE: Changing the batch size
            will affect reproducibility even with a fixed seed.

    Returns:
        A numpy array containing the measurement samples.

    """
    return self._sample_batches(shots, batch_size)

CompiledStateProbs

CompiledStateProbs(
    circuit: Circuit,
    *,
    sample_detectors: bool = False,
    strategy: DecompositionStrategy = "cat5",
    seed: int | None = None
)

Bases: _CompiledSamplerBase


              flowchart TD
              tsim.sampler.CompiledStateProbs[CompiledStateProbs]
              tsim.sampler._CompiledSamplerBase[_CompiledSamplerBase]

                              tsim.sampler._CompiledSamplerBase --> tsim.sampler.CompiledStateProbs
                


              click tsim.sampler.CompiledStateProbs href "" "tsim.sampler.CompiledStateProbs"
              click tsim.sampler._CompiledSamplerBase href "" "tsim.sampler._CompiledSamplerBase"
            

Computes measurement probabilities for a given state.

Uses joint decomposition [0, n] where: - compiled_scalar_graphs[0]: normalization (0 outputs plugged) - compiled_scalar_graphs[1]: full joint probability (all outputs plugged)

Parameters:

Name Type Description Default
circuit Circuit

The quantum circuit to compile.

required
sample_detectors bool

If True, compute detector/observable probabilities.

False
strategy DecompositionStrategy

Stabilizer rank decomposition strategy. Must be one of "cat5", "bss", "cutting".

'cat5'
seed int | None

Random seed. If None, a random seed is generated. Note that deterministic results are only guaranteed for a fixed batch size.

None
Source code in src/tsim/sampler.py
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
def __init__(
    self,
    circuit: Circuit,
    *,
    sample_detectors: bool = False,
    strategy: DecompositionStrategy = "cat5",
    seed: int | None = None,
):
    """Create a probability estimator.

    Args:
        circuit: The quantum circuit to compile.
        sample_detectors: If True, compute detector/observable probabilities.
        strategy: Stabilizer rank decomposition strategy.
            Must be one of "cat5", "bss", "cutting".
        seed: Random seed. If None, a random seed is generated. Note that
            deterministic results are only guaranteed for a fixed batch size.

    """
    super().__init__(
        circuit,
        sample_detectors=sample_detectors,
        mode="joint",
        seed=seed,
        strategy=strategy,
    )

probability_of

probability_of(
    state: ndarray, *, batch_size: int
) -> np.ndarray

Compute probabilities for a batch of error samples given a measurement state.

Parameters:

Name Type Description Default
state ndarray

The measurement outcome state to compute probability for.

required
batch_size int

Number of error samples to use for estimation.

required

Returns:

Type Description
ndarray

Array of probabilities P(state | error_sample) for each error sample.

Source code in src/tsim/sampler.py
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
def probability_of(self, state: np.ndarray, *, batch_size: int) -> np.ndarray:
    """Compute probabilities for a batch of error samples given a measurement state.

    Args:
        state: The measurement outcome state to compute probability for.
        batch_size: Number of error samples to use for estimation.

    Returns:
        Array of probabilities P(state | error_sample) for each error sample.

    """
    if batch_size < 1:
        raise ValueError(f"batch_size must be at least 1, got {batch_size}")
    expected_outputs = self._program.num_outputs
    if state.shape != (expected_outputs,):
        raise ValueError(
            f"state must have shape ({expected_outputs},), got {state.shape}"
        )
    f_samples = jnp.asarray(self._channel_sampler.sample(batch_size))
    p_norm = jnp.ones(batch_size)
    p_joint = jnp.ones(batch_size)

    if len(self._program.direct_f_indices) > 0:
        direct_bits = (
            f_samples[:, self._program.direct_f_indices].astype(jnp.bool_)
            ^ self._program.direct_flips
        )
        n_direct = len(self._program.direct_f_indices)
        targets = state[self._program.output_order[:n_direct]]
        p_joint = p_joint * (direct_bits == targets).all(axis=1)

    for component in self._program.components:
        assert len(component.compiled_scalar_graphs) == 2

        f_selected = f_samples[:, component.f_selection]

        norm_circuit, joint_circuit = component.compiled_scalar_graphs

        # Normalization: only f-params
        p_norm = p_norm * jnp.abs(evaluate(norm_circuit, f_selected))

        # Joint probability: f-params + state
        component_state = state[list(component.output_indices)]
        tiled_state = jnp.tile(component_state, (batch_size, 1))
        joint_params = jnp.hstack([f_selected, tiled_state])
        p_joint = p_joint * jnp.abs(evaluate(joint_circuit, joint_params))

    return np.asarray(p_joint / p_norm)

sample_component

sample_component(
    component: CompiledComponent,
    f_params: Array,
    key: Array,
) -> tuple[jax.Array, PRNGKey, jax.Array]

Sample outputs from a single component using autoregressive sampling.

Parameters:

Name Type Description Default
component CompiledComponent

The compiled component to sample from.

required
f_params Array

Error parameters, shape (batch_size, num_f_params).

required
key Array

JAX random key.

required

Returns:

Type Description
Array

Tuple of (samples, next_key, max_norm_deviation) where samples has shape

Array

(batch_size, num_outputs_for_component).

Source code in src/tsim/sampler.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def sample_component(
    component: CompiledComponent,
    f_params: jax.Array,
    key: PRNGKey,
) -> tuple[jax.Array, PRNGKey, jax.Array]:
    """Sample outputs from a single component using autoregressive sampling.

    Args:
        component: The compiled component to sample from.
        f_params: Error parameters, shape (batch_size, num_f_params).
        key: JAX random key.

    Returns:
        Tuple of (samples, next_key, max_norm_deviation) where samples has shape
        (batch_size, num_outputs_for_component).

    """
    # Skip JIT for small components (overhead not worth it)
    if len(component.output_indices) <= 1:
        return _sample_component(component, f_params, key)
    return _sample_component_jit(component, f_params, key)

sample_program

sample_program(
    program: CompiledProgram, f_params: Array, key: Array
) -> jax.Array

Sample all outputs from a compiled program.

Parameters:

Name Type Description Default
program CompiledProgram

The compiled program to sample from.

required
f_params Array

Error parameters, shape (batch_size, num_f_params).

required
key Array

JAX random key.

required

Returns:

Type Description
Array

Samples array of shape (batch_size, num_outputs), reordered to

Array

match the original output indices.

Source code in src/tsim/sampler.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def sample_program(
    program: CompiledProgram,
    f_params: jax.Array,
    key: PRNGKey,
) -> jax.Array:
    """Sample all outputs from a compiled program.

    Args:
        program: The compiled program to sample from.
        f_params: Error parameters, shape (batch_size, num_f_params).
        key: JAX random key.

    Returns:
        Samples array of shape (batch_size, num_outputs), reordered to
        match the original output indices.

    """
    results: list[jax.Array] = []

    if program.num_outputs == 0:
        batch_size = f_params.shape[0]
        return jnp.zeros((batch_size, 0), dtype=jnp.bool_)

    if len(program.direct_f_indices) > 0:
        direct_bits = (
            f_params[:, program.direct_f_indices].astype(jnp.bool_)
            ^ program.direct_flips
        )
        results.append(direct_bits)

    for component in program.components:
        samples, key, max_norm_deviation = sample_component(component, f_params, key)
        if np.isclose(max_norm_deviation, 1):
            raise ValueError(
                "A vanishing marginal probability distribution was encountered (normalization 0). "
                "This is likely the result of an underflow error. Please report this "
                "as a bug at https://github.com/QuEraComputing/tsim/issues/new."
            )  # pragma: no cover
        if max_norm_deviation > 1e-5:
            warnings.warn(
                "A marginal probability was not normalized correctly "
                f"(normalization deviated from 1 by {max_norm_deviation:.1e}). "
                "This is likely a floating point precision issue.",
                stacklevel=2,
            )
        results.append(samples)

    combined = jnp.concatenate(results, axis=1)
    if program.output_reindex is not None:
        combined = combined[:, program.output_reindex]
    return combined