Skip to content

Commit

Permalink
Automated Synthesis of Encoder Circuits for Stabilizer Codes (#275)
Browse files Browse the repository at this point in the history
## Description

This PR introduces functionality to automatically synthesize encoder
circuits for arbitrary stabilizer codes. Since this is closely related
to state preparation circuit synthesis, this PR refactors and unifies
the respective synthesis methods into a `circuit_synthesis` module.

Planned Features:

- [x] Optimal (gate or depth) encoder circuit synthesis for CSS codes
- [x] Heuristic encoder circuit synthesis for CSS codes
- [ ] ~~Optimal (gate or depth) circuit synthesis for non-CSS ancilla
states~~
- [ ] ~~Heuristic circuit synthesis for non-CSS ancilla states~~

The last parts were canceled because qecc functionality already allows
for the construction of such state preparation circuits.


## 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 20, 2024
1 parent b53722d commit ef25257
Show file tree
Hide file tree
Showing 156 changed files with 5,337 additions and 842 deletions.
14 changes: 7 additions & 7 deletions docs/StatePrep.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"metadata": {},
"outputs": [],
"source": [
"from mqt.qecc.ft_stateprep import gate_optimal_prep_circuit\n",
"from mqt.qecc.circuit_synthesis import gate_optimal_prep_circuit\n",
"\n",
"non_ft_sp = gate_optimal_prep_circuit(steane_code, zero_state=True, max_timeout=2)\n",
"\n",
Expand Down Expand Up @@ -76,7 +76,7 @@
"metadata": {},
"outputs": [],
"source": [
"from mqt.qecc.ft_stateprep import gate_optimal_verification_circuit\n",
"from mqt.qecc.circuit_synthesis import gate_optimal_verification_circuit\n",
"\n",
"ft_sp = gate_optimal_verification_circuit(non_ft_sp)\n",
"\n",
Expand Down Expand Up @@ -105,7 +105,7 @@
"metadata": {},
"outputs": [],
"source": [
"from mqt.qecc.ft_stateprep import NoisyNDFTStatePrepSimulator\n",
"from mqt.qecc.circuit_synthesis import NoisyNDFTStatePrepSimulator\n",
"\n",
"p = 0.05\n",
"\n",
Expand Down Expand Up @@ -178,8 +178,8 @@
"metadata": {},
"outputs": [],
"source": [
"from mqt.qecc.circuit_synthesis import heuristic_prep_circuit\n",
"from mqt.qecc.codes import SquareOctagonColorCode\n",
"from mqt.qecc.ft_stateprep import heuristic_prep_circuit\n",
"\n",
"cc = SquareOctagonColorCode(5)\n",
"cc_non_ft_sp = heuristic_prep_circuit(cc, zero_state=True, optimize_depth=True)\n",
Expand Down Expand Up @@ -222,7 +222,7 @@
"metadata": {},
"outputs": [],
"source": [
"from mqt.qecc.ft_stateprep import naive_verification_circuit\n",
"from mqt.qecc.circuit_synthesis import naive_verification_circuit\n",
"\n",
"cc_ft_naive = naive_verification_circuit(cc_non_ft_sp)\n",
"\n",
Expand Down Expand Up @@ -293,7 +293,7 @@
"metadata": {},
"outputs": [],
"source": [
"from mqt.qecc.ft_stateprep import DeterministicVerificationHelper\n",
"from mqt.qecc.circuit_synthesis import DeterministicVerificationHelper\n",
"\n",
"det_helper = DeterministicVerificationHelper(non_ft_sp)"
]
Expand Down Expand Up @@ -393,7 +393,7 @@
"source": [
"from qsample import callbacks, noise\n",
"\n",
"from mqt.qecc.ft_stateprep import NoisyDFTStatePrepSimulator\n",
"from mqt.qecc.circuit_synthesis import NoisyDFTStatePrepSimulator\n",
"\n",
"error_model = noise.E1_1 # depolarizing error model\n",
"err_params = {\"q\": [1e-4, 5e-4, 1e-3, 5e-3, 1e-2, 5e-2, 1e-1, 5e-1]}\n",
Expand Down
1 change: 1 addition & 0 deletions docs/images/full_ft_scheme.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/library/StatePrep.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Fault tolerant state preparation

QECC provides functionality to synthesize and simulate state preparation circuits for logical basis states for arbitrary :math:`[[n, k, d]]` quantum CSS codes.

.. currentmodule:: mqt.qecc.ft_stateprep
.. currentmodule:: mqt.qecc.circuit_synthesis

Non-fault tolerant state preparation circuits can be synthesized using :func:`depth_optimal_prep_circuit`, :func:`gate_optimal_prep_circuit` and :func:`heuristic_prep_circuit`.

Expand Down
4 changes: 2 additions & 2 deletions scripts/ft_stateprep/eval/estimate_logical_error_rate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
from qiskit import QuantumCircuit

from mqt.qecc import CSSCode
from mqt.qecc.codes import HexagonalColorCode, SquareOctagonColorCode
from mqt.qecc.ft_stateprep import (
from mqt.qecc.circuit_synthesis import (
NoisyNDFTStatePrepSimulator,
gate_optimal_prep_circuit,
gate_optimal_verification_circuit,
heuristic_prep_circuit,
heuristic_verification_circuit,
naive_verification_circuit,
)
from mqt.qecc.codes import HexagonalColorCode, SquareOctagonColorCode


def main() -> None:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Methods for synthesizing fault tolerant state preparation circuits."""
"""Methods and utilities for synthesizing fault-tolerant circuits and gadgets."""

from __future__ import annotations

from .encoding import depth_optimal_encoding_circuit, gate_optimal_encoding_circuit, heuristic_encoding_circuit
from .simulation import LutDecoder, NoisyNDFTStatePrepSimulator
from .simulation_det import NoisyDFTStatePrepSimulator
from .state_prep import (
Expand All @@ -16,6 +17,7 @@
naive_verification_circuit,
)
from .state_prep_det import DeterministicVerification, DeterministicVerificationHelper
from .synthesis_utils import qiskit_to_stim_circuit

__all__ = [
"DeterministicVerification",
Expand All @@ -24,12 +26,16 @@
"NoisyDFTStatePrepSimulator",
"NoisyNDFTStatePrepSimulator",
"StatePrepCircuit",
"depth_optimal_encoding_circuit",
"depth_optimal_prep_circuit",
"gate_optimal_encoding_circuit",
"gate_optimal_prep_circuit",
"gate_optimal_verification_circuit",
"gate_optimal_verification_stabilizers",
"heuristic_encoding_circuit",
"heuristic_prep_circuit",
"heuristic_verification_circuit",
"heuristic_verification_stabilizers",
"naive_verification_circuit",
"qiskit_to_stim_circuit",
]
257 changes: 257 additions & 0 deletions src/mqt/qecc/circuit_synthesis/encoding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
"""Methods for synthesizing encoding circuits for CSS codes."""

from __future__ import annotations

import functools
import logging
import operator
from typing import TYPE_CHECKING

import numpy as np
import z3

from ..codes import InvalidCSSCodeError
from .synthesis_utils import build_css_circuit_from_cnot_list, heuristic_gaussian_elimination, optimal_elimination

if TYPE_CHECKING: # pragma: no cover
import numpy.typing as npt
from qiskit import QuantumCircuit

from ..codes import CSSCode

logger = logging.getLogger(__name__)


def heuristic_encoding_circuit(
code: CSSCode, optimize_depth: bool = True, balance_checks: bool = True
) -> QuantumCircuit:
"""Synthesize an encoding circuit for the given CSS code using a heuristic greedy search.
Args:
code: The CSS code to synthesize the encoding circuit for.
optimize_depth: Whether to optimize the depth of the circuit.
balance_checks: Whether to balance the entries of the stabilizer matrix via row operations.
Returns:
The synthesized encoding circuit and the qubits that are used to encode the logical qubits.
"""
logging.info("Starting encoding circuit synthesis.")

checks, logicals, use_x_checks = _get_matrix_with_fewest_checks(code)
n_checks = checks.shape[0]

if balance_checks:
_balance_matrix(logicals)

checks, cnots = heuristic_gaussian_elimination(
np.vstack((checks, logicals)),
parallel_elimination=optimize_depth,
)

# after reduction there still might be some overlap between initialized qubits and encoding qubits, we simply perform CNOTs to correct this
encoding_qubits = np.where(checks[n_checks:, :].sum(axis=0) != 0)[0]
initialization_qubits = np.where(checks[:n_checks, :].sum(axis=0) != 0)[0]
# remove encoding qubits from initialization qubits
initialization_qubits = np.setdiff1d(initialization_qubits, encoding_qubits)
rows = [] # type: list[int]
qubit_to_row = {}
for qubit in initialization_qubits:
cand = np.where(checks[:n_checks, qubit] == 1)[0]
np.setdiff1d(cand, np.array(rows))
rows.append(cand[0])
qubit_to_row[qubit] = cand[0]

for init_qubit in initialization_qubits:
for encoding_qubit in encoding_qubits:
row = qubit_to_row[init_qubit]
if checks[row, encoding_qubit] == 1:
cnots.append((init_qubit, encoding_qubit))
checks[row, encoding_qubit] = 0

cnots = cnots[::-1]

return _build_css_encoder_from_cnot_list(n_checks, checks, cnots, use_x_checks)


def gate_optimal_encoding_circuit(
code: CSSCode,
min_gates: int = 1,
max_gates: int = 10,
min_timeout: int = 1,
max_timeout: int = 3600,
) -> QuantumCircuit:
"""Synthesize an encoding circuit for the given CSS code using the minimal number of gates.
Args:
code: The CSS code to synthesize the encoding circuit for.
min_gates: The minimum number of gates to use in the circuit.
max_gates: The maximum number of gates to use in the circuit.
min_timeout: The minimum time to spend on the synthesis.
max_timeout: The maximum time to spend on the synthesis.
Returns:
The synthesized encoding circuit and the qubits that are used to encode the logical qubits.
"""
logging.info("Starting optimal encoding circuit synthesis.")
checks, logicals, use_x_checks = _get_matrix_with_fewest_checks(code)
assert checks is not None
n_checks = checks.shape[0]
checks_and_logicals = np.vstack((checks, logicals))
termination_criteria = functools.partial(
_final_matrix_constraint_partially_full_reduction,
full_reduction_rows=list(range(checks.shape[0], checks.shape[0] + logicals.shape[0])),
)
res = optimal_elimination(
checks_and_logicals,
termination_criteria,
"column_ops",
min_param=min_gates,
max_param=max_gates,
min_timeout=min_timeout,
max_timeout=max_timeout,
)
if res is None:
return None
reduced_checks_and_logicals, cnots = res
cnots = cnots[::-1]

return _build_css_encoder_from_cnot_list(n_checks, reduced_checks_and_logicals, cnots, use_x_checks)


def depth_optimal_encoding_circuit(
code: CSSCode,
min_depth: int = 1,
max_depth: int = 10,
min_timeout: int = 1,
max_timeout: int = 3600,
) -> QuantumCircuit:
"""Synthesize an encoding circuit for the given CSS code using minimal depth.
Args:
code: The CSS code to synthesize the encoding circuit for.
min_depth: The minimum number of gates to use in the circuit.
max_depth: The maximum number of gates to use in the circuit.
min_timeout: The minimum time to spend on the synthesis.
max_timeout: The maximum time to spend on the synthesis.
Returns:
The synthesized encoding circuit and the qubits that are used to encode the logical qubits.
"""
logging.info("Starting optimal encoding circuit synthesis.")
checks, logicals, use_x_checks = _get_matrix_with_fewest_checks(code)
assert checks is not None
n_checks = checks.shape[0]
checks_and_logicals = np.vstack((checks, logicals))
termination_criteria = functools.partial(
_final_matrix_constraint_partially_full_reduction,
full_reduction_rows=list(range(checks.shape[0], checks.shape[0] + logicals.shape[0])),
)
res = optimal_elimination(
checks_and_logicals,
termination_criteria,
"parallel_ops",
min_param=min_depth,
max_param=max_depth,
min_timeout=min_timeout,
max_timeout=max_timeout,
)
if res is None:
return None
reduced_checks_and_logicals, cnots = res
cnots = cnots[::-1]

return _build_css_encoder_from_cnot_list(n_checks, reduced_checks_and_logicals, cnots, use_x_checks)


def _get_matrix_with_fewest_checks(code: CSSCode) -> tuple[npt.NDArray[np.int8], npt.NDArray[np.int8], bool]:
"""Return the stabilizer matrix with the fewest checks, the corresponding logicals and a bool indicating whether X- or Z-checks have been returned."""
if code.Hx is None or code.Hz is None:
msg = "The code must have both X and Z stabilizers defined."
raise InvalidCSSCodeError(msg)

use_x_checks = code.Hx.shape[0] < code.Hz.shape[0]
checks = code.Hx if use_x_checks else code.Hz
logicals = code.Lx if use_x_checks else code.Lz
return checks, logicals, use_x_checks


def _final_matrix_constraint_partially_full_reduction(
columns: npt.NDArray[z3.BoolRef | bool], full_reduction_rows: list[int]
) -> z3.BoolRef:
assert len(columns.shape) == 3

at_least_n_row_columns = z3.PbEq(
[(z3.Or(list(columns[-1, full_reduction_rows, col])), 1) for col in range(columns.shape[2])],
len(full_reduction_rows),
)

fully_reduced = z3.And(at_least_n_row_columns)

partial_reduction_rows = list(set(range(columns.shape[1])) - set(full_reduction_rows))

# assert that the partial_reduction_rows are partially reduced, i.e. there are at least columns.shape[2] - (columns.shape[1] - len(full_reduction_rows)) non-zero columns
partially_reduced = z3.PbEq(
[(z3.Not(z3.Or(list(columns[-1, partial_reduction_rows, col]))), 1) for col in range(columns.shape[2])],
columns.shape[2] - (columns.shape[1] - len(full_reduction_rows)),
)

# assert that there is no overlap between the full_reduction_rows and the partial_reduction_rows
overlap_constraints = [True]
for col in range(columns.shape[2]):
has_entry_partial = z3.Or(list(columns[-1, partial_reduction_rows, col]))
has_entry_full = z3.Or(list(columns[-1, full_reduction_rows, col]))
overlap_constraints.append(z3.Not(z3.And(has_entry_partial, has_entry_full)))

return z3.And(fully_reduced, partially_reduced, z3.And(overlap_constraints))


def _build_css_encoder_from_cnot_list(
n_checks: int, checks_and_logicals: npt.NDArray[np.int8], cnots: list[tuple[int, int]], use_x_checks: bool
) -> tuple[QuantumCircuit, list[int]]:
encoding_qubits = np.where(checks_and_logicals[n_checks:, :].sum(axis=0) != 0)[0]
if use_x_checks:
hadamards = np.where(checks_and_logicals[:n_checks, :].sum(axis=0) != 0)[0]
else:
hadamards = np.where(checks_and_logicals[:n_checks, :].sum(axis=0) == 0)[0]
cnots = [(j, i) for i, j in cnots]

hadamards = np.setdiff1d(hadamards, encoding_qubits)
circ = build_css_circuit_from_cnot_list(checks_and_logicals.shape[1], cnots, list(hadamards))
return circ, encoding_qubits


def _balance_matrix(m: npt.NDArray[np.int8]) -> None:
"""Balance the columns of the matrix.
Try to balance the number of 1's in each column via row operations without increasing the row-weight.
"""
variance = np.var(m.sum(axis=0))
reduced = False

while not reduced:
reduced = True
# compute row operations that do not increase the row-weights
row_ops = []
for i, row_1 in enumerate(m):
for j, row_2 in enumerate(m):
if i == j:
continue
s = (row_1 + row_2) % 2
if s.sum() > row_1.sum() or s.sum() > row_2.sum():
continue
# compute associated column weights
m[j] = s # noqa: B909

new_variance = np.var(m.sum(axis=0))
if new_variance < variance:
row_ops.append((i, j, new_variance))

m[j] = row_2 # noqa: B909
# sort by lowest variance
row_ops.sort(key=operator.itemgetter(2))
# apply best row operation
if row_ops:
i, j = row_ops[0][:2]
m[i] = (m[i] + m[j]) % 2
reduced = False
variance = row_ops[0][2]
Loading

0 comments on commit ef25257

Please sign in to comment.