Gates
Unitary and non-unitary (measurement/projection) single- and two-site operations, plus the spin-sector projector/measurement machinery for AKLT- style protocols.
QuantumCircuitsMPS.AbstractGate — Type
AbstractGateAbstract base type for all quantum gates (unitaries, projections, and measurement-like operations).
A gate type plugs into the backends through a small protocol:
support(gate) -> Int: number of sites the gate acts on (required).build_operator(gate, site_or_sites, local_dim; kwargs...) -> ITensor: operator construction for the MPS backend.gate_matrix(gate) -> Matrix{ComplexF64}: dense matrix for the state-vector backend (random gates usegate_matrix(gate, rng; local_dim)).needs_normalization(gate) -> Bool(trait, defaultfalse):truefor non-unitary gates (e.g.Projection) — backends renormalize (and, for MPS, truncate) after applying such a gate.is_measurement(gate) -> Bool(trait):truefor gates that Born-sample via the:born_measurementRNG stream (e.g.Measure,Reset).- Gates with non-operator semantics (Born sampling + classical feedback) override
execute!(state, gate, region)instead — seeMeasureinsrc/Core/apply.jl.
The Clifford backend supports only Clifford-group gates, via per-gate _apply_single! methods; any other gate is rejected with an ArgumentError (see src/Clifford/Clifford.jl).
QuantumCircuitsMPS.PauliX — Type
Pauli X gate (NOT gate, bit flip).
QuantumCircuitsMPS.PauliY — Type
Pauli Y gate.
QuantumCircuitsMPS.PauliZ — Type
Pauli Z gate (phase flip).
QuantumCircuitsMPS.Hadamard — Type
Hadamard()Single-qubit Hadamard gate, H = (X + Z)/√2:
(1/√2) [ 1 1 ]
[ 1 -1 ]Satisfies H² = I.
QuantumCircuitsMPS.PhaseGate — Type
Phase gate (S gate, √Z), diag(1, i).
QuantumCircuitsMPS.Rx — Type
Rx(θ)Single-qubit rotation about the X axis, convention Rx(θ) = exp(-iθX/2):
[ cos(θ/2) -i·sin(θ/2) ]
[ -i·sin(θ/2) cos(θ/2) ]QuantumCircuitsMPS.Ry — Type
Ry(θ)Single-qubit rotation about the Y axis, convention Ry(θ) = exp(-iθY/2):
[ cos(θ/2) -sin(θ/2) ]
[ sin(θ/2) cos(θ/2) ]QuantumCircuitsMPS.Rz — Type
Rz(θ)Single-qubit rotation about the Z axis, convention Rz(θ) = exp(-iθZ/2):
[ exp(-iθ/2) 0 ]
[ 0 exp(iθ/2) ]QuantumCircuitsMPS.CZ — Type
CZControlled-Z gate. Symmetric under qubit exchange. |00⟩→|00⟩, |01⟩→|01⟩, |10⟩→|10⟩, |11⟩→-|11⟩
QuantumCircuitsMPS.CNOT — Type
CNOTControlled-NOT gate. Control = site 1, target = site 2. |00⟩→|00⟩, |01⟩→|01⟩, |10⟩→|11⟩, |11⟩→|10⟩
QuantumCircuitsMPS.SWAP — Type
SWAPSwap gate. Exchanges the states of the two sites. |00⟩→|00⟩, |01⟩→|10⟩, |10⟩→|01⟩, |11⟩→|11⟩
QuantumCircuitsMPS.HaarRandom — Type
HaarRandom(n::Int=2)n-site Haar random unitary gate (default: 2-site).
Each application draws a fresh Haar-random unitary of size d^n × d^n (d = local dimension) via QR decomposition of a complex Ginibre matrix, consuming from the :gates_realization RNG stream.
RNG contract: for n = 2 the consumption pattern and produced matrices are bit-identical to the historical two-site implementation (exact CT.jl U() algorithm) — golden regressions depend on this.
QuantumCircuitsMPS.RandomClifford — Type
RandomClifford(n::Int=2)n-site Haar-random Clifford group element gate (default: 2-site).
Each application draws a fresh Haar-random element of the n-qubit Clifford group (via QuantumClifford.random_clifford) and exposes it as a dense d^n × d^n unitary matrix (d = local dimension), consuming from the :gates_realization RNG stream — exactly like HaarRandom, but restricted to the (finite) Clifford group instead of the full continuous unitary group.
This gate is the MPS/state-vector-backend-facing dense-matrix representation of a random Clifford operator. The Clifford backend itself samples and applies random Clifford operators directly on its tableau representation (native QuantumClifford.apply!/random_clifford), never going through this dense matrix path.
QuantumCircuitsMPS.GaussianHaar — Type
GaussianHaar()2-site fermionic Gaussian unitary gate: a Haar-random orthogonal rotation O ∈ SO(4) acting on the 4 Majorana operators of the two sites (Majorana indices (2i-1, 2i, 2i+1, 2i+2) for adjacent sites i, i+1 — see QuantumCircuitsMPS.majorana_indices), drawn from the :gates_realization RNG stream via QuantumCircuitsMPS.haar_orthogonal.
This is the fermionic-Gaussian analog of a 2-qubit Haar-random unitary (HaarRandom(2)), restricted to the Gaussian (free-fermion) subgroup that preserves the Majorana-covariance-matrix (Γ) representation of the state — see Jian, Bauer, Fisher (and related free-fermion measurement-induced-phase- transition literature) for the covariance-matrix formalism this gate acts on.
Only supported on backend=:gaussian: GaussianHaar has no dense gate_matrix/build_operator representation (the Gaussian backend never materializes dense unitaries), so applying it on the MPS or state-vector backends throws an ArgumentError.
QuantumCircuitsMPS.BondParity — Type
BondParity()Projective measurement of the bond parity iγ_{2i}γ_{2i+1} between two adjacent sites (i, i+1) — the parity operator built from the "inner" pair of Majoranas straddling the bond (site i's second Majorana and site i+1's first Majorana, in the QuantumCircuitsMPS.majorana_indices convention). This is the natural 2-site projective measurement in the Majorana-covariance-matrix (Γ) formalism used by the Gaussian backend (see QuantumCircuitsMPS.parity_projection_upsilon).
Like Measure, BondParity Born-samples via the :born_measurement RNG stream (is_measurement(::BondParity) = true) and collapses the state.
Only supported on backend=:gaussian: BondParity has no dense gate_matrix/build_operator representation, so applying it on the MPS or state-vector backends throws an ArgumentError.
QuantumCircuitsMPS.MatrixGate — Type
MatrixGate(U::AbstractMatrix)
MatrixGate(U::AbstractMatrix; d::Int)Gate defined by an explicit d^n × d^n matrix U acting on n sites of local dimension d.
Size-inference rule (API contract)
When d is NOT given, the number of sites n and local dimension d are inferred from the matrix size N:
N = 2^n(n ≥ 1) →n-site qubit gate (d = 2)N = 3^n(n ≥ 2) →n-site spin-1 gate (d = 3)- anything else →
ArgumentError
The two families are disjoint (powers of 2 are even, powers of 3 are odd), so inference is unambiguous. Single-site spin-1 matrices (3×3) are NOT accepted by inference: v0.1 introduced no new single-site qudit machinery. The inferred d is validated against the state's local dimension at apply time.
Explicit local dimension (d keyword)
Inference cannot cover every local dimension: e.g. a 16×16 matrix could be a 4-qubit gate (2⁴) or a two-site spin-3/2 gate (4²). Passing d resolves the ambiguity and enables any d ≥ 2 (arbitrary spin-S / qudit sites): MatrixGate(U; d=4) with a 16×16 U is a two-site gate on d=4 (spin-3/2) sites. The matrix size must be an exact power of d (ArgumentError otherwise). MatrixGate(U; d=2) / d=3 agree with inference for the sizes inference accepts (and additionally allow a single-site 3×3 with d=3).
Matrix convention
U[out, in] = ⟨out|U|in⟩ with standard Kronecker ordering: U = kron(A, B) acts with A on the FIRST site of the region and B on the second, i.e. basis states |b₁ b₂ … bₙ⟩ are ordered with the LAST site as the fastest digit.
Notes
MatrixGateassumesUis unitary: NO normalization is applied after application (matching all other unitary gates).- To apply the same single/multi-site unitary at many places, combine with a broadcast geometry; for one specific region use
Sites(collection):apply!(state, MatrixGate(U), Sites(1:2)).
Example
X = [0 1; 1 0]
apply!(state, MatrixGate(kron(X, [1 0; 0 1])), Sites(1:2)) # X on site 1QuantumCircuitsMPS.ProductGate — Type
ProductGate(inner::AbstractGate, region_geometry::AbstractGeometry)A Set-like composite gate: applying it applies inner once per element of region_geometry (a BROADCAST geometry: AllSites, Bricklayer, or EachSite), in the canonical elements order. The whole product counts as ONE gate — in apply_with_prob! outcomes it is ONE categorical branch (K = 1), so a single :gates_spacetime coin governs the entire layer.
RNG semantics
Each element application is an independent call of inner's own execute!/build_operator path, so random inner gates (e.g. HaarRandom()) draw ONE FRESH realization per element from :gates_realization — exactly as if the layer had been applied with apply!(state, inner, region_geometry).
For "the SAME unitary on every element", do NOT use ProductGate — spell it MatrixGate(U) applied to a broadcast geometry: apply!(state, MatrixGate(U), Bricklayer(:even)).
Calling convention (v0.1 API contract)
ProductGate carries its target geometry itself; its region argument, where one is syntactically required, must be Sites(u) where u covers EXACTLY the union of elements(region_geometry, L, bc) — any other region throws an ArgumentError. The accepted spellings:
pg = ProductGate(HaarRandom(), Bricklayer(:even))
# Deterministic, in a circuit (geometry omitted — RECOMMENDED):
Circuit(L=8, bc=:periodic) do c
apply!(c, pg) # records geometry = Sites(union)
apply!(c, pg, Sites(1:8)) # equivalent explicit form
end
# Eager, on a state:
apply!(state, pg) # geometry omitted — RECOMMENDED
apply!(state, pg, Sites(1:8)) # equivalent explicit form
# Stochastic outcome (K = 1; the outcome tuple REQUIRES a geometry key, so
# the explicit Sites(union) spelling is the ONLY valid one here):
apply_with_prob!(c; outcomes=[
(probability=0.5, gate=ProductGate(HaarRandom(), Bricklayer(:even)),
geometry=Sites(1:8)),
(probability=0.5, gate=ProductGate(CZ(), Bricklayer(:even)),
geometry=Sites(1:8)),
])Erroneous spellings (all ArgumentError): passing the inner broadcast geometry again (apply!(state, pg, Bricklayer(:even)) or geometry=Bricklayer(:even) in an outcome — that would multiply the product per element), a Sites region that is not exactly the union, or a staircase/Pointer geometry. Deterministic builder forms error at BUILD time; outcome-tuple misuse errors at run time (the builder's outcome validation is gate-agnostic).
Event log
With log_events=true, each element application is logged as a GateApplied with the INNER gate's label (elementidx = position within the product); the engine additionally logs one wrapper GateApplied with this gate's label (`"∏" * gatelabel(inner), e.g."∏Haar"`).
Restrictions
region_geometrymust be a broadcast geometry (is_broadcast(geo) == true). For a single region, applyinnerdirectly:apply!(x, inner, geometry).- Nesting (
ProductGateinsideProductGate) is not supported. support(pg)is undefined (throws): the site count depends onL/bc.
QuantumCircuitsMPS.Projection — Type
Projection(outcome::Int)Projector onto computational basis state |outcome⟩ (level index, 0-based). outcome=0 projects onto |0⟩, outcome=1 onto |1⟩; on spin sites of local dimension d, any level 0 ≤ outcome ≤ d-1 is valid (level k ↔ m = S-k). The outcome is validated against the state's local dimension at apply time.
QuantumCircuitsMPS.Reset — Type
ResetReset gate: projects to |0⟩ or |1⟩ based on Born probability, then flips to |0⟩ if needed. Equivalent to measure + conditional X.
QuantumCircuitsMPS.Measure — Type
Measure(basis::Symbol=:Z; feedback=nothing)Projective measurement gate with optional classical feedback (v0.1).
Born-samples the measured site via the :born_measurement stream, collapses the state, records a MeasurementOutcome event (when the event log is enabled), then dispatches feedback with the observed outcome:
feedback=OnOutcome(1 => PauliX())— declarative outcome → gate map (applied on the measured sites)feedback=(state, sites, outcome) -> ...— arbitrary closure (auto-wrapped asCallbackFeedback);sites::Vector{Int}feedback=nothing(default) — plain projective measurement (the legacyMeasurementgate was removed in v0.4.0;Measure(:Z)is its replacement)
Reset() is semantically Measure(:Z; feedback=OnOutcome(1 => PauliX())) (bit-identical trajectories under the same seeds).
RNG contract
Feedback executes inside with_guarded_stream(registry, :gates_spacetime): drawing from :gates_spacetime during feedback throws an error, so feedback can never desynchronize the fixed spacetime coin sequence. Random feedback gates (HaarRandom, ...) draw from :gates_realization as usual.
Example (adaptive random-unitary feedback)
m = Measure(:Z; feedback=(st, s, o) -> o == 1 && apply!(st, HaarRandom(1), SingleSite(s[1])))
apply!(state, m, AllSites())Only :Z basis is supported in v0.1. SpinSectorMeasurement does not support feedback= in v0.1 (informative error).
QuantumCircuitsMPS.OnOutcome — Type
OnOutcome(pairs::Pair{Int,<:AbstractGate}...)Declarative measurement feedback: maps measurement outcomes to gates applied on the measured sites.
OnOutcome(1 => PauliX()) # flip back on outcome 1 (Reset)
OnOutcome(0 => Rz(0.3), 1 => HaarRandom(1)) # per-outcome actionsOutcomes without a registered gate are left untouched. Duplicate outcome keys and empty argument lists are ArgumentErrors.
QuantumCircuitsMPS.total_spin_projector — Function
total_spin_projector(S::Int; s::Real=1, d::Int=Int(2*Rational(s)+1)) -> Matrix{Float64}Construct the projector onto total spin sector S for two spin-s particles (default s=1, the historical spin-1 case).
The tensor product decomposes as s ⊗ s = 0 ⊕ 1 ⊕ ... ⊕ 2s, so valid sectors are S ∈ 0:Int(2s). Returns a d²×d² projector matrix (d = 2s+1).
Arguments
S: Total spin sector (0 to 2s)s: Local spin (positive integer or half-integer; default 1)d: Local dimension (must equal 2s+1; kept as an explicit kwarg for backward compatibility with the historicaltotal_spin_projector(S; d=3))
Examples
P0 = total_spin_projector(0) # spin-1 singlet projector (dim=1)
P2 = total_spin_projector(2) # spin-1 quintet projector (dim=5)
P0_32 = total_spin_projector(0; s=3//2) # spin-3/2 singlet (16×16, rank 1)
# Completeness for any s: Σ_S P_S = I
@assert sum(total_spin_projector(S; s=2) for S in 0:4) ≈ I(25)Physics
For s=1 the three historical hardcoded Clebsch-Gordan polynomials are used (byte-identical output to pre-v0.4 releases):
- P₂ = (1/6)(S₁·S₂)² + (1/2)(S₁·S₂) + (1/3)I
- P₁ = -(1/2)(S₁·S₂)² - (1/2)(S₁·S₂) + I
- P₀ = (1/3)(S₁·S₂)² - (1/3)I
For any other s, the general Lagrange interpolation in S₁·S₂ is used: PS = Π{S'≠S} (S₁·S₂ − λ{S'}) / (λS − λ{S'}) with eigenvalues λS = ½[S(S+1) − 2s(s+1)] — no Clebsch-Gordan tables needed.
QuantumCircuitsMPS.verify_spin_projectors — Function
verify_spin_projectors(; s::Real=1, tol::Float64=1e-10)Verify that the spin-s pair projectors satisfy required properties. Returns true if all checks pass, throws error otherwise.
Checks (over all sectors S = 0..2s):
- Completeness: ΣS PS = I
- Idempotence: PS² = PS for all S
- Orthogonality: Pᵢ·Pⱼ = 0 for i ≠ j
- Correct dimensions: tr(P_S) = 2S+1
QuantumCircuitsMPS.SpinSectorProjection — Type
SpinSectorProjection(projector::Matrix{Float64})Coherent projection onto specified spin sectors (no measurement/collapse).
Applies projector operator P to two adjacent spin sites, then renormalizes: |ψ⟩ → P|ψ⟩ / ||P|ψ⟩||
The projector must be d²×d² for two sites of local dimension d (9×9 for the historical spin-1 case; 16×16 for spin-3/2 pairs, etc.). The matrix size is validated against the state's local dimension at apply time.
Example
# Project onto S=0 and S=1 sectors (remove S=2) for spin-1 pairs
P01 = total_spin_projector(0) + total_spin_projector(1)
gate = SpinSectorProjection(P01)
# Spin-3/2 pair: remove the S=3 sector
P012 = sum(total_spin_projector(S; s=3//2) for S in 0:2)
gate32 = SpinSectorProjection(P012)Physics
This is a coherent operation that preserves quantum superposition. For AKLT: Repeated application of P₀+P₁ should converge to ground state.
QuantumCircuitsMPS.SpinSectorMeasurement — Type
SpinSectorMeasurement(sectors::Vector{Int}=)True Born measurement of total spin sector for two adjacent spin-1 sites.
Performs projective measurement that collapses the state to a definite spin sector. Outcome probabilities follow Born rule: P(S) = ⟨ψ|Pₛ|ψ⟩
Arguments
sectors: Which sectors to measure (default: [0, 1, 2] for all sectors)
Example
# Measure all three sectors
gate = SpinSectorMeasurement([0, 1, 2])
# Measure only S=0 or S=1 (post-select)
gate = SpinSectorMeasurement([0, 1])Physics
This is the research question: Does forced measurement to S∈{0,1} produce different physics than coherent projection? Unknown behavior to explore.
Returns
After application, the measurement outcome S can be retrieved from state history.
Arbitrary Spin-S Support
New in v0.4.0 (T39): SimulationState(...; site_type="S=k/2") for any half-integer or integer spin S up to S=10, on the MPS and state-vector backends (local_dim = 2S+1). The Clifford backend remains qubit-only.
The "Z<m>" label convention. Basis states are indexed by two equivalent coordinates:
- a level index
k = 0, 1, …, 2S(0-based, descending magnetic quantum number), and - the magnetic quantum number
m = S - k(so level 0 =m = +S, level2S=m = -S).
ProductState initial-state labels and Projection/measurement outcomes use the level index k (matching the qubit "Proj0"/"Proj1" convention and the state-vector digit convention); initialize!'s spin_state keyword and ITensors state(...) calls use the "Z<m>" string form, where <m> is written as a plain integer for integer spins ("Z1", "Z0", "Z-1") and as a <numerator>/2 fraction for half-integer spins ("Z3/2", "Z-1/2"). "Up"/"Dn" alias the two extremal levels (m = +S / m = -S) at every spin. Per-level projector operators follow the same level-index naming: "Proj0", …, "Proj$(2S)".
using QuantumCircuitsMPS
# Spin-3/2 chain: state_type "S=3/2" ⇒ local_dim = 4, levels 0..3 ↔ m = 3/2, 1/2, -1/2, -3/2
state = SimulationState(L=6, bc=:open, site_type="S=3/2",
rng=RNGRegistry(gates_spacetime=1, gates_realization=2, born_measurement=3))
initialize!(state, ProductState(spin_state="Z1/2")) # every site at level 1 (m = 1/2)
# Categorical (2S+1)-outcome measurement — same Measure(:Z) API as qubits
apply!(state, Measure(:Z), SingleSite(1))
# Sz-weighted Magnetization: ⟨Sz⟩ ∈ [-S, S] on spin sites (vs. ±1 Pauli convention on qubits)
Magnetization(:Z)(state)total_spin_projector(S; s=1) and SpinSectorProjection/ SpinSectorMeasurement generalize the AKLT forced-measurement machinery to arbitrary spin-s pairs (s=1 keeps its original hardcoded S=0/1/2 projector polynomials for bitwise regression stability; s≠1 uses the Lagrange/Casimir eigenvalue-product formula) — see AKLT Example: Forced Measurement Protocol in the tutorials for the spin-1 case and the Gates section above for the full signatures.