-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Documentation for Encoding Circuit Synthesis (#333)
## Description This PR adds documentation for how to synthesize encoding circuits for CSS codes. ## Checklist: <!--- This checklist serves as a reminder of a couple of things that ensure your pull request will be merged swiftly. --> - [x] The pull request only contains commits that are related to it. - [x] I have added appropriate tests and documentation. - [x] I have made sure that all CI jobs on GitHub pass. - [x] The pull request introduces no new warnings and follows the project's style guidelines.
- Loading branch information
Showing
6 changed files
with
365 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,296 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "markdown", | ||
"id": "c594f3e1-63c2-4d40-bec6-5ea21a78422d", | ||
"metadata": {}, | ||
"source": [ | ||
"# Encoder Circuit Synthesis for CSS Codes\n", | ||
"\n", | ||
"QECC provides functionality for synthesizing encoding circuits of arbitrary CSS codes. An encoder for an $[[n,k,d]]$ code is an isometry that encodes $k$ logical qubits into $n$ physical qubits. \n", | ||
"\n", | ||
"Let's consider the synthesis of the encoding circuit of the $[[7,1,3]]$ Steane code." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "0234dd1e-bdb9-4030-be5d-f9952a750333", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"from mqt.qecc import CSSCode\n", | ||
"from mqt.qecc.circuit_synthesis import (\n", | ||
" depth_optimal_encoding_circuit,\n", | ||
" gate_optimal_encoding_circuit,\n", | ||
" heuristic_encoding_circuit,\n", | ||
")\n", | ||
"\n", | ||
"steane_code = CSSCode.from_code_name(\"steane\")\n", | ||
"\n", | ||
"print(\"Stabilizers:\\n\")\n", | ||
"print(steane_code.stabs_as_pauli_strings())\n", | ||
"print(\"\\nLogicals:\\n\")\n", | ||
"print(steane_code.x_logicals_as_pauli_strings())" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "6e5fc2e3-9bca-4520-9ba4-68571dc4c222", | ||
"metadata": {}, | ||
"source": [ | ||
"There is not a unique encoding circuit but usually we would like to obtain an encoding circuit that is optimal with respect to some metric. QECC has functionality for synthesizing gate- or depth-optimal encoding circuits. \n", | ||
"\n", | ||
"Under the hood, this uses the SMT solver [z3](https://github.com/Z3Prover/z3). Of course this method scales only up to a few qubits. Synthesizing depth-optimal circuits is usually faster than synthesizing gate-optimal circuits." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "f0a64914-d6f8-48ae-b49d-4dbc425dd92d", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"depth_opt, q_enc = depth_optimal_encoding_circuit(steane_code, max_timeout=5)\n", | ||
"\n", | ||
"print(f\"Encoding qubits are qubits {q_enc}.\")\n", | ||
"print(f\"Circuit has depth {depth_opt.depth()}.\")\n", | ||
"print(f\"Circuit has {depth_opt.num_nonlocal_gates()} CNOTs.\")\n", | ||
"\n", | ||
"depth_opt.draw()" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "c284d44e-00db-41f1-988b-47d73b790df2", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"gate_opt, q_enc = gate_optimal_encoding_circuit(steane_code, max_timeout=5)\n", | ||
"\n", | ||
"print(f\"Encoding qubits are qubits {q_enc}.\")\n", | ||
"print(f\"Circuit has depth {gate_opt.depth()}.\")\n", | ||
"print(f\"Circuit has {gate_opt.num_nonlocal_gates()} CNOTs.\")\n", | ||
"\n", | ||
"gate_opt.draw()" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "4d60e0a1-ea9a-45b2-9dd5-327a4db9b089", | ||
"metadata": {}, | ||
"source": [ | ||
"QECC obtains optimal solutions for circuits by iteratively trying out different parameters to close in on the optimum. Each run will only be run until the number of seconds specified by `max_timeout`. If a solution is found in this time it is returned. Otherwise, `None` will be returned. \n", | ||
"\n", | ||
"In addition to the circuit, the synthesis methods also return the encoding qubits. All other qubits are assumed to be initialized in the $|0\\rangle$ state. \n", | ||
"\n", | ||
"For larger codes, synthesizing optimal circuits is not feasible. In this case, QECC provides a heuristic synthesis method that tries to use as few CNOTs with the lowest depth as possible. " | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "f0412b86-57b4-433d-8a02-5041c4aa4dd8", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"heuristic_circ, q_enc = heuristic_encoding_circuit(steane_code)\n", | ||
"\n", | ||
"print(f\"Encoding qubits are qubits {q_enc}.\")\n", | ||
"print(f\"Circuit has depth {heuristic_circ.depth()}.\")\n", | ||
"print(f\"Circuit has {heuristic_circ.num_nonlocal_gates()} CNOTs.\")\n", | ||
"\n", | ||
"heuristic_circ.draw()" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "eb8659b7-b3cb-4db5-9615-3969a8424456", | ||
"metadata": {}, | ||
"source": [ | ||
"## Synthesizing Encoders for Concatenated Codes\n", | ||
"\n", | ||
"Encoders for concatenated codes can be constructed by concatenating encoding circuits. We can concatenate the $[[4,2,2]]$ code (with stabilizer generators $XXXX$ and $ZZZZ$) with itself by encoding $4$ qubits into two blocks of the code and then encoding these qubits one more time. This gives an $[[8,4,2]]$ code. The distance is still $2$ but if done the right way, some minimal-weight logicals have weight $4$.\n", | ||
"\n", | ||
"As an exercise, let's construct the concatenated circuit.\n", | ||
"\n", | ||
"We start off by defining the code:" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "65a4c54a-a630-45b8-8f1f-96c858e4a792", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"import numpy as np\n", | ||
"\n", | ||
"d = 2\n", | ||
"x_stabs = np.ones((1, 4), dtype=np.int8)\n", | ||
"z_stabs = x_stabs\n", | ||
"code = CSSCode(d, x_stabs, z_stabs)\n", | ||
"\n", | ||
"print(\"Stabilizers:\\n\")\n", | ||
"print(code.stabs_as_pauli_strings())\n", | ||
"print(\"\\nLogicals:\\n\")\n", | ||
"print(code.x_logicals_as_pauli_strings())\n", | ||
"print(code.z_logicals_as_pauli_strings())" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "0962bf7e-a7db-4481-bc10-7194acaddf28", | ||
"metadata": {}, | ||
"source": [ | ||
"We have to be careful with the logicals. Each *anticommuting* pair of logicals defines one logical qubit. \n", | ||
"\n", | ||
"As before, we synthesize the encoding circuit:" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "95572fcb-8331-4cd4-9271-77d23c61109f", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"encoder, q_enc = depth_optimal_encoding_circuit(code, max_timeout=5)\n", | ||
"\n", | ||
"print(f\"Encoding qubits are qubits {q_enc}.\")\n", | ||
"print(f\"Circuit has depth {encoder.depth()}.\")\n", | ||
"print(f\"Circuit has {encoder.num_nonlocal_gates()} CNOTs.\")\n", | ||
"\n", | ||
"encoder.draw()" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "b28680cc-2bc8-4f74-a49e-a323770f2d41", | ||
"metadata": {}, | ||
"source": [ | ||
"Propagating Paulis from the encoding qubits at the input to the output will not necessarily yield the exact logicals given above. But the logical operators will be stabilizer equivalent.\n", | ||
"\n", | ||
"Concatenating the circuits can be done as follows with qiskit:" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "0f30bdac-6411-4acb-b178-f62d863ffa85", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"from qiskit import QuantumCircuit\n", | ||
"\n", | ||
"from mqt.qecc.circuit_synthesis.circuit_utils import reorder_qubits\n", | ||
"\n", | ||
"n = 4\n", | ||
"\n", | ||
"first_layer = QuantumCircuit(n).tensor(encoder)\n", | ||
"second_layer = encoder.tensor(encoder)\n", | ||
"\n", | ||
"initialized_qubits = set(range(2 * n)) - set(q_enc) - {q + n for q in q_enc}\n", | ||
"qubit_mapping = {q_enc[0]: 0, q_enc[1]: 3, q_enc[0] + n: 2, q_enc[1] + n: 1}\n", | ||
"qubit_mapping.update({q: i + 2 * len(q_enc) for i, q in enumerate(initialized_qubits)})\n", | ||
"second_layer = reorder_qubits(second_layer, qubit_mapping)\n", | ||
"\n", | ||
"encoder_concat_naive = first_layer.compose(second_layer)\n", | ||
"\n", | ||
"print(f\"Encoding qubits are qubits {q_enc}.\")\n", | ||
"print(f\"Circuit has depth {encoder_concat_naive.depth()}.\")\n", | ||
"print(f\"Circuit has {encoder_concat_naive.num_nonlocal_gates()} CNOTs.\")\n", | ||
"\n", | ||
"encoder_concat_naive.draw()" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "fc530584-d692-4f2c-b1ff-705577d47ebb", | ||
"metadata": {}, | ||
"source": [ | ||
"Qubits $1$ and $2$ are still the encoding qubits and if we propagate Pauli $X$ and $Z$ to the output, we find that this is indeed the encoder for an $[[8,2,2]]$ code.\n", | ||
"\n", | ||
"This circuit has $3$ times as many CNOT gates as the encoder for the unconcatenated code because we needed to encode 3 times. Instead of concatenating the encoder circuits we can synthesize the encoders directly from the stabilizers of the concatenated code. The stabilizers of the concatenated code are the stabilizers of the original code on the respective subset of qubits with the addition of the \"encoded\" stabilizers of the inner code. We have a choice of how exactly we encode the stabilizers of the inner code. In the circuit picture, we have a choice of how we \"wire the qubits together\". Depending on how we do this, the code might have different logical operators." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "b07b6d3b-6547-4a8c-887d-47e0f508acf0", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"permutation = [3, 0, 2, 1]\n", | ||
"\n", | ||
"x_prod = (code.Lx[0] + code.Lx[1]) % 2\n", | ||
"Hx = np.vstack((np.kron(np.eye(2, dtype=np.int8), code.Hx), np.hstack((x_prod, x_prod[permutation]))))\n", | ||
"\n", | ||
"z_prod = (code.Lz[0] + code.Lz[1]) % 2\n", | ||
"Hz = np.vstack((np.kron(np.eye(2, dtype=np.int8), code.Hz), np.hstack((z_prod, z_prod[permutation]))))\n", | ||
"\n", | ||
"concatenated = CSSCode(4, Hx, Hz)\n", | ||
"\n", | ||
"print(\"Stabilizers:\\n\")\n", | ||
"print(concatenated.stabs_as_pauli_strings())\n", | ||
"\n", | ||
"print(\"\\nLogicals:\\n\")\n", | ||
"print(concatenated.x_logicals_as_pauli_strings())\n", | ||
"print(concatenated.z_logicals_as_pauli_strings())" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "2ec9a70e-84ea-4306-a426-1f47f5b6c281", | ||
"metadata": {}, | ||
"source": [ | ||
"Now we can directly synthesize the encoder:" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "7ee3daca-8dfc-459e-b449-ec3f5b040090", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"encoder_concat_direct, q_enc = depth_optimal_encoding_circuit(concatenated, max_timeout=5)\n", | ||
"\n", | ||
"print(f\"Encoding qubits are qubits {q_enc}.\")\n", | ||
"print(f\"Circuit has depth {encoder_concat_direct.depth()}.\")\n", | ||
"print(f\"Circuit has {encoder_concat_direct.num_nonlocal_gates()} CNOTs.\")\n", | ||
"\n", | ||
"encoder_concat_direct.draw()" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "65d384a9-b1f5-491a-8691-d4ef7966b14f", | ||
"metadata": {}, | ||
"source": [ | ||
"We see that the circuit is more compact then the naively concatenated one. This is because the synthesis method exploits redundancy in the check matrix of the concatenated code." | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"kernelspec": { | ||
"display_name": "python11", | ||
"language": "python", | ||
"name": "python11" | ||
}, | ||
"language_info": { | ||
"codemirror_mode": { | ||
"name": "ipython", | ||
"version": 3 | ||
}, | ||
"file_extension": ".py", | ||
"mimetype": "text/x-python", | ||
"name": "python", | ||
"nbconvert_exporter": "python", | ||
"pygments_lexer": "ipython3" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 5 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
Encoder Circuit Synthesis for CSS Codes | ||
======================================= | ||
|
||
QECC provides functionality to synthesize encoder circuits for arbitrary :math:`[[n, k, d]]` quantum CSS codes. | ||
|
||
.. currentmodule:: mqt.qecc.circuit_synthesis | ||
|
||
Non-fault tolerant state preparation circuits can be synthesized using :func:`depth_optimal_encoding_circuit`, :func:`gate_optimal_encoding_circuit` and :func:`heuristic_encoding_circuit`. | ||
|
||
.. autofunction:: depth_optimal_encoding_circuit | ||
|
||
.. autofunction:: gate_optimal_encoding_circuit | ||
|
||
.. autofunction:: heuristic_encoding_circuit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,3 +16,4 @@ Library | |
SamplePauliError | ||
LightsOutDecoder | ||
StatePrep | ||
Encoders |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
"""General circuit constructions.""" | ||
|
||
from __future__ import annotations | ||
|
||
from qiskit import QuantumCircuit, QuantumRegister | ||
|
||
|
||
def reorder_qubits(circ: QuantumCircuit, qubit_mapping: dict[int, int]) -> QuantumCircuit: | ||
"""Reorders the qubits in a QuantumCircuit based on the given mapping. | ||
Parameters: | ||
circuit (QuantumCircuit): The original quantum circuit. | ||
qubit_mapping (dict[int, int]): A dictionary mapping original qubit indices to new qubit indices. | ||
Returns: | ||
QuantumCircuit: A new quantum circuit with qubits reordered. | ||
""" | ||
# Validate the qubit_mapping | ||
if sorted(qubit_mapping.keys()) != list(range(len(circ.qubits))) or sorted(qubit_mapping.values()) != list( | ||
range(len(circ.qubits)) | ||
): | ||
msg = "Invalid qubit_mapping: It must be a permutation of the original qubit indices." | ||
raise ValueError(msg) | ||
|
||
# Create a new quantum register | ||
num_qubits = len(circ.qubits) | ||
new_register = QuantumRegister(num_qubits, "q") | ||
new_circuit = QuantumCircuit(new_register) | ||
|
||
# Remap instructions based on the qubit_mapping | ||
for instruction, qubits, clbits in circ.data: | ||
new_qubits = [new_register[qubit_mapping[circ.find_bit(q)[0]]] for q in qubits] | ||
new_circuit.append(instruction, new_qubits, clbits) | ||
|
||
return new_circuit |
Oops, something went wrong.