Skip to content

tn_noise_learning.simulate#

Synthetic datasets from a known channel: exact outcome distributions sampled one setting at a time.

A setting is a pair (state_labels, basis_labels) with one entry per qubit. One exact contraction gives the joint distribution over all 2**num_qubits bit strings of that setting, from which all its shots are drawn with a single multinomial draw. Effect labels follow the convention 2 * basis + bit.

outcome_distribution #

outcome_distribution(
    channel: LPDO,
    circuit: QuantumCircuit | None,
    state_labels: Labels,
    basis_labels: Labels,
    *,
    states: ndarray,
    povms: ndarray,
    optimize: str = "auto-hq",
) -> np.ndarray

Joint probability of every bit string for one setting, shape (2,) * num_qubits with qubit 0 first.

Source code in src/tn_noise_learning/simulate.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def outcome_distribution(
    channel: LPDO,
    circuit: QuantumCircuit | None,
    state_labels: Labels,
    basis_labels: Labels,
    *,
    states: np.ndarray,
    povms: np.ndarray,
    optimize: str = "auto-hq",
) -> np.ndarray:
    """Joint probability of every bit string for one setting, shape ``(2,) * num_qubits`` with qubit 0 first."""
    n = channel.num_sites
    if n > MAX_SAMPLED_SITES:
        raise ValueError(f"Exact sampling enumerates 2**n outcomes; n must be at most {MAX_SAMPLED_SITES}, got {n}.")
    prep = qtn.TensorNetwork(
        [
            qtn.Tensor(states[j, state_labels[j]], inds=(raw_ind(j), raw_ind(j, dagger=True)), tags=site_tag(j))
            for j in range(n)
        ]
    )
    if circuit is None:
        prep.reindex_(
            {raw_ind(j): input_ind(j) for j in range(n)}
            | {raw_ind(j, dagger=True): input_ind(j, dagger=True) for j in range(n)}
        )
    else:
        prep = apply_circuit(prep, circuit)
    measurement = qtn.TensorNetwork(
        [
            qtn.Tensor(
                povms[j, 2 * b : 2 * b + 2], inds=(f"o{j}", output_ind(j, dagger=True), output_ind(j)), tags=site_tag(j)
            )
            for j, b in enumerate(basis_labels)
        ]
    )
    tn = qtn.TensorNetwork([channel.network(), prep, measurement], check_collisions=False)
    probs = np.real(tn.contract(output_inds=[f"o{j}" for j in range(n)], optimize=optimize).data)
    probs = np.clip(probs, 0, None)
    return probs / probs.sum()

sample_shots #

sample_shots(
    channel: LPDO,
    circuit: QuantumCircuit | None = None,
    *,
    num_settings: int = 100,
    shots_per_setting: int = 100,
    seed: int | None = None,
    states: ndarray | None = None,
    povms: ndarray | None = None,
    settings: Sequence[Setting] | None = None,
) -> NoiseDataset

Sample a dataset from channel applied after circuit.

PARAMETER DESCRIPTION
channel

ground-truth channel; stored in the dataset as ideal_channel.

TYPE: LPDO

circuit

ideal circuit applied before the channel (None for the identity).

TYPE: QuantumCircuit | None DEFAULT: None

num_settings

number of random (preparation, basis) settings when settings is not given.

TYPE: int DEFAULT: 100

shots_per_setting

shots drawn per setting.

TYPE: int DEFAULT: 100

seed

seed for settings, shots and the final shuffle.

TYPE: int | None DEFAULT: None

states

input states per qubit, shape (num_qubits, num_states, 2, 2); default Pauli eigenstates.

TYPE: ndarray | None DEFAULT: None

povms

effects per qubit as consecutive basis pairs; default Pauli eigenstates.

TYPE: ndarray | None DEFAULT: None

settings

explicit settings, overriding num_settings.

TYPE: Sequence[Setting] | None DEFAULT: None

Source code in src/tn_noise_learning/simulate.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
def sample_shots(
    channel: LPDO,
    circuit: QuantumCircuit | None = None,
    *,
    num_settings: int = 100,
    shots_per_setting: int = 100,
    seed: int | None = None,
    states: np.ndarray | None = None,
    povms: np.ndarray | None = None,
    settings: Sequence[Setting] | None = None,
) -> NoiseDataset:
    """Sample a dataset from ``channel`` applied after ``circuit``.

    Args:
        channel: ground-truth channel; stored in the dataset as ``ideal_channel``.
        circuit: ideal circuit applied before the channel (``None`` for the identity).
        num_settings: number of random ``(preparation, basis)`` settings when ``settings`` is not given.
        shots_per_setting: shots drawn per setting.
        seed: seed for settings, shots and the final shuffle.
        states: input states per qubit, shape ``(num_qubits, num_states, 2, 2)``; default Pauli eigenstates.
        povms: effects per qubit as consecutive basis pairs; default Pauli eigenstates.
        settings: explicit settings, overriding ``num_settings``.
    """
    rng = np.random.default_rng(seed)
    n = channel.num_sites
    states = pauli_eigenstates(n) if states is None else np.asarray(states, dtype=complex)
    povms = pauli_eigenstates(n) if povms is None else np.asarray(povms, dtype=complex)
    _check_bases(povms)
    if settings is None:
        settings = [
            (rng.integers(0, states.shape[1], n), rng.integers(0, povms.shape[1] // 2, n)) for _ in range(num_settings)
        ]
    bits = np.array(np.unravel_index(np.arange(2**n), (2,) * n)).T
    inputs, outcomes = [], []
    for raw_prep, raw_basis in settings:
        prep, basis = np.asarray(raw_prep), np.asarray(raw_basis)
        counts = rng.multinomial(
            shots_per_setting,
            outcome_distribution(channel, circuit, prep, basis, states=states, povms=povms).ravel(),
        )
        for flat in np.flatnonzero(counts):
            inputs.append(np.repeat(prep[None], counts[flat], axis=0))
            outcomes.append(np.repeat((2 * basis + bits[flat])[None], counts[flat], axis=0))
    order = rng.permutation(len(settings) * shots_per_setting)
    return NoiseDataset(
        np.concatenate(inputs)[order], np.concatenate(outcomes)[order], states, povms, circuit, ideal_channel=channel
    )