mimiqcircuits.decomposition

Quantum circuit decomposition framework.

This module provides a flexible framework for decomposing quantum circuits into different target gate sets.

Core Concepts

RewriteRule: Single-step transformation rules that define how to transform operations, but not when to stop.

DecompositionBasis: Recursive decomposition targets that define both what is terminal (operations that shouldn’t be decomposed) and how to decompose non-terminal operations.

Main Functions

Available Bases

Available Rules

Usage

Basic decomposition to canonical basis (GateU + GateCX):

>>> from mimiqcircuits import Circuit, GateH, GateCCX
>>> from mimiqcircuits.decomposition import decompose
>>> c = Circuit()
>>> c.push(GateH(), 0)
1-qubit circuit with 1 instruction:
└── H @ q[0]

>>> c.push(GateCCX(), 0, 1, 2)
3-qubit circuit with 2 instructions:
├── H @ q[0]
└── C₂X @ q[0:1], q[2]

>>> decomposed = decompose(c)

Decompose to Clifford+T:

>>> from mimiqcircuits.decomposition import decompose, CliffordTBasis
>>> decomposed = decompose(c, CliffordTBasis())

Use a specific rewrite rule:

>>> from mimiqcircuits.decomposition import decompose_step, ZYZRewrite
>>> stepped = decompose_step(c, ZYZRewrite())

Creating Custom Rules

You can create custom rewrite rules by subclassing RewriteRule:

>>> from mimiqcircuits.decomposition import RewriteRule
>>> class MyRewrite(RewriteRule):
...     def matches(self, op):
...         return isinstance(op, SomeGate)
...
...     def decompose_step(self, op, qubits, bits, zvars):
...         from mimiqcircuits import Circuit
...         circ = Circuit()
...         # ... decomposition logic ...
...         return circ

And custom decomposition bases by subclassing DecompositionBasis:

>>> from mimiqcircuits.decomposition import DecompositionBasis
>>> class MyBasis(DecompositionBasis):
...     def isterminal(self, op):
...         return isinstance(op, (TargetGate1, TargetGate2))
...
...     def decompose(self, op, qubits, bits, zvars):
...         # Use a rewrite rule or custom logic
...         return SomeRewrite().decompose_step(op, qubits, bits, zvars)
class mimiqcircuits.decomposition.RewriteRule[source]

Bases: ABC

Abstract base class for single-step rewrite rules.

A RewriteRule defines how to transform an operation into a sequence of simpler operations, but not when to stop. For recursive decomposition to a target set of operations, see DecompositionBasis.

Subclasses must implement:

Example

>>> class MyRewrite(RewriteRule):
...     def matches(self, op: Operation) -> bool:
...         return isinstance(op, GateToffoli)
...
...     def decompose_step(self, op, qubits, bits, zvars):
...         from mimiqcircuits import Circuit
...         circ = Circuit()
...         # ... decomposition logic ...
...         return circ
abstract matches(op)[source]

Check if this rule can transform the given operation.

Parameters:

op (Operation) – The operation to check.

Returns:

True if this rule can decompose the operation.

Return type:

bool

abstract decompose_step(op, qubits, bits, zvars)[source]

Apply this rule to transform an operation.

This method should only be called if matches() returns True.

Parameters:
  • op (Operation) – The operation to transform.

  • qubits (Sequence[int]) – Qubit indices for the operation.

  • bits (Sequence[int]) – Classical bit indices for the operation.

  • zvars (Sequence[int]) – Z-variable indices for the operation.

Returns:

A Circuit containing the decomposed instructions.

Raises:

DecompositionError – If the operation cannot be decomposed.

Return type:

Circuit

class mimiqcircuits.decomposition.DecompositionBasis[source]

Bases: ABC

Abstract base class for decomposition targets.

A DecompositionBasis combines two concerns:
  1. What is terminal: Operations that should not be decomposed further.

  2. How to decompose: The transformation logic for non-terminal operations.

Subclasses must implement:

Example

>>> class CliffordT(DecompositionBasis):
...     def isterminal(self, op: Operation) -> bool:
...         return isinstance(op, (GateH, GateS, GateT, GateCX))
...
...     def decompose(self, op, qubits, bits, zvars):
...         # Use a rewrite rule to decompose
...         return CanonicalRewrite().decompose_step(op, qubits, bits, zvars)
abstract isterminal(op)[source]

Check if an operation is terminal in this basis.

Terminal operations are not decomposed further - they represent the target instruction set that decomposition reduces circuits to.

Parameters:

op (Operation) – The operation to check.

Returns:

True if the operation is terminal.

Return type:

bool

abstract decompose(op, qubits, bits, zvars)[source]

Decompose a non-terminal operation.

This method should only be called for non-terminal operations (i.e., when isterminal() returns False).

Parameters:
  • op (Operation) – The operation to decompose.

  • qubits (Sequence[int]) – Qubit indices for the operation.

  • bits (Sequence[int]) – Classical bit indices for the operation.

  • zvars (Sequence[int]) – Z-variable indices for the operation.

Returns:

A Circuit containing the decomposed instructions.

Raises:

DecompositionError – If the operation cannot be decomposed.

Return type:

Circuit

exception mimiqcircuits.decomposition.DecompositionError[source]

Bases: Exception

Raised when an operation cannot be decomposed.

class mimiqcircuits.decomposition.CanonicalBasis[source]

Bases: DecompositionBasis

Decomposition basis targeting the canonical primitive gate set.

The canonical basis decomposes all operations to a minimal set of primitives:
  • GateU: Arbitrary single-qubit unitary

  • GateCX: Two-qubit CNOT gate

  • Measure, Reset: Measurement and reset operations

  • Barrier: Scheduling barriers

  • Classical operations, annotations, noise channels

This is the default basis used by decompose().

Example

>>> from mimiqcircuits import Circuit, GateH, GateCCX
>>> from mimiqcircuits.decomposition import decompose, CanonicalBasis
>>> c = Circuit()
>>> c.push(GateH(), 0)
1-qubit circuit with 1 instruction:
└── H @ q[0]

>>> c.push(GateCCX(), 0, 1, 2)
3-qubit circuit with 2 instructions:
├── H @ q[0]
└── C₂X @ q[0:1], q[2]

>>> decomposed = decompose(c, CanonicalBasis())
>>> # All gates are now GateU or GateCX
isterminal(op)[source]

Check if an operation is terminal in the canonical basis.

Terminal operations include:
  • Core gates: GateU, GateCX

  • Measurement/reset: Measure, Reset and variants

  • Control flow: Barrier, IfStatement (if inner op is terminal)

  • Classical operations, annotations

  • Noise channels, expectation values

  • Z-register operations: Add, Multiply, Pow

  • Delay (timing information)

Parameters:

op (Operation) – The operation to check.

Returns:

True if the operation is terminal.

Return type:

bool

decompose(op, qubits, bits, zvars)[source]

Decompose a non-terminal operation using canonical rewrite.

Parameters:
  • op (Operation) – The operation to decompose.

  • qubits (Sequence[int]) – Qubit indices for the operation.

  • bits (Sequence[int]) – Classical bit indices for the operation.

  • zvars (Sequence[int]) – Z-variable indices for the operation.

Returns:

A Circuit containing the decomposed instructions.

Raises:

DecompositionError – If the operation cannot be decomposed.

Return type:

Circuit

class mimiqcircuits.decomposition.CliffordTBasis(sk_depth=None, rotations=None)[source]

Bases: DecompositionBasis

Decomposition basis targeting the Clifford+T gate set.

Clifford+T is the standard universal gate set of error-corrected quantum computing. The terminal operations of this basis are those of StimBasis plus GateT and GateTDG, so the two bases always keep the same Clifford and stabilizer operations:

  • single-qubit Cliffords: GateID, GateX, GateY, GateZ, GateH, GateHXY, GateHYZ, GateS, GateSDG, GateSX, GateSXDG, GateSY, GateSYDG;

  • two-qubit Cliffords: GateCX, GateCY, GateCZ, GateSWAP, GateISWAP, GateISWAPDG, GateDCX, GateECR;

  • Pauli rotations by +-pi/2: GateRXX, GateRYY, GateRZZ, and RPauli with a non-identity Pauli string;

  • the non-Clifford GateT and GateTDG;

  • measurements, resets and measure-and-resets in the X, Y and Z bases, MeasureXX, MeasureYY, MeasureZZ and MeasurePauli;

  • Pauli noise: PauliX, PauliY, PauliZ, Depolarizing1, Depolarizing2 and PauliNoise;

  • barriers and annotations;

  • classical operations, which StimBasis does not keep;

  • an IfStatement whose body is terminal in this basis, with any condition. StimBasis only keeps a conditional GateX, GateY or GateZ on a single bit.

Non-terminal operations are rewritten by the first rule that matches, in this order:

  1. SpecialAngleRewrite: rotations by multiples of pi/4 to exact Clifford+T.

  2. ToffoliToCliffordTRewrite: GateCCX to Clifford+T with 7 T gates.

  3. A GateU whose angles are all zero (a pure global phase) is dropped.

  4. ZYZRewrite: GateU to RZ*RY*RZ.

  5. ToZRotationRewrite: GateRX, GateRY to GateRZ and Cliffords.

  6. The rotations rule (default GridSynthRewrite): remaining GateRZ to an approximate Clifford+T sequence.

  7. CanonicalRewrite: any other operation, toward GateU and GateCX.

The rotations rule only sees angles that step 1 could not decompose exactly. The body of an IfStatement goes through the same pipeline and stays conditional. No T-count optimization is performed on the result.

Parameters:
  • sk_depth (int | None) – Use SolovayKitaevRewrite with this recursion depth as the rotations rule.

  • rotations (RewriteRule) – Rule that synthesizes the remaining Z-rotations. The default GridSynthRewrite() approximates each rotation to operator-norm error 1e-10. The rule must match GateRZ with a numeric angle and emit only gates of this basis.

Raises:

ValueError – If both sk_depth and rotations are given, or if rotations does not match GateRZ.

Decomposing an operation that no rule handles, such as a noise channel other than the Pauli ones above, raises a DecompositionError.

Example

>>> from mimiqcircuits import Circuit, GateH, GateT, GateRZ
>>> from mimiqcircuits.decomposition import decompose, CliffordTBasis
>>> from mimiqcircuits.decomposition import GridSynthRewrite
>>> from symengine import pi
>>> c = Circuit()
>>> c.push(GateRZ(pi/4), 0)  # special angle, decomposed exactly
1-qubit circuit with 1 instruction:
└── RZ((1/4)*pi) @ q[0]

>>> decomposed = decompose(c, CliffordTBasis())
>>> # An arbitrary angle is approximated to 1e-10 by gridsynth
>>> c2 = Circuit()
>>> c2.push(GateRZ(0.123), 0)
1-qubit circuit with 1 instruction:
└── RZ(0.123) @ q[0]

>>> decomposed2 = decompose(c2, CliffordTBasis())
>>> # A looser error bound per rotation gives fewer T gates
>>> basis = CliffordTBasis(rotations=GridSynthRewrite(epsilon=1e-6))
>>> decomposed3 = decompose(c2, basis)
>>> # Solovay-Kitaev instead of gridsynth
>>> decomposed4 = decompose(c2, CliffordTBasis(sk_depth=0))
rotations: RewriteRule
__init__(sk_depth=None, rotations=None)[source]
isterminal(op)[source]

Check if an operation is terminal in the Clifford+T basis.

Parameters:

op (Operation) – The operation to check.

Returns:

True if the operation is terminal in StimBasis, is GateT, GateTDG or a classical operation, or is an IfStatement whose body is terminal in this basis.

Return type:

bool

decompose(op, qubits, bits, zvars)[source]

Decompose a non-terminal operation toward Clifford+T.

Applies the rules listed in the class docstring, in that order.

Parameters:
  • op (Operation) – The operation to decompose.

  • qubits (Sequence[int]) – Qubit indices for the operation.

  • bits (Sequence[int]) – Classical bit indices for the operation.

  • zvars (Sequence[int]) – Z-variable indices for the operation.

Returns:

A Circuit containing the decomposed instructions.

Raises:

DecompositionError – If the operation cannot be decomposed.

Return type:

Circuit

class mimiqcircuits.decomposition.FlattenedBasis[source]

Bases: DecompositionBasis

Flatten container operations while leaving all other operations untouched.

isterminal(op)[source]

Containers are non-terminal; all other operations are terminal.

decompose(op, qubits, bits, zvars)[source]

Decompose one container expansion step using FlattenContainers.

class mimiqcircuits.decomposition.QASMBasis[source]

Bases: DecompositionBasis

Decomposition basis targeting the OpenQASM 2.0 gate library.

The terminal gates are the OpenQASM 2.0 built-ins and the gates of the qelib1.inc include file that have a direct MIMIQ counterpart, so a decomposed circuit can be written as OpenQASM 2.0 and read back by any parser that ships the standard qelib1.inc.

Terminal Operations

Fundamental gates (OpenQASM 2.0 built-in):
  • GateU, GateCX

Single-qubit gates from qelib1.inc:
  • Legacy U gates: GateU3, GateU2, GateU1

  • Pauli: GateID, GateX, GateY, GateZ

  • Hadamard: GateH

  • Phase: GateS, GateSDG, GateT, GateTDG

  • Rotations: GateRX, GateRY, GateRZ

Two-qubit gates from qelib1.inc:
  • Controlled Paulis and Hadamard: GateCY, GateCZ, GateCH

  • Controlled rotations: GateCRX, GateCRY, GateCRZ

  • GateSWAP

  • Ising couplings: GateRXX, GateRZZ

Three- and four-qubit gates from qelib1.inc:
  • GateCCX, GateCSWAP, GateC3X

Non-unitary operations:
  • Measure, Reset, Barrier

  • IfStatement whose inner operation is terminal

Custom gates:
  • GateCall whose declaration contains only terminal operations

Every other operation is decomposed through CanonicalRewrite. Operations that OpenQASM 2.0 cannot express and that have no decomposition into the gates above (noise channels, classical operations, annotations, Z-register operations, …) raise DecompositionError.

MeasureReset decomposes into Measure followed by Reset. This leaves the same state and bit as its canonical decomposition, a measurement and an X conditioned on its outcome, and unlike that conditional it can be written for a circuit of any number of bits. OpenQASM 2.0 has no loops, so WhileStatement raises DecompositionError.

Example

>>> from mimiqcircuits import Circuit, GateH, GateCCX
>>> from mimiqcircuits.decomposition import decompose, QASMBasis
>>> c = Circuit()
>>> c.push(GateH(), 0)
1-qubit circuit with 1 instruction:
└── H @ q[0]

>>> c.push(GateCCX(), 0, 1, 2)
3-qubit circuit with 2 instructions:
├── H @ q[0]
└── C₂X @ q[0:1], q[2]

>>> # Decompose to QASM-compatible gates
>>> qasm_circuit = decompose(c, QASMBasis())
isterminal(op)[source]

Check if an operation is terminal in the OpenQASM 2.0 basis.

Parameters:

op (Operation) – The operation to check.

Returns:

True if the operation is in the QASM gate set.

Return type:

bool

decompose(op, qubits, bits, zvars)[source]

Decompose a non-terminal operation using canonical rewrite.

Parameters:
  • op (Operation) – The operation to decompose.

  • qubits (Sequence[int]) – Qubit indices for the operation.

  • bits (Sequence[int]) – Classical bit indices for the operation.

  • zvars (Sequence[int]) – Z-variable indices for the operation.

Returns:

A Circuit containing the decomposed instructions.

Raises:

DecompositionError – If the operation cannot be decomposed.

Return type:

Circuit

class mimiqcircuits.decomposition.RuleBasis(rule)[source]

Bases: DecompositionBasis

A decomposition basis that recursively applies a single rewrite rule.

This allows any RewriteRule to be used directly as a decomposition basis. The rule is applied recursively until no more operations match.

An operation is terminal if the rule does not match it.

Parameters:

rule (RewriteRule) – The rewrite rule to apply.

Example

>>> from mimiqcircuits import Circuit, GateH
>>> from mimiqcircuits.decomposition import decompose, RuleBasis, ZYZRewrite
>>> c = Circuit()
>>> c.push(GateH(), 0)
1-qubit circuit with 1 instruction:
└── H @ q[0]

>>> # Decompose using ZYZ rewrite rule
>>> decomposed = decompose(c, RuleBasis(ZYZRewrite()))
rule: RewriteRule
isterminal(op)[source]

Check if the operation is terminal (rule does not match).

Parameters:

op (Operation) – The operation to check.

Returns:

True if the rule does not match this operation.

Return type:

bool

decompose(op, qubits, bits, zvars)[source]

Apply the rewrite rule to decompose the operation.

Parameters:
  • op (Operation) – The operation to decompose.

  • qubits (Sequence[int]) – Qubit indices for the operation.

  • bits (Sequence[int]) – Classical bit indices for the operation.

  • zvars (Sequence[int]) – Z-variable indices for the operation.

Returns:

A Circuit containing the decomposed instructions.

Return type:

Circuit

__init__(rule)
class mimiqcircuits.decomposition.StimBasis[source]

Bases: DecompositionBasis

Decomposition basis targeting the Stim stabilizer simulator gate set.

Stim is a high-performance stabilizer circuit simulator by Craig Gidney. It supports only Clifford gates, stabilizer operations, noise channels, and quantum error correction annotations.

Decomposition Pipeline

The decomposition follows this priority order:

  1. A GateU whose angles are all zero (a pure global phase) is dropped.

  2. ZYZRewrite: GateU -> RZ * RY * RZ

  3. SpecialAngleRewrite (Clifford-only): Rotations at k*pi/2 -> Clifford gates

  4. CanonicalRewrite: Fallback for other gates

Non-Clifford rotations (angles not multiples of pi/2) will raise a DecompositionError.

Terminal Operations

Single-qubit Clifford gates:
  • GateID

  • Pauli: GateX, GateY, GateZ

  • Hadamard: GateH, GateHXY, GateHYZ

  • Phase gates: GateS, GateSDG

  • Square root gates: GateSX, GateSXDG, GateSY, GateSYDG

Two-qubit Clifford gates:
  • Controlled Pauli: GateCX, GateCY, GateCZ

  • Swap family: GateSWAP, GateISWAP, ISWAP†

  • GateDCX, GateECR

Pauli rotations by +-pi/2:
  • GateRXX, GateRYY, GateRZZ with angle +-pi/2

  • RPauli with angle +-pi/2 and a non-identity Pauli string

Angles are compared modulo 2*pi, since a rotation by 3*pi/2 equals a rotation by -pi/2 up to a global phase. Other angles are decomposed.

Feedback:
  • IfStatement of GateX, GateY or GateZ conditioned on a single bit being 1. The same gate conditioned on the bit being 0 is decomposed into the gate followed by the gate conditioned on 1. Any other conditional operation has its body decomposed while staying conditional, and raises a DecompositionError if that does not lead to a conditional Pauli.

Measurements (all Pauli bases):
  • Measure, MeasureX, MeasureY

  • Two-qubit parities: MeasureXX, MeasureYY, MeasureZZ

  • Pauli products: MeasurePauli

Resets (all Pauli bases):
  • Reset, ResetX, ResetY

Measure-and-reset (all Pauli bases):
  • MeasureReset, MeasureResetX, MeasureResetY

Noise channels:
  • PauliX, PauliY, PauliZ

  • Depolarizing1, Depolarizing2

  • PauliNoise

QEC annotations:
  • Detector, ObservableInclude, QubitCoordinates

  • Barrier

Stim Gate Mapping

MIMIQ Operation

Stim Operation

GateID

I

GateX

X

GateY

Y

GateZ

Z

GateH

H

GateHXY

H_XY

GateHYZ

H_YZ

GateS

S

GateSDG

S_DAG

GateSX

SQRT_X

GateSXDG

SQRT_X_DAG

GateSY

SQRT_Y

GateSYDG

SQRT_Y_DAG

GateCX

CNOT / CX

GateCY

CY

GateCZ

CZ

GateSWAP

SWAP

GateISWAP

ISWAP

ISWAP†

ISWAP_DAG

GateDCX

DCX (custom)

GateECR

ECR (custom)

GateRXX(+-pi/2)

SQRT_XX / SQRT_XX_DAG

GateRYY(+-pi/2)

SQRT_YY / SQRT_YY_DAG

GateRZZ(+-pi/2)

SQRT_ZZ / SQRT_ZZ_DAG

RPauli(P, +-pi/2)

SPP / SPP_DAG

IfStatement(GateX(), 1)

CX rec[-k] q

IfStatement(GateY(), 1)

CY rec[-k] q

IfStatement(GateZ(), 1)

CZ rec[-k] q

Measure

M

MeasureX

MX

MeasureY

MY

MeasureXX

MXX

MeasureYY

MYY

MeasureZZ

MZZ

MeasurePauli

MPP

Reset

R

ResetX

RX

ResetY

RY

MeasureReset

MR

MeasureResetX

MRX

MeasureResetY

MRY

PauliX

X_ERROR

PauliY

Y_ERROR

PauliZ

Z_ERROR

Depolarizing1

DEPOLARIZE1

Depolarizing2

DEPOLARIZE2

PauliNoise

PAULI_CHANNEL_1, PAULI_CHANNEL_2 or E / ELSE_CORRELATED_ERROR

Detector

DETECTOR

ObservableInclude

OBSERVABLE_INCLUDE

QubitCoordinates

QUBIT_COORDS

Example

>>> from mimiqcircuits import Circuit, GateH, GateS, GateCX
>>> from mimiqcircuits.decomposition import decompose, StimBasis
>>> from symengine import pi
>>> # Decompose Clifford circuit to Stim-compatible gates
>>> c = Circuit()
>>> c.push(GateH(), 0)
1-qubit circuit with 1 instruction:
└── H @ q[0]

>>> c.push(GateS(), 0)
1-qubit circuit with 2 instructions:
├── H @ q[0]
└── S @ q[0]

>>> c.push(GateCX(), 0, 1)
2-qubit circuit with 3 instructions:
├── H @ q[0]
├── S @ q[0]
└── CX @ q[0], q[1]

>>> decomposed = decompose(c, StimBasis())
>>> # RZ(pi/2) = S is Clifford, so this works
>>> from mimiqcircuits import GateRZ
>>> c2 = Circuit()
>>> c2.push(GateRZ(pi/2), 0)
1-qubit circuit with 1 instruction:
└── RZ((1/2)*pi) @ q[0]

>>> decomposed2 = decompose(c2, StimBasis())

Note

Non-Clifford rotations will raise a DecompositionError::
>>> from mimiqcircuits import *
>>> c3 = Circuit()
>>> c3.push(GateRZ(pi/4), 0)  # T gate is non-Clifford
1-qubit circuit with 1 instruction:
└── RZ((1/4)*pi) @ q[0]

>>> decompose(c3, StimBasis())  # raises DecompositionError doctest: +ELLIPSIS
Traceback (most recent call last):
...
mimiqcircuits.decomposition.abstract.DecompositionError: Rotation RZ cannot be decomposed into the Stim basis. Only rotations with angles that are multiples of pi/2 are Clifford gates.
isterminal(op)[source]

Check if an operation is terminal in the Stim basis.

Parameters:

op (Operation) – The operation to check.

Returns:

True if the operation is in the Stim Clifford gate set.

Return type:

bool

decompose(op, qubits, bits, zvars)[source]

Decompose a non-terminal operation toward the Stim Clifford gate set.

Applies rules in priority order:
  1. Drops a GateU whose angles are all zero (a global phase)

  2. ZYZRewrite for GateU

  3. SpecialAngleRewrite (Clifford-only) for rotations at k*pi/2

  4. CanonicalRewrite as fallback

Non-Clifford rotations (angles not multiples of pi/2) will raise an error.

Parameters:
  • op (Operation) – The operation to decompose.

  • qubits (Sequence[int]) – Qubit indices for the operation.

  • bits (Sequence[int]) – Classical bit indices for the operation.

  • zvars (Sequence[int]) – Z-variable indices for the operation.

Returns:

A Circuit containing the decomposed instructions.

Raises:

DecompositionError – If the operation cannot be decomposed to Clifford gates.

Return type:

Circuit

class mimiqcircuits.decomposition.CanonicalRewrite[source]

Bases: RewriteRule

Rewrite rule that decomposes operations using their built-in decomposition.

This rule delegates to each operation’s _decompose method, which typically reduces gates to a minimal set of primitives (GateU, GateCX, etc.).

The canonical decomposition is the default decomposition defined by each gate class. Most gates decompose down to GateU (arbitrary single-qubit) and GateCX (two-qubit entangling gate).

Example

>>> from mimiqcircuits import GateH
>>> from mimiqcircuits.decomposition import CanonicalRewrite
>>> rule = CanonicalRewrite()
>>> rule.matches(GateH())
True
>>> circ = rule.decompose_step(GateH(), [0], [], [])
>>> print(circ)
1-qubit circuit with 1 instruction:
└── U((1/2)*pi, 0, pi, 0.0) @ q[0]
matches(op)[source]

Check if this rule can decompose the operation.

Matches every operation that has a canonical decomposition, that is whose supports_canonical_rewrite() is true. The answer does not depend on any basis: an operation that CanonicalBasis keeps terminal (such as ResetX) still matches, so other bases can expand it. Primitives without a decomposition (GateU, GateCX, Measure, Reset, Barrier, …) do not match.

Parameters:

op (Operation) – The operation to check.

Returns:

True if the operation should be rewritten by canonical decomposition.

Return type:

bool

decompose_step(op, qubits, bits, zvars)[source]

Decompose the operation using its built-in decomposition.

Parameters:
  • op (Operation) – The operation to decompose.

  • qubits (Sequence[int]) – Qubit indices for the operation.

  • bits (Sequence[int]) – Classical bit indices for the operation.

  • zvars (Sequence[int]) – Z-variable indices for the operation.

Returns:

A Circuit containing the decomposed instructions.

Return type:

Circuit

class mimiqcircuits.decomposition.FlattenContainers[source]

Bases: RewriteRule

Flatten container operations into their constituent instructions.

This rule expands: - GateCall - Block - Inverse(GateCall) - Inverse(Block) (if present)

The rule does not perform gate-level decomposition; it only expands container structure. Decomposition behavior is delegated to CanonicalRewrite.

matches(op)[source]

Check if an operation is a flattenable container.

decompose_step(op, qubits, bits, zvars)[source]

Flatten one container operation.

class mimiqcircuits.decomposition.ZYZRewrite[source]

Bases: RewriteRule

Rewrite rule for ZYZ Euler angle decomposition.

Decomposes single-qubit unitaries into the ZYZ Euler angle form: RZ(phi) * RY(theta) * RZ(lambda)

This is a standard decomposition for arbitrary single-qubit gates, producing only Z and Y rotations (plus a global phase for GateU).

Supported operations:
  • GateU(theta, phi, lambda, gamma) -> RZ(lambda) * RY(theta) * RZ(phi) * Phase(gamma)

  • GateRX(theta) -> S * RY(theta) * Sdg

Identity rotations (angle = 0) are omitted from the output.

Example

>>> from mimiqcircuits import GateU
>>> from mimiqcircuits.decomposition import ZYZRewrite
>>> from symengine import pi
>>> rule = ZYZRewrite()
>>> circ = rule.decompose_step(GateU(pi/2, pi/4, pi/3), [0], [], [])
matches(op)[source]

Check if this rule can decompose the operation.

Matches GateU (unless it’s identity) and GateRX.

Parameters:

op (Operation) – The operation to check.

Returns:

True if this rule can decompose the operation.

Return type:

bool

decompose_step(op, qubits, bits, zvars)[source]

Apply ZYZ decomposition to the operation.

Parameters:
  • op (Operation) – The operation to decompose (GateU or GateRX).

  • qubits (Sequence[int]) – Qubit indices for the operation.

  • bits (Sequence[int]) – Classical bit indices (unused).

  • zvars (Sequence[int]) – Z-variable indices (unused).

Returns:

A Circuit containing the decomposed instructions.

Raises:

DecompositionError – If the operation is not supported.

Return type:

Circuit

class mimiqcircuits.decomposition.SpecialAngleRewrite(only_cliffords=False)[source]

Bases: RewriteRule

Rewrite rule for decomposing rotations with special angles to Clifford+T.

Decomposes single-qubit rotations whose angle is a multiple of pi/4 into explicit Clifford or Clifford+T gates.

This rule only matches rotations with angles k*pi/4 (or k*pi/2 in Clifford-only mode). Generic rotations with arbitrary angles are not matched.

Parameters:

only_cliffords (bool) – If True, only match angles k*pi/2 (Clifford gates only). If False (default), match angles k*pi/4 (Clifford+T).

Supported operations:
  • GateRX(theta) where theta = k*pi/4

  • GateRY(theta) where theta = k*pi/4

  • GateRZ(lambda) where lambda = k*pi/4

Example

>>> from mimiqcircuits import GateRZ
>>> from mimiqcircuits.decomposition import SpecialAngleRewrite
>>> from symengine import pi
>>> rule = SpecialAngleRewrite()
>>> rule.matches(GateRZ(pi/4))  # True, decomposes to T
True
>>> rule.matches(GateRZ(0.123))  # False, not a special angle
False
only_cliffords: bool
matches(op)[source]

Check if this rule can decompose the operation.

Parameters:

op (Operation) – The operation to check.

Returns:

True if the operation is a rotation with a special angle.

Return type:

bool

decompose_step(op, qubits, bits, zvars)[source]

Decompose the rotation to Clifford+T gates.

Parameters:
  • op (Operation) – The rotation operation to decompose.

  • qubits (Sequence[int]) – Qubit indices for the operation.

  • bits (Sequence[int]) – Classical bit indices (unused).

  • zvars (Sequence[int]) – Z-variable indices (unused).

Returns:

A Circuit containing the decomposed instructions.

Raises:

DecompositionError – If the operation cannot be decomposed.

Return type:

Circuit

__init__(only_cliffords=False)
class mimiqcircuits.decomposition.ToZRotationRewrite[source]

Bases: RewriteRule

Rewrite rule that converts RX and RY rotations to RZ + Clifford gates.

This is useful for gate sets where only Z-rotations are available as the parametric rotation (e.g., some hardware native gate sets).

Transformations:
  • RX(theta) -> H * RZ(theta) * H

  • RY(theta) -> Sdg * H * RZ(theta) * H * S

Example

>>> from mimiqcircuits import GateRX, GateRY
>>> from mimiqcircuits.decomposition import ToZRotationRewrite
>>> from symengine import pi
>>> rule = ToZRotationRewrite()
>>> circ = rule.decompose_step(GateRX(pi/3), [0], [], [])
matches(op)[source]

Check if this rule can decompose the operation.

Parameters:

op (Operation) – The operation to check.

Returns:

True for GateRX and GateRY operations.

Return type:

bool

decompose_step(op, qubits, bits, zvars)[source]

Convert RX or RY to RZ + Clifford gates.

Parameters:
  • op (Operation) – The rotation operation (GateRX or GateRY).

  • qubits (Sequence[int]) – Qubit indices for the operation.

  • bits (Sequence[int]) – Classical bit indices (unused).

  • zvars (Sequence[int]) – Z-variable indices (unused).

Returns:

A Circuit containing the decomposed instructions.

Raises:

DecompositionError – If the operation is not supported.

Return type:

Circuit

class mimiqcircuits.decomposition.ToffoliToCliffordTRewrite[source]

Bases: RewriteRule

Decompose GateCCX into an explicit Clifford+T sequence.

matches(op)[source]

Return True for Toffoli (CCX) gates.

decompose_step(op, qubits, bits, zvars)[source]

Apply the standard 7-T Toffoli decomposition.

class mimiqcircuits.decomposition.SolovayKitaevRewrite(depth=3, simplify=True, net_max_depth=10, net_max_points=50000, net_min_dist=0.02)[source]

Bases: RewriteRule

Rewrite rule using the Solovay-Kitaev algorithm.

Approximates arbitrary single-qubit unitaries using a sequence of Clifford+T gates. The algorithm recursively refines an initial approximation using group commutators, achieving ε-approximation with O(log^c(1/ε)) gates.

Parameters:
  • depth (int) – Recursion depth (default: 3). Higher = better precision, more gates.

  • simplify (bool) – Whether to simplify the resulting sequence (default: True).

  • net_max_depth (int) – Maximum depth for epsilon-net generation (default: 15).

  • net_max_points (int) – Maximum points in epsilon-net (default: 100,000).

  • net_min_dist (float) – Minimum distance between epsilon-net points (default: 0.01).

Supported operations:
  • GateRZ(λ): Z-rotation by angle λ

  • GateRY(θ): Y-rotation by angle θ

  • GateRX(θ): X-rotation by angle θ

  • GateU(θ, φ, λ, γ): General single-qubit unitary

Symbolic parameters are NOT supported.

Example

>>> from mimiqcircuits import GateRZ
>>> from mimiqcircuits.decomposition import SolovayKitaevRewrite
>>> rule = SolovayKitaevRewrite(depth=0)
>>> circ = rule.decompose_step(GateRZ(0.123), [0], [], [])

Note

The first call may be slow due to epsilon-net generation. Subsequent calls reuse the cached net.

depth: int = 3
simplify: bool = True
net_max_depth: int = 10
net_max_points: int = 50000
net_min_dist: float = 0.02
matches(op)[source]

Check if this rule can decompose the operation.

Matches single-qubit rotations with concrete (non-symbolic) angles.

Parameters:

op (Operation) – The operation to check.

Returns:

True if this rule can decompose the operation.

Return type:

bool

decompose_step(op, qubits, bits, zvars)[source]

Decompose the operation using Solovay-Kitaev.

Parameters:
  • op (Operation) – The operation to decompose.

  • qubits (Sequence[int]) – Qubit indices for the operation.

  • bits (Sequence[int]) – Classical bit indices (unused).

  • zvars (Sequence[int]) – Z-variable indices (unused).

Returns:

A Circuit containing the Clifford+T approximation.

Raises:

DecompositionError – If the operation cannot be decomposed.

Return type:

Circuit

__init__(depth=3, simplify=True, net_max_depth=10, net_max_points=50000, net_min_dist=0.02)
class mimiqcircuits.decomposition.GridSynthRewrite(epsilon=1e-10, seed=0)[source]

Bases: RewriteRule

Rewrite rule that approximates GateRZ with a Clifford+T sequence, using the Ross–Selinger gridsynth algorithm.

For GateRZ(λ) the rule emits a sequence of GateH, GateS, GateSDG, GateZ, GateT and GateX whose unitary V satisfies min_φ ‖RZ(λ) − e^{iφ}V‖ ≤ epsilon in operator norm. The approximation holds up to a global phase, which is dropped. The T-count is about 3·log₂(1/epsilon) and, except for rare failures of the integer factoring step, optimal among ancilla-free Clifford+T circuits with this error. Angles that are multiples of π/4 give their exact short decomposition.

The factoring step is randomized. For a fixed seed, the same angle and tolerance always give the same sequence. Results are cached per angle, since circuits often repeat the same rotation.

The synthesis runs in the compiled mimiqcircuits._core extension without holding the GIL, so several threads can synthesize in parallel.

Parameters:
  • epsilon (float) – Operator-norm tolerance, 1e-100 <= epsilon < 1 (default 1e-10). A rotation takes a few milliseconds at 1e-10 and up to about a second at 1e-100.

  • seed (int) – Seed of the randomized factoring step, an integer in [0, 2**64) (default 0).

Raises:
  • ValueError – If epsilon or seed is out of range.

  • TypeError – If epsilon is not a real number or seed is not an integer.

Supported operations:
  • GateRZ(λ) with a numeric angle λ.

Symbolic angles are not matched. GateRX, GateRY and GateU must first be reduced to Z-rotations, for example with ZYZRewrite and ToZRotationRewrite. CliffordTBasis does this and uses this rule by default.

Example

>>> from mimiqcircuits import GateRZ, GateT
>>> from mimiqcircuits.decomposition import GridSynthRewrite
>>> def tcount(c):
...     return sum(isinstance(i.operation, GateT) for i in c)
>>> # Approximate one rotation to the default tolerance 1e-10
>>> circ = GridSynthRewrite().decompose_step(GateRZ(0.123), [0], [], [])
>>> # A coarser tolerance gives fewer T gates
>>> coarse = GridSynthRewrite(epsilon=1e-4).decompose_step(GateRZ(0.123), [0], [], [])
>>> tcount(coarse) < tcount(circ)
True

References

N. J. Ross and P. Selinger, “Optimal ancilla-free Clifford+T approximation of z-rotations”, Quantum Inf. Comput. 16, 901 (2016), arXiv:1403.2975.

epsilon: float = 1e-10
seed: int = 0
matches(op)[source]

Check if this rule can decompose the operation.

Parameters:

op (Operation) – The operation to check.

Returns:

True if the operation is a GateRZ with a numeric angle.

Return type:

bool

decompose_step(op, qubits, bits, zvars)[source]

Decompose the Z-rotation into Clifford+T gates.

Parameters:
  • op (Operation) – The GateRZ to decompose.

  • qubits (Sequence[int]) – Qubit indices for the operation.

  • bits (Sequence[int]) – Classical bit indices (unused).

  • zvars (Sequence[int]) – Z-variable indices (unused).

Returns:

A Circuit containing the Clifford+T approximation.

Raises:
Return type:

Circuit

__init__(epsilon=1e-10, seed=0)
mimiqcircuits.decomposition.decompose(source, basis=None, wrap=False)[source]

Recursively decompose a circuit to a target basis.

Decomposes all operations in the source until they are terminal in the given basis. This is the main entry point for circuit decomposition.

Parameters:
  • source (Circuit | Instruction | Operation) – The circuit, instruction, or operation to decompose.

  • basis (DecompositionBasis | RewriteRule | None) – The target decomposition basis. If a RewriteRule is provided, it is wrapped in a RuleBasis. Defaults to CanonicalBasis.

  • wrap (bool) – If True, wrap non-terminal ops instead of flattening them into primitives. Gates become a GateCall to a GateDecl, shared between identical gates. Other operations, such as MeasureReset or MeasurePauli, become a Block holding their decomposition, whose gates are wrapped in turn. This produces a circuit with only terminal ops, GateCalls and Blocks.

Returns:

A new Circuit containing only terminal operations (and GateCalls and Blocks if wrap=True).

Return type:

Circuit

Example

>>> from mimiqcircuits import *
>>> from mimiqcircuits.decomposition import decompose, CanonicalBasis
>>> c = Circuit()
>>> c.push(GateH(), 0)
1-qubit circuit with 1 instruction:
└── H @ q[0]

>>> c.push(GateCCX(), 0, 1, 2)
3-qubit circuit with 2 instructions:
├── H @ q[0]
└── C₂X @ q[0:1], q[2]

>>> decomposed = decompose(c)
>>> # decomposed contains only GateU and GateCX

Using a specific basis:

>>> from mimiqcircuits.decomposition import CliffordTBasis
>>> decomposed = decompose(c, CliffordTBasis())

Using a rewrite rule directly:

>>> from mimiqcircuits.decomposition import ZYZRewrite
>>> decomposed = decompose(c, ZYZRewrite())
mimiqcircuits.decomposition.decompose_step(source, rule=None)[source]

Apply a single decomposition step to a circuit.

Unlike decompose(), this function applies the rule only once (non-recursively). Operations that don’t match the rule are left unchanged.

Parameters:
Returns:

A new Circuit with the rule applied once.

Return type:

Circuit

Example

>>> from mimiqcircuits import *
>>> from mimiqcircuits.decomposition import decompose_step, ZYZRewrite
>>> c = Circuit()
>>> c.push(GateH(), 0)
1-qubit circuit with 1 instruction:
└── H @ q[0]

>>> # Apply ZYZ rewrite once (non-recursive)
>>> stepped = decompose_step(c, ZYZRewrite())
mimiqcircuits.decomposition.eachdecomposed(source, basis=None, wrap=False)[source]

Return an iterator over decomposed instructions.

This is memory-efficient for large circuits as it yields instructions one at a time without materializing the full decomposed circuit.

Parameters:
  • source (Circuit | Instruction | Operation) – The circuit, instruction, or operation to decompose.

  • basis (DecompositionBasis | RewriteRule | None) – The target decomposition basis. Defaults to CanonicalBasis.

  • wrap (bool) – If True, wrap non-terminal gates into GateDecl/GateCall and other non-terminal operations into a Block.

Returns:

An iterator yielding decomposed instructions.

Return type:

DecomposeIterator

Example

>>> from mimiqcircuits import Circuit, GateH
>>> from mimiqcircuits.decomposition import eachdecomposed
>>> c = Circuit()
>>> c.push(GateH(), 0)
1-qubit circuit with 1 instruction:
└── H @ q[0]

>>> for inst in eachdecomposed(c):
...     print(inst)
U((1/2)*pi, 0, pi, 0.0) @ q[0]
class mimiqcircuits.decomposition.DecomposeIterator(source, basis, wrap=False, cache=None)[source]

Bases: Iterator[Instruction]

Iterator that yields instructions from recursive decomposition.

Performs depth-first decomposition of a circuit to a target basis. This is memory-efficient for large circuits as it doesn’t materialize the full decomposed circuit at once.

Parameters:
  • source (Circuit | Instruction | Operation) – The source to decompose (Circuit, Instruction, or Operation).

  • basis (DecompositionBasis) – The target decomposition basis.

  • wrap (bool) – If True, wrap non-terminal ops instead of flattening them: gates into GateDecl/GateCall, other operations (such as MeasureReset) into a Block holding their wrapped decomposition.

  • cache (dict | None) – Shared cache dict for wrapped GateDecls (used when wrap=True).

Example

>>> from mimiqcircuits import *
>>> c = Circuit()
>>> c.push(GateH(), 0)
1-qubit circuit with 1 instruction:
└── H @ q[0]

>>> for inst in DecomposeIterator(c, CanonicalBasis()):
...     print(inst)
U((1/2)*pi, 0, pi, 0.0) @ q[0]
__init__(source, basis, wrap=False, cache=None)[source]

Modules

abstract

Abstract base classes for the decomposition framework.

basis

Decomposition bases for quantum circuits.

decompose(source[, basis, wrap])

Recursively decompose a circuit to a target basis.

rules

Rewrite rules for quantum circuit decomposition.