Skip to content

Commit fd87be7

Browse files
inctechspehamTompre-commit-ci[bot]
authored
Compiler for Code Switching (#524)
## Description Compiler for determining the minimum number of code switching operations. ## Checklist: <!--- This checklist serves as a reminder of a couple of things that ensure your pull request will be merged swiftly. --> - [x] The pull request only contains commits that are focused and relevant to this change. - [x] I have added appropriate tests that cover the new/changed functionality. - [x] I have updated the documentation to reflect these changes. - [x] I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals. - [x] I have added migration instructions to the upgrade guide (if needed). - [x] The changes follow the project's style guidelines and introduce no new warnings. - [x] The changes are fully tested and pass the CI checks. - [x] I have reviewed my own code changes. --------- Signed-off-by: Erik Weilandt <48921674+inctechs@users.noreply.github.com> Co-authored-by: Tom Peham <tom.peham@tum.de> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 9381173 commit fd87be7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+4287
-9
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,5 @@ out/build
174174

175175
node_modules/
176176
wheelhouse/
177+
178+
pytest.ini

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel
1111

1212
### Added
1313

14+
- New `MinimalCodeSwitchingCompiler` class that implements a compiler for minimal-overhead code switching on the logical level. ([#524], [arXiv:2512.04170](https://arxiv.org/abs/2512.04170)) ([**@inctechs**])
1415
- Added `gottesman_encoding_circuit` methods that constructs a stim encoding circuit for a given stabilizer code using the method described in Gottesman's "Surviving as a Quantum Computer in a Classical World" Chapter 6.4.1. ([#486]) ([**@pehamtom**])
1516
- Added class `SteaneNDFTStatePrepSimulator` for simulating non-deterministic state preparation protocols for CSS codes using verification with multiple ancilla states. ([#462]) ([**@pehamtom**])
1617
- Extended estimation of error rates in `NoisyNDFTStatePrepSimulator` via `secondary_logical_error_rate`. Now Z (X) error rates can also be estimated for the preparation of logical zero (plus). ([#462]) ([**@pehamtom**])
@@ -46,6 +47,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool
4647

4748
<!-- PR links -->
4849

50+
[#524]: https://github.com/munich-quantum-toolkit/qecc/pull/524
4951
[#592]: https://github.com/munich-quantum-toolkit/qecc/pull/592
5052
[#543]: https://github.com/munich-quantum-toolkit/qecc/pull/543
5153
[#503]: https://github.com/munich-quantum-toolkit/qecc/pull/503
@@ -61,6 +63,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool
6163

6264
[**@pehamtom**]: https://github.com/pehamtom
6365
[**@denialhaag**]: https://github.com/denialhaag
66+
[**@inctechs**]: https://github.com/inctechs
6467

6568
<!-- General links -->
6669

README.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,12 @@ It is part of the [_Munich Quantum Toolkit (MQT)_](https://mqt.readthedocs.io).
3333
The SMT solver Z3 is used to determine minimal solutions of the MaxSAT problem, resulting in minimum-weight decoding estimates.
3434
- Decode bosonic quantum LDPC codes and conduct numerical simulations for analog information decoding under phenomenological (cat qubit) noise.
3535
- Synthesize non-deterministic and deterministic fault-tolerant state preparation circuits for qubit CSS codes.
36+
- Find the minimum number of code switching operations and their placement in a given quantum circuit that employs code switching as a way to implement logical operations fault-tolerantly.
3637

3738
> [!NOTE]
3839
> Usage for _Lattice Surgery Compilation Beyond the Surface Code_ as well as _Exploiting Movable Logical Qubits for Lattice Surgery Compilation_ is described in [`docs/cococo.md`](https://github.com/munich-quantum-toolkit/qecc/blob/cococo/docs/cococo.md) in the `cococo` branch.
3940
> The code quality in the branch is actively being improved.
4041
41-
> [!NOTE]
42-
> Usage for _Minimizing the Number of Code Switching Operations in Fault-Tolerant Quantum Circuits_ is described in [`docs/CodeSwitching.md`](https://github.com/munich-quantum-toolkit/qecc/blob/code-switching-compiler/docs/CodeSwitching.md) in the `code-switching-compiler` branch.
43-
> This branch undergoes some final improvements before being merged into `main`.
44-
4542
> [!WARNING]
4643
> The C++ implementation of the [union find decoder for LDPC codes](https://arxiv.org/pdf/2301.05731) and the [circuit transpilation framework](https://arxiv.org/abs/2209.0118) have been removed with `v2.0.0` and are no longer available.
4744
> QECC is now entirely a Python package.

docs/CodeSwitching.md

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
---
2+
file_format: mystnb
3+
kernelspec:
4+
name: python3
5+
mystnb:
6+
number_source_lines: true
7+
---
8+
9+
# Code Switching Optimization
10+
11+
Different Quantum Error Correction Codes (QECCs) support distinct sets of gates that can be implemented transversally. Transversal
12+
gates, which act on individual physical qubits of different logical code blocks, are inherently fault-tolerant as they do not spread
13+
errors uncontrollably through a quantum circuit. Code switching has been proposed as a technique that employs multiple QECCs
14+
whose respective sets of transversal gates complement each other to achieve universality. Logical qubits are dynamically transferred
15+
between these codes depending on which gate needs to be applied; in other words, the logical information is switched from one code
16+
to the other.
17+
18+
However, code switching is a costly operation in terms of space and time overhead. Therefore, given a quantum circuit, we want to find the **minimum number of switches** required to execute it.
19+
20+
QECC has functionality to automatically determine the minimum number of switching operations required to perform a circuit using two complementary gate sets.
21+
The problem can be modelled as a **Min-Cut / Max-Flow** problem on a directed graph. The graph is constructed such that:
22+
23+
- **Source (SRC):** Represents the first code (e.g., 2D Color Code).
24+
- **Sink (SNK):** Represents the second code (e.g., 3D Color Code).
25+
- **Nodes:** Quantum gates in the circuit.
26+
- **Edges:**
27+
- **Infinite Capacity:** Connect gates unique to one code (e.g., T gates) to their respective terminal (Sink).
28+
- **Temporal Edges:** Finite capacity edges connecting sequential operations on the same qubit. A "cut" here represents a code switch.
29+
30+
The minimum cut separating the Source from the Sink corresponds to the optimal switching strategy.
31+
32+
## Basic Usage
33+
34+
Let's look at how to use the `MinimalCodeSwitchingCompiler` to analyze a simple quantum circuit. Assume the two codes in question are the 2D and 3D color codes, which have transversal gate sets $\{H, CX\}$ and $\{T, CX\}$, respectively.
35+
36+
```{code-cell} ipython3
37+
from qiskit import QuantumCircuit
38+
from mqt.qecc.code_switching import MinimalCodeSwitchingCompiler, CompilerConfig
39+
40+
# Define the transversal gate sets:
41+
# Code A (Source): 2D Color Code
42+
SOURCE_GATES = {"H", "CX"}
43+
44+
# Code B (Sink): 3D Color Code
45+
SINK_GATES = {"T", "CX"}
46+
47+
# Initialize the compiler
48+
mcsc = MinimalCodeSwitchingCompiler(
49+
gate_set_code_source=SOURCE_GATES,
50+
gate_set_code_sink=SINK_GATES
51+
)
52+
```
53+
54+
Next, we create a Qiskit circuit that forces the compiler to make decisions. We will interleave Hadamard gates (Source-favored) and T gates (Sink-favored), separated by CNOTs (Common to both).
55+
56+
```{code-cell} ipython3
57+
qc = QuantumCircuit(6)
58+
59+
qc.h(range(3))
60+
qc.t(range(3,6))
61+
62+
qc.barrier()
63+
64+
qc.cx(1, 4)
65+
qc.cx(3, 4)
66+
qc.cx(2, 3)
67+
qc.cx(2, 4)
68+
qc.cx(0, 4)
69+
qc.cx(5, 3)
70+
71+
qc.barrier()
72+
73+
qc.h(range(3))
74+
qc.t(range(3,6))
75+
```
76+
77+
```{code-cell} ipython3
78+
:tags: [hide-input]
79+
qc.draw('mpl')
80+
```
81+
82+
The only optimization potential lies in the middle for the CNOT portion of the circuit, as the initial and final layers of single qubit gates force us to be in specific codes.
83+
We can now build the graph from the circuit and compute the minimum cut.
84+
85+
```{code-cell} ipython3
86+
# Build the graph representation of the circuit
87+
mcsc.build_from_qiskit(qc)
88+
89+
# Compute Min-Cut
90+
num_switches, switch_pos, set_S, set_T = mcsc.compute_min_cut()
91+
92+
print(f"Total switches required: {num_switches}")
93+
print("Switch locations (qubit, depth):")
94+
for pos in switch_pos:
95+
print(f" - Qubit {pos[0]} after operation depth {pos[1]}")
96+
```
97+
98+
The output positions provide the exact locations (qubit, depth) where a code switch operation must be inserted into the circuit.
99+
100+
Note that under specific conditions, CNOT operations can be implemented transversally even when the control and target qubits
101+
are encoded in different codes. This property, however, is directional. In the 2D-3D color code scheme, it holds only when the
102+
control qubit is encoded in the 3D color code and the target qubit in the 2D color code.
103+
104+
To account for this, we can pass a dictionary specifying gates that can be implemented one-way transverally together with their direction.
105+
To see how this affect the optimization, consider the following circuit:
106+
107+
```{code-cell} ipython3
108+
qc = QuantumCircuit(2)
109+
qc.t(0)
110+
qc.h(1)
111+
qc.cx(0, 1)
112+
```
113+
114+
```{code-cell} ipython3
115+
:tags: [hide-input]
116+
qc.draw('mpl')
117+
```
118+
119+
Calculating the minimum number of switches without considering the one-way transversal CNOT property yields:
120+
121+
```{code-cell} ipython3
122+
mcsc = MinimalCodeSwitchingCompiler({"H", "CX"}, {"T", "CX"})
123+
mcsc.build_from_qiskit(qc)
124+
num_switches, switch_pos, _, _ = mcsc.compute_min_cut()
125+
```
126+
127+
```{code-cell} ipython3
128+
:tags: [hide-input]
129+
print(f"Total switches required (without one-way CNOT): {num_switches}")
130+
print("Switch locations (qubit, depth):")
131+
for pos in switch_pos:
132+
print(f" - Qubit {pos[0]} after operation depth {pos[1]}")
133+
```
134+
135+
Hence, a single switch right after the T gate on qubit 0 is required. However, if we consider the one-way transversal CNOT property, we can avoid this switch:
136+
137+
```{code-cell} ipython3
138+
mcsc = MinimalCodeSwitchingCompiler({"H", "CX"}, {"T", "CX"})
139+
mcsc.build_from_qiskit(qc, one_way_gates={"CX": ("SNK", "SRC")})
140+
num_switches, switch_pos, _, _ = mcsc.compute_min_cut()
141+
```
142+
143+
```{code-cell} ipython3
144+
:tags: [hide-input]
145+
print(f"Total switches required: {num_switches}")
146+
```
147+
148+
We specify in the graph-construction method `build_from_qiskit` that CNOTs (`CX`) are implemented transversally when the control qubit is encoded in the Sink code (3D color code) and the target qubit is encoded in the Source code (2D color code), i.e., `(control, target) <=> ("SNK", "SRC")`.
149+
150+
## Extensions to the Min-Cut Model
151+
152+
Finding the minimum number of switches is a good starting point, but in practice, we might want to consider additional factors such as:
153+
154+
- **Depth Optimization:** Choosing the placing of the switching positions such that switching operations are placed preferably on idling qubits while keeping the total number of switches minimal. This has the potential to reduce the overall circuit depth increase caused by the switching operations.
155+
- **Code Bias:** If one of the codes has a significantly higher overhead for switching operations, we might want to minimize switches into that code specifically.
156+
157+
### Depth Optimization
158+
159+
To incorporate depth optimization, we can assign an idle bonus to weights of the temporal edges based on whether the qubit is idling or active. For example, we can assign a lower weight to edges corresponding to idling qubits, encouraging the min-cut algorithm to place switches there.
160+
161+
```{code-cell} ipython3
162+
qc = QuantumCircuit(3)
163+
qc.h(0)
164+
qc.t(1)
165+
qc.t(2)
166+
qc.cx(1, 2)
167+
qc.cx(0, 2)
168+
```
169+
170+
```{code-cell} ipython3
171+
:tags: [hide-input]
172+
qc.draw('mpl')
173+
```
174+
175+
Running the regular min-cut computation yields a switch on qubit 2 after the T gate (we allow one-way CNOTs here):
176+
177+
```{code-cell} ipython3
178+
mcsc = MinimalCodeSwitchingCompiler({"H", "CX"}, {"T", "CX"})
179+
mcsc.build_from_qiskit(qc, one_way_gates={"CX": ("SNK", "SRC")})
180+
num_switches, switch_pos, _, _ = mcsc.compute_min_cut()
181+
```
182+
183+
```{code-cell} ipython3
184+
:tags: [hide-input]
185+
print(f"Total switches required (with one-way CNOT): {num_switches}")
186+
print("Switch locations (qubit, depth):")
187+
for pos in switch_pos:
188+
print(f" - Qubit {pos[0]} after operation depth {pos[1]}")
189+
```
190+
191+
However, if we assign a lower weight to the temporal edge of qubit 0 (which is idling), the algorithm chooses to place the switch there instead:
192+
193+
```{code-cell} ipython3
194+
mcsc = MinimalCodeSwitchingCompiler({"H", "CX"}, {"T", "CX"})
195+
mcsc.build_from_qiskit(qc, one_way_gates={"CX": ("SNK", "SRC")}, idle_bonus=True)
196+
num_switches, switch_pos, _, _ = mcsc.compute_min_cut()
197+
```
198+
199+
```{code-cell} ipython3
200+
:tags: [hide-input]
201+
print(f"Total switches required (with one-way CNOT): {num_switches}")
202+
print("Switch locations (qubit, depth):")
203+
for pos in switch_pos:
204+
print(f" - Qubit {pos[0]} after operation depth {pos[1]}")
205+
```
206+
207+
### Code Bias
208+
209+
To minimize switches into a specific code, we can add a certain code bias. The implementation already has a bias towards the source code.
210+
211+
```{code-cell} ipython3
212+
qc = QuantumCircuit(2)
213+
qc.h(1)
214+
qc.t(0)
215+
qc.cx(0, 1)
216+
```
217+
218+
```{code-cell} ipython3
219+
:tags: [hide-input]
220+
qc.draw('mpl')
221+
```
222+
223+
The default behavior with a bias towards the source code yields that the switch is placed after the T gate on qubit 0 such that the CNOT is in the source code:
224+
225+
```{code-cell} ipython3
226+
mcsc = MinimalCodeSwitchingCompiler({"H", "CX"}, {"T", "CX"})
227+
mcsc.build_from_qiskit(qc, one_way_gates={"CX": ("SNK", "SRC")})
228+
num_switches, switch_pos, _, _ = mcsc.compute_min_cut()
229+
```
230+
231+
```{code-cell} ipython3
232+
:tags: [hide-input]
233+
print(f"Total switches required (with one-way CNOT): {num_switches}")
234+
print("Switch locations (qubit, depth):")
235+
for pos in switch_pos:
236+
print(f" - Qubit {pos[0]} after operation depth {pos[1]}")
237+
```
238+
239+
However, if we wanted to instead bias towards the sink code, we could adjust the compiler configuration as follows:
240+
241+
```{code-cell} ipython3
242+
config = CompilerConfig(biased_code="SNK")
243+
mcsc = MinimalCodeSwitchingCompiler({"H", "CX"}, {"T", "CX"}, config=config)
244+
mcsc.build_from_qiskit(qc, one_way_gates={"CX": ("SNK", "SRC")}, code_bias=True)
245+
num_switches, switch_pos, _, _ = mcsc.compute_min_cut()
246+
```
247+
248+
```{code-cell} ipython3
249+
:tags: [hide-input]
250+
print(f"Total switches required (with one-way CNOT): {num_switches}")
251+
print("Switch locations (qubit, depth):")
252+
for pos in switch_pos:
253+
print(f" - Qubit {pos[0]} after operation depth {pos[1]}")
254+
```

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ installation
2929
LightsOutDecoder
3030
StatePrep
3131
CatStates
32+
CodeSwitching
3233
Encoders
3334
AnalogInfo
3435
references

pyproject.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ dependencies = [
6464
"bposd>=1.6",
6565
"qecsim>=1.0b9",
6666
"sinter>=1.14.0",
67-
"tqdm>=4.66.2"
67+
"tqdm>=4.66.2",
68+
"networkx>=3.4.2",
6869
]
6970
dynamic = ["version"]
7071

@@ -122,6 +123,9 @@ filterwarnings = [
122123

123124
[tool.coverage]
124125
run.source = ["mqt.qecc"]
126+
run.disable_warnings = [
127+
"no-sysmon",
128+
]
125129
report.exclude_also = [
126130
'\.\.\.',
127131
'if TYPE_CHECKING:',
@@ -130,9 +134,6 @@ report.exclude_also = [
130134
'def __dir__()', # Ignore __dir__ method that exists mainly for better IDE support
131135
'@overload' # Overloads are only for static typing
132136
]
133-
run.disable_warnings = [
134-
"no-sysmon",
135-
]
136137

137138
[tool.mypy]
138139
files = ["src/mqt", "tests", "noxfile.py"]
@@ -151,7 +152,7 @@ exclude = [
151152

152153
[[tool.mypy.overrides]]
153154
module = ["qiskit.*", "qecsim.*", "qiskit_aer.*", "matplotlib.*", "scipy.*", "ldpc.*", "pytest_console_scripts.*",
154-
"z3.*", "bposd.*", "numba.*", "pymatching.*", "stim.*", "multiprocess.*", "sinter.*", "qsample.*", "pandas.*", "tqdm.*"]
155+
"z3.*", "bposd.*", "numba.*", "pymatching.*", "stim.*", "multiprocess.*", "sinter.*", "qsample.*", "pandas.*", "tqdm.*", "networkx.*"]
155156
ignore_missing_imports = true
156157

157158

0 commit comments

Comments
 (0)