Skip to content
Merged
Show file tree
Hide file tree
Changes from 97 commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
fe606c4
update git_ignore
inctechs Oct 8, 2025
ad9e877
Merge remote-tracking branch 'origin/main' into code-switching-compiler
inctechs Oct 23, 2025
892cba3
add networkx to mypy ignore
inctechs Oct 27, 2025
9d5fdb8
implement first version of min-cut solution
inctechs Oct 27, 2025
e5e16fc
add one way transveral CNOTs to be allowed
inctechs Oct 28, 2025
bdb5b9b
add edge_type keyword to edges
inctechs Nov 4, 2025
0e9b3cf
change default order of magnitude for temporal edges
inctechs Nov 4, 2025
bb33537
add code bias functionality
inctechs Nov 4, 2025
91df0eb
clear/ reorder code
inctechs Nov 4, 2025
eee407a
read in QC as dag to support depth and prepare idle switching
inctechs Nov 5, 2025
c1d3f52
change qubit activity collection to list instead of set
inctechs Nov 5, 2025
adc87f5
make Global default temporal edge weight
inctechs Nov 5, 2025
3bf1bff
add idle bonus calculation and idle switching funcitonality
inctechs Nov 5, 2025
1c7684e
update docstrings and comments
inctechs Nov 5, 2025
73ffd1c
fix counting of switches
inctechs Nov 5, 2025
5b08d68
Revert "fix counting of switches"
inctechs Nov 5, 2025
ad6ff67
fix counting of switching operations
inctechs Nov 5, 2025
c63e2aa
add placeholder switch operations to qiskit circuit
inctechs Nov 6, 2025
9b5bb38
add extraction of switching position
inctechs Nov 6, 2025
4317cd4
add feature that idle bonus only considered for certain length
inctechs Nov 12, 2025
2c58f77
change return of min-cut function
inctechs Nov 12, 2025
38d1304
add scripts for generating ciruits
inctechs Nov 13, 2025
1914d5b
add function to generate universal random qiskit circuit
inctechs Nov 13, 2025
4b40503
add edge ration
inctechs Nov 13, 2025
796e032
change default folder name of circuit output
inctechs Nov 13, 2025
926fb01
update default number of seeds
inctechs Nov 13, 2025
e330bca
add lookahead function
inctechs Nov 13, 2025
4689579
add scripts for performance analysis
inctechs Nov 14, 2025
f07cc68
change default number of generated cicuits
inctechs Nov 14, 2025
ef3dfd4
remove raw data return form min_cut
inctechs Nov 14, 2025
0ec634d
make lookahead deterministic
inctechs Nov 14, 2025
63c982d
add lookahead to init file
inctechs Nov 14, 2025
a5c02dd
change simulations to be gate distribution sensitive
inctechs Nov 14, 2025
f99ee41
add feature to avoid generating circuits twice
inctechs Nov 16, 2025
6d9e3ac
add script to generate array with steps of 64
inctechs Nov 16, 2025
1436eb8
add all qubits numbers to performance bash
inctechs Nov 16, 2025
80fbb09
add intermediate results
inctechs Nov 17, 2025
a82e97d
add new results
inctechs Nov 18, 2025
c0c39ca
update simulations
inctechs Nov 18, 2025
d37f0ab
move utils functions to utils file
inctechs Nov 21, 2025
a785748
set up code switching compilation test
inctechs Nov 21, 2025
595ecba
Merge remote-tracking branch 'origin/main' into code-switching-compiler
inctechs Nov 21, 2025
4d7b7a1
repair test for idle bonus
inctechs Nov 21, 2025
a333e5d
redefine api by making functions private
inctechs Nov 21, 2025
1d90c17
add full function docstring extract switch locations
inctechs Nov 21, 2025
1af4cd8
add one way transversal test
inctechs Nov 21, 2025
82fdcf1
add CompilerConfig for different settings and keep it tidy
inctechs Nov 21, 2025
e73f6fc
add CompilerConfig to init
inctechs Nov 21, 2025
5c8f2ec
connect biased code to CompilerConfig
inctechs Nov 21, 2025
ffc1375
add code bias test
inctechs Nov 21, 2025
42ae8cf
add test for compilation utils
inctechs Nov 23, 2025
2e4e588
remove dag layer visualization used for debugging previously
inctechs Nov 23, 2025
9f75e39
Merge remote-tracking branch 'origin/main' into code-switching-compiler
inctechs Nov 24, 2025
76a2649
work in CodeRabbitAI code review
inctechs Nov 26, 2025
3c23ca5
fix more CodeRabbitAIs comments
inctechs Nov 26, 2025
0ffd8f1
rename variables in scripts
inctechs Nov 28, 2025
6cb8917
rename class to MinimalCodeSwitchingCompiler
inctechs Nov 28, 2025
2defa2d
generalise Graph construction
inctechs Dec 1, 2025
648fe47
remove unused methods to connect to terminals and connecting CNOTs
inctechs Dec 1, 2025
7e2dd98
fix typo with parameter names
inctechs Dec 1, 2025
c8e66ca
add idle bonus calcualtion from the paper
inctechs Dec 1, 2025
2dfb57d
move random circuit generation from utils to scripts
inctechs Dec 1, 2025
fe44822
rename naive code switching method and adjust collaterals
inctechs Dec 1, 2025
364550b
remove unnecessary comments
inctechs Dec 1, 2025
85b2d35
move node parser back to graph class
inctechs Dec 1, 2025
d9fe652
remove redundancy
inctechs Dec 1, 2025
4afedfb
rename tests for naive switching
inctechs Dec 1, 2025
45a3a97
remove stress test, adapt parsing node test, fix num_switches typo
inctechs Dec 1, 2025
24793dd
improve placeholder test
inctechs Dec 1, 2025
ec589b7
remove personal playground folder
inctechs Dec 2, 2025
7415903
add networkx to pyproject.toml
inctechs Dec 2, 2025
e5b3beb
automatic reordering by uv
inctechs Dec 2, 2025
30509e0
fix logical error where same back to back gates are allowed
inctechs Dec 2, 2025
85fc4b6
rabbit edits
inctechs Dec 2, 2025
e458dae
rename package to code_switching
inctechs Dec 2, 2025
5b529a0
add safe guard for non negative bonus
inctechs Dec 2, 2025
a8d3ca2
save uv.lock
inctechs Dec 2, 2025
b92b2f7
Merge remote-tracking branch 'origin/main' into code-switching-compiler
inctechs Dec 2, 2025
27060ce
add CodeSwitching docs
inctechs Dec 2, 2025
518d63e
rename unary edges to bias edges
inctechs Dec 3, 2025
364b621
generalise one-way transversal gate definition.
inctechs Dec 3, 2025
bd00389
sketch doc structure
inctechs Dec 3, 2025
13d38b8
fix bug in docs
inctechs Dec 3, 2025
b71d9b4
fix bug in code cells
inctechs Dec 3, 2025
f63fec2
fix typos in the docs
inctechs Dec 3, 2025
3b38882
Merge remote-tracking branch 'origin/main' into code-switching-compiler
inctechs Dec 9, 2025
909c3c6
Update docs/CodeSwitching.md
inctechs Dec 9, 2025
dff65bc
Apply suggestions from code review for docs
inctechs Dec 9, 2025
e81fe16
Apply suggestions from code review: fix naming of class in docstrings
inctechs Dec 9, 2025
5674e4f
edit Changelog.md
inctechs Dec 9, 2025
9adb064
Merge branch 'code-switching-compiler' of https://github.com/munich-q…
inctechs Dec 9, 2025
ed674dc
fix latest coderabbit feedback
inctechs Dec 9, 2025
e901ac5
add missing references to changelog
inctechs Dec 9, 2025
2908247
Merge remote-tracking branch 'origin' into code-switching-compiler
inctechs Jan 14, 2026
94f31ea
🎨 pre-commit fixes
pre-commit-ci[bot] Jan 14, 2026
1eb3f39
fix last coderabbit changes
inctechs Jan 14, 2026
7f5b1e5
Merge branch 'code-switching-compiler' of https://github.com/munich-q…
inctechs Jan 14, 2026
a43628c
remove note from README and add key feature description
inctechs Jan 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,5 @@ out/build

node_modules/
wheelhouse/

pytest.ini
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel

### Added

- 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**])
- 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**])
- Added class `SteaneNDFTStatePrepSimulator` for simulating non-deterministic state preparation protocols for CSS codes using verification with multiple ancilla states. ([#462]) ([**@pehamtom**])
- 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**])
Expand Down Expand Up @@ -46,6 +47,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool

<!-- PR links -->

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

[**@pehamtom**]: https://github.com/pehamtom
[**@denialhaag**]: https://github.com/denialhaag
[**@inctechs**]: https://github.com/inctechs

<!-- General links -->

Expand Down
254 changes: 254 additions & 0 deletions docs/CodeSwitching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
---
file_format: mystnb
kernelspec:
name: python3
mystnb:
number_source_lines: true
---

# Code Switching Optimization

Different Quantum Error Correction Codes (QECCs) support distinct sets of gates that can be implemented transversally. Transversal
gates, which act on individual physical qubits of different logical code blocks, are inherently fault-tolerant as they do not spread
errors uncontrollably through a quantum circuit. Code switching has been proposed as a technique that employs multiple QECCs
whose respective sets of transversal gates complement each other to achieve universality. Logical qubits are dynamically transferred
between these codes depending on which gate needs to be applied; in other words, the logical information is switched from one code
to the other.

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.

QECC has functionality to automatically determine the minimum number of switching operations required to perform a circuit using two complementary gate sets.
The problem can be modelled as a **Min-Cut / Max-Flow** problem on a directed graph. The graph is constructed such that:

- **Source (SRC):** Represents the first code (e.g., 2D Color Code).
- **Sink (SNK):** Represents the second code (e.g., 3D Color Code).
- **Nodes:** Quantum gates in the circuit.
- **Edges:**
- **Infinite Capacity:** Connect gates unique to one code (e.g., T gates) to their respective terminal (Sink).
- **Temporal Edges:** Finite capacity edges connecting sequential operations on the same qubit. A "cut" here represents a code switch.

The minimum cut separating the Source from the Sink corresponds to the optimal switching strategy.

## Basic Usage

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.

```{code-cell} ipython3
from qiskit import QuantumCircuit
from mqt.qecc.code_switching import MinimalCodeSwitchingCompiler, CompilerConfig

# Define the transversal gate sets:
# Code A (Source): 2D Color Code
SOURCE_GATES = {"H", "CX"}

# Code B (Sink): 3D Color Code
SINK_GATES = {"T", "CX"}

# Initialize the compiler
mcsc = MinimalCodeSwitchingCompiler(
gate_set_code_source=SOURCE_GATES,
gate_set_code_sink=SINK_GATES
)
```

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).

```{code-cell} ipython3
qc = QuantumCircuit(6)

qc.h(range(3))
qc.t(range(3,6))

qc.barrier()

qc.cx(1, 4)
qc.cx(3, 4)
qc.cx(2, 3)
qc.cx(2, 4)
qc.cx(0, 4)
qc.cx(5, 3)

qc.barrier()

qc.h(range(3))
qc.t(range(3,6))
```

```{code-cell} ipython3
:tags: [hide-input]
qc.draw('mpl')
```

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.
We can now build the graph from the circuit and compute the minimum cut.

```{code-cell} ipython3
# Build the graph representation of the circuit
mcsc.build_from_qiskit(qc)

# Compute Min-Cut
num_switches, switch_pos, set_S, set_T = mcsc.compute_min_cut()

print(f"Total switches required: {num_switches}")
print("Switch locations (qubit, depth):")
for pos in switch_pos:
print(f" - Qubit {pos[0]} after operation depth {pos[1]}")
```

The output positions provide the exact locations (qubit, depth) where a code switch operation must be inserted into the circuit.

Note that under specific conditions, CNOT operations can be implemented transversally even when the control and target qubits
are encoded in different codes. This property, however, is directional. In the 2D-3D color code scheme, it holds only when the
control qubit is encoded in the 3D color code and the target qubit in the 2D color code.

To account for this, we can pass a dictionary specifying gates that can be implemented one-way transverally together with their direction.
To see how this affect the optimization, consider the following circuit:

```{code-cell} ipython3
qc = QuantumCircuit(2)
qc.t(0)
qc.h(1)
qc.cx(0, 1)
```

```{code-cell} ipython3
:tags: [hide-input]
qc.draw('mpl')
```

Calculating the minimum number of switches without considering the one-way transversal CNOT property yields:

```{code-cell} ipython3
mcsc = MinimalCodeSwitchingCompiler({"H", "CX"}, {"T", "CX"})
mcsc.build_from_qiskit(qc)
num_switches, switch_pos, _, _ = mcsc.compute_min_cut()
```

```{code-cell} ipython3
:tags: [hide-input]
print(f"Total switches required (without one-way CNOT): {num_switches}")
print("Switch locations (qubit, depth):")
for pos in switch_pos:
print(f" - Qubit {pos[0]} after operation depth {pos[1]}")
```

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:

```{code-cell} ipython3
mcsc = MinimalCodeSwitchingCompiler({"H", "CX"}, {"T", "CX"})
mcsc.build_from_qiskit(qc, one_way_gates={"CX": ("SNK", "SRC")})
num_switches, switch_pos, _, _ = mcsc.compute_min_cut()
```

```{code-cell} ipython3
:tags: [hide-input]
print(f"Total switches required: {num_switches}")
```

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")`.

## Extensions to the Min-Cut Model

Finding the minimum number of switches is a good starting point, but in practice, we might want to consider additional factors such as:

- **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.
- **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.

### Depth Optimization

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.

```{code-cell} ipython3
qc = QuantumCircuit(3)
qc.h(0)
qc.t(1)
qc.t(2)
qc.cx(1, 2)
qc.cx(0, 2)
```

```{code-cell} ipython3
:tags: [hide-input]
qc.draw('mpl')
```

Running the regular min-cut computation yields a switch on qubit 2 after the T gate (we allow one-way CNOTs here):

```{code-cell} ipython3
mcsc = MinimalCodeSwitchingCompiler({"H", "CX"}, {"T", "CX"})
mcsc.build_from_qiskit(qc, one_way_gates={"CX": ("SNK", "SRC")})
num_switches, switch_pos, _, _ = mcsc.compute_min_cut()
```

```{code-cell} ipython3
:tags: [hide-input]
print(f"Total switches required (with one-way CNOT): {num_switches}")
print("Switch locations (qubit, depth):")
for pos in switch_pos:
print(f" - Qubit {pos[0]} after operation depth {pos[1]}")
```

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:

```{code-cell} ipython3
mcsc = MinimalCodeSwitchingCompiler({"H", "CX"}, {"T", "CX"})
mcsc.build_from_qiskit(qc, one_way_gates={"CX": ("SNK", "SRC")}, idle_bonus=True)
num_switches, switch_pos, _, _ = mcsc.compute_min_cut()
```

```{code-cell} ipython3
:tags: [hide-input]
print(f"Total switches required (with one-way CNOT): {num_switches}")
print("Switch locations (qubit, depth):")
for pos in switch_pos:
print(f" - Qubit {pos[0]} after operation depth {pos[1]}")
```

### Code Bias

To minimize switches into a specific code, we can add a certain code bias. The implementation already has a bias towards the source code.

```{code-cell} ipython3
qc = QuantumCircuit(2)
qc.h(1)
qc.t(0)
qc.cx(0, 1)
```

```{code-cell} ipython3
:tags: [hide-input]
qc.draw('mpl')
```

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:

```{code-cell} ipython3
mcsc = MinimalCodeSwitchingCompiler({"H", "CX"}, {"T", "CX"})
mcsc.build_from_qiskit(qc, one_way_gates={"CX": ("SNK", "SRC")})
num_switches, switch_pos, _, _ = mcsc.compute_min_cut()
```

```{code-cell} ipython3
:tags: [hide-input]
print(f"Total switches required (with one-way CNOT): {num_switches}")
print("Switch locations (qubit, depth):")
for pos in switch_pos:
print(f" - Qubit {pos[0]} after operation depth {pos[1]}")
```

However, if we wanted to instead bias towards the sink code, we could adjust the compiler configuration as follows:

```{code-cell} ipython3
config = CompilerConfig(biased_code="SNK")
mcsc = MinimalCodeSwitchingCompiler({"H", "CX"}, {"T", "CX"}, config=config)
mcsc.build_from_qiskit(qc, one_way_gates={"CX": ("SNK", "SRC")}, code_bias=True)
num_switches, switch_pos, _, _ = mcsc.compute_min_cut()
```

```{code-cell} ipython3
:tags: [hide-input]
print(f"Total switches required (with one-way CNOT): {num_switches}")
print("Switch locations (qubit, depth):")
for pos in switch_pos:
print(f" - Qubit {pos[0]} after operation depth {pos[1]}")
```
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ installation
LightsOutDecoder
StatePrep
CatStates
CodeSwitching
Encoders
AnalogInfo
references
Expand Down
11 changes: 6 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ dependencies = [
"bposd>=1.6",
"qecsim>=1.0b9",
"sinter>=1.14.0",
"tqdm>=4.66.2"
"tqdm>=4.66.2",
"networkx>=3.4.2",
]
dynamic = ["version"]

Expand Down Expand Up @@ -122,6 +123,9 @@ filterwarnings = [

[tool.coverage]
run.source = ["mqt.qecc"]
run.disable_warnings = [
"no-sysmon",
]
report.exclude_also = [
'\.\.\.',
'if TYPE_CHECKING:',
Expand All @@ -130,9 +134,6 @@ report.exclude_also = [
'def __dir__()', # Ignore __dir__ method that exists mainly for better IDE support
'@overload' # Overloads are only for static typing
]
run.disable_warnings = [
"no-sysmon",
]

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

[[tool.mypy.overrides]]
module = ["qiskit.*", "qecsim.*", "qiskit_aer.*", "matplotlib.*", "scipy.*", "ldpc.*", "pytest_console_scripts.*",
"z3.*", "bposd.*", "numba.*", "pymatching.*", "stim.*", "multiprocess.*", "sinter.*", "qsample.*", "pandas.*", "tqdm.*"]
"z3.*", "bposd.*", "numba.*", "pymatching.*", "stim.*", "multiprocess.*", "sinter.*", "qsample.*", "pandas.*", "tqdm.*", "networkx.*"]
ignore_missing_imports = true


Expand Down
Loading
Loading