Custom Gates in YAQS¶
Note
This is a reference guide with static code blocks; it is not executed during the documentation build. Runnable circuit examples are in Circuit Observables and Equivalence Checking.
YAQS represents every digital gate as a
BaseGate instance from
GateLibrary. Circuits enter YAQS
as Qiskit qiskit.circuit.QuantumCircuit objects; the library converts
each DAG operation into an internal gate, then applies that gate during
circuit simulation or equivalence checking.
This page explains that pipeline, how built-in and custom gates are translated, and how to supply an analytic generator when you want long-range two-qubit gates to use the TDVP window path.
How YAQS handles gates end-to-end¶
flowchart TD
qc[QuantumCircuit] --> dag[DAGCircuit]
dag --> convert[convert_dag_to_tensor_algorithm]
convert --> hardcoded[GateLibrary alias]
convert --> fallback[GateLibrary.custom matrix]
hardcoded --> bg[BaseGate]
fallback --> bg
bg --> sim[Simulator / digital_tjm]
bg --> equiv[EquivalenceChecker]
sim --> mps[MPS site updates]
equiv --> mpo[MPO or dense tensor backend]
Internal gate objects¶
Each BaseGate carries:
Field |
Role |
|---|---|
|
Dense unitary as a |
|
Tensor layout used in MPS/MPO contractions (for two-qubit gates: shape |
|
Number of qubits ( |
|
MPS site indices the gate acts on, set via |
|
Optional. For two-qubit built-ins that support TDVP: a list of two |
|
Qiskit operation name or |
Built-in gates (cx, h, rxx, …) are classes on
GateLibrary.
GateLibrary.custom(matrix) returns a generic
BaseGate backed only by the
unitary matrix.
Translation from Qiskit¶
convert_dag_to_tensor_algorithm() walks
a qiskit.dagcircuit.DAGCircuit and produces a list of BaseGate
objects.
For each operation node:
Unsupported instructions raise
ValueError:reset,delay,store, mid-circuitmeasure, classically controlled ops, and control-flow instructions (if_else,for_loop, …).barriernodes are skipped during conversion (ignored for unitary evolution).Known Qiskit names (for example
h,cx,u3,u1,swap,rxx) map to hardcodedGateLibraryclasses viagetattr(GateLibrary, name).Any other one- or two-qubit unitary falls back to the matrix path: Qiskit’s
to_matrix()/qiskit.quantum_info.Operatordata is wrapped asGateLibrary.custom(matrix)with the Qiskitop.namepreserved.
Note
Three-qubit and larger gates (including Toffoli / CCX) are not supported by the Qiskit translation layer. Only one- and two-qubit unitaries are accepted on the matrix fallback path.
Symbolic parameters must be bound before translation; unbound
qiskit.circuit.Parameter objects raise ValueError.
Application during circuit simulation¶
Simulator runs digital circuits through
digital_tjm:
Single-qubit gates — contract the gate tensor onto the corresponding MPS site.
Two-qubit gates — routed by
DigitalSimParams.gate_mode(see Configuring Simulation Parameters):mpo(default) — TEBD/SVD on nearest-neighbor pairs; long-range gates via extended gate MPO.swaps— TEBD with SWAP routing for long-range pairs.tdvp— TEBD on nearest-neighbor pairs; long-range pairs use 2TDVP only when the gate has agenerator; otherwise the MPO path is used.full-tdvp— 2TDVP on every two-qubit gate that has agenerator; generator-less gates fall back to TEBD (NN) or MPO (long-range).
Measurements are handled differently from unitary conversion:
During simulation, terminal
measurenodes are removed from the live DAG so evolution can proceed; sampling uses the remaining circuit structure.During equivalence checking, final measurements are stripped from both circuits before comparison; mid-circuit measurements raise
ValueError.
Plain barrier instructions are dropped in simulation except barriers labelled
SAMPLE_OBSERVABLES (used for digital layer sampling).
Equivalence checking¶
EquivalenceChecker compares two circuits by forming
\(W = U_2^\dagger U_1\) and testing whether \(W\) is identity-like up to global
phase. Custom and QASM-defined gates use the same translation path as
simulation; unknown one- and two-qubit unitaries work via matrix fallback. See
Equivalence Checking for backend choice (representation="mpo"
recommended).
Custom gates from Qiskit (most common)¶
You do not need to register custom gates manually when they come from Qiskit.
UnitaryGate¶
import numpy as np
from qiskit import QuantumCircuit
from qiskit.circuit.library import UnitaryGate
u = np.array([[0, 1], [1, 0]], dtype=complex)
qc = QuantumCircuit(1)
qc.append(UnitaryGate(u), [0])
YAQS translates this to a matrix-backed BaseGate named "unitary" with no
generator attribute.
OpenQASM 2 custom gates¶
OpenQASM 2 lets you declare reusable gate bodies (fixed or parameterized) and
call them like built-in instructions. Pass a file path or raw OpenQASM string
directly to check() or
run(), or load with qiskit.qasm2.loads / load
first. Qiskit produces a qiskit.circuit.QuantumCircuit whose operations
retain the user-defined gate names.
YAQS does not maintain a separate registry of QASM gate definitions. Each DAG
node is translated by name: if the name matches a
GateLibrary entry, the hardcoded
class is used; otherwise, for one- and two-qubit gates, YAQS builds a
matrix-backed gate from Qiskit’s unitary representation (matrix fallback). You
do not need to inline or transpile custom gates to a fixed basis set before
simulation or equivalence checking.
from mqt.yaqs import EquivalenceChecker, Simulator, State, DigitalSimParams
qasm = """
OPENQASM 2.0;
include "qelib1.inc";
gate entangle a,b {
h a;
cx a,b;
}
qreg q[2];
entangle q[0], q[1];
"""
checker = EquivalenceChecker(representation="mpo")
checker.check(qasm, qasm)
state = State(2, initial="zeros")
Simulator(show_progress=False).run(state, qasm, DigitalSimParams(shots=128, max_bond_dim=4))
The same rules apply to legacy or backend-specific Qiskit gate names that
are not in the hardcoded alias list: if Qiskit can supply a unitary matrix and
the gate acts on at most two qubits, translation succeeds via matrix fallback.
Names that already have
GateLibrary aliases (including
common single-qubit parameterizations) continue to use the built-in
implementations.
TDVP behaviour for matrix-backed custom gates¶
Matrix-backed custom gates have no analytic generator. In gate_mode="tdvp"
or "full-tdvp":
Gate width |
Routing |
|---|---|
Nearest-neighbor (|i − j| = 1) |
TEBD/SVD |
Long-range (|i − j| > 1) |
Extended gate MPO (same as |
This is intentional: 2TDVP requires a split local generator; a bare unitary
matrix does not provide one. Use gate_mode="mpo" if you want consistent
long-range handling for all custom unitaries without defining generators.
Manually defining custom gates¶
Use this path for library code, tests, or workflows that construct gates directly in YAQS (without Qiskit).
Matrix-only custom gate¶
import numpy as np
from mqt.yaqs.core.libraries.gate_library import GateLibrary
unitary = np.eye(4, dtype=complex) # example 2-qubit unitary
gate = GateLibrary.custom(unitary)
gate.name = "my_gate"
gate.set_sites(0, 1)
# gate.matrix, gate.tensor, gate.sites are ready for TEBD/MPO application
GateLibrary.custom validates that the matrix is square with dimension \(2^n\).
For equivalence checking, comparing two circuits that use the same custom unitary (or a custom gate versus an equivalent decomposition) works the same as for built-in gates.
Subclassing BaseGate¶
Built-in gates subclass BaseGate
and often override set_sites to set tensor, optional mpo_tensors, and—for
TDVP-capable two-qubit gates—generator. See
CX in
gate_library for a reference implementation.
Generators and the TDVP path¶
Some two-qubit GateLibrary gates
(cx, cz, cp, rxx, ryy, rzz, …) define a generator: a list of
two 2×2 complex matrices [G_a, G_b], one per qubit, ordered by
increasing site index after set_sites.
construct_generator_mpo() embeds these local
operators on the gate support and places identity 2×2 blocks elsewhere,
producing an MPO that represents the sum generator on the full chain.
apply_two_qubit_gate_tdvp() then runs
two-site TDVP (tdvp_mode="2site") on a window around the gate for a total
evolution time of 1 (split across tdvp_sweeps substeps on
DigitalSimParams).
The controlled-NOT gate illustrates the pattern (see source for exact matrices):
from mqt.yaqs.core.libraries.gate_library import GateLibrary
gate = GateLibrary.cx()
gate.set_sites(0, 1)
# gate.generator is a list of two 2x2 arrays, set inside set_sites
Routing checks getattr(gate, "generator", None) is not None in
apply_two_qubit_gate(). Plain
GateLibrary.custom(...) does not set generator; TDVP long-range
application therefore does not activate unless you add one yourself.
Attaching a generator to a custom gate¶
Advanced use: if you know a two-local generator decomposition compatible with
YAQS digital TDVP, you can assign it after set_sites:
import numpy as np
from mqt.yaqs.core.libraries.gate_library import GateLibrary
unitary = ... # 4x4 unitary on qubits (i, j), i < j
G_i = ... # 2x2 local operator on site i
G_j = ... # 2x2 local operator on site j
gate = GateLibrary.custom(unitary)
gate.set_sites(i, j)
gate.generator = [G_i, G_j]
Warning
YAQS does not verify that exp(-i (G_i ⊗ I + I ⊗ G_j)) at evolution time 1
reproduces unitary. Deriving consistent local generators is the caller’s
responsibility; use built-in gates as templates. Reversed qubit order is handled
internally when building the generator MPO, but the local matrices must match
the increasing site index convention used in
construct_generator_mpo().
Generators apply only to two-qubit digital gates. Single-qubit custom gates always use direct MPS contraction; there is no single-qubit TDVP gate path in circuit simulation.
Quick reference¶
Source |
|
TDVP long-range ( |
Equivalence check |
|---|---|---|---|
Built-in |
Yes (in |
2TDVP window |
Supported |
Qiskit |
No |
MPO fallback |
Supported (matrix fallback) |
|
No (unless you set it) |
MPO fallback |
Supported |
|
Yes (if you set it) |
2TDVP window |
Supported |
3+ qubit Qiskit gates |
— |
Not translated |
Not supported |
Rejected Qiskit instructions (translation)¶
Instruction |
Conversion |
Simulation |
Equivalence |
|---|---|---|---|
|
Skipped |
Removed (except |
Skipped in MPO zones |
Final |
Rejected in raw conversion |
Removed from DAG |
Stripped before check |
Mid-circuit |
Rejected |
Removed only if per-qubit terminal |
|
|
Rejected |
— |
— |