catalyst.passes.graph_decomposition

graph_decomposition(qnode, gate_set: Iterable[type | str] | dict[type | str, float], fixed_decomps: dict | None = None, alt_decomps: dict | None = None, bytecode_rules: str | None = None, _builtin_rule_path: Path = PosixPath('/home/docs/checkouts/readthedocs.org/user_builds/xanaduai-catalyst/envs/stable/lib/python3.11/site-packages/catalyst/resources/decomposition_rules_3126674625638fee8413d5828b24d2b074a0aeb4.mlirbc'))

Specify that the -graph-decomposition MLIR compiler pass for applying the graph-based decomposition should be applied to the decorated QNode during qjit() compilation.

The graph-based decomposition pass decomposes gates into a weighted target gate_set by applying user-provided and built-in decomposition rules. The graph-based framework allows multiple decomposition rules to be defined for a quantum operation, and the graph solver will determine the optimal decomposition rules to apply, minimizing the overall gate count or the cost according to user-specified weights.

Note

The QNode itself will not be changed or transformed by applying these decorators.

As a result, circuit inspection tools such as draw() will continue to display the circuit as written in Python.

To instead view the optimized circuit, the MLIR must be viewed after the "QuantumCompilationStage" stage via the get_compilation_stage() function.

Parameters:
  • fn (QNode) – the QNode to apply the graph decomposition compiler pass to.

  • gate_set (Iterable[type | str] | dict[type | str, float]) – the set of gates that are permissable after decomposition.

  • fixed_decomps – map operators to specific decomposition rules that will be applied if the operators need to be decomposed (i.e. when they’re not in the target gate set).

Return type:

QNode

Example

import pennylane as qp
import pennylane.numpy as np

from catalyst import qjit
from catalyst.jax_primitives import decomposition_rule
from catalyst.passes import cancel_inverses, graph_decomposition, merge_rotations


@decomposition_rule(op_type=qp.PauliX)
def x_to_rx(wire: int):
    qp.RX(np.pi, wire)


@decomposition_rule(op_type=qp.PauliY)
def y_to_ry(wire: int):
    qp.RY(np.pi, wire)


@decomposition_rule(op_type=qp.Hadamard)
def h_to_rx_ry(wire: int):
    qp.RX(np.pi / 2, wire)
    qp.RY(np.pi / 2, wire)


@qjit(capture=True)
@graph_decomposition(gate_set={qp.Rot})
@merge_rotations
@graph_decomposition(
    gate_set={qp.RX, qp.RY},
    fixed_decomps={qp.PauliX: x_to_rx, qp.PauliY: y_to_ry},
    alt_decomps={qp.H: [h_to_rx_ry]},
)
@cancel_inverses
@qp.qnode(qp.device("lightning.qubit", wires=2))
def circuit(x: float, y: float):
    qp.H(0)
    qp.H(0)
    qp.RX(x, wires=0)
    qp.PauliX(0)
    qp.RY(y, wires=0)
    qp.PauliY(0)
    qp.RY(x + y, wires=0)

    # register custom decomposition rules
    x_to_rx(int)
    y_to_ry(int)
    h_to_rx_ry(int)

    return qp.state()
>>> qp.specs(circuit, level="device")(1.23, 4.56).resources.gate_types
{'Rot': 2}