Gates

Unitary and non-unitary (measurement/projection) single- and two-site operations, plus the spin-sector projector/measurement machinery for AKLT- style protocols.

QuantumCircuitsMPS.AbstractGateType
AbstractGate

Abstract 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 use gate_matrix(gate, rng; local_dim)).
  • needs_normalization(gate) -> Bool (trait, default false): true for non-unitary gates (e.g. Projection) — backends renormalize (and, for MPS, truncate) after applying such a gate.
  • is_measurement(gate) -> Bool (trait): true for gates that Born-sample via the :born_measurement RNG stream (e.g. Measure, Reset).
  • Gates with non-operator semantics (Born sampling + classical feedback) override execute!(state, gate, region) instead — see Measure in src/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).

source
QuantumCircuitsMPS.RxType
Rx(θ)

Single-qubit rotation about the X axis, convention Rx(θ) = exp(-iθX/2):

[ cos(θ/2)     -i·sin(θ/2) ]
[ -i·sin(θ/2)   cos(θ/2)   ]
source
QuantumCircuitsMPS.RyType
Ry(θ)

Single-qubit rotation about the Y axis, convention Ry(θ) = exp(-iθY/2):

[ cos(θ/2)  -sin(θ/2) ]
[ sin(θ/2)   cos(θ/2) ]
source
QuantumCircuitsMPS.RzType
Rz(θ)

Single-qubit rotation about the Z axis, convention Rz(θ) = exp(-iθZ/2):

[ exp(-iθ/2)   0         ]
[ 0            exp(iθ/2) ]
source
QuantumCircuitsMPS.CZType
CZ

Controlled-Z gate. Symmetric under qubit exchange. |00⟩→|00⟩, |01⟩→|01⟩, |10⟩→|10⟩, |11⟩→-|11⟩

source
QuantumCircuitsMPS.CNOTType
CNOT

Controlled-NOT gate. Control = site 1, target = site 2. |00⟩→|00⟩, |01⟩→|01⟩, |10⟩→|11⟩, |11⟩→|10⟩

source
QuantumCircuitsMPS.SWAPType
SWAP

Swap gate. Exchanges the states of the two sites. |00⟩→|00⟩, |01⟩→|10⟩, |10⟩→|01⟩, |11⟩→|11⟩

source
QuantumCircuitsMPS.HaarRandomType
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.

source
QuantumCircuitsMPS.RandomCliffordType
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.

source
QuantumCircuitsMPS.GaussianHaarType
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.

source
QuantumCircuitsMPS.BondParityType
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.

source
QuantumCircuitsMPS.MatrixGateType
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

  • MatrixGate assumes U is 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 1
source
QuantumCircuitsMPS.ProductGateType
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_geometry must be a broadcast geometry (is_broadcast(geo) == true). For a single region, apply inner directly: apply!(x, inner, geometry).
  • Nesting (ProductGate inside ProductGate) is not supported.
  • support(pg) is undefined (throws): the site count depends on L/bc.
source
QuantumCircuitsMPS.ProjectionType
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.

source
QuantumCircuitsMPS.ResetType
Reset

Reset gate: projects to |0⟩ or |1⟩ based on Born probability, then flips to |0⟩ if needed. Equivalent to measure + conditional X.

source
QuantumCircuitsMPS.MeasureType
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 as CallbackFeedback); sites::Vector{Int}
  • feedback=nothing (default) — plain projective measurement (the legacy Measurement gate 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).

source
QuantumCircuitsMPS.OnOutcomeType
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 actions

Outcomes without a registered gate are left untouched. Duplicate outcome keys and empty argument lists are ArgumentErrors.

source
QuantumCircuitsMPS.total_spin_projectorFunction
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 historical total_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.

source
QuantumCircuitsMPS.verify_spin_projectorsFunction
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):

  1. Completeness: ΣS PS = I
  2. Idempotence: PS² = PS for all S
  3. Orthogonality: Pᵢ·Pⱼ = 0 for i ≠ j
  4. Correct dimensions: tr(P_S) = 2S+1
source
QuantumCircuitsMPS.SpinSectorProjectionType
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.

source
QuantumCircuitsMPS.SpinSectorMeasurementType
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.

source

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, level 2S = 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.