Skip to content

Commit

Permalink
Documentation for Encoding Circuit Synthesis (#333)
Browse files Browse the repository at this point in the history
## 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
pehamTom authored Nov 26, 2024
1 parent af75ffd commit ef2c8ff
Show file tree
Hide file tree
Showing 6 changed files with 365 additions and 10 deletions.
296 changes: 296 additions & 0 deletions docs/Encoders.ipynb
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
}
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ please let us know at our :doc:`Support <Support>` page or by reaching out to us
LightsOutDecoder
EccFramework
StatePrep
Encoders
Publications

.. toctree::
Expand Down
14 changes: 14 additions & 0 deletions docs/library/Encoders.rst
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
1 change: 1 addition & 0 deletions docs/library/Library.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ Library
SamplePauliError
LightsOutDecoder
StatePrep
Encoders
35 changes: 35 additions & 0 deletions src/mqt/qecc/circuit_synthesis/circuit_utils.py
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
Loading

0 comments on commit ef2c8ff

Please sign in to comment.