Skip to content

Conversation

@inctechs
Copy link
Collaborator

@inctechs inctechs commented Oct 8, 2025

Description

Compiler for determining the minimum number of code switching operations.

Checklist:

  • The pull request only contains commits that are focused and relevant to this change.
  • I have added appropriate tests that cover the new/changed functionality.
  • I have updated the documentation to reflect these changes.
  • I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals.
  • I have added migration instructions to the upgrade guide (if needed).
  • The changes follow the project's style guidelines and introduce no new warnings.
  • The changes are fully tested and pass the CI checks.
  • I have reviewed my own code changes.

@coderabbitai
Copy link

coderabbitai bot commented Oct 8, 2025

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.
📝 Walkthrough

Walkthrough

Adds a graph-based code-switching compiler and utilities, CLI scripts to generate and simulate QASM circuits with orchestration shell scripts, tests, documentation, and dependency/config updates (.gitignore and networkx).

Changes

Cohort / File(s) Summary
Config & deps
\.gitignore, pyproject.toml
Ignore pytest.ini; add networkx>=3.4.2 and networkx.* mypy override; consolidate coverage settings.
Package API
src/mqt/qecc/code_switching/__init__.py
New package initializer re-exporting compiler and utility symbols.
Code-switching compiler
src/mqt/qecc/code_switching/code_switching_compiler.py
New CompilerConfig and MinimalCodeSwitchingCompiler that map Qiskit circuits to a directed graph (temporal/infinite/bias edges, idle bonuses), compute min-cut, and extract switch locations. Large, logic-dense file requiring review.
Compilation utilities
src/mqt/qecc/code_switching/compilation_utils.py
New naive_switching(circuit) heuristic and insert_switch_placeholders(circuit, switch_positions, placeholder_depth=1) for switch counting and placeholder insertion.
Circuit generation
scripts/cs_compiler/generate_random_circuits.py
New CLI to generate seeded random universal circuits (H, T, CX, ID) with selectable gate distributions and write QASM files (skips existing).
Generation orchestration
scripts/cs_compiler/run_generate_circuits.sh
New Bash runner using GNU parallel to generate circuits across sizes and distributions.
Simulation
scripts/cs_compiler/simulate_circuit_performance.py
New CLI to load QASM, compute naive and min-cut switching counts/timings, deduplicate by seed, and append results to CSV.
Simulation orchestration
scripts/cs_compiler/run_performance_simulations.sh
New Bash orchestrator to run simulations in parallel across sizes, distributions, and seeds.
Tests
tests/code_switching/__init__.py, tests/code_switching/test_code_switching_compilation.py, tests/code_switching/test_compilation_utils.py
New test package and suites covering parsing, idle-bonus math, min-cut scenarios, naive switching, and placeholder insertion.
Docs
docs/CodeSwitching.md, docs/index.md
New documentation describing the min-cut code-switching approach; added to docs toctree.
Changelog
CHANGELOG.md
Added entry for MinimalCodeSwitchingCompiler.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant Gen as Generator\n(generate_random_circuits.py)
    participant FS as FileSystem
    participant Sim as Simulator\n(simulate_circuit_performance.py)
    participant Compiler as MinimalCodeSwitchingCompiler
    participant Qiskit as Qiskit

    User->>Gen: run --n --num_circuits --distr_type
    loop per-seed
        Gen->>Qiskit: random_universal_circuit(...) -> QuantumCircuit
        Qiskit-->>Gen: QuantumCircuit
        Gen->>FS: write .qasm (skip if exists)
    end

    User->>Sim: run --qasm_path --n --seed --distr_type
    Sim->>FS: read .qasm
    FS-->>Sim: qasm content
    Sim->>Qiskit: loads -> QuantumCircuit
    Sim->>Sim: naive_switching(circuit) => naive_count
    Sim->>Compiler: build_from_qiskit(circuit,...)
    Compiler->>Compiler: build graph (temporal/bias/idle)
    Compiler->>Compiler: compute_min_cut()
    Compiler-->>Sim: mincut_count + switch_positions
    Sim->>Sim: compute savings, append_to_csv
    Sim-->>User: CSV updated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested labels

dependencies, python

Suggested reviewers

  • pehamTom
  • burgholzer

Poem

🐰 I hopped through circuits, seeded and spun,
Gates twitched and tangled till the min-cut was done.
I nudged placeholders, wrote QASM with delight,
CSVs sang softly beneath the moonlight.
A carrot-coded patch — nibble, compile, run!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete. While it marks several checklist items as completed, two critical items are unchecked: changelog entry and CI passing confirmation. The description also lacks concrete details about the implementation, design decisions, dependencies, and test coverage. Complete the missing checklist items by adding a changelog entry documenting the new MinimalCodeSwitchingCompiler feature and confirming CI tests pass. Consider adding implementation details and dependency information to enhance clarity.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Compiler for Code Switching' clearly and concisely summarizes the main change—a new compiler for determining minimum code switching operations.
Docstring Coverage ✅ Passed Docstring coverage is 95.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pyproject.toml (1)

50-68: Add networkx to [project.dependencies] as explicit direct dependency.

While networkx is currently available transitively through pymatching and qecsim, the code directly imports it (src/mqt/qecc/circuit_compilation/code_switching_compiler.py:14). Best practice is to explicitly declare all direct imports to avoid fragility from transitive dependency changes.

Apply the suggested diff with networkx>=3.0 (covers tested versions 3.4.2 and 3.5 from lock file).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe606c4 and 9d5fdb8.

📒 Files selected for processing (3)
  • pyproject.toml (1 hunks)
  • src/mqt/qecc/circuit_compilation/__init__.py (1 hunks)
  • src/mqt/qecc/circuit_compilation/code_switching_compiler.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/mqt/qecc/circuit_compilation/__init__.py (1)
src/mqt/qecc/circuit_compilation/code_switching_compiler.py (1)
  • CodeSwitchGraph (20-179)
🪛 Ruff (0.14.1)
src/mqt/qecc/circuit_compilation/code_switching_compiler.py

178-178: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
🔇 Additional comments (3)
pyproject.toml (1)

156-157: mypy override for networkx looks fine.

Adding "networkx.*" to ignore_missing_imports aligns with new NX usage.

src/mqt/qecc/circuit_compilation/__init__.py (1)

12-14: Public re-export looks good.

CodeSwitchGraph is correctly exposed via __all__.

src/mqt/qecc/circuit_compilation/code_switching_compiler.py (1)

115-127: Redundant edge additions in add_cnot_links.

add_infinite_edge already adds both directions; calling it twice duplicates work. Keep one call.

Apply this diff:

-        self.add_infinite_edge(control_node, target_node)
-        self.add_infinite_edge(target_node, control_node)
+        self.add_infinite_edge(control_node, target_node)

Likely an incorrect or invalid review comment.

@codecov
Copy link

codecov bot commented Oct 27, 2025

Codecov Report

❌ Patch coverage is 95.79832% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...mqt/qecc/code_switching/code_switching_compiler.py 94.3% 9 Missing ⚠️
src/mqt/qecc/code_switching/compilation_utils.py 98.6% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

For this we fixed the arbitrary convention that the 2D Code corresponds
to the source and the 3D code to the sink.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (2)
src/mqt/qecc/circuit_compilation/code_switching_compiler.py (2)

112-133: Fix inline comments that contradict implementation.

The inline comments claim the opposite of what the code does:

  • Line 129 says "Source code can perform T" but line 130 connects T to sink
  • Line 132 says "Sink code can perform H" but line 133 connects H to source

The docstring at lines 115-116 correctly describes the intended behavior (source=2D Color Code with H, sink=3D Surface Code with T).

Apply this diff to fix the comments:

-        # Source code can perform T and CNOT gates
+        # Sink code (3D Surface Code) can perform T gates
         if gate_type == "T":
             self.add_infinite_edge(self.sink, node_id)
-        # Sink code can perform H and CNOT gates
+        # Source code (2D Color Code) can perform H gates
         if gate_type == "H":
             self.add_infinite_edge(node_id, self.source)

186-198: Remove unused noqa directive.

The # noqa: N806 on line 197 is unnecessary because the N806 rule (non-lowercase variable names) is not enabled in your Ruff configuration.

Apply this diff:

-        cut_value, (S, T) = nx.minimum_cut(self.G, self.source, self.sink, capacity="capacity")  # noqa: N806
+        cut_value, (S, T) = nx.minimum_cut(self.G, self.source, self.sink, capacity="capacity")

Based on static analysis hints.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d5fdb8 and e5e16fc.

📒 Files selected for processing (1)
  • src/mqt/qecc/circuit_compilation/code_switching_compiler.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.14.1)
src/mqt/qecc/circuit_compilation/code_switching_compiler.py

70-70: Boolean-typed positional argument in function definition

(FBT001)


70-70: Boolean default positional argument in function definition

(FBT002)


86-86: Boolean-typed positional argument in function definition

(FBT001)


86-86: Boolean default positional argument in function definition

(FBT002)


98-98: Boolean-typed positional argument in function definition

(FBT001)


98-98: Boolean default positional argument in function definition

(FBT002)


135-135: Boolean-typed positional argument in function definition

(FBT001)


135-135: Boolean default positional argument in function definition

(FBT002)


147-147: Boolean-typed positional argument in function definition

(FBT001)


147-147: Boolean default positional argument in function definition

(FBT002)


197-197: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04

this helps to distinguish between unary edges used for bias and temporal
edges used for actual switching locations
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (3)
src/mqt/qecc/circuit_compilation/code_switching_compiler.py (3)

88-98: Document the bidirectional parameter.

The bidirectional parameter is missing from the docstring and should be keyword-only for clarity.

This issue was previously flagged in past reviews.


152-162: Document the one_way_transversal_cnot parameter.

The one_way_transversal_cnot parameter is missing from the docstring and should be keyword-only for clarity.

This issue was previously flagged in past reviews.


145-150: Fix inline comments that contradict the implementation.

The inline comments at lines 145 and 148 are inconsistent with the implementation and the detailed explanation in lines 132-133:

  • Line 145 says "Source code can perform T and CNOT gates" but then connects T to sink (line 147)
  • Line 148 says "Sink code can perform H and CNOT gates" but then connects H to source (line 150)

According to your convention (lines 132-133), source = 2D Color Code (performs H), sink = 3D Surface Code (performs T), which matches the implementation but not these comments.

Apply this diff to fix the inline comments:

-        # Source code can perform T and CNOT gates
+        # Sink code (3D Surface Code) can perform T gates
         if gate_type == "T":
             self.add_infinite_edge(self.sink, node_id)
-        # Sink code can perform H and CNOT gates
+        # Source code (2D Color Code) can perform H gates
         if gate_type == "H":
             self.add_infinite_edge(node_id, self.source)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5e16fc and 91df0eb.

📒 Files selected for processing (1)
  • src/mqt/qecc/circuit_compilation/code_switching_compiler.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.14.3)
src/mqt/qecc/circuit_compilation/code_switching_compiler.py

71-71: Boolean-typed positional argument in function definition

(FBT001)


71-71: Boolean default positional argument in function definition

(FBT002)


88-88: Boolean-typed positional argument in function definition

(FBT001)


88-88: Boolean default positional argument in function definition

(FBT002)


100-100: Boolean-typed positional argument in function definition

(FBT001)


100-100: Boolean default positional argument in function definition

(FBT002)


152-152: Boolean-typed positional argument in function definition

(FBT001)


152-152: Boolean default positional argument in function definition

(FBT002)


165-165: Boolean-typed positional argument in function definition

(FBT001)


165-165: Boolean default positional argument in function definition

(FBT002)


165-165: Boolean-typed positional argument in function definition

(FBT001)


165-165: Boolean default positional argument in function definition

(FBT002)


208-208: Boolean-typed positional argument in function definition

(FBT001)


208-208: Boolean default positional argument in function definition

(FBT002)


219-219: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
🔇 Additional comments (2)
src/mqt/qecc/circuit_compilation/code_switching_compiler.py (2)

220-231: LGTM: Edge-counting logic correctly handles bidirectional edges.

The deduplication using tuple(sorted((u, v))) as a key (lines 226-227) properly avoids double-counting bidirectional edges when determining the number of code switches. The filtering by edge type and cut-crossing check are also correct.


20-68: Well-structured API for code-switching graph construction.

The CodeSwitchGraph class provides a clean, composable API with separate methods for adding different edge types (infinite, regular, bias) and node types. The use of NetworkX for graph representation and min-cut computation is appropriate for this domain.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (7)
src/mqt/qecc/circuit_compilation/code_switching_compiler.py (7)

26-33: Correct source/sink mapping in class docstring.

Doc claims SRC anchors T/CNOT and SNK anchors H/CNOT, but connect_to_code() actually ties T nodes to SNK (Line 152) and H nodes to SRC (Line 155), leaving CNOT unanchored. Please align the docstring with the implementation so downstream users don’t build the wrong mental model.

-      - Source (SRC) and sink (SNK) nodes represent two different codes:
-          * Source-connected nodes (T, CNOT) → operations that can be done transversally in code A.
-          * Sink-connected nodes (H, CNOT) → operations that can be done transversally in code B.
+      - Source (SRC) and sink (SNK) nodes represent two different codes:
+          * Source-connected nodes (H) → operations transversal in the 2D Color Code.
+          * Sink-connected nodes (T) → operations transversal in the 3D Surface Code.
+          * CNOT operations are supported by both codes and remain unanchored.

103-117: Fix add_regular_edge documentation to match implementation.

The doc still advertises “a regular directed edge” with default capacity 1.0, but the function defaults to DEFAULT_TEMPORAL_EDGE_CAPACITY (100.0) and, unless bidirectional=False, inserts both directions. Please correct the wording and defaults so the public API description is accurate.

-        """Add a regular (finite-capacity) directed edge.
+        """Add regular (finite-capacity) temporal edge(s) between two nodes.
@@
-        capacity : float, optional
-            Edge capacity (default is 1.0).
+        capacity : float, optional
+            Edge capacity (default is 100.0 via DEFAULT_TEMPORAL_EDGE_CAPACITY).
+        bidirectional : bool, optional
+            If True (default), adds both u→v and v→u with the given capacity; otherwise only u→v.

119-126: Document biased_code correctly.

biased_code is a str flag choosing between "SRC" and "SNK", yet the docstring still declares it as float/capacity. Please update the parameter section so users understand which values are valid and what effect they have.

-        biased_code : float
-            Capacity of the biased_code edges to be added.
+        node_id : str
+            Node to bias toward one of the terminal codes.
+        biased_code : str, optional
+            Either "SRC" or "SNK", indicating which terminal should receive the stronger unary edge (default "SRC").

157-167: Document one_way_transversal_cnot parameter.

The optional one_way_transversal_cnot flag flips edge directionality but is absent from the docstring. Please describe its behavior (True ⇒ only control→target, False ⇒ bidirectional) so callers understand when to enable it.

         target_node : str
             Node representing the target qubit's CNOT operation.
+        one_way_transversal_cnot : bool, optional
+            If True, adds only the control→target infinite edge. If False (default),
+            adds both directions to keep the qubits in the same code.

234-245: Document all build_from_qiskit feature flags.

The docstring covers one_way_transversal_cnot and code_bias but leaves idle_bonus undocumented, and the descriptions are very high-level. Please add explicit entries so users know what enabling idle_bonus does (idle-aware temporal capacities) and reiterate how code_bias affects unary edges.

         code_bias : bool, optional
-            If True, add bias edges for CNOT nodes.
+            If True, add bias edges for CNOT nodes to prefer one code over the other.
+        idle_bonus : bool, optional
+            If True, reduce temporal edge capacity after idle stretches via _edge_capacity_with_idle_bonus.

284-311: Fix compute_min_cut signature, docs, and unused noqa.

With return_raw_data=False (the default), the method returns (num_switches: int, S, T), not a float. The signature and Returns section should reflect this union, and the parameter needs documenting. While touching the block, please drop the now-unused # noqa.

-    def compute_min_cut(self, return_raw_data: bool = False) -> tuple[float, set[str], set[str]]:
+    def compute_min_cut(self, *, return_raw_data: bool = False) -> tuple[int | float, set[str], set[str]]:
@@
-        Returns:
+        Parameters
+        ----------
+        return_raw_data : bool, optional
+            If True, return the raw NetworkX cut capacity (float). If False (default),
+            return the counted number of switching edges (int).
+
+        Returns
         -------
-        Tuple[float, Set[str], Set[str]]
-            A tuple (cut_value, S, T) where:
-              - cut_value is the total capacity of the minimum cut,
+        tuple[int | float, set[str], set[str]]
+            A tuple (cut_value_or_count, S, T) where:
+              - cut_value_or_count is the raw capacity (return_raw_data=True) or number of switches (False),
@@
-        cut_value, (S, T) = nx.minimum_cut(self.G, self.source, self.sink, capacity="capacity")  # noqa: N806
+        cut_value, (S, T) = nx.minimum_cut(self.G, self.source, self.sink, capacity="capacity")

73-90: Docstring must reflect bidirectional edge behavior.

add_edge_with_capacity() says it adds “a directed edge”, yet with bidirectional=True (the default) it installs both u→v and v→u and also accepts an edge_type label. Please update the docstring (and ideally the summary line) so call sites know the method creates reciprocal edges unless they explicitly pass bidirectional=False.

-        """Add a directed edge with specified capacity between two nodes.
+        """Add edges with specified capacity between two nodes.
@@
         capacity : float
             Edge capacity.
+        edge_type : str, optional
+            Edge category stored on the graph (default "temporal").
+        bidirectional : bool, optional
+            If True (default), adds both u→v and v→u with the same capacity and edge_type.
+            If False, adds only the directed edge u→v.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91df0eb and 3bf1bff.

📒 Files selected for processing (1)
  • src/mqt/qecc/circuit_compilation/code_switching_compiler.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.14.3)
src/mqt/qecc/circuit_compilation/code_switching_compiler.py

74-74: Boolean-typed positional argument in function definition

(FBT001)


74-74: Boolean default positional argument in function definition

(FBT002)


91-91: Boolean-typed positional argument in function definition

(FBT001)


91-91: Boolean default positional argument in function definition

(FBT002)


104-104: Boolean-typed positional argument in function definition

(FBT001)


104-104: Boolean default positional argument in function definition

(FBT002)


157-157: Boolean-typed positional argument in function definition

(FBT001)


157-157: Boolean default positional argument in function definition

(FBT002)


224-224: Boolean-typed positional argument in function definition

(FBT001)


224-224: Boolean default positional argument in function definition

(FBT002)


225-225: Boolean-typed positional argument in function definition

(FBT001)


225-225: Boolean default positional argument in function definition

(FBT002)


226-226: Boolean-typed positional argument in function definition

(FBT001)


226-226: Boolean default positional argument in function definition

(FBT002)


284-284: Boolean-typed positional argument in function definition

(FBT001)


284-284: Boolean default positional argument in function definition

(FBT002)


295-295: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (8)
src/mqt/qecc/circuit_compilation/code_switching_compiler.py (8)

26-33: Fix class docstring to reflect actual code anchors.

The docstring still claims T/CNOT attach to the source and H/CNOT to the sink, and mentions a 1.0 default capacity. The implementation anchors T nodes to the sink, H nodes to the source, and uses DEFAULT_TEMPORAL_EDGE_CAPACITY = 100.0. Please sync the documentation with the code.

@@
-      - Source (SRC) and sink (SNK) nodes represent two different codes:
-          * Source-connected nodes (T, CNOT) → operations that can be done transversally in code A.
-          * Sink-connected nodes (H, CNOT) → operations that can be done transversally in code B.
+      - Source (SRC) and sink (SNK) nodes represent two different codes:
+          * Source-connected nodes (H) → operations that can be done transversally in the 2D Color Code.
+          * Sink-connected nodes (T) → operations that can be done transversally in the 3D Surface Code.
+          * CNOT operations are supported by both codes and remain unanchored.
@@
-      - Finite-capacity edges (default 1.0) represent potential code transitions along qubit timelines.
+      - Finite-capacity edges (default 100.0) represent potential code transitions along qubit timelines.

73-90: Document edge_type/bidirectional and clarify bidirectional behavior.

add_edge_with_capacity adds both directions by default and accepts edge_type, but neither parameter is documented. Please update the docstring and make it clear this method creates reciprocal edges when bidirectional=True.

-    def add_edge_with_capacity(
-        self, u: str, v: str, capacity: float, edge_type: str = "temporal", bidirectional: bool = True
-    ) -> None:
-        """Add a directed edge with specified capacity between two nodes.
+    def add_edge_with_capacity(
+        self, u: str, v: str, capacity: float, edge_type: str = "temporal", bidirectional: bool = True
+    ) -> None:
+        """Add an edge (optionally bidirectional) with specified capacity between two nodes.
@@
         capacity : float
             Edge capacity.
+        edge_type : str, optional
+            Label describing the edge category (e.g., "temporal", "entangling", "unary"). Default is "temporal".
+        bidirectional : bool, optional
+            If True (default), also creates the reverse edge (v→u) with the same capacity and edge_type.
+            If False, only the edge u→v is added.

91-101: Explain bidirectional semantics for infinite edges.

add_infinite_edge forwards bidirectional to add_edge_with_capacity, but the docstring omits it. Documenting this parameter avoids misuse.

     def add_infinite_edge(self, u: str, v: str, bidirectional: bool = True) -> None:
         """Add an edge of infinite capacity between two nodes.
@@
         v : str
             Target node identifier.
+        bidirectional : bool, optional
+            If True (default), also adds the reverse infinite-capacity edge (v→u). If False, only u→v is added.

103-118: Fix default capacity note and document bidirectional flag.

DEFAULT_TEMPORAL_EDGE_CAPACITY is 100.0 and bidirectional controls reverse edges, yet the docstring still states a 1.0 default and omits the flag.

     def add_regular_edge(
         self, u: str, v: str, capacity: float = DEFAULT_TEMPORAL_EDGE_CAPACITY, bidirectional: bool = True
     ) -> None:
         """Add a regular (finite-capacity) directed edge.
@@
         capacity : float, optional
-            Edge capacity (default is 1.0).
+            Edge capacity (default is 100.0).
+        bidirectional : bool, optional
+            If True (default), also adds the reverse edge (v→u) with the same capacity.

119-132: Correct add_bias_edges parameter docs.

The docstring lists only biased_code (wrongly typed as float) and omits node_id. Please match the signature and explain the string options.

-    def add_bias_edges(self, node_id: str, biased_code: str = "SRC") -> None:
-        """Add biased_code unary edges to the terminal nodes slightly preferring one code over the other.
+    def add_bias_edges(self, node_id: str, biased_code: str = "SRC") -> None:
+        """Add unary edges to bias a node toward one code or the other.
@@
-        biased_code : float
-            Capacity of the biased_code edges to be added.
+        node_id : str
+            Node identifier receiving the bias edges.
+        biased_code : str, optional
+            Which code to favor. Use "SRC" (default) to bias toward the source/2D Color Code,
+            or "SNK" to bias toward the sink/3D Surface Code.

150-155: Align inline comments with actual anchoring behavior.

These comments say the source handles T and the sink handles H, but the implementation (and surrounding docstring) do the opposite. Please update the comments to avoid misleading future maintainers.

-        # Source code can perform T and CNOT gates
+        # Sink code anchors T gates
@@
-        # Sink code can perform H and CNOT gates
+        # Source code anchors H gates

157-167: Document one_way_transversal_cnot.

This flag changes the topology (unidirectional vs bidirectional edges) yet the docstring never mentions it. Add a brief description so callers know when to enable it.

     def add_cnot_links(self, control_node: str, target_node: str, one_way_transversal_cnot: bool = False) -> None:
         """Add bidirectional infinite-capacity edges between two CNOT-related nodes to enforce that both qubits remain in the same code.
@@
         target_node : str
             Node representing the target qubit's CNOT operation.
+        one_way_transversal_cnot : bool, optional
+            If True, add only the control→target edge (one-way transversal CNOT). If False (default),
+            add edges in both directions to keep the qubits in the same code.

284-311: Align compute_min_cut signature/docs with behavior and drop unused noqa.

return_raw_data isn’t documented, the return type changes between raw/processed modes, and the # noqa is redundant. Please update the signature, docstring, and remove the directive.

-    def compute_min_cut(self, return_raw_data: bool = False) -> tuple[float, set[str], set[str]]:
+    def compute_min_cut(self, return_raw_data: bool = False) -> tuple[float | int, set[str], set[str]]:
         """Compute the minimum s-t cut between the source and sink.
 
+        Parameters
+        ----------
+        return_raw_data : bool, optional
+            If True, return the raw min-cut capacity from NetworkX (float).
+            If False (default), return the number of temporal/entangling edges crossing the cut (int).
+
         Returns:
         -------
-        Tuple[float, Set[str], Set[str]]
-            A tuple (cut_value, S, T) where:
-              - cut_value is the total capacity of the minimum cut,
+        tuple[float | int, set[str], set[str]]
+            A tuple (cut_value, S, T) where:
+              - cut_value is either the raw cut capacity (return_raw_data=True) or the number of switches (return_raw_data=False),
               - S is the set of nodes reachable from the source,
               - T is the complementary set of nodes.
         """
-        cut_value, (S, T) = nx.minimum_cut(self.G, self.source, self.sink, capacity="capacity")  # noqa: N806
+        cut_value, (S, T) = nx.minimum_cut(self.G, self.source, self.sink, capacity="capacity")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91df0eb and 3bf1bff.

📒 Files selected for processing (1)
  • src/mqt/qecc/circuit_compilation/code_switching_compiler.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.14.3)
src/mqt/qecc/circuit_compilation/code_switching_compiler.py

74-74: Boolean-typed positional argument in function definition

(FBT001)


74-74: Boolean default positional argument in function definition

(FBT002)


91-91: Boolean-typed positional argument in function definition

(FBT001)


91-91: Boolean default positional argument in function definition

(FBT002)


104-104: Boolean-typed positional argument in function definition

(FBT001)


104-104: Boolean default positional argument in function definition

(FBT002)


157-157: Boolean-typed positional argument in function definition

(FBT001)


157-157: Boolean default positional argument in function definition

(FBT002)


224-224: Boolean-typed positional argument in function definition

(FBT001)


224-224: Boolean default positional argument in function definition

(FBT002)


225-225: Boolean-typed positional argument in function definition

(FBT001)


225-225: Boolean default positional argument in function definition

(FBT002)


226-226: Boolean-typed positional argument in function definition

(FBT001)


226-226: Boolean default positional argument in function definition

(FBT002)


284-284: Boolean-typed positional argument in function definition

(FBT001)


284-284: Boolean default positional argument in function definition

(FBT002)


295-295: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)

prior every edge with nodes in different codes were counted but this
included one-way
now infinite edges are of type "fixed" and won't be count
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
src/mqt/qecc/circuit_compilation/code_switching_compiler.py (2)

120-121: Specify the default capacity value in the docstring.

The docstring for the capacity parameter doesn't mention its default value, making it unclear to users what capacity will be used if not specified.

Apply this diff:

         capacity : float, optional
-            Edge capacity.
+            Edge capacity (default is 100.0).

127-140: Fix parameter documentation: wrong type and missing parameter.

The docstring has two critical issues:

  1. biased_code is documented as float but the signature shows it's str with default "SRC"
  2. The node_id parameter is completely missing from the docstring
  3. The description "Capacity of the biased_code edges" is confusing—it should describe which code to bias toward

Apply this diff:

         """Add biased_code unary edges to the terminal nodes slightly preferring one code over the other.
 
         Parameters
         ----------
-        biased_code : float
-            Capacity of the biased_code edges to be added.
+        node_id : str
+            Node identifier to which bias edges will be added.
+        biased_code : str, optional
+            Which code to bias toward: "SRC" (source/2D Color Code) or "SNK" (sink/3D Surface Code).
+            Default is "SRC".
         """
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c7684e and ad6ff67.

📒 Files selected for processing (1)
  • src/mqt/qecc/circuit_compilation/code_switching_compiler.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.14.3)
src/mqt/qecc/circuit_compilation/code_switching_compiler.py

74-74: Boolean-typed positional argument in function definition

(FBT001)


74-74: Boolean default positional argument in function definition

(FBT002)


95-95: Boolean-typed positional argument in function definition

(FBT001)


95-95: Boolean default positional argument in function definition

(FBT002)


110-110: Boolean-typed positional argument in function definition

(FBT001)


110-110: Boolean default positional argument in function definition

(FBT002)


165-165: Boolean-typed positional argument in function definition

(FBT001)


165-165: Boolean default positional argument in function definition

(FBT002)


234-234: Boolean-typed positional argument in function definition

(FBT001)


234-234: Boolean default positional argument in function definition

(FBT002)


235-235: Boolean-typed positional argument in function definition

(FBT001)


235-235: Boolean default positional argument in function definition

(FBT002)


236-236: Boolean-typed positional argument in function definition

(FBT001)


236-236: Boolean default positional argument in function definition

(FBT002)


297-297: Boolean-typed positional argument in function definition

(FBT001)


297-297: Boolean default positional argument in function definition

(FBT002)


308-308: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)

now we can add to a given qiskit circuit the switching operations as
placeholder operations with a given depth to simulate how much switching
operations increase circuit depth
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (8)
tests/code_switching/test_code_switching_compilation.py (3)

45-45: Remove unused noqa directive.

Ruff flags # noqa: N806 as unused (RUF100) because the N806 rule is not enabled.

-    N = 100  # noqa: N806
+    N = 100

65-67: Remove unused noqa directive.

Ruff flags # noqa: SLF001 as unused (RUF100) because the SLF001 rule is not enabled.

-    eff_cap = simple_graph._edge_capacity_with_idle_bonus(  # noqa: SLF001
+    eff_cap = simple_graph._edge_capacity_with_idle_bonus(

75-75: Remove unused noqa directive.

Ruff flags # noqa: N806 as unused (RUF100) because the N806 rule is not enabled.

-    huge_N = 1_000_000  # noqa: N806
+    huge_N = 1_000_000
src/mqt/qecc/code_switching/code_switching_compiler.py (5)

187-205: Fix incorrect parameter type in docstring.

The docstring states biased_code : float but the parameter is str | None. Additionally, invalid biased_code values are silently ignored.

     def _add_bias_edges(self, node_id: str, biased_code: str | None = None) -> None:
-        """Add biased_code bias edges to the terminal nodes slightly preferring one code over the other.
+        """Add bias edges to terminal nodes, slightly preferring one code over the other.

         Parameters
         ----------
-        biased_code : float
-            Capacity of the biased_code edges to be added.
+        biased_code : str | None, optional
+            Which code to bias towards ("SRC" or "SNK"). Defaults to config.biased_code.
         """

239-265: Docstring is missing total_edges parameter description.

         Parameters
         ----------
         depths : list[int]
             The ordered list of depth indices for a given qubit's gates.
+        total_edges : int
+            Total count of temporal edges in the circuit, used for normalization.
         base_capacity : float, optional
             The default temporal edge capacity.

319-339: Incomplete docstring for _process_gate_operation.

The Parameters section only documents node and depth, but the method has 10 parameters. Since this is a private helper, consider either documenting all parameters or removing the incomplete stub.

     def _process_gate_operation(
         self,
         node: DAGOpNode,
         depth: int,
         circuit: QuantumCircuit,
         qubit_activity: dict[int, list[int]],
         qubit_last_node: list[str | None],
         one_way_gates: dict[str, tuple[str, str]],
         code_bias: bool,
         idle_bonus: bool,
         total_temporal_edges: int,
     ) -> None:
-        """Handle node creation, temporal edges, and code constraints for a single gate.
-
-        Parameters.
-        ----------
-        node : DAGOpNode
-            The gate operation node from the DAG.
-        depth : int
-            The depth (layer index) of the operation in the circuit.
-        """
+        """Handle node creation, temporal edges, and code constraints for a single gate."""

427-427: Remove unused noqa directive.

Ruff flags # noqa: N806 as unused (RUF100).

-        _, (S, T) = nx.minimum_cut(self.G, self.source, self.sink, capacity="capacity")  # noqa: N806
+        _, (S, T) = nx.minimum_cut(self.G, self.source, self.sink, capacity="capacity")

431-431: Remove unused noqa directive.

Ruff flags # noqa: N803 as unused (RUF100).

-    def _extract_switch_locations(self, S: set[str], T: set[str]) -> tuple[int, list[tuple[int, int]]]:  # noqa: N803
+    def _extract_switch_locations(self, S: set[str], T: set[str]) -> tuple[int, list[tuple[int, int]]]:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27060ce and 13d38b8.

📒 Files selected for processing (3)
  • docs/CodeSwitching.md (1 hunks)
  • src/mqt/qecc/code_switching/code_switching_compiler.py (1 hunks)
  • tests/code_switching/test_code_switching_compilation.py (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: burgholzer
Repo: munich-quantum-toolkit/yaqs PR: 212
File: CHANGELOG.md:12-15
Timestamp: 2025-10-14T14:37:38.047Z
Learning: In the munich-quantum-toolkit/yaqs project, changelog entries follow the template: "- $TITLE ([#$NUMBER]($URL)) ([**AUTHOR**](https://github.com/$AUTHOR))". Issue references should not be included in changelog entries; the PR number is sufficient for traceability.
📚 Learning: 2025-11-24T10:19:41.147Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1326
File: python/mqt/core/__init__.py:22-22
Timestamp: 2025-11-24T10:19:41.147Z
Learning: In the munich-quantum-toolkit/core repository, Ruff is configured with 'ALL' rules enabled by default, and only specific rules are selectively disabled. When reviewing changes that enable previously-disabled rules (like PLC0415), noqa directives for those rules become necessary and should be retained.

Applied to files:

  • tests/code_switching/test_code_switching_compilation.py
  • src/mqt/qecc/code_switching/code_switching_compiler.py
📚 Learning: 2025-11-27T21:26:39.677Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qmap PR: 846
File: python/mqt/qmap/plugins/qiskit/sc/load_calibration.py:34-34
Timestamp: 2025-11-27T21:26:39.677Z
Learning: In the qmap project, the Ruff linter has the "PL" (pylint) rule category enabled, which includes PLC0415 (import-outside-top-level). Therefore, `# noqa: PLC0415` directives on lazy imports are appropriate and necessary, not unused.

Applied to files:

  • tests/code_switching/test_code_switching_compilation.py
  • src/mqt/qecc/code_switching/code_switching_compiler.py
🪛 LanguageTool
docs/CodeSwitching.md

[grammar] ~106-~106: Please add a punctuation mark at the end of paragraph.
Context: ...de and the target qubit in the 2D color code To account for this, we can can pass a...

(PUNCTUATION_PARAGRAPH_END)


[grammar] ~108-~108: Ensure spelling is correct
Context: ...g gates that can be implemented one-way transverally together with their direction. To see h...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~153-~153: Ensure spelling is correct
Context: ...ch that switching operations are placed preferribly on idling qubits while keeping the tota...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 Ruff (0.14.7)
tests/code_switching/test_code_switching_compilation.py

45-45: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


65-65: Unused noqa directive (non-enabled: SLF001)

Remove unused noqa directive

(RUF100)


75-75: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)

src/mqt/qecc/code_switching/code_switching_compiler.py

327-327: Boolean-typed positional argument in function definition

(FBT001)


328-328: Boolean-typed positional argument in function definition

(FBT001)


367-367: Boolean-typed positional argument in function definition

(FBT001)


427-427: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


431-431: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
🔇 Additional comments (8)
tests/code_switching/test_code_switching_compilation.py (3)

41-81: Test logic is sound; float comparisons are appropriately handled.

The test correctly uses:

  • Exact equality for 0.0 comparisons (lines 48, 51) — safe because zero is exactly representable in IEEE 754
  • pytest.approx for non-zero float comparisons (lines 62, 70)

The test coverage for idle bonus logic is thorough, including edge cases (short idle, active bonus, large graph scaling).


87-151: Integration tests provide solid coverage of min-cut behavior.

The tests exercise:

  • Forced switches (H→T pattern)
  • No switches needed (same-code gates)
  • One-way transversal gates with direction tuples
  • Invariance of one-way transversal behavior across source/sink swaps

The assertions on num_switches and switch_pos correctly validate the expected min-cut results.


153-175: Code bias test validates configuration-driven behavior.

The test demonstrates that CompilerConfig(biased_code="SNK") correctly shifts switch positions compared to the default source bias. The assertion switch_pos_sink_bias != switch_pos_source_bias along with the specific position check ensures the bias mechanism works as intended.

docs/CodeSwitching.md (1)

9-34: Documentation is well-structured and explains the min-cut model clearly.

The introduction effectively motivates code switching and explains the graph-based formulation. The node/edge semantics are clearly defined.

src/mqt/qecc/code_switching/code_switching_compiler.py (4)

26-34: CompilerConfig dataclass is well-designed.

The configuration parameters are sensibly typed with reasonable defaults. The dataclass approach provides clean initialization and immutability.


362-403: Code constraint logic is well-structured.

The method correctly handles:

  • Source-unique gates (infinite edge to source)
  • Sink-unique gates (infinite edge to sink)
  • Common gates with multi-qubit handling and optional one-way transversal direction
  • Code bias edges for common gates

405-413: Good input validation in _parse_one_way_direction.

The static method validates the direction tuple and raises a clear ValueError for invalid inputs, preventing silent failures.


69-91: Initialization and graph setup look good.

The constructor properly initializes the graph with source/sink nodes and computes common gates. The base_unary_capacity calculation correctly combines the configuration parameters.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (8)
tests/code_switching/test_code_switching_compilation.py (3)

45-45: Remove unused noqa directive.

Ruff flags # noqa: N806 as unused (RUF100) because the N806 rule is not enabled.

-    N = 100  # noqa: N806
+    N = 100

65-67: Remove unused noqa directive.

Ruff flags # noqa: SLF001 as unused (RUF100) because the SLF001 rule is not enabled.

-    eff_cap = simple_graph._edge_capacity_with_idle_bonus(  # noqa: SLF001
+    eff_cap = simple_graph._edge_capacity_with_idle_bonus(
         depths=[0, 10], total_edges=N, base_capacity=base_cap
     )

75-75: Remove unused noqa directive.

Ruff flags # noqa: N806 as unused (RUF100) because the N806 rule is not enabled.

-    huge_N = 1_000_000  # noqa: N806
+    huge_N = 1_000_000
src/mqt/qecc/code_switching/code_switching_compiler.py (5)

427-427: Remove unused noqa directive.

Ruff flags # noqa: N806 as unused (RUF100).

-        _, (S, T) = nx.minimum_cut(self.G, self.source, self.sink, capacity="capacity")  # noqa: N806
+        _, (S, T) = nx.minimum_cut(self.G, self.source, self.sink, capacity="capacity")

431-431: Remove unused noqa directive.

Ruff flags # noqa: N803 as unused (RUF100).

-    def _extract_switch_locations(self, S: set[str], T: set[str]) -> tuple[int, list[tuple[int, int]]]:  # noqa: N803
+    def _extract_switch_locations(self, S: set[str], T: set[str]) -> tuple[int, list[tuple[int, int]]]:

239-265: Docstring missing total_edges parameter.

The total_edges parameter is not documented in the docstring.

         Parameters
         ----------
         depths : list[int]
             The ordered list of depth indices for a given qubit's gates.
+        total_edges : int
+            The total count of temporal edges in the circuit, used for normalization.
         base_capacity : float, optional
             The default temporal edge capacity.

319-339: Incomplete docstring for private helper.

The docstring only documents node and depth, but the method has 10 parameters. Since this is a private helper, consider either completing the documentation or simplifying to a one-line summary.

     def _process_gate_operation(
         ...
     ) -> None:
-        """Handle node creation, temporal edges, and code constraints for a single gate.
-
-        Parameters.
-        ----------
-        node : DAGOpNode
-            The gate operation node from the DAG.
-        depth : int
-            The depth (layer index) of the operation in the circuit.
-        """
+        """Handle node creation, temporal edges, and code constraints for a single gate."""

187-205: Docstring has incorrect parameter type.

The docstring states biased_code : float but the parameter is str | None. Additionally, invalid values are silently ignored.

     def _add_bias_edges(self, node_id: str, biased_code: str | None = None) -> None:
-        """Add biased_code bias edges to the terminal nodes slightly preferring one code over the other.
+        """Add bias edges to the terminal nodes slightly preferring one code over the other.
 
         Parameters
         ----------
-        biased_code : float
-            Capacity of the biased_code edges to be added.
+        biased_code : str, optional
+            Which code to bias towards ("SRC" or "SNK"). Defaults to config.biased_code.
         """
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27060ce and 13d38b8.

📒 Files selected for processing (3)
  • docs/CodeSwitching.md (1 hunks)
  • src/mqt/qecc/code_switching/code_switching_compiler.py (1 hunks)
  • tests/code_switching/test_code_switching_compilation.py (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-24T10:19:41.147Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1326
File: python/mqt/core/__init__.py:22-22
Timestamp: 2025-11-24T10:19:41.147Z
Learning: In the munich-quantum-toolkit/core repository, Ruff is configured with 'ALL' rules enabled by default, and only specific rules are selectively disabled. When reviewing changes that enable previously-disabled rules (like PLC0415), noqa directives for those rules become necessary and should be retained.

Applied to files:

  • tests/code_switching/test_code_switching_compilation.py
  • src/mqt/qecc/code_switching/code_switching_compiler.py
📚 Learning: 2025-11-27T21:26:39.677Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qmap PR: 846
File: python/mqt/qmap/plugins/qiskit/sc/load_calibration.py:34-34
Timestamp: 2025-11-27T21:26:39.677Z
Learning: In the qmap project, the Ruff linter has the "PL" (pylint) rule category enabled, which includes PLC0415 (import-outside-top-level). Therefore, `# noqa: PLC0415` directives on lazy imports are appropriate and necessary, not unused.

Applied to files:

  • tests/code_switching/test_code_switching_compilation.py
  • src/mqt/qecc/code_switching/code_switching_compiler.py
🧬 Code graph analysis (2)
tests/code_switching/test_code_switching_compilation.py (1)
src/mqt/qecc/code_switching/code_switching_compiler.py (6)
  • CompilerConfig (27-33)
  • parse_node_id (466-473)
  • compute_idle_bonus (206-237)
  • _edge_capacity_with_idle_bonus (239-265)
  • build_from_qiskit (267-317)
  • compute_min_cut (415-429)
src/mqt/qecc/code_switching/code_switching_compiler.py (1)
src/mqt/qecc/circuit_synthesis/circuits.py (2)
  • depth (333-344)
  • num_qubits (250-257)
🪛 LanguageTool
docs/CodeSwitching.md

[grammar] ~106-~106: Please add a punctuation mark at the end of paragraph.
Context: ...de and the target qubit in the 2D color code To account for this, we can can pass a...

(PUNCTUATION_PARAGRAPH_END)


[grammar] ~108-~108: Ensure spelling is correct
Context: ...g gates that can be implemented one-way transverally together with their direction. To see h...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~153-~153: Ensure spelling is correct
Context: ...ch that switching operations are placed preferribly on idling qubits while keeping the tota...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 Ruff (0.14.7)
tests/code_switching/test_code_switching_compilation.py

45-45: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


65-65: Unused noqa directive (non-enabled: SLF001)

Remove unused noqa directive

(RUF100)


75-75: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)

src/mqt/qecc/code_switching/code_switching_compiler.py

327-327: Boolean-typed positional argument in function definition

(FBT001)


328-328: Boolean-typed positional argument in function definition

(FBT001)


367-367: Boolean-typed positional argument in function definition

(FBT001)


427-427: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


431-431: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
🔇 Additional comments (10)
tests/code_switching/test_code_switching_compilation.py (7)

1-16: Imports and license header look correct.

The imports properly reference the public API surface (MinimalCodeSwitchingCompiler from mqt.qecc.code_switching) and CompilerConfig from the internal module.


19-24: Fixture is well-designed.

The simple_graph fixture provides a reusable compiler instance with gate sets matching the documented 2D/3D color code example.


31-38: LGTM!

Good coverage testing both valid parsing and the error case for invalid node IDs.


87-103: Good integration test for mandatory switching.

Tests the fundamental case where H (source-only) followed by T (sink-only) requires exactly one switch.


105-118: LGTM!

Correctly verifies that circuits staying within a single code (H, CX) require no switches.


121-150: Comprehensive one-way transversal testing.

Good coverage of:

  • Default behavior requiring a switch
  • One-way gate enabling zero switches
  • Invariance when swapping source/sink definitions

153-175: Code bias test is well-structured.

Verifies that changing biased_code from "SRC" to "SNK" shifts the switch position as expected.

src/mqt/qecc/code_switching/code_switching_compiler.py (3)

1-23: Imports and module setup look good.

Clean separation of runtime imports and TYPE_CHECKING-only imports. The regex pattern is appropriately compiled at module level for performance.


69-86: Constructor is well-structured.

Proper initialization of the graph with source/sink nodes and computation of base capacity from configuration.


36-67: **Class docstring and structure are well-designed.**The comprehensive docstring explains the graph model well. "For each edge (u, v) in R, R[u][v]['capacity'] is equal to the capacity of (u, v) in G if it exists in G or zero otherwise." The implementation correctly ensures non-negative capacities via max(0.0, ...) in _edge_capacity_with_idle_bonus (line 265), which aligns with NetworkX's expectations.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (7)
docs/CodeSwitching.md (7)

104-108: Fix grammar, punctuation, and spelling issues.

Three issues flagged by static analysis:

  1. Line 106: Missing period at end of paragraph
  2. Line 108: Duplicated word "can can"
  3. Line 108: Spelling error "transverally" → "transversally"
 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
+control qubit is encoded in the 3D color code and the target qubit in the 2D color code.

-To account for this, we can can pass a dictionary specifying gates that can be implemented one-way transverally together with their direction.
+To account for this, we can pass a dictionary specifying gates that can be implemented one-way transversally together with their direction.

137-145: Fix undefined variable in loop (second of 6 occurrences).

Same issue as lines 125-133: iterate over switch_pos instead of positions.

 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()
 print(f"Total switches required (without one-way CNOT): {num_switches}")
 print("Switch locations (qubit, depth):")
-for pos in positions:
+for pos in switch_pos:
     print(f" - Qubit {pos[0]} after operation depth {pos[1]}")

151-154: Fix spelling error "preferribly" → "preferably".

-- **Depth Optimization:** Choosing the positions of the switching positions such that switching operations are placed preferribly 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.
+- **Depth Optimization:** Choosing the positions 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.

176-184: Fix undefined variable in loop (third of 6 occurrences).

Same issue: iterate over switch_pos instead of positions.

 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()
 print(f"Total switches required (without one-way CNOT): {num_switches}")
 print("Switch locations (qubit, depth):")
-for pos in positions:
+for pos in switch_pos:
     print(f" - Qubit {pos[0]} after operation depth {pos[1]}")

188-196: Fix undefined variable in loop (fourth of 6 occurrences).

Same issue: iterate over switch_pos instead of positions.

 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()
 print(f"Total switches required (without one-way CNOT): {num_switches}")
 print("Switch locations (qubit, depth):")
-for pos in positions:
+for pos in switch_pos:
     print(f" - Qubit {pos[0]} after operation depth {pos[1]}")

216-224: Fix undefined variable in loop (fifth of 6 occurrences).

Same issue: iterate over switch_pos instead of positions.

 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()
 print(f"Total switches required (without one-way CNOT): {num_switches}")
 print("Switch locations (qubit, depth):")
-for pos in positions:
+for pos in switch_pos:
     print(f" - Qubit {pos[0]} after operation depth {pos[1]}")

228-237: Fix undefined variable in loop (sixth of 6 occurrences).

Same issue: iterate over switch_pos instead of positions.

 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()
 print(f"Total switches required (without one-way CNOT): {num_switches}")
 print("Switch locations (qubit, depth):")
-for pos in positions:
+for pos in switch_pos:
     print(f" - Qubit {pos[0]} after operation depth {pos[1]}")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13d38b8 and b71d9b4.

📒 Files selected for processing (1)
  • docs/CodeSwitching.md (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-02T07:37:46.860Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1301
File: mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp:144-151
Timestamp: 2025-12-02T07:37:46.860Z
Learning: In MLIR transformation code (mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp and similar), when inserting operations before a target operation, prefer `rewriter.setInsertionPoint(op)` over `rewriter.setInsertionPointAfter(op->getPrevNode())`. The former is cleaner, avoids null pointer edge cases (when op is first in block), and is semantically clearer.

Applied to files:

  • docs/CodeSwitching.md
🪛 LanguageTool
docs/CodeSwitching.md

[grammar] ~106-~106: Please add a punctuation mark at the end of paragraph.
Context: ...de and the target qubit in the 2D color code To account for this, we can can pass a...

(PUNCTUATION_PARAGRAPH_END)


[grammar] ~108-~108: Ensure spelling is correct
Context: ...g gates that can be implemented one-way transverally together with their direction. To see h...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~153-~153: Ensure spelling is correct
Context: ...ch that switching operations are placed preferribly on idling qubits while keeping the tota...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
🔇 Additional comments (1)
docs/CodeSwitching.md (1)

125-133: Fix undefined variable in loop (first of 6 occurrences).

The code unpacks the min-cut result into switch_pos (line 128) but the loop iterates over positions (undefined variable), which will cause a NameError at runtime. This same bug appears 5 more times in this file (lines 143, 182, 194, 222, 235).

 mcsc = MinimalCodeSwitchingCompiler({"H", "CX"}, {"T", "CX"})
 mcsc.build_from_qiskit(qc)
 num_switches, switch_pos, _, _ = mcsc.compute_min_cut()
 print(f"Total switches required (without one-way CNOT): {num_switches}")
 print("Switch locations (qubit, depth):")
-for pos in positions:
+for pos in switch_pos:
     print(f" - Qubit {pos[0]} after operation depth {pos[1]}")

Likely an incorrect or invalid review comment.

Copy link
Collaborator

@pehamTom pehamTom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, gonna approve already but would be great if you could look at my comments for the docs.

Overall, nice work!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (7)
docs/CodeSwitching.md (2)

104-105: Fix typo: "transverally" → "transversally".

-To account for this, we can pass a dictionary specifying gates that can be implemented one-way transverally together with their direction.
+To account for this, we can pass a dictionary specifying gates that can be implemented one-way transversally together with their direction.

154-154: Fix typo: "preferribly" → "preferably".

-- **Depth Optimization:** Choosing the placing of the switching positions such that switching operations are placed preferribly 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.
+- **Depth Optimization:** Choosing the placement 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.
src/mqt/qecc/code_switching/code_switching_compiler.py (5)

26-34: Consider adding validation for biased_code.

The biased_code field accepts any string, but only "SRC" or "SNK" are valid values. Invalid values are silently ignored in _add_bias_edges, which could mask configuration errors.

 @dataclass
 class CompilerConfig:
     """Holds all configuration parameters for the MinimalCodeSwitchingCompiler."""

     edge_capacity_ratio: float = 0.001
     default_temporal_edge_capacity: float = 1.0
     switching_time: int = 2
     biased_code: str = "SRC"
+
+    def __post_init__(self) -> None:
+        """Validate configuration parameters."""
+        if self.biased_code not in ("SRC", "SNK"):
+            msg = f"Invalid biased_code: {self.biased_code}. Must be 'SRC' or 'SNK'."
+            raise ValueError(msg)

187-205: Fix incorrect parameter type in docstring.

The docstring states biased_code : float but the parameter type is actually str | None.

     def _add_bias_edges(self, node_id: str, biased_code: str | None = None) -> None:
         """Add biased_code bias edges to the terminal nodes slightly preferring one code over the other.

         Parameters
         ----------
-        biased_code : float
-            Capacity of the biased_code edges to be added.
+        biased_code : str | None, optional
+            Which code to bias towards ("SRC" or "SNK"). Defaults to config.biased_code.
         """

334-342: Incomplete docstring for private helper.

The docstring only documents node and depth but the method has 10 parameters. Consider either documenting all parameters or simplifying to a single-line docstring for this private helper.

     def _process_gate_operation(
         self,
         node: DAGOpNode,
         depth: int,
         circuit: QuantumCircuit,
         qubit_activity: dict[int, list[int]],
         qubit_last_node: list[str | None],
         one_way_gates: dict[str, tuple[str, str]],
         code_bias: bool,
         idle_bonus: bool,
         total_temporal_edges: int,
     ) -> None:
-        """Handle node creation, temporal edges, and code constraints for a single gate.
-
-        Parameters.
-        ----------
-        node : DAGOpNode
-            The gate operation node from the DAG.
-        depth : int
-            The depth (layer index) of the operation in the circuit.
-        """
+        """Handle node creation, temporal edges, and code constraints for a single gate."""

454-466: Always extract the earlier node when a temporal edge crosses the cut.

The code unconditionally takes node u (line 464), but when (v in S and u in T), u is the later node in time. This violates the comment's intent to "take the earlier node in time as the insertion point" (line 463). Since bidirectional edges exist and iteration order is not guaranteed, the code can select the wrong node.

Extract depths from both endpoints and use the one with lower depth:

             if (u in S and v in T) or (v in S and u in T):
-                    # We can take e.g. the 'earlier' node in time as the insertion point
-                    qubit, depth = self.parse_node_id(u)
-                    switch_positions.append((qubit, depth))
+                    # Take the earlier node (lower depth) as the insertion point
+                    q_u, d_u = self.parse_node_id(u)
+                    q_v, d_v = self.parse_node_id(v)
+                    if d_u <= d_v:
+                        switch_positions.append((q_u, d_u))
+                    else:
+                        switch_positions.append((q_v, d_v))

430-434: Remove unused noqa directives.

Ruff flags these noqa directives as unused (RUF100) because the N806/N803 rules are not enabled.

-        _, (S, T) = nx.minimum_cut(self.G, self.source, self.sink, capacity="capacity")  # noqa: N806
+        _, (S, T) = nx.minimum_cut(self.G, self.source, self.sink, capacity="capacity")
         num_switches, switch_positions = self._extract_switch_locations(S, T)
         return num_switches, switch_positions, S, T

-    def _extract_switch_locations(self, S: set[str], T: set[str]) -> tuple[int, list[tuple[int, int]]]:  # noqa: N803
+    def _extract_switch_locations(self, S: set[str], T: set[str]) -> tuple[int, list[tuple[int, int]]]:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b38882 and ed674dc.

📒 Files selected for processing (3)
  • CHANGELOG.md (1 hunks)
  • docs/CodeSwitching.md (1 hunks)
  • src/mqt/qecc/code_switching/code_switching_compiler.py (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-12-02T07:37:46.860Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1301
File: mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp:144-151
Timestamp: 2025-12-02T07:37:46.860Z
Learning: In MLIR transformation code (mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp and similar), when inserting operations before a target operation, prefer `rewriter.setInsertionPoint(op)` over `rewriter.setInsertionPointAfter(op->getPrevNode())`. The former is cleaner, avoids null pointer edge cases (when op is first in block), and is semantically clearer.

Applied to files:

  • docs/CodeSwitching.md
  • src/mqt/qecc/code_switching/code_switching_compiler.py
📚 Learning: 2025-11-27T21:26:39.677Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qmap PR: 846
File: python/mqt/qmap/plugins/qiskit/sc/load_calibration.py:34-34
Timestamp: 2025-11-27T21:26:39.677Z
Learning: In the qmap project, the Ruff linter has the "PL" (pylint) rule category enabled, which includes PLC0415 (import-outside-top-level). Therefore, `# noqa: PLC0415` directives on lazy imports are appropriate and necessary, not unused.

Applied to files:

  • src/mqt/qecc/code_switching/code_switching_compiler.py
📚 Learning: 2025-11-24T10:19:41.147Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1326
File: python/mqt/core/__init__.py:22-22
Timestamp: 2025-11-24T10:19:41.147Z
Learning: In the munich-quantum-toolkit/core repository, Ruff is configured with 'ALL' rules enabled by default, and only specific rules are selectively disabled. When reviewing changes that enable previously-disabled rules (like PLC0415), noqa directives for those rules become necessary and should be retained.

Applied to files:

  • src/mqt/qecc/code_switching/code_switching_compiler.py
🧬 Code graph analysis (1)
src/mqt/qecc/code_switching/code_switching_compiler.py (1)
src/mqt/qecc/circuit_synthesis/circuits.py (2)
  • depth (333-344)
  • num_qubits (250-257)
🪛 LanguageTool
docs/CodeSwitching.md

[grammar] ~104-~104: Ensure spelling is correct
Context: ...g gates that can be implemented one-way transverally together with their direction. To see h...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~154-~154: Ensure spelling is correct
Context: ...ch that switching operations are placed preferribly on idling qubits while keeping the tota...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 Ruff (0.14.8)
src/mqt/qecc/code_switching/code_switching_compiler.py

330-330: Boolean-typed positional argument in function definition

(FBT001)


331-331: Boolean-typed positional argument in function definition

(FBT001)


370-370: Boolean-typed positional argument in function definition

(FBT001)


430-430: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


434-434: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)

🔇 Additional comments (1)
src/mqt/qecc/code_switching/code_switching_compiler.py (1)

36-91: LGTM on the class structure and initialization.

The MinimalCodeSwitchingCompiler class is well-structured with clear separation between graph construction (build_from_qiskit), constraint application (_apply_code_constraints), and analysis (compute_min_cut). The docstring provides comprehensive documentation of the graph model and attributes.

coderabbitai[bot]
coderabbitai bot previously requested changes Jan 14, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@scripts/cs_compiler/run_performance_simulations.sh`:
- Line 9: Remove the leftover commented-out array declaration to reduce clutter:
delete the line containing the commented declaration "# declare -a
n_values=(\"64\" \"128\" \"256\" \"512\")" from the script so the file no longer
contains that unused commented code.
- Around line 1-8: Add shell safety options to the top of
run_performance_simulations.sh: after the shebang (#!/bin/bash) enable strict
error handling by inserting set -euo pipefail and, if needed for portability,
ensure IFS is set (e.g., IFS=$'\n\t') so the script behaves consistently with
run_generate_circuits.sh and fails fast on unset variables, pipeline failures,
or command errors.
- Line 41: The parallel invocation in run_performance_simulations.sh uses $(seq
0 99) which can break under strict shells (set -u) and is less idiomatic;
replace that subshell expansion with a safe, quoted source such as a brace
expansion or a pre-built array and ensure the expansion is quoted in the
parallel command so the third argument list is robust (update the line using
parallel --jobs 5 run_and_simulate ::: "${n_values[@]}" ::: "${distr_types[@]}"
::: $(seq 0 99) to use {0..99} or an explicitly declared array variable instead
of unquoted $(seq ...)).

In `@scripts/cs_compiler/simulate_circuit_performance.py`:
- Around line 98-99: Add a brief inline comment explaining the rationale for the
depth calculation where depth = 2 * args.n (e.g., why the benchmark uses twice
the number of qubits as circuit depth—such as mapping to two layers per qubit,
matched gate layers, or a target entangling + single-qubit layer per qubit);
place the comment next to the assignment of depth (referencing the variables
depth and args.n) so future readers know the performance model assumption behind
the multiplier.

In `@src/mqt/qecc/code_switching/code_switching_compiler.py`:
- Around line 330-331: The parameters code_bias and idle_bonus on the
CodeSwitchingCompiler (or the function where they appear) should be made
keyword-only to avoid boolean positional args; change the signature to place
them after a bare * (or use *, code_bias: bool, idle_bonus: bool) so callers
must use keywords, and update the build_from_qiskit function call site to pass
code_bias=... and idle_bonus=... when invoking the compiler/constructor; ensure
all other call sites use the new keyword form.

In `@src/mqt/qecc/code_switching/compilation_utils.py`:
- Around line 119-124: Validate qubit indices from switch_positions before
grouping: ensure each qidx is an integer within 0 <= qidx < len(circuit.qubits)
and raise a clear ValueError if not (e.g. "Invalid qubit index X; circuit has N
qubits") instead of allowing an IndexError later when accessing circuit.qubits;
perform the same check where you later iterate over placeholders_by_qubit (the
loop that uses q_index to index circuit.qubits) so both the grouping step
(placeholders_by_qubit population) and the subsequent usage validate indices and
fail with a helpful message.
♻️ Duplicate comments (9)
scripts/cs_compiler/generate_random_circuits.py (1)

57-59: Remove unused last_gate tracking.

last_gate is assigned on lines 58, 103, and 116 but never read. Only last_non_id_gate is used for the back-to-back restriction logic.

♻️ Proposed cleanup
-    # Track last gate and last non-id single-qubit gate
-    last_gate = ["id"] * num_qubits
+    # Track last non-id single-qubit gate to avoid back-to-back repeats
     last_non_id_gate = ["id"] * num_qubits

Also remove assignments at lines 103 and 116:

-                    last_gate[q] = last_gate[target] = "cx"
-            last_gate[q] = gate
scripts/cs_compiler/run_generate_circuits.sh (1)

25-25: Use python3 explicitly for portability.

On some systems, python may not exist or may point to Python 2.

-    python "${SCRIPT_DIR}/generate_random_circuits.py" --n "$n" --num_circuits "$num_circuits" --distr_type "$distr_type"
+    python3 "${SCRIPT_DIR}/generate_random_circuits.py" --n "$n" --num_circuits "$num_circuits" --distr_type "$distr_type"
scripts/cs_compiler/run_performance_simulations.sh (1)

22-36: Make Python script path independent of working directory.

The relative path simulate_circuit_performance.py on line 30 will fail if the script is invoked from the repository root rather than the scripts/cs_compiler/ directory.

🐛 Proposed fix

Add SCRIPT_DIR resolution after exports and use absolute path:

 export base_dir
 export results_dir
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+export SCRIPT_DIR
 
 run_and_simulate() {
     local n=$1
     local distr_type=$2
     local seed=$3
 
     local qasm_path="${base_dir}/${distr_type}/${n}/${n}_${seed}.qasm"
     local csv_path="${results_dir}/${distr_type}/results_${n}.csv"
 
-    python simulate_circuit_performance.py \
+    python3 "${SCRIPT_DIR}/simulate_circuit_performance.py" \
         --qasm_path "$qasm_path" \
         --n "$n" \
         --seed "$seed" \
         --output_csv "$csv_path" \
         --distr_type "$distr_type"
 }
src/mqt/qecc/code_switching/compilation_utils.py (2)

23-26: Docstring still says dict but return type is list[str | None].

The Returns section documents dict: final code assignment per qubit, but the type annotation and implementation return list[str | None].

📝 Suggested fix
     Returns:
-        int: total number of code switches
-        dict: final code assignment per qubit
+    -------
+    tuple[int, list[str | None]]
+        A tuple containing:
+        - int: total number of code switches
+        - list[str | None]: final code assignment per qubit (index -> "A", "B", or None)

103-108: Remove stale expand_placeholder parameter from docstring.

The docstring documents expand_placeholder : bool, optional at lines 105-108, but this parameter doesn't exist in the function signature.

📝 Suggested fix
     placeholder_depth : int, optional
         Virtual depth (single-qubit layers) the placeholder should represent.
-    expand_placeholder : bool, optional
-        If True, expand each placeholder into `placeholder_depth` calls to
-        `QuantumCircuit.id(qubit)` so that `QuantumCircuit.depth()` increases.
-        If False, append a single `SwitchGate` marker (informational only).
 
     Returns:
src/mqt/qecc/code_switching/code_switching_compiler.py (4)

187-194: Docstring incorrectly types biased_code as float.

The docstring says biased_code : float but the parameter is actually str | None. The description also doesn't match the parameter's purpose.

📝 Suggested fix
     def _add_bias_edges(self, node_id: str, biased_code: str | None = None) -> None:
         """Add biased_code bias edges to the terminal nodes slightly preferring one code over the other.
 
         Parameters
         ----------
-        biased_code : float
-            Capacity of the biased_code edges to be added.
+        biased_code : str | None, optional
+            Which code to bias towards ("SRC" or "SNK"). Defaults to config.biased_code.
         """

462-465: Always extract the earlier node when a temporal edge crosses the cut.

The code always takes node u (line 464), but when (v in S and u in T), u is the later node in time. This contradicts the comment on line 463. Since bidirectional edges exist and iteration order is non-deterministic, the code may select the wrong node.

🐛 Proposed fix
             if (u in S and v in T) or (v in S and u in T):
-                    # We can take e.g. the 'earlier' node in time as the insertion point
-                    qubit, depth = self.parse_node_id(u)
-                    switch_positions.append((qubit, depth))
+                    # Take the earlier node (lower depth) as the insertion point
+                    q_u, d_u = self.parse_node_id(u)
+                    q_v, d_v = self.parse_node_id(v)
+                    if d_u <= d_v:
+                        switch_positions.append((q_u, d_u))
+                    else:
+                        switch_positions.append((q_v, d_v))

26-34: Consider validating biased_code in CompilerConfig.

The biased_code field only accepts "SRC" or "SNK", but invalid values would be silently ignored in _add_bias_edges. Adding validation provides earlier, clearer error feedback.

💡 Suggested validation
 `@dataclass`
 class CompilerConfig:
     """Holds all configuration parameters for the MinimalCodeSwitchingCompiler."""
 
     edge_capacity_ratio: float = 0.001
     default_temporal_edge_capacity: float = 1.0
     switching_time: int = 2
     biased_code: str = "SRC"
+
+    def __post_init__(self) -> None:
+        """Validate configuration parameters."""
+        if self.biased_code not in ("SRC", "SNK"):
+            msg = f"Invalid biased_code: {self.biased_code}. Must be 'SRC' or 'SNK'."
+            raise ValueError(msg)

430-434: Remove unused noqa directives.

Ruff reports # noqa: N806 (line 430) and # noqa: N803 (line 434) as unused since these rules aren't enabled. Based on static analysis hints.

📝 Suggested fix
-        _, (S, T) = nx.minimum_cut(self.G, self.source, self.sink, capacity="capacity")  # noqa: N806
+        _, (S, T) = nx.minimum_cut(self.G, self.source, self.sink, capacity="capacity")
         num_switches, switch_positions = self._extract_switch_locations(S, T)
         return num_switches, switch_positions, S, T
 
-    def _extract_switch_locations(self, S: set[str], T: set[str]) -> tuple[int, list[tuple[int, int]]]:  # noqa: N803
+    def _extract_switch_locations(self, S: set[str], T: set[str]) -> tuple[int, list[tuple[int, int]]]:
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e901ac5 and 94f31ea.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • pyproject.toml
  • scripts/cs_compiler/generate_random_circuits.py
  • scripts/cs_compiler/run_generate_circuits.sh
  • scripts/cs_compiler/run_performance_simulations.sh
  • scripts/cs_compiler/simulate_circuit_performance.py
  • src/mqt/qecc/code_switching/__init__.py
  • src/mqt/qecc/code_switching/code_switching_compiler.py
  • src/mqt/qecc/code_switching/compilation_utils.py
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2025-10-14T14:37:38.047Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/yaqs PR: 212
File: CHANGELOG.md:12-15
Timestamp: 2025-10-14T14:37:38.047Z
Learning: In the munich-quantum-toolkit/yaqs project, changelog entries follow the template: "- $TITLE ([#$NUMBER]($URL)) ([**AUTHOR**](https://github.com/$AUTHOR))". Issue references should not be included in changelog entries; the PR number is sufficient for traceability.

Applied to files:

  • CHANGELOG.md
📚 Learning: 2025-12-05T17:45:37.602Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1360
File: .github/workflows/reusable-mlir-tests.yml:40-43
Timestamp: 2025-12-05T17:45:37.602Z
Learning: In the munich-quantum-toolkit/core repository, patch releases of LLVM dependencies don't require documentation updates, changelog entries, or additional tests beyond what's validated by passing CI checks.

Applied to files:

  • CHANGELOG.md
📚 Learning: 2025-11-04T15:22:19.558Z
Learnt from: marcelwa
Repo: munich-quantum-toolkit/core PR: 1243
File: test/python/qdmi/qiskit/conftest.py:155-157
Timestamp: 2025-11-04T15:22:19.558Z
Learning: The munich-quantum-toolkit/core repository requires Python 3.10 or later, so Python 3.10+ features (such as `zip(..., strict=...)`, pattern matching, etc.) are acceptable and should not be flagged as compatibility issues.

Applied to files:

  • pyproject.toml
📚 Learning: 2025-12-13T20:08:45.549Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qmap PR: 862
File: pyproject.toml:65-66
Timestamp: 2025-12-13T20:08:45.549Z
Learning: In the qmap project (pyproject.toml), maintain broad compatibility with dependencies across supported Python versions. Avoid artificially raising minimum version requirements unless there's a specific need (e.g., to guarantee binary wheel availability for certain Python versions, or to access required features). The goal is to keep the software as broadly compatible as possible with the rest of the ecosystem.

Applied to files:

  • pyproject.toml
📚 Learning: 2025-11-04T14:26:25.420Z
Learnt from: marcelwa
Repo: munich-quantum-toolkit/core PR: 1243
File: test/python/qdmi/qiskit/conftest.py:11-19
Timestamp: 2025-11-04T14:26:25.420Z
Learning: In the munich-quantum-toolkit/core repository, Qiskit is always available as a dependency during testing, so import guards for qiskit-dependent imports in test files (e.g., test/python/qdmi/qiskit/*.py) are not necessary.

Applied to files:

  • pyproject.toml
📚 Learning: 2025-12-25T13:28:25.619Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: src/mqt/predictor/rl/predictorenv.py:271-271
Timestamp: 2025-12-25T13:28:25.619Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with an extensive set of rules including SLF (flake8-self) in the extend-select list. The `# noqa: SLF001` directive for private member access (e.g., `self.state._layout = self.layout` in src/mqt/predictor/rl/predictorenv.py) is necessary and appropriate, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/qecc/code_switching/code_switching_compiler.py
📚 Learning: 2026-01-08T10:07:32.871Z
Learnt from: flowerthrower
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 20
File: python/mqt/core/plugins/catalyst/device.py:69-69
Timestamp: 2026-01-08T10:07:32.871Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, Ruff is configured with select = ["ALL"] and only specific rules are disabled. SLF001 (flake8-self - private member access) is enabled, so `# noqa: SLF001` directives are necessary when accessing private attributes like `device._to_matrix_ops` in python/mqt/core/plugins/catalyst/device.py, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/qecc/code_switching/code_switching_compiler.py
📚 Learning: 2026-01-09T16:51:20.555Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qcec PR: 816
File: python/mqt/qcec/__init__.py:20-20
Timestamp: 2026-01-09T16:51:20.555Z
Learning: In the munich-quantum-toolkit/qcec repository, RUF067 (non-empty-init-module) is a Ruff preview rule introduced in version 0.14.11 that flags non-empty __init__.py files. The `# noqa: RUF067` directive in python/mqt/qcec/__init__.py is necessary and correct to suppress warnings for legitimate code (like the Windows DLL patch).

Applied to files:

  • src/mqt/qecc/code_switching/code_switching_compiler.py
📚 Learning: 2025-11-27T21:26:39.677Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qmap PR: 846
File: python/mqt/qmap/plugins/qiskit/sc/load_calibration.py:34-34
Timestamp: 2025-11-27T21:26:39.677Z
Learning: In the qmap project, the Ruff linter has the "PL" (pylint) rule category enabled, which includes PLC0415 (import-outside-top-level). Therefore, `# noqa: PLC0415` directives on lazy imports are appropriate and necessary, not unused.

Applied to files:

  • src/mqt/qecc/code_switching/code_switching_compiler.py
📚 Learning: 2025-11-24T10:19:41.147Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1326
File: python/mqt/core/__init__.py:22-22
Timestamp: 2025-11-24T10:19:41.147Z
Learning: In the munich-quantum-toolkit/core repository, Ruff is configured with 'ALL' rules enabled by default, and only specific rules are selectively disabled. When reviewing changes that enable previously-disabled rules (like PLC0415), noqa directives for those rules become necessary and should be retained.

Applied to files:

  • src/mqt/qecc/code_switching/code_switching_compiler.py
📚 Learning: 2025-12-25T13:28:10.402Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: tests/compilation/test_predictor_rl.py:225-225
Timestamp: 2025-12-25T13:28:10.402Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with the "SLF" (flake8-self) rule category enabled in extend-select. SLF001 (private member access) is active, so `# noqa: SLF001` directives are necessary when accessing private methods/attributes (e.g., `_ensure_device_averages_cached()`), even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/qecc/code_switching/code_switching_compiler.py
📚 Learning: 2025-12-14T15:23:54.712Z
Learnt from: flowerthrower
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 23
File: docs/conf.py:110-130
Timestamp: 2025-12-14T15:23:54.712Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, the Ruff configuration has 'ALL' rules enabled with only specific rules disabled. PLR6301 (no-self-use) is active, so `# noqa: PLR6301` directives are necessary for methods that don't use self, even if Ruff reports RUF100 warnings suggesting the directive is unused.

Applied to files:

  • src/mqt/qecc/code_switching/code_switching_compiler.py
🧬 Code graph analysis (4)
scripts/cs_compiler/simulate_circuit_performance.py (3)
src/mqt/qecc/code_switching/code_switching_compiler.py (2)
  • MinimalCodeSwitchingCompiler (36-476)
  • compute_min_cut (418-432)
src/mqt/qecc/code_switching/compilation_utils.py (1)
  • naive_switching (15-81)
src/mqt/qecc/circuit_synthesis/circuits.py (1)
  • depth (333-344)
src/mqt/qecc/code_switching/compilation_utils.py (1)
src/mqt/qecc/circuit_synthesis/circuits.py (1)
  • num_qubits (250-257)
src/mqt/qecc/code_switching/__init__.py (2)
src/mqt/qecc/code_switching/code_switching_compiler.py (2)
  • CompilerConfig (27-33)
  • MinimalCodeSwitchingCompiler (36-476)
src/mqt/qecc/code_switching/compilation_utils.py (2)
  • insert_switch_placeholders (84-170)
  • naive_switching (15-81)
src/mqt/qecc/code_switching/code_switching_compiler.py (1)
src/mqt/qecc/circuit_synthesis/circuits.py (2)
  • depth (333-344)
  • num_qubits (250-257)
🪛 Ruff (0.14.11)
src/mqt/qecc/code_switching/code_switching_compiler.py

330-330: Boolean-typed positional argument in function definition

(FBT001)


331-331: Boolean-typed positional argument in function definition

(FBT001)


370-370: Boolean-typed positional argument in function definition

(FBT001)


430-430: Unused noqa directive (non-enabled: N806)

Remove unused noqa directive

(RUF100)


434-434: Unused noqa directive (non-enabled: N803)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
🔇 Additional comments (19)
CHANGELOG.md (2)

50-50: LGTM! Link definitions correctly added.

The PR link definition for [#524] (line 50) and the contributor link definition for [**@inctechs**] (line 66) have been properly added, resolving the previously identified issue. Both follow the established format and correctly resolve the references in the changelog entry.

Also applies to: 66-66


14-14: arXiv link is valid and accessible.

The changelog entry includes an inline arXiv reference [arXiv:2512.04170](https://arxiv.org/abs/2512.04170), which is a novel addition—no other entries in this changelog cite academic papers. The link resolves correctly and provides valuable scholarly context for the new compiler feature.

pyproject.toml (2)

67-68: LGTM! NetworkX dependency properly added.

The missing runtime dependency has been addressed. The version constraint >=3.4.2 is appropriate for the project's Python 3.10+ requirement.


153-156: Mypy override correctly updated for networkx.

The networkx.* addition to the ignore list aligns with the new dependency and ensures type-checking passes without stub packages.

scripts/cs_compiler/generate_random_circuits.py (4)

27-46: LGTM on core function structure.

The function signature, docstring, and parameter handling are well-designed. Gate probabilities are properly normalized.


84-105: CX fallback now respects back-to-back restriction.

The fix properly filters fallback_gates to exclude the last non-ID gate, maintaining the constraint documented in the docstring.


136-144: Gate distribution validation properly implemented.

The validation at lines 142-144 provides a clear error message for invalid gate_distr_type values.


174-191: CLI entry point is well-structured.

Arguments are clearly documented with sensible defaults. The script correctly wires CLI args to generate_circuits.

scripts/cs_compiler/run_generate_circuits.sh (3)

9-9: Good addition of shell safety options.

set -euo pipefail ensures the script fails fast on errors, undefined variables, or pipeline failures.


22-26: Function correctly passes all required arguments.

The distr_type is now properly forwarded to the Python script, addressing the previous issue.


30-31: Parallel invocation is correctly structured.

Array expansion is properly quoted and the Cartesian product of n_values × distr_types is correctly passed to GNU parallel.

src/mqt/qecc/code_switching/__init__.py (1)

1-20: Clean module structure with well-defined public API.

The module properly re-exports the compiler and utility functions, with __all__ correctly listing all public symbols. The rename from circuit_compilation to code_switching addresses the previous naming concern.

scripts/cs_compiler/simulate_circuit_performance.py (3)

1-32: LGTM!

The module docstring correctly describes the CLI usage, and imports are properly organized with TYPE_CHECKING for runtime-only imports.


35-43: LGTM!

The function correctly creates parent directories before writing (addressing the prior concern) and handles header creation appropriately.


46-56: LGTM!

The deduplication logic is correct and handles the non-existent file case appropriately.

src/mqt/qecc/code_switching/compilation_utils.py (1)

143-170: LGTM!

The layer iteration, operation copying, and placeholder insertion logic is correct. The handling of out-of-range layer indices by appending at the end is a reasonable fallback.

src/mqt/qecc/code_switching/code_switching_compiler.py (3)

36-91: LGTM!

The MinimalCodeSwitchingCompiler class is well-documented with clear attribute descriptions. The initialization correctly sets up the graph structure with source/sink nodes.


206-267: LGTM!

The idle bonus computation and edge capacity adjustment logic is well-designed. The normalization formula ensures bonuses act as tie-breakers without dominating the min-cut decision.


365-417: LGTM!

The _apply_code_constraints method correctly handles source-unique, sink-unique, and common gates with appropriate infinite edge additions. The one-way direction parsing provides clear validation.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 14, 2026

✅ Actions performed

Reviews paused.

@inctechs
Copy link
Collaborator Author

@coderabbitai pause

@coderabbitai
Copy link

coderabbitai bot commented Jan 14, 2026

✅ Actions performed

Reviews paused.

@inctechs inctechs dismissed coderabbitai[bot]’s stale review January 14, 2026 12:18

errors with comments that are resolved but still keep the review from going thorugh

@inctechs inctechs merged commit fd87be7 into main Jan 16, 2026
15 checks passed
@inctechs inctechs deleted the code-switching-compiler branch January 16, 2026 11:27
@github-project-automation github-project-automation bot moved this from In Progress to Done in MQT Error Correction Jan 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants