Custom and Qiskit 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 Noisy Circuit Simulation 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
StrongSimParams.gate_mode/WeakSimParams.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 strong-simulation 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, WeakSimParams
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, WeakSimParams(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
StrongSimParams).
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
assert len(gate.generator) == 2
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 |
— |
— |