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