qLDPC 0.3.1__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {qldpc-0.3.1 → qldpc-0.3.2}/PKG-INFO +1 -1
- {qldpc-0.3.1 → qldpc-0.3.2}/pyproject.toml +1 -1
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/abstract/groups_test.py +4 -4
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/abstract/rings.py +2 -3
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/__init__.py +2 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/benchmarking.py +8 -27
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/bookkeeping.py +15 -4
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/common.py +24 -4
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/common_test.py +8 -0
- qldpc-0.3.2/src/qldpc/circuits/conftest_probe.py +13 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/encoding.py +19 -9
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/encoding_test.py +11 -2
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/memory/memory.py +1 -1
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/noise_model.py +11 -21
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/transversal.py +45 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/transversal_test.py +18 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/codes/common.py +100 -28
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/codes/common_test.py +22 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/decoders/custom.py +256 -139
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/decoders/custom_test.py +41 -1
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/decoders/dems.py +1 -1
- qldpc-0.3.2/src/qldpc/math.py +360 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/math_test.py +41 -6
- qldpc-0.3.1/src/qldpc/math.py +0 -168
- {qldpc-0.3.1 → qldpc-0.3.2}/README.md +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/__init__.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/abstract/__init__.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/abstract/groups.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/abstract/linalg.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/abstract/linalg_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/abstract/rings_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/abstract/wedderburn_artin.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/abstract/wedderburn_artin_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/cache.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/cache_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/benchmarking_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/bookkeeping_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/memory/__init__.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/memory/alpha_syndrome.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/memory/alpha_syndrome_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/memory/memory_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/memory/syndrome_measurement.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/memory/syndrome_measurement_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/circuits/noise_model_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/codes/__init__.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/codes/classical.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/codes/classical_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/codes/distance.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/codes/distance_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/codes/quantum.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/codes/quantum_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/conftest.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/decoders/__init__.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/decoders/dems_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/decoders/retrieval.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/decoders/retrieval_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/decoders/sinter.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/decoders/sinter_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/external/__init__.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/external/codes.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/external/codes_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/external/gap.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/external/gap_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/external/groups.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/external/groups_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/objects.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/objects_test.py +0 -0
- {qldpc-0.3.1 → qldpc-0.3.2}/src/qldpc/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qLDPC
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Tools for constructing and analyzing quantum low density parity check (qLDPC) codes.
|
|
5
5
|
Keywords: quantum computing,quantum error correction,low density parity check codes,LDPC
|
|
6
6
|
Author: Michael A. Perlin
|
|
@@ -132,8 +132,8 @@ def assert_valid_lifts(group: abstract.Group) -> None:
|
|
|
132
132
|
# invert elements: g -> g**(-1)
|
|
133
133
|
assert all(
|
|
134
134
|
np.array_equal(
|
|
135
|
-
np.
|
|
136
|
-
[
|
|
135
|
+
np.flatnonzero(group.inversion_matrix[:, group.index(gg)]),
|
|
136
|
+
[group.index(~gg)],
|
|
137
137
|
)
|
|
138
138
|
for gg in group_members
|
|
139
139
|
)
|
|
@@ -156,8 +156,8 @@ def assert_valid_lifts(group: abstract.Group) -> None:
|
|
|
156
156
|
else:
|
|
157
157
|
assert all(
|
|
158
158
|
np.array_equal(
|
|
159
|
-
np.
|
|
160
|
-
[
|
|
159
|
+
np.flatnonzero(group.adjoint_lift(aa)[:, group.index(bb)]),
|
|
160
|
+
[group.index(aa * bb * ~aa)],
|
|
161
161
|
)
|
|
162
162
|
for aa, bb in itertools.product(group_members, repeat=2)
|
|
163
163
|
)
|
|
@@ -1082,10 +1082,9 @@ def _get_block_howell_form(matrix: galois.FieldArray, *, right: bool = False) ->
|
|
|
1082
1082
|
if right and size > 1:
|
|
1083
1083
|
matrix = matrix.transpose(0, 1, 3, 2)
|
|
1084
1084
|
|
|
1085
|
-
# row-reduce as an expanded 2-D matrix
|
|
1085
|
+
# row-reduce as an expanded 2-D matrix, keeping a basis of the row space (no all-zero rows)
|
|
1086
1086
|
shape = (num_block_rows * size, num_block_cols * size)
|
|
1087
|
-
matrix = matrix.transpose(0, 2, 1, 3).reshape(shape).view(field).
|
|
1088
|
-
matrix = matrix[qldpc.math.first_nonzero_cols(matrix) < matrix.shape[1]].view(field)
|
|
1087
|
+
matrix = matrix.transpose(0, 2, 1, 3).reshape(shape).view(field).row_space()
|
|
1089
1088
|
|
|
1090
1089
|
if size > 1:
|
|
1091
1090
|
# insert zero rows to shift pivots down so that they always lie on the diagonal of a block
|
|
@@ -50,6 +50,7 @@ from .transversal import (
|
|
|
50
50
|
get_transversal_circuit,
|
|
51
51
|
get_transversal_circuits,
|
|
52
52
|
get_transversal_ops,
|
|
53
|
+
get_transversal_s,
|
|
53
54
|
)
|
|
54
55
|
|
|
55
56
|
__all__ = [
|
|
@@ -92,4 +93,5 @@ __all__ = [
|
|
|
92
93
|
"get_transversal_circuit",
|
|
93
94
|
"get_transversal_circuits",
|
|
94
95
|
"get_transversal_ops",
|
|
96
|
+
"get_transversal_s",
|
|
95
97
|
]
|
|
@@ -302,11 +302,10 @@ def get_logical_error_and_discard_rate(
|
|
|
302
302
|
post_select_observables: The observables to post-select on. Any time the decoder's
|
|
303
303
|
prediction for these observable flips do not agree with the actual observable flips, the
|
|
304
304
|
shot is discarded rather than counting as an error.
|
|
305
|
-
dem_to_decode: The detector error model to decode.
|
|
306
|
-
|
|
307
|
-
this method
|
|
308
|
-
|
|
309
|
-
dem_to_decode = dem_arrays.post_selected_on(post_select).simplified().to_dem()
|
|
305
|
+
dem_to_decode: The detector error model to decode. This DEM should include _all_ detectors,
|
|
306
|
+
including any that are post-selected on: the decoder receives every detector bit, for
|
|
307
|
+
consistency with how sinter works. If dem_to_decode is None, this method decodes with
|
|
308
|
+
the (simplified) DEM sampled from circuit_or_dem.
|
|
310
309
|
|
|
311
310
|
Returns:
|
|
312
311
|
A fraction of samples in which at least one observable was decoded incorrectly.
|
|
@@ -318,12 +317,12 @@ def get_logical_error_and_discard_rate(
|
|
|
318
317
|
|
|
319
318
|
if dem_to_decode is not None:
|
|
320
319
|
same_num_observables = dem_to_decode.num_observables == dem.num_observables
|
|
321
|
-
same_num_detectors = dem_to_decode.num_detectors == dem.num_detectors
|
|
320
|
+
same_num_detectors = dem_to_decode.num_detectors == dem.num_detectors
|
|
322
321
|
if not same_num_observables or not same_num_detectors:
|
|
323
322
|
raise ValueError(
|
|
324
323
|
f"Incompatible detector error models."
|
|
325
|
-
"\n(num_detectors, num_observables) in the DEM to sample
|
|
326
|
-
f" {(dem.num_detectors
|
|
324
|
+
"\n(num_detectors, num_observables) in the DEM to sample:"
|
|
325
|
+
f" {(dem.num_detectors, dem.num_observables)}"
|
|
327
326
|
"\n(num_detectors, num_observables) in the DEM to decode:"
|
|
328
327
|
f" {(dem_to_decode.num_detectors, dem_to_decode.num_observables)}"
|
|
329
328
|
)
|
|
@@ -341,31 +340,13 @@ def get_logical_error_and_discard_rate(
|
|
|
341
340
|
|
|
342
341
|
# if applicable, post-select on flag detectors
|
|
343
342
|
if postselection_mask is not None:
|
|
344
|
-
# remove shots in which post-selection detectors fired
|
|
345
343
|
shot_mask = ~np.any(det_data & postselection_mask, axis=1)
|
|
346
344
|
det_data = det_data[shot_mask]
|
|
347
345
|
obs_data = obs_data[shot_mask]
|
|
348
|
-
|
|
349
|
-
# remove post-selected detectors from the detector sample data
|
|
350
|
-
detector_mask = np.ones(dem.num_detectors, dtype=bool)
|
|
351
|
-
detector_mask[list(post_select)] = False
|
|
352
|
-
det_data_unpacked = np.unpackbits(
|
|
353
|
-
det_data, count=dem.num_detectors, bitorder="little", axis=1
|
|
354
|
-
)
|
|
355
|
-
det_data = np.packbits(det_data_unpacked[:, detector_mask], bitorder="little", axis=1)
|
|
356
|
-
|
|
357
|
-
# record the fraction of shots that were discarded
|
|
358
346
|
discard_rate += np.sum(~shot_mask) / num_samples
|
|
359
347
|
|
|
360
|
-
if dem_to_decode is None:
|
|
361
|
-
# remove the post-selected detectors from the DEM
|
|
362
|
-
dem_arrays = dem_arrays.post_selected_on(post_select).simplified()
|
|
363
|
-
dem = dem_arrays.to_dem()
|
|
364
|
-
|
|
365
|
-
# compile a decoder for this detector error model
|
|
366
|
-
compiled_sinter_decoder = sinter_decoder.compile_decoder_for_dem(dem_to_decode or dem)
|
|
367
|
-
|
|
368
348
|
# decode and identify incorrectly predicted observable flips
|
|
349
|
+
compiled_sinter_decoder = sinter_decoder.compile_decoder_for_dem(dem_to_decode or dem)
|
|
369
350
|
predicted_flips = compiled_sinter_decoder.decode_shots_bit_packed(det_data)
|
|
370
351
|
incorrectly_predicted_flips = obs_data ^ predicted_flips
|
|
371
352
|
|
|
@@ -43,11 +43,22 @@ class QubitIDs:
|
|
|
43
43
|
checks_z: tuple[int, ...] = ()
|
|
44
44
|
|
|
45
45
|
def __init__(
|
|
46
|
-
self,
|
|
46
|
+
self,
|
|
47
|
+
data: int | Sequence[int],
|
|
48
|
+
check: int | Sequence[int] = (),
|
|
49
|
+
ancilla: int | Sequence[int] = (),
|
|
47
50
|
) -> None:
|
|
48
|
-
self.data = tuple(data)
|
|
49
|
-
self.
|
|
50
|
-
self.
|
|
51
|
+
self.data = tuple(data if isinstance(data, Sequence) else range(data))
|
|
52
|
+
check_start = self.data[-1] + 1 if self.data else 0
|
|
53
|
+
self.check = tuple(
|
|
54
|
+
check if isinstance(check, Sequence) else range(check_start, check_start + check)
|
|
55
|
+
)
|
|
56
|
+
ancilla_start = self.check[-1] + 1 if self.check else check_start
|
|
57
|
+
self.ancilla = tuple(
|
|
58
|
+
ancilla
|
|
59
|
+
if isinstance(ancilla, Sequence)
|
|
60
|
+
else range(ancilla_start, ancilla_start + ancilla)
|
|
61
|
+
)
|
|
51
62
|
|
|
52
63
|
def __iter__(self) -> Iterator[tuple[int, ...]]:
|
|
53
64
|
"""Iterate over the collections of qubits tracked by this QubitIDs object."""
|
|
@@ -19,7 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import functools
|
|
21
21
|
from collections.abc import Mapping, Sequence
|
|
22
|
-
from typing import Callable, ParamSpec, TypeVar
|
|
22
|
+
from typing import TYPE_CHECKING, Callable, ParamSpec, TypeVar
|
|
23
23
|
|
|
24
24
|
import galois
|
|
25
25
|
import numpy as np
|
|
@@ -28,6 +28,19 @@ import stim
|
|
|
28
28
|
|
|
29
29
|
from qldpc import codes, math
|
|
30
30
|
|
|
31
|
+
####################################################################################################
|
|
32
|
+
# try to import tsim and define a circuit type that may be either of stim.Circuit or tsim.Circuit
|
|
33
|
+
try:
|
|
34
|
+
import tsim
|
|
35
|
+
|
|
36
|
+
stim_or_tsim_Circuit = TypeVar("stim_or_tsim_Circuit", stim.Circuit, tsim.Circuit)
|
|
37
|
+
except ImportError: # pragma: no cover
|
|
38
|
+
if not TYPE_CHECKING:
|
|
39
|
+
tsim = None
|
|
40
|
+
stim_or_tsim_Circuit = TypeVar("stim_or_tsim_Circuit", bound=stim.Circuit)
|
|
41
|
+
####################################################################################################
|
|
42
|
+
|
|
43
|
+
|
|
31
44
|
CircuitOrTableau = TypeVar("CircuitOrTableau", stim.Circuit, stim.Tableau)
|
|
32
45
|
Params = ParamSpec("Params")
|
|
33
46
|
|
|
@@ -47,8 +60,11 @@ def restrict_to_qubits(
|
|
|
47
60
|
|
|
48
61
|
|
|
49
62
|
def with_remapped_qubits(
|
|
50
|
-
circuit:
|
|
51
|
-
|
|
63
|
+
circuit: stim_or_tsim_Circuit,
|
|
64
|
+
qubit_map: Mapping[int, int] | Sequence[int],
|
|
65
|
+
*,
|
|
66
|
+
inverse: bool = False,
|
|
67
|
+
) -> stim_or_tsim_Circuit:
|
|
52
68
|
"""The same circuit, but with relabeled qubits.
|
|
53
69
|
|
|
54
70
|
Qubits not in qubit_map get mapped to themselves.
|
|
@@ -60,8 +76,12 @@ def with_remapped_qubits(
|
|
|
60
76
|
inverse: If True, invert the provided qubit_map. Default: False.
|
|
61
77
|
|
|
62
78
|
Returns:
|
|
63
|
-
|
|
79
|
+
The input circuit with remapped qubits.
|
|
64
80
|
"""
|
|
81
|
+
if tsim is not None and isinstance(circuit, tsim.Circuit):
|
|
82
|
+
output = with_remapped_qubits(circuit.stim_circuit, qubit_map, inverse=inverse)
|
|
83
|
+
return tsim.Circuit.from_stim_program(output)
|
|
84
|
+
|
|
65
85
|
qubit_map = (
|
|
66
86
|
qubit_map
|
|
67
87
|
if isinstance(qubit_map, Mapping)
|
|
@@ -23,6 +23,7 @@ import numpy as np
|
|
|
23
23
|
import pytest
|
|
24
24
|
import stim
|
|
25
25
|
import sympy.combinatorics as comb
|
|
26
|
+
import tsim
|
|
26
27
|
|
|
27
28
|
from qldpc import circuits, codes
|
|
28
29
|
from qldpc.math import symplectic_conjugate
|
|
@@ -95,6 +96,13 @@ def test_qubit_remap(pytestconfig: pytest.Config, num_qubits: int = 8) -> None:
|
|
|
95
96
|
circuit_b = stim.Circuit("MPP X1*!Y3 \n M !4")
|
|
96
97
|
assert circuit_a == circuit_b
|
|
97
98
|
|
|
99
|
+
# a tsim.Circuit input is remapped and returned as a tsim.Circuit
|
|
100
|
+
tsim_remapped = circuits.with_remapped_qubits(
|
|
101
|
+
tsim.Circuit.from_stim_program(circuit), qubit_map
|
|
102
|
+
)
|
|
103
|
+
assert isinstance(tsim_remapped, tsim.Circuit)
|
|
104
|
+
assert tsim_remapped.stim_circuit == circuits.with_remapped_qubits(circuit, qubit_map)
|
|
105
|
+
|
|
98
106
|
|
|
99
107
|
def test_finding_unaddressed_measurements() -> None:
|
|
100
108
|
"""Identify measurements in a circuit that are not addressed by any detectors."""
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
import qldpc.circuits.common as c
|
|
4
|
+
c._TSIM_BRANCH_HITS = 0
|
|
5
|
+
_orig = c.with_remapped_qubits
|
|
6
|
+
import functools
|
|
7
|
+
def wrapped(circuit, *a, **k):
|
|
8
|
+
if c.tsim is not None and isinstance(circuit, c.tsim.Circuit):
|
|
9
|
+
c._TSIM_BRANCH_HITS += 1
|
|
10
|
+
return _orig(circuit, *a, **k)
|
|
11
|
+
c.with_remapped_qubits = wrapped
|
|
12
|
+
def pytest_sessionfinish(session, exitstatus):
|
|
13
|
+
print("\nTSIM_BRANCH_HITS_ON_WITH_REMAPPED:", c._TSIM_BRANCH_HITS)
|
|
@@ -226,7 +226,7 @@ def get_state_stabilizers(
|
|
|
226
226
|
|
|
227
227
|
# identify stabilizers that are supported entirely on the data qubits of the code
|
|
228
228
|
stabilizers = []
|
|
229
|
-
for row in matrix.
|
|
229
|
+
for row in matrix.row_space():
|
|
230
230
|
state_xs = row[cols_state_x]
|
|
231
231
|
state_zs = row[cols_state_z]
|
|
232
232
|
other_xs = row[cols_other_x]
|
|
@@ -294,17 +294,27 @@ def _get_logical_tableau_from_code_data(
|
|
|
294
294
|
sector_l = slice(dimension)
|
|
295
295
|
sector_g = slice(dimension, dimension + gauge_dimension)
|
|
296
296
|
sector_s = slice(dimension + gauge_dimension, len(encoder))
|
|
297
|
-
x2x, x2z, z2x, z2z,
|
|
297
|
+
x2x, x2z, z2x, z2z, _, z_signs = decoded_tableau.to_numpy()
|
|
298
298
|
|
|
299
299
|
# sanity check: stabilizers, logicals, and gauge operators should not pick up destabilizers
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
300
|
+
ops_acquired_destabilizers = (
|
|
301
|
+
np.any(z2x[:, sector_s])
|
|
302
|
+
or np.any(x2x[sector_l, sector_s])
|
|
303
|
+
or np.any(x2x[sector_g, sector_s])
|
|
304
|
+
)
|
|
303
305
|
|
|
304
306
|
# sanity check: gauge operators should not pick up logical factors
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
307
|
+
gauges_acquired_logicals = (
|
|
308
|
+
np.any(x2x[sector_g, sector_l])
|
|
309
|
+
or np.any(x2z[sector_g, sector_l])
|
|
310
|
+
or np.any(z2x[sector_g, sector_l])
|
|
311
|
+
or np.any(z2z[sector_g, sector_l])
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# sanity check: no stabilizers get flipped
|
|
315
|
+
stabilizers_flipped = np.any(z_signs[sector_s])
|
|
316
|
+
|
|
317
|
+
if ops_acquired_destabilizers or gauges_acquired_logicals or stabilizers_flipped:
|
|
318
|
+
raise ValueError("The provided physical circuit does not implement a logical operation")
|
|
309
319
|
|
|
310
320
|
return logical_tableau
|
|
@@ -42,8 +42,8 @@ def test_encoding_circuit(pytestconfig: pytest.Config) -> None:
|
|
|
42
42
|
size=code.dimension + code.gauge_dimension,
|
|
43
43
|
)
|
|
44
44
|
basis_prep = stim.Circuit()
|
|
45
|
-
basis_prep.append("H", np.
|
|
46
|
-
basis_prep.append("S", np.
|
|
45
|
+
basis_prep.append("H", np.flatnonzero(bases != Pauli.Z))
|
|
46
|
+
basis_prep.append("S", np.flatnonzero(bases == Pauli.Y))
|
|
47
47
|
simulator.do(basis_prep)
|
|
48
48
|
|
|
49
49
|
encoder = circuits.get_encoding_circuit(code, only_zero=only_zero)
|
|
@@ -104,6 +104,15 @@ def test_logical_tableau() -> None:
|
|
|
104
104
|
reconstructed_logical_tableau = circuits.get_logical_tableau(code, physical_circuit)
|
|
105
105
|
assert logical_circuit.to_tableau() == reconstructed_logical_tableau
|
|
106
106
|
|
|
107
|
+
# logical Pauli operations (which flip logical operator signs) are valid logical operations
|
|
108
|
+
logical_x = math.op_to_string(code.get_logical_ops(Pauli.X, symplectic=True)[0])
|
|
109
|
+
reconstructed_logical_tableau = circuits.get_logical_tableau(code, logical_x.to_tableau())
|
|
110
|
+
assert stim.Circuit("X 0").to_tableau() == reconstructed_logical_tableau
|
|
111
|
+
|
|
112
|
+
# a physical circuit that does not preserve the code space is not a logical operation
|
|
113
|
+
with pytest.raises(ValueError, match="does not implement a logical operation"):
|
|
114
|
+
circuits.get_logical_tableau(code, stim.Circuit("X 0"))
|
|
115
|
+
|
|
107
116
|
|
|
108
117
|
def test_state_stabilizers(pytestconfig: pytest.Config) -> None:
|
|
109
118
|
"""Identify the stabilizers of state prepared by a circuit."""
|
|
@@ -276,7 +276,7 @@ def _get_basis_memory_experiment_parts(
|
|
|
276
276
|
readout.append("SHIFT_COORDS", [], (1, 0, 0))
|
|
277
277
|
check_support = code.get_matrix(basis)
|
|
278
278
|
for kk, check_id in enumerate(basis_check_ids):
|
|
279
|
-
data_support = np.
|
|
279
|
+
data_support = np.flatnonzero(check_support[kk])
|
|
280
280
|
readout.append(
|
|
281
281
|
"DETECTOR",
|
|
282
282
|
[measurement_record.get_target_rec(data_ids[qq]) for qq in data_support]
|
|
@@ -110,20 +110,10 @@ import itertools
|
|
|
110
110
|
import math
|
|
111
111
|
import re
|
|
112
112
|
from collections.abc import Callable, Collection, Iterable, Iterator, Mapping
|
|
113
|
-
from typing import TYPE_CHECKING, TypeVar
|
|
114
113
|
|
|
115
114
|
import stim
|
|
116
115
|
|
|
117
|
-
|
|
118
|
-
import tsim
|
|
119
|
-
|
|
120
|
-
stim_or_tsim_Circuit = TypeVar("stim_or_tsim_Circuit", stim.Circuit, tsim.Circuit)
|
|
121
|
-
except ImportError: # pragma: no cover
|
|
122
|
-
if not TYPE_CHECKING:
|
|
123
|
-
tsim = None
|
|
124
|
-
stim_or_tsim_Circuit = TypeVar("stim_or_tsim_Circuit", bound=stim.Circuit)
|
|
125
|
-
|
|
126
|
-
from .common import with_remapped_qubits
|
|
116
|
+
from .common import stim_or_tsim_Circuit, tsim, with_remapped_qubits
|
|
127
117
|
|
|
128
118
|
####################################################################################################
|
|
129
119
|
# global constants
|
|
@@ -238,7 +228,8 @@ CORRELATED_ERROR_NAMES = frozenset({"CORRELATED_ERROR", "E", "ELSE_CORRELATED_ER
|
|
|
238
228
|
def as_noiseless_circuit(circuit: stim_or_tsim_Circuit) -> stim_or_tsim_Circuit:
|
|
239
229
|
"""Wrap a circuit in a noiseless, one-repitition stim.CircuitRepeatBlock."""
|
|
240
230
|
if tsim is not None and isinstance(circuit, tsim.Circuit):
|
|
241
|
-
|
|
231
|
+
output = as_noiseless_circuit(circuit.stim_circuit)
|
|
232
|
+
return tsim.Circuit.from_stim_program(output)
|
|
242
233
|
block = stim.CircuitRepeatBlock(repeat_count=1, body=circuit.copy(), tag=DEFAULT_IMMUNE_OP_TAG)
|
|
243
234
|
noiseless_circuit = stim.Circuit()
|
|
244
235
|
noiseless_circuit.append(block)
|
|
@@ -930,16 +921,15 @@ class NoiseModel:
|
|
|
930
921
|
The input circuit with added noise.
|
|
931
922
|
"""
|
|
932
923
|
if tsim is not None and isinstance(circuit, tsim.Circuit):
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
insert_ticks=insert_ticks,
|
|
941
|
-
)
|
|
924
|
+
output = self.noisy_circuit(
|
|
925
|
+
circuit.stim_circuit,
|
|
926
|
+
system_qubits=system_qubits,
|
|
927
|
+
immune_qubits=immune_qubits,
|
|
928
|
+
immune_op_tag=immune_op_tag,
|
|
929
|
+
immunize_gates=immunize_gates,
|
|
930
|
+
insert_ticks=insert_ticks,
|
|
942
931
|
)
|
|
932
|
+
return tsim.Circuit.from_stim_program(output)
|
|
943
933
|
|
|
944
934
|
system_qubits = frozenset(
|
|
945
935
|
range(circuit.num_qubits) if system_qubits is None else system_qubits
|
|
@@ -32,6 +32,51 @@ from .common import restrict_to_qubits
|
|
|
32
32
|
from .encoding import _get_logical_tableau_from_code_data, get_encoder_and_decoder
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
@restrict_to_qubits
|
|
36
|
+
def get_transversal_s(code: codes.CSSCode, *, validate: bool = True) -> stim.Circuit:
|
|
37
|
+
"""Get a physical circuit for a transversal logical S = diag(1, i) gate of the code.
|
|
38
|
+
|
|
39
|
+
The returned circuit applies one physical S or S_DAG gate to each qubit, and thereby enacts
|
|
40
|
+
a logical S gate on every logical qubit of the code.
|
|
41
|
+
|
|
42
|
+
This construction only supports self-dual codes with equivalent logicals (SWEL):
|
|
43
|
+
1. Self dual = CSS code with identical X and Z stabilizers.
|
|
44
|
+
2. Equivalent logicals = applying a Hadamard to every physical qubit enacts a logical Hadamard
|
|
45
|
+
on every logical qubit. This property depends on the choice of logical operator basis.
|
|
46
|
+
"""
|
|
47
|
+
if validate:
|
|
48
|
+
logs_z = code.get_logical_ops(Pauli.Z)
|
|
49
|
+
if not np.array_equal(
|
|
50
|
+
code.canonicalized.matrix_x, code.canonicalized.matrix_z
|
|
51
|
+
) or not np.array_equal(logs_z @ logs_z.T, np.eye(code.dimension, dtype=int)):
|
|
52
|
+
raise ValueError(
|
|
53
|
+
"Transversal S gate construction currently only supports SWEL stabilizer codes"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Build a test circuit: physical S on all qubits
|
|
57
|
+
test_circuit = stim.Circuit()
|
|
58
|
+
test_circuit.append("S", range(len(code)))
|
|
59
|
+
test_tableau = test_circuit.to_tableau()
|
|
60
|
+
|
|
61
|
+
# Identify Pauli corrections in the "decoded" frame of logicals/stabilizers/destabilizers.
|
|
62
|
+
encoder, decoder = get_encoder_and_decoder(code)
|
|
63
|
+
decoded_tableau = encoder.then(test_tableau).then(decoder)
|
|
64
|
+
*_, x_signs, z_signs = decoded_tableau.to_numpy()
|
|
65
|
+
# A flip of an X-type operator is corrected by its Z-type dual, and vice versa.
|
|
66
|
+
decoded_frame_correction = stim.PauliString.from_numpy(xs=z_signs, zs=x_signs)
|
|
67
|
+
|
|
68
|
+
# Map the decoded-frame Pauli correction back to a physical Pauli. For SWEL codes this
|
|
69
|
+
# correction is purely Z-type (mod stabilizers), and appending a Z after an S makes it S_DAG,
|
|
70
|
+
# so we can realize the correction by emitting S_DAG (rather than S) on the corrected qubits.
|
|
71
|
+
physical_correction = decoded_frame_correction.after(encoder, targets=range(len(code)))
|
|
72
|
+
_, physical_zs = physical_correction.to_numpy()
|
|
73
|
+
|
|
74
|
+
circuit = stim.Circuit()
|
|
75
|
+
circuit.append("S", np.flatnonzero(~physical_zs))
|
|
76
|
+
circuit.append("S_DAG", np.flatnonzero(physical_zs))
|
|
77
|
+
return circuit
|
|
78
|
+
|
|
79
|
+
|
|
35
80
|
@restrict_to_qubits
|
|
36
81
|
def get_transversal_ops(
|
|
37
82
|
code: codes.QuditCode,
|
|
@@ -26,6 +26,24 @@ import stim
|
|
|
26
26
|
from qldpc import circuits, codes, external
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
def test_transversal_s() -> None:
|
|
30
|
+
"""Build a physical circuit for the transversal logical S gate of a SWEL code."""
|
|
31
|
+
# the Steane code is SWEL, so its logical S gate is transversal
|
|
32
|
+
code = codes.SteaneCode()
|
|
33
|
+
circuit = circuits.get_transversal_s(code)
|
|
34
|
+
|
|
35
|
+
# the circuit is built entirely from single-qubit S and S_DAG gates (no Pauli corrections)
|
|
36
|
+
assert {instruction.name for instruction in circuit} <= {"S", "S_DAG"}
|
|
37
|
+
|
|
38
|
+
# the circuit implements a logical S gate
|
|
39
|
+
logical_tableau = circuits.get_logical_tableau(code, circuit)
|
|
40
|
+
assert logical_tableau == stim.Circuit("S 0").to_tableau()
|
|
41
|
+
|
|
42
|
+
# non-SWEL codes are rejected
|
|
43
|
+
with pytest.raises(ValueError, match="SWEL"):
|
|
44
|
+
circuits.get_transversal_s(codes.ToricCode(2))
|
|
45
|
+
|
|
46
|
+
|
|
29
47
|
def test_transversal_ops() -> None:
|
|
30
48
|
"""Construct SWAP-transversal logical Cliffords of a code."""
|
|
31
49
|
code: codes.CSSCode
|