Source code for qrisp.circuit.pass_management.passes.decompose
"""********************************************************************************
* Copyright (c) 2026 the Qrisp authors
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is
* available at https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************
"""
from __future__ import annotations
from collections.abc import Callable
import numpy as np
import sympy as sp
from qrisp.circuit.operation import Operation
from qrisp.circuit.pass_management.circuit_pass import CircuitPass
from qrisp.circuit.quantum_circuit import QuantumCircuit
def _collect_gphases(qc: QuantumCircuit) -> QuantumCircuit:
"""Remove all ``gphase`` gates from *qc*, accumulate their phases, and
append a single ``gphase`` on the first qubit of the last instruction
whose ``num_qubits > 0``.
"""
accumulated_phase = 0.0
kept = []
anchor_qubit = None
for instr in qc.data:
if instr.op.name == "gphase":
accumulated_phase += instr.op.params[0]
else:
kept.append(instr)
if instr.op.num_qubits > 0:
anchor_qubit = instr.qubits[0]
# Rebuild circuit from kept instructions
new_qc = qc.clearcopy()
for instr in kept:
new_qc.append(instr.op, instr.qubits, instr.clbits)
# Emit accumulated phase
accumulated_phase = accumulated_phase % (2 * np.pi)
if isinstance(accumulated_phase, sp.Expr) or (abs(accumulated_phase) > 1e-10 and anchor_qubit is not None):
new_qc.gphase(accumulated_phase, anchor_qubit)
return new_qc
[docs]
def decompose(
level: int | float = np.inf,
decompose_predicate: Callable[[Operation], bool] | None = None,
collect_gphases: bool = False,
) -> Callable[[QuantumCircuit], QuantumCircuit]:
"""Create a pass that recursively decomposes synthesized gates.
Every gate that has a ``.definition`` (a sub-circuit) is dissolved into
its constituent elementary gates up to the specified recursion *level*.
Gates whose definitions themselves contain further synthesized gates are
expanded deeper with each level increment.
By default *level* is ``np.inf``, so **all** synthesized gates are
decomposed all the way down to elementary gates in a single pass.
Parameters
----------
level : int or float, optional
Maximum recursion depth for decomposition. ``0`` leaves the circuit
unchanged. ``1`` dissolves one layer of synthesized gates. Higher
values expand deeper nested definitions. The default is ``np.inf``,
which decomposes all synthesized gates down to elementary gates.
decompose_predicate : Callable[[Operation], bool], optional
An optional predicate function that receives each :class:`Operation`
as its only argument and returns ``True`` if the gate should be
decomposed. Gates for which the predicate returns ``False`` are left
intact even if they have a ``.definition`` and are within the
recursion *level*. When ``None`` (the default), all synthesized
gates are decomposed.
collect_gphases : bool, optional
If ``True``, all :class:`~qrisp.circuit.GPhaseGate` instructions in
the decomposed circuit are removed and their phases accumulated into
a single ``gphase`` gate appended to the first qubit of the last
non-trivial instruction (one with ``num_qubits > 0``). This ensures
the global phase gate does not act on a freshly allocated qubit,
avoiding interference with qubit-freshness mechanisms. Default is
``False``.
Returns
-------
Callable[[QuantumCircuit], QuantumCircuit]
A pass function suitable for :meth:`PassManager.add_pass` or
:meth:`PassManager.__iadd__`.
Examples
--------
Decompose an MCX gate down to elementary gates::
>>> from qrisp import QuantumCircuit, PassManager
>>> from qrisp import decompose
>>> qc = QuantumCircuit(3)
>>> qc.mcx([0, 1], 2)
>>> print(qc)
qb_71: ──■──
│
qb_72: ──■──
┌─┴─┐
qb_73: ┤ X ├
└───┘
>>> pm = PassManager()
>>> pm += decompose()
>>> decomposed_qc = pm.run(qc)
>>> print(decomposed_qc)
┌─────┐
qb_71: ┤ Tdg ├───────■─────────■────■───────────────────────■──
├─────┤┌───┐ │ ┌───┐┌─┴─┐ │ ┌─────┐┌───┐ ┌───┐ ┌─┴─┐
qb_72: ┤ Tdg ├┤ X ├──┼──┤ T ├┤ X ├──┼──┤ Tdg ├┤ X ├─┤ T ├─┤ X ├
└┬───┬┘└─┬─┘┌─┴─┐├───┤└───┘┌─┴─┐└─────┘└─┬─┘┌┴───┴┐├───┤
qb_73: ─┤ H ├───■──┤ X ├┤ T ├─────┤ X ├─────────■──┤ Tdg ├┤ H ├
└───┘ └───┘└───┘ └───┘ └─────┘└───┘
Decompose only specific gates with a predicate::
>>> pm2 = PassManager()
>>> pm2 += decompose(decompose_predicate=lambda op: "cx" in op.name)
Decompose only one layer::
>>> pm3 = PassManager()
>>> pm3 += decompose(level=1)
"""
@CircuitPass
def _decompose(qc: QuantumCircuit) -> QuantumCircuit:
from qrisp.circuit.transpiler import transpile
result = transpile(
qc,
transpilation_level=level,
transpile_predicate=decompose_predicate,
)
if collect_gphases:
result = _collect_gphases(result)
return result
_decompose.__name__ = f"decompose(level={level})"
_decompose.__doc__ = f"Recursively decompose synthesized gates up to recursion depth {level}."
return _decompose