loqs 1.1__py3-none-any.whl

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.
Files changed (61) hide show
  1. loqs/__init__.py +25 -0
  2. loqs/backends/__init__.py +224 -0
  3. loqs/backends/circuit/__init__.py +120 -0
  4. loqs/backends/circuit/basecircuit.py +618 -0
  5. loqs/backends/circuit/listcircuit.py +284 -0
  6. loqs/backends/circuit/pygsticircuit.py +278 -0
  7. loqs/backends/circuit/stimcircuit.py +823 -0
  8. loqs/backends/model/__init__.py +43 -0
  9. loqs/backends/model/basemodel.py +146 -0
  10. loqs/backends/model/dictmodel.py +303 -0
  11. loqs/backends/model/pygstimodel.py +608 -0
  12. loqs/backends/model/stimdictmodel.py +363 -0
  13. loqs/backends/reps.py +457 -0
  14. loqs/backends/state/__init__.py +38 -0
  15. loqs/backends/state/basestate.py +117 -0
  16. loqs/backends/state/npsvstate.py +574 -0
  17. loqs/backends/state/qsimstate.py +388 -0
  18. loqs/backends/state/stimstate.py +460 -0
  19. loqs/codepacks/__init__.py +41 -0
  20. loqs/codepacks/codepack_5_1_3_quantinuum2022.py +2262 -0
  21. loqs/codepacks/codepack_7_1_3_quantinuum2021.py +1118 -0
  22. loqs/codepacks/codepack_surf17_tomita2014.py +1081 -0
  23. loqs/codepacks/codepack_trivial_counter.py +81 -0
  24. loqs/core/__init__.py +40 -0
  25. loqs/core/frame.py +250 -0
  26. loqs/core/history.py +341 -0
  27. loqs/core/instructions/__init__.py +23 -0
  28. loqs/core/instructions/builders.py +1322 -0
  29. loqs/core/instructions/instruction.py +473 -0
  30. loqs/core/instructions/instructionlabel.py +202 -0
  31. loqs/core/instructions/instructionstack.py +218 -0
  32. loqs/core/programresults.py +891 -0
  33. loqs/core/qeccode.py +138 -0
  34. loqs/core/quantumprogram.py +989 -0
  35. loqs/core/recordables/__init__.py +24 -0
  36. loqs/core/recordables/measurementoutcomes.py +147 -0
  37. loqs/core/recordables/patchdict.py +109 -0
  38. loqs/core/recordables/pauliframe.py +244 -0
  39. loqs/core/recordables/qeccodepatch.py +152 -0
  40. loqs/core/syndromelabel.py +48 -0
  41. loqs/internal/__init__.py +22 -0
  42. loqs/internal/castable.py +151 -0
  43. loqs/internal/displayable.py +174 -0
  44. loqs/internal/encoder/__init__.py +12 -0
  45. loqs/internal/encoder/baseencoder.py +479 -0
  46. loqs/internal/encoder/hdf5encoder.py +1091 -0
  47. loqs/internal/encoder/jsonencoder.py +853 -0
  48. loqs/internal/serializable.py +1455 -0
  49. loqs/tools/__init__.py +21 -0
  50. loqs/tools/fttools.py +307 -0
  51. loqs/tools/pygstitools.py +770 -0
  52. loqs/tools/qectools.py +287 -0
  53. loqs/tools/qsimtools.py +112 -0
  54. loqs/tools/reptools.py +402 -0
  55. loqs/types.py +10 -0
  56. loqs-1.1.dist-info/METADATA +168 -0
  57. loqs-1.1.dist-info/RECORD +61 -0
  58. loqs-1.1.dist-info/WHEEL +5 -0
  59. loqs-1.1.dist-info/licenses/LICENSE +202 -0
  60. loqs-1.1.dist-info/licenses/NOTICE +6 -0
  61. loqs-1.1.dist-info/top_level.txt +1 -0
loqs/__init__.py ADDED
@@ -0,0 +1,25 @@
1
+ #####################################################################################################################
2
+ # Logical Qubit Simulator (LoQS) v. 1.1 #
3
+ # Copyright 2026 National Technology & Engineering Solutions of Sandia, LLC (NTESS). #
4
+ # Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. #
5
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except #
6
+ # in compliance with the License. You may obtain a copy of the License at #
7
+ # http://www.apache.org/licenses/LICENSE-2.0 or in the LICENSE file in the root LoQS directory. #
8
+ #####################################################################################################################
9
+
10
+ """Logical Qubit Simulator (LoQS)
11
+
12
+ A simulator for logical qubits with arbitrary noise and flexible QEC code definitions
13
+ """
14
+
15
+ # Import first (most dependencies)
16
+ from . import internal
17
+
18
+ # Import before core
19
+ from . import backends
20
+
21
+ from . import core
22
+
23
+ # Import after core
24
+ from . import codepacks
25
+ from . import tools
@@ -0,0 +1,224 @@
1
+ #####################################################################################################################
2
+ # Logical Qubit Simulator (LoQS) v. 1.1 #
3
+ # Copyright 2026 National Technology & Engineering Solutions of Sandia, LLC (NTESS). #
4
+ # Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. #
5
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except #
6
+ # in compliance with the License. You may obtain a copy of the License at #
7
+ # http://www.apache.org/licenses/LICENSE-2.0 or in the LICENSE file in the root LoQS directory. #
8
+ #####################################################################################################################
9
+
10
+ """Quantum simulation backends for LoQS
11
+
12
+ There are three kinds of backends in LoQS: [](api:backends.circuit),
13
+ [](api:backends.model), and [](api:backends.state).
14
+
15
+ !!! warning
16
+
17
+ For backends that depend on optional third-party packages,
18
+ it is recommended to not import from the module/class file directly.
19
+ Instead, try to import from `loqs.backends`, which dynamically checks
20
+ if that backend is available using the below methods.
21
+
22
+ Examples
23
+ --------
24
+ >>> from loqs.backends import get_available_backends
25
+ >>> list_of_backends = get_available_backends()
26
+ """
27
+
28
+ from typing import TYPE_CHECKING, Any
29
+ from importlib import import_module
30
+ from dataclasses import dataclass
31
+
32
+ from .reps import RepEnum, GateRep, InstrumentRep, RepTuple
33
+
34
+ from .circuit import BasePhysicalCircuit, ListPhysicalCircuit
35
+ from .model import BaseNoiseModel, DictNoiseModel, TimeDependentBaseNoiseModel
36
+ from .state import BaseQuantumState, NumpyStatevectorQuantumState
37
+ from .state.basestate import OutcomeDict
38
+
39
+
40
+ @dataclass
41
+ class BackendAvailability:
42
+ """Dataclass to track backend availability"""
43
+
44
+ name: str
45
+ """Backend name"""
46
+ available: bool
47
+ """Whether the backend is available or not"""
48
+ error: str | None = None
49
+ """Error message when trying to import the backend"""
50
+
51
+
52
+ # Backend availability tracking
53
+ _backend_availability: dict[str, BackendAvailability] = {}
54
+
55
+
56
+ def _check_backend_availability(backend_name: str, import_path: str) -> bool:
57
+ """Check if a backend is available and update availability tracking"""
58
+ try:
59
+ import_module(import_path)
60
+ _backend_availability[backend_name] = BackendAvailability(
61
+ backend_name, True
62
+ )
63
+ return True
64
+ except ImportError as e:
65
+ _backend_availability[backend_name] = BackendAvailability(
66
+ backend_name, False, str(e)
67
+ )
68
+ return False
69
+
70
+
71
+ def get_available_backends() -> list[str]:
72
+ """Get list of available backend names"""
73
+ return [
74
+ name
75
+ for name, avail in _backend_availability.items()
76
+ if avail.available
77
+ ]
78
+
79
+
80
+ def is_backend_available(backend_name: str) -> bool:
81
+ """Check if a specific backend is available"""
82
+ return _backend_availability.get(
83
+ backend_name, BackendAvailability(backend_name, False)
84
+ ).available
85
+
86
+
87
+ def get_backend_error(backend_name: str) -> str | None:
88
+ """Get the error message for an unavailable backend"""
89
+ return _backend_availability.get(
90
+ backend_name, BackendAvailability(backend_name, False)
91
+ ).error
92
+
93
+
94
+ # Check availability of all backends at import time
95
+ _check_backend_availability("pygsti_circuit", "pygsti")
96
+ _check_backend_availability("pygsti_model", "pygsti")
97
+ _check_backend_availability("stim_circuit", "stim")
98
+ _check_backend_availability("stim_state", "stim")
99
+ _check_backend_availability("qsim_state", "quantumsim")
100
+
101
+
102
+ # Import concrete backend classes with conditional availability
103
+ def __getattr__(name: str) -> Any:
104
+ """Lazy import of backend classes based on availability"""
105
+ if name == "PyGSTiPhysicalCircuit":
106
+ if is_backend_available("pygsti_circuit"):
107
+ from .circuit.pygsticircuit import PyGSTiPhysicalCircuit
108
+
109
+ return PyGSTiPhysicalCircuit
110
+ else:
111
+ raise ImportError(
112
+ f"PyGSTi circuit backend is not available. "
113
+ f"Error: {get_backend_error('pygsti_circuit')}"
114
+ )
115
+ elif name == "STIMPhysicalCircuit":
116
+ if is_backend_available("stim_circuit"):
117
+ from .circuit.stimcircuit import STIMPhysicalCircuit
118
+
119
+ return STIMPhysicalCircuit
120
+ else:
121
+ raise ImportError(
122
+ f"STIM circuit backend is not available. "
123
+ f"Error: {get_backend_error('stim_circuit')}"
124
+ )
125
+ elif name == "PyGSTiNoiseModel":
126
+ if is_backend_available("pygsti_model"):
127
+ from .model.pygstimodel import PyGSTiNoiseModel
128
+
129
+ return PyGSTiNoiseModel
130
+ else:
131
+ raise ImportError(
132
+ f"PyGSTi model backend is not available. "
133
+ f"Error: {get_backend_error('pygsti_model')}"
134
+ )
135
+ elif name == "STIMQuantumState":
136
+ if is_backend_available("stim_state"):
137
+ from .state.stimstate import STIMQuantumState
138
+
139
+ return STIMQuantumState
140
+ else:
141
+ raise ImportError(
142
+ f"STIM state backend is not available. "
143
+ f"Error: {get_backend_error('stim_state')}"
144
+ )
145
+ elif name == "QSimQuantumState":
146
+ if is_backend_available("qsim_state"):
147
+ from .state.qsimstate import QSimQuantumState
148
+
149
+ return QSimQuantumState
150
+ else:
151
+ raise ImportError(
152
+ f"QSim backend is not available. "
153
+ f"Error: {get_backend_error('qsim')}"
154
+ )
155
+
156
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
157
+
158
+
159
+ def propagate_state(
160
+ circuit: BasePhysicalCircuit,
161
+ model: BaseNoiseModel,
162
+ state: BaseQuantumState,
163
+ inplace: bool = True,
164
+ ) -> tuple[BaseQuantumState, OutcomeDict]:
165
+ """Given a circuit and model, propagate a state forward in time.
166
+
167
+ This is a wrapper for [](api:BaseNoiseModel.get_reps)
168
+ and [](api:BaseQuantumState.apply_reps_inplace) (or
169
+ the non-inplace version if `inplace=False`).
170
+ It does also try to find compatible reptypes by
171
+ searching for a match in output reps from `model`
172
+ and input reps from `state`.
173
+
174
+ Parameters
175
+ ----------
176
+ circuit:
177
+ The circuit to run
178
+
179
+ model:
180
+ The noise model to use to convert circuit operations
181
+ into representations the state can apply
182
+
183
+ state:
184
+ The state to move forward in time
185
+
186
+ inplace:
187
+ Whether to modify the state in-place (`True`, default)
188
+ or propagate a copy forward (`False`).
189
+ This should probably remain `True` for memory reasons.
190
+
191
+ Returns
192
+ -------
193
+ BaseQuantumState, OutcomeDict
194
+ The output of [](api:BaseQuantumState.apply_reps).
195
+ If `inplace=True`, then the state is also returned
196
+ to provide a consistent API.
197
+ """
198
+ # Find a compatible model/state oprep
199
+ opreps = []
200
+ for oprep in model.output_gate_reps:
201
+ if oprep in state.input_reps:
202
+ opreps.append(oprep)
203
+ assert (
204
+ len(opreps) > 0
205
+ ), "Could not find matching gate rep between model output and state input"
206
+
207
+ instreps = []
208
+ for instrep in model.output_instrument_reps:
209
+ if instrep in state.input_reps:
210
+ instreps.append(instrep)
211
+ assert (
212
+ len(instreps) > 0
213
+ ), "Could not find matching instrument rep between model output and state input"
214
+
215
+ # Look up reps from model
216
+ reps = model.get_reps(circuit, list(opreps), list(instreps))
217
+
218
+ # Apply operator reps to state
219
+ if inplace:
220
+ outcomes = state.apply_reps_inplace(reps)
221
+ else:
222
+ state, outcomes = state.apply_reps(reps)
223
+
224
+ return state, outcomes
@@ -0,0 +1,120 @@
1
+ #####################################################################################################################
2
+ # Logical Qubit Simulator (LoQS) v. 1.1 #
3
+ # Copyright 2026 National Technology & Engineering Solutions of Sandia, LLC (NTESS). #
4
+ # Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. #
5
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except #
6
+ # in compliance with the License. You may obtain a copy of the License at #
7
+ # http://www.apache.org/licenses/LICENSE-2.0 or in the LICENSE file in the root LoQS directory. #
8
+ #####################################################################################################################
9
+
10
+ """Circuit backend classes.
11
+
12
+ For LoQS, a circuit is essentially a list of layers that are lists of gate names and target qubits.
13
+ Qubit labels are assumed to be either a `str` or `int`.
14
+
15
+ The circuit backend interface is enforced by the abstract [](api:BasePhysicalCircuit) class,
16
+ which generally has the capabilities:
17
+
18
+ - Property getters for:
19
+ - The underlying circuit
20
+ - Circuit depth
21
+ - Qubit labels
22
+ - Append circuit (in-place and copy)
23
+ - Delete qubits (in-place and copy)
24
+ - Insert circuit (in-place and copy)
25
+ - Map qubit labels (in-place and copy)
26
+ - Merge, i.e. layer combination (in-place and copy)
27
+ - Set qubit labels (in-place and copy)
28
+
29
+ The packages currently available as circuit backends:
30
+
31
+ - Native `list` via [](api:ListPhysicalCircuit)
32
+ - `pygsti` via [](api:PyGSTiPhysicalCircuit) (requires `loqs[pygsti]`)
33
+ - `stim` via [](api:STIMPhysicalCircuit) (requires `loqs[stim]`)
34
+
35
+ !!! warning
36
+
37
+ For backends that depend on optional third-party packages,
38
+ it is recommended to not import from the module/class file directly.
39
+ Instead, try to import from [](api:loqs.backends), which dynamically checks
40
+ if that backend is available.
41
+
42
+ Examples
43
+ --------
44
+
45
+ Below use syndrome extraction circuits for the surface code
46
+ as an example for how to generate complex tiled circuits
47
+ from simple templates.
48
+
49
+ TODO: This should probably be shifted to a codepack?
50
+ Here we generate the syndrome extraction circuit
51
+ for Surface-17 based on [@tomita_lowdistance_2014].
52
+
53
+ >>> from loqs.backends import PyGSTiPhysicalCircuit as PhysCirc
54
+ >>> X_template = PhysCirc([('Gh', 'aux'), ('Gcnot', 'aux', 'b'),
55
+ ... ('Gcnot', 'aux', 'a'), ('Gcnot', 'aux', 'd'),
56
+ ... ('Gcnot', 'aux', 'c'), ('Gh', 'aux'), ('Iz', 'aux')],
57
+ ... qubit_labels=['a', 'b', 'c', 'd', 'aux']
58
+ ... ) # Fig 2a
59
+ >>> Z_template = PhysCirc([[], ('Gcnot', 'b', 'aux'),
60
+ ... ('Gcnot', 'a', 'aux'), ('Gcnot', 'd', 'aux'),
61
+ ... ('Gcnot', 'c', 'aux'), [], ('Iz','aux')],
62
+ ... qubit_labels=['a', 'b', 'c', 'd', 'aux']
63
+ ... ) # Fig 2b (with idle layers to match X check H layers)
64
+ >>> qubits = [f"D{i}" for i in range(9)] + [f"A{i}" for i in range(9, 17)]
65
+ >>> X_syndrome = PhysCirc.from_circuit_tiling(
66
+ ... X_template,
67
+ ... qubits,
68
+ ... [
69
+ ... [None, None, "D1", "D2" , "A9"],
70
+ ... ["D0", "D1", "D3", "D4", "A11"],
71
+ ... ["D4", "D5", "D7", "D8", "A14"],
72
+ ... ["D6", "D7", None, None, "A16"],
73
+ ... ],
74
+ ... merge_offsets=0 # Can all overlap
75
+ ... )
76
+ >>> Z_syndrome = PhysCirc.from_circuit_tiling(
77
+ ... Z_template,
78
+ ... qubits,
79
+ ... [
80
+ ... [None, "D0", None, "D3", "A10"],
81
+ ... ["D1", "D2", "D4", "D5", "A12"],
82
+ ... ["D3", "D4", "D6", "D7", "A13"],
83
+ ... ["D5", None, "D8", None, "A15"],
84
+ ... ],
85
+ ... merge_offsets=0 # Can all overlap
86
+ ... )
87
+ >>> full_syndrome = X_syndrome.merge(Z_syndrome, 0)
88
+ >>> print(full_syndrome)
89
+ Physical pyGSTi circuit:
90
+ Qubit D0 ---| |-|CA10|-|TA11|-| |-| |-| |-| |---
91
+ Qubit D1 ---| |-|TA11|-|CA12|-| |-|TA9 |-| |-| |---
92
+ Qubit D2 ---| |-|CA12|-| |-|TA9 |-| |-| |-| |---
93
+ Qubit D3 ---| |-| |-|CA13|-|CA10|-|TA11|-| |-| |---
94
+ Qubit D4 ---| |-|CA13|-|TA14|-|TA11|-|CA12|-| |-| |---
95
+ Qubit D5 ---| |-|TA14|-|CA15|-|CA12|-| |-| |-| |---
96
+ Qubit D6 ---| |-| |-|TA16|-| |-|CA13|-| |-| |---
97
+ Qubit D7 ---| |-|TA16|-| |-|CA13|-|TA14|-| |-| |---
98
+ Qubit D8 ---| |-| |-| |-|TA14|-|CA15|-| |-| |---
99
+ Qubit A9 ---|Gh|-| |-| |-|CD2 |-|CD1 |-|Gh|-|Iz|---
100
+ Qubit A10 ---| |-|TD0 |-| |-|TD3 |-| |-| |-|Iz|---
101
+ Qubit A11 ---|Gh|-|CD1 |-|CD0 |-|CD4 |-|CD3 |-|Gh|-|Iz|---
102
+ Qubit A12 ---| |-|TD2 |-|TD1 |-|TD5 |-|TD4 |-| |-|Iz|---
103
+ Qubit A13 ---| |-|TD4 |-|TD3 |-|TD7 |-|TD6 |-| |-|Iz|---
104
+ Qubit A14 ---|Gh|-|CD5 |-|CD4 |-|CD8 |-|CD7 |-|Gh|-|Iz|---
105
+ Qubit A15 ---| |-| |-|TD5 |-| |-|TD8 |-| |-|Iz|---
106
+ Qubit A16 ---|Gh|-|CD7 |-|CD6 |-| |-| |-|Gh|-|Iz|---
107
+ <BLANKLINE>
108
+ >>> print(repr(full_syndrome))
109
+ Physical pyGSTi circuit: \
110
+ Circuit([Gh:A9Gh:A11Gh:A14Gh:A16][Gcnot:A11:D1Gcnot:A14:D5Gcnot:A16:D7\
111
+ Gcnot:D0:A10Gcnot:D2:A12Gcnot:D4:A13][Gcnot:A11:D0Gcnot:A14:D4Gcnot:A16:D6\
112
+ Gcnot:D1:A12Gcnot:D3:A13Gcnot:D5:A15][Gcnot:A9:D2Gcnot:A11:D4Gcnot:A14:D8\
113
+ Gcnot:D3:A10Gcnot:D5:A12Gcnot:D7:A13][Gcnot:A9:D1Gcnot:A11:D3Gcnot:A14:D7\
114
+ Gcnot:D4:A12Gcnot:D6:A13Gcnot:D8:A15][Gh:A9Gh:A11Gh:A14Gh:A16]\
115
+ [Iz:A9Iz:A11Iz:A14Iz:A16Iz:A10Iz:A12Iz:A13Iz:A15]\
116
+ @(D0,D1,D2,D3,D4,D5,D6,D7,D8,A9,A10,A11,A12,A13,A14,A15,A16))
117
+ """
118
+
119
+ from .basecircuit import BasePhysicalCircuit
120
+ from .listcircuit import ListPhysicalCircuit