# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
# All rights reserved.
#
# SPDX-License-Identifier: MIT
#
# Licensed under the MIT License
"""Qiskit utilities for Clifford synthesis module."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from mqt.core import load
from mqt.core.plugins.qiskit import mqt_to_qiskit
from qiskit.quantum_info import Clifford, PauliList
from ...clifford_synthesis import (
CliffordSynthesizer,
SynthesisConfiguration,
Tableau,
)
if TYPE_CHECKING:
from qiskit.circuit import QuantumCircuit
from ...clifford_synthesis import SynthesisResults
from ...types import CircuitInputType
__all__ = [
"optimize_clifford",
"synthesize_clifford",
]
def __dir__() -> list[str]:
return __all__
def _reverse_paulis(paulis: list[str]) -> list[str]:
return [s[0] + s[:0:-1] if s[0] in "+-" else s[::-1] for s in paulis]
def _import_tableau(tableau: str | Clifford | PauliList | Tableau, include_destabilizers: bool = False) -> Tableau:
"""Import a tableau from a string, a Clifford, a PauliList, or a Tableau."""
if isinstance(tableau, Clifford):
mode = "B" if include_destabilizers else "S"
try:
return Tableau(str(_reverse_paulis(tableau.to_labels(mode=mode))))
except AttributeError:
if include_destabilizers:
return Tableau(
str(_reverse_paulis(tableau.stab.to_labels())),
str(_reverse_paulis(tableau.destab.to_labels())),
)
return Tableau(str(_reverse_paulis(tableau.stab.to_labels())))
elif isinstance(tableau, PauliList):
return Tableau(str(_reverse_paulis(tableau.to_labels())))
elif isinstance(tableau, str):
return Tableau(tableau)
return tableau
def _config_from_kwargs(kwargs: dict[str, Any]) -> SynthesisConfiguration:
"""Create a :class:`SynthesisConfiguration` from keyword arguments."""
config = SynthesisConfiguration()
for key, value in kwargs.items():
if hasattr(config, key):
setattr(config, key, value)
else:
msg = f"Invalid keyword argument: {key}"
raise ValueError(msg)
if not config.solver_parameters:
config.solver_parameters = {}
if config.use_maxsat:
config.solver_parameters["pb.compile_equality"] = True
config.solver_parameters["maxres.hill_climb"] = True
config.solver_parameters["maxres.pivot_on_correction_set"] = False
else:
config.solver_parameters["bca"] = True
config.solver_parameters["restart.emafastglue"] = 0.05
config.solver_parameters["restart.emaslowglue"] = 1e-6
config.solver_parameters["restart.margin"] = 1.07
config.solver_parameters["rephase.base"] = 3000
config.solver_parameters["search.sat.conflicts"] = 100
return config
[docs]
def synthesize_clifford(
target_tableau: str | Clifford | PauliList | Tableau,
initial_tableau: str | Clifford | PauliList | Tableau | None = None,
include_destabilizers: bool = False,
**kwargs: Any, # noqa: ANN401
) -> tuple[QuantumCircuit, SynthesisResults]:
"""Synthesize a Clifford circuit from a given tableau starting from an (optional) initial tableau.
Args:
target_tableau:
The target tableau to synthesize.
If a string is given, it is interpreted as a semicolon separated binary matrix or a list of Pauli strings. The Pauli strings follow the same format as in `Stim <https://github.com/quantumlib/Stim>`_.
If a :class:`~qiskit.quantum_info.Clifford` or a :class:`~qiskit.quantum_info.PauliList` is given, it is converted to a :class:`~mqt.qmap.clifford_synthesis.Tableau`.
If a :class:`~mqt.qmap.clifford_synthesis.Tableau` is given, it is used directly.
initial_tableau:
The initial tableau to start from.
If a string is given, it is interpreted as a semicolon separated binary matrix or a list of Pauli strings.
If a :class:`~qiskit.quantum_info.Clifford` or a :class:`~qiskit.quantum_info.PauliList` is given, it is converted to a :class:`~mqt.qmap.clifford_synthesis.Tableau`.
If a :class:`~mqt.qmap.clifford_synthesis.Tableau` is given, it is used directly.
If no initial tableau is given, the synthesis starts from the identity tableau.
include_destabilizers:
Flag to set whether destabilizers should be considered in the synthesis
**kwargs:
Additional keyword arguments to configure the synthesis.
See :class:`~mqt.qmap.clifford_synthesis.SynthesisConfiguration` for a list of available options.
Returns:
A tuple containing the synthesized circuit and the synthesis results.
"""
config = _config_from_kwargs(kwargs)
tableau = _import_tableau(target_tableau, include_destabilizers)
if initial_tableau is not None:
synthesizer = CliffordSynthesizer(_import_tableau(initial_tableau), tableau)
else:
synthesizer = CliffordSynthesizer(tableau)
synthesizer.synthesize(config)
return mqt_to_qiskit(synthesizer.result_circuit), synthesizer.results
[docs]
def optimize_clifford(
circuit: CircuitInputType,
initial_tableau: str | Clifford | PauliList | Tableau | None = None,
include_destabilizers: bool = False,
**kwargs: Any, # noqa: ANN401
) -> tuple[QuantumCircuit, SynthesisResults]:
"""Optimize a Clifford circuit starting from an (optional) initial tableau.
Args:
circuit:
The circuit to optimize.
If a string is given, it is interpreted as a QASM string or a filename.
If a :class:`~qiskit.circuit.QuantumCircuit` is given, it is converted to a :class:`~mqt.core.ir.QuantumComputation`.
If a :class:`~mqt.core.ir.QuantumComputation` is given, it is used as is.
initial_tableau:
The initial tableau to start from.
If a string is given, it is interpreted as a semicolon separated binary matrix or a list of Pauli strings.
If a :class:`~qiskit.quantum_info.Clifford` is given or a :class:`~qiskit.quantum_info.PauliList` is given, it is converted to a Tableau.
If a :class:`~mqt.qmap.clifford_synthesis.Tableau` is given, it is used directly.
If no initial tableau is given, the synthesis starts from the identity tableau.
include_destabilizers:
Flag to set whether destabilizers should be considered in the synthesis
**kwargs:
Additional keyword arguments to configure the synthesis.
See :class:`~mqt.qmap.clifford_synthesis.SynthesisConfiguration` for a list of available options.
Returns:
A tuple containing the optimized circuit and the synthesis results.
"""
config = _config_from_kwargs(kwargs)
qc = load(circuit)
if initial_tableau is not None:
synthesizer = CliffordSynthesizer(_import_tableau(initial_tableau, include_destabilizers), qc)
else:
synthesizer = CliffordSynthesizer(qc, include_destabilizers)
synthesizer.synthesize(config)
return mqt_to_qiskit(synthesizer.result_circuit), synthesizer.results