API
Hilbert Spaces
SecondQuantizedAlgebra.HilbertSpace — Type
HilbertSpaceAbstract supertype for Hilbert spaces. Concrete subtypes: FockSpace, NLevelSpace, CollectiveNLevelSpace, PauliSpace, SpinSpace, PhaseSpace, and ProductSpace for tensor products. Compose with ⊗ or tensor.
SecondQuantizedAlgebra.ProductSpace — Type
ProductSpace{T} <: HilbertSpaceComposite Hilbert space formed by the tensor product of multiple subspaces. The type parameter T is a concrete Tuple type for type-stable storage. Constructed via ⊗ or tensor, not directly.
Operators on a ProductSpace take a positional index specifying which subspace they act on, e.g. Destroy(h, :a, 1).
Examples
julia> FockSpace(:cavity) ⊗ NLevelSpace(:atom, 2)
ℋ(cavity) ⊗ ℋ(atom)SecondQuantizedAlgebra.NLevelSpace — Type
NLevelSpace(name::Symbol, n::Int)
NLevelSpace(name::Symbol, n::Int, ground_state::Int)
NLevelSpace(name::Symbol, levels::Tuple{Vararg{Symbol}})Hilbert space for an N-level system (atoms, qubits, qudits). Hosts Transition operators $|i\rangle\langle j| \cdot |k\rangle\langle l| = \delta_{jk} |i\rangle\langle l|$.
The ground_state (default 1) selects the projector $|g\rangle\langle g|$ that the canonical basis omits; arithmetic keeps it atomic and expand_completeness materialises $|g\rangle\langle g| = 1 - \sum_{k \neq g}|k\rangle\langle k|$ on demand. Levels can be integers (default 1:n) or symbolic names.
Examples
julia> NLevelSpace(:atom, 3)
ℋ(atom)
julia> NLevelSpace(:atom, (:g, :e))
ℋ(atom)See also Transition, expand_completeness.
SecondQuantizedAlgebra.CollectiveNLevelSpace — Type
CollectiveNLevelSpace(name::Symbol, n::Int)
CollectiveNLevelSpace(name::Symbol, levels::Tuple{Vararg{Symbol}})Hilbert space for collective transitions of identical N-level systems. It hosts CollectiveTransition operators and deliberately carries no ground-state projector convention: collectively, the diagonal sum is the particle-number operator rather than the identity.
SecondQuantizedAlgebra.PauliSpace — Type
PauliSpace(name::Symbol) <: HilbertSpaceHilbert space for a two-level system. Hosts Pauli operators satisfying $\sigma_j \sigma_k = \delta_{jk} I + i \epsilon_{jkl} \sigma_l$.
Examples
julia> PauliSpace(:spin)
ℋ(spin)SecondQuantizedAlgebra.SpinSpace — Type
SpinSpace(name::Symbol) <: HilbertSpaceHilbert space for collective spin angular momentum. Hosts Spin operators satisfying $[S_j, S_k] = i \epsilon_{jkl} S_l$. The algebra is independent of the spin size $S$, which enters only via the QuantumOpticsBase.SpinBasis chosen for numeric evaluation.
Examples
julia> SpinSpace(:S)
ℋ(S)See also Spin, PauliSpace.
SecondQuantizedAlgebra.PhaseSpace — Type
PhaseSpace(name::Symbol) <: HilbertSpaceHilbert space for position and momentum quadratures. Hosts Position and Momentum operators satisfying $[p, x] = -i$; arithmetic canonicalizes products to place position left of momentum.
Examples
julia> PhaseSpace(:osc)
ℋ(osc)QuantumInterface.:⊗ — Function
⊗(spaces::HilbertSpace...)Create a ProductSpace from multiple Hilbert spaces. Flattens nested ProductSpace arguments so that (A ⊗ B) ⊗ C == A ⊗ B ⊗ C. Unicode input \otimes<tab>; ASCII alias tensor.
Examples
julia> FockSpace(:a) ⊗ FockSpace(:b) ⊗ NLevelSpace(:atom, 2)
ℋ(a) ⊗ ℋ(b) ⊗ ℋ(atom)See also ProductSpace, tensor.
QuantumInterface.tensor — Function
tensor(spaces::HilbertSpace...)ASCII alias for ⊗: create a ProductSpace from multiple Hilbert spaces.
Examples
julia> tensor(FockSpace(:a), FockSpace(:b))
ℋ(a) ⊗ ℋ(b)See also ⊗, ProductSpace.
q-Numbers
SecondQuantizedAlgebra.QField — Type
QFieldAbstract supertype for all second-quantized operator expressions.
Subtypes:
QSym: leaf operators (e.g.Destroy,Create,Transition)QAdd: compound expressions (a sum ofQTermproducts)
Supports arithmetic (+, -, *, ^, /), adjoint, and comparison via ==/isequal. All arithmetic eagerly applies normal ordering and returns QAdd.
SecondQuantizedAlgebra.QSym — Type
QSym <: QFieldAbstract type for fundamental (leaf) operators in the expression tree.
Every QSym carries three fields identifying its site:
name::Symbol— display namespace_index::Int— position inProductSpace(1 for single spaces)index::Index— symbolic summation index (Index), orNO_INDEX
Concrete subtypes: Destroy, Create, Transition, Pauli, Spin, Position, Momentum.
SecondQuantizedAlgebra.QAdd — Type
QAdd <: QFieldThe sole compound expression type: a sum of eagerly-ordered operator products.
All arithmetic on QSym operators produces a QAdd. Iterating over a QAdd yields Pair{QTerm, CNum} entries; read term.ops for the operator sequence and term.ne for the scoped index constraints.
See also QTerm, prefactor, operators, Σ, constraint_pairs.
SecondQuantizedAlgebra.QTerm — Type
QTermA single entry of a QAdd sum: an ordered operator product plus the pairwise index inequality constraints that scope that product.
Fields
ops::Vector{Op}— the ordered operator productne::Vector{NonEqualPair}— pairwiseα ≠ βconstraints (canonicalized)
QTerm is the dict key in QTermDict; iterating a QAdd yields Pair{QTerm, CNum}, and callers reach term.ops / term.ne directly.
SecondQuantizedAlgebra.QTermDict — Type
QTermDictAlias for Dict{QTerm, CNum} — the storage type backing every QAdd. Each entry maps an exact term identity (ops, ne) to its prefactor. Iteration yields Pair{QTerm, CNum}; callers reach term.ops and term.ne directly.
SecondQuantizedAlgebra.Op — Type
Op <: QSymThe single concrete leaf operator. A kind::OpKind tag selects the physical role (annihilation, creation, transition, collective transition, Pauli, spin, position, momentum); the remaining fields are shared storage interpreted per kind:
name::Symbol: display namespace_index::Int: position in aProductSpaceindex::Index: symbolic site index, orNO_INDEXl1, l2, g, nlev::Int32: packed level/axis data. Transition uses all four (i,j, ground state, number of levels); Pauli/Spin store the axis inl1; Fock/PhaseSpace leave them zero.
Construct via the role-named functions Destroy, Create, Transition, CollectiveTransition, Pauli, Spin, Position, Momentum; test the role via is_destroy and siblings, or read it with optype. Collapsing the former per-type hierarchy into one concrete struct makes the operator vector concrete-eltype, so the per-operator hooks dispatch statically. See docs/src/devdocs.md.
SecondQuantizedAlgebra.OpKind — Type
OpKindRuntime tag distinguishing the eight operator roles carried by the single concrete leaf type Op. The integer values double as the cross-family sort order (see _type_order/order_key).
SecondQuantizedAlgebra.prefactor — Function
prefactor(s::QAdd) -> Complex{Num}Return the Complex{Num} prefactor of a single-term QAdd.
Throws ArgumentError if s contains more than one term. For multi-term expressions, iterate over the QAdd directly.
Examples
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> prefactor(2 * a' * a)
2See also operators, sorted_arguments.
SecondQuantizedAlgebra.operators — Function
operators(eqs::AbstractMeanfieldEquations)The operator products on the left-hand sides, one per tracked moment. Paired with states by position (states(eqs)[i] == average(operators(eqs)[i])).
operators(s::QAdd) -> Vector{Op}Return the ordered operator sequence of a single-term QAdd.
Throws ArgumentError if s contains more than one term. For multi-term expressions, iterate over the QAdd directly and read term.ops from each QTerm.
Examples
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> length(operators(a' * a))
2See also prefactor, sorted_arguments.
SecondQuantizedAlgebra.sorted_arguments — Function
sorted_arguments(q::QAdd) -> Vector{QAdd}Return each term of q as a single-entry QAdd, in deterministic sort order.
Examples
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> length(SecondQuantizedAlgebra.sorted_arguments(a + a'))
2SecondQuantizedAlgebra.constraint_pairs — Function
constraint_pairs(q::QAdd) -> Vector{Tuple{Index, Index}}Return the deduplicated union of every term's non_equal pairs in q. This is an introspection helper only; it does not define the expression semantics.
Examples
julia> h = FockSpace(:site);
julia> @qnumbers a::Destroy(h);
julia> i = Index(h, :i, 3, h); j = Index(h, :j, 3, h);
julia> q = Σ(IndexedOperator(a', i) * IndexedOperator(a, i), i, [j]);
julia> length(SecondQuantizedAlgebra.constraint_pairs(q))
1SecondQuantizedAlgebra.normal_order — Function
normal_order(expr::QField) -> QAddRoute every term of expr through the canonicalization pipeline.
In practice this is the identity on anything built through public arithmetic: *, +, -, ^, commutator, Σ, substitute, and adjoint all canonicalize eagerly, so the result of any such call is already normal-ordered. Reach for normal_order explicitly only when an expression was assembled through low-level internals that bypass the arithmetic, or when interfacing with code that expects a finalizer call. simplify uses it internally before simplifying coefficients.
Examples
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> a * a'
1 + a' * a
julia> normal_order(a * a')
1 + a' * aSee also simplify, expand_completeness, normal_to_symmetric, symmetric_to_normal.
SecondQuantizedAlgebra.qadjoint — Function
qadjoint(x)Hermitian conjugate that distributes through mixed operator/symbolic expressions.
On a QField returns adjoint(x). On a Number returns adjoint(x). On a SymbolicUtils.BasicSymbolic tree, recurses into arguments so coefficients distribute (e.g. qadjoint(2im * a) becomes -2im * a') rather than producing an opaque conj(...) wrapper.
Aliased as qconj and dagger.
SecondQuantizedAlgebra.inner_adjoint — Function
inner_adjoint(x)Push the adjoint inside ⟨...⟩ averages: rewrites conj(⟨X⟩) as ⟨X†⟩ so the result stays expressed as an average of an operator rather than a conj wrapper around one. Used when building equations of motion where both sides must share the canonical "average-of-operator" form for substitution and hashing.
On non-average sub-expressions, behaves like qadjoint.
SecondQuantizedAlgebra.symmetric_to_normal — Function
symmetric_to_normal(expr) -> QAddConvert expr from symmetric (Weyl) to normal ordering. Exact inverse of normal_to_symmetric.
Examples
julia> hf = FockSpace(:f);
julia> @qnumbers a::Destroy(hf);
julia> symmetric_to_normal(normal_to_symmetric(a' * a))
a' * a
julia> hp = PhaseSpace(:osc);
julia> @qnumbers x::Position(hp) p::Momentum(hp);
julia> symmetric_to_normal(normal_to_symmetric(x * p))
x * pSee also normal_to_symmetric, normal_order.
SecondQuantizedAlgebra.normal_to_symmetric — Function
normal_to_symmetric(expr) -> QAddConvert expr from normal to symmetric (Weyl) ordering. Exact inverse of symmetric_to_normal.
Examples
julia> hf = FockSpace(:f);
julia> @qnumbers a::Destroy(hf);
julia> normal_to_symmetric(a' * a)
-0.5 + a' * a
julia> hp = PhaseSpace(:osc);
julia> @qnumbers x::Position(hp) p::Momentum(hp);
julia> normal_to_symmetric(x * p)
0.5im + x * pSee also symmetric_to_normal, normal_order.
SecondQuantizedAlgebra.expand_completeness — Function
expand_completeness(q) -> QAddRewrite every ground-state projector $\sigma^{gg}$ in q via the completeness relation $\sigma^{gg} = 1 - \sum_{k \neq g} \sigma^{kk}$.
* keeps $\sigma^{gg}$ atomic; reach for expand_completeness when downstream code needs the projector eliminated, e.g. before converting to a numeric basis where the $\sigma^{kk}$ for $k \neq g$ form the independent degrees of freedom.
Examples
julia> h = NLevelSpace(:atom, 2);
julia> σ11 = Transition(h, :σ, 1, 1);
julia> expand_completeness(σ11)
1 - σ₂₂See also assume_distinct_index, normal_order.
SecondQuantizedAlgebra.anticommutator — Function
anticommutator(a, b) -> QAddReturn the anticommutator $\{a, b\} = a\,b + b\,a$ as a QAdd.
Examples
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> anticommutator(a, a')
1 + 2 * a' * aSee also commutator.
SecondQuantizedAlgebra.is_average — Function
is_average(x) -> BoolWhether x is a symbolic average object created by average.
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> is_average(average(a)), is_average(a)
(true, false)SecondQuantizedAlgebra.is_indexed_sum — Function
is_indexed_sum(x) -> BoolWhether x is a moment-layer indexed-sum node created by average on a summed QAdd. The summation scope is recovered with get_sum_indices and get_sum_non_equal.
SecondQuantizedAlgebra.undo_average — Function
undo_average(expr) -> QAddRecursively strip symbolic averages and return the underlying operator expression. Summation metadata is restored. Also accepts a Symbolics.Equation, returning a Pair{QAdd, QAdd}.
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> undo_average(average(a' * a)) == a' * a
trueSymbolicUtils.substitute — Function
substitute(expr::QField, rules::AbstractDict; replace_adjoint=true)Substitute symbolic parameters and/or operators in expr using rules.
Scalar keys (anything other than a QSym) are substituted in coefficients using SymbolicUtils. Operator keys are applied simultaneously to the original operator leaves: replacement expressions are not searched again for operator keys. This makes mode transformations such as a => g*a + h*b well-defined even when the replacement contains the original operator.
When replace_adjoint=true, missing adjoint operator rules are added automatically, e.g. a => b also implies a' => b' unless a' is already specified explicitly.
Examples
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> @variables x;
julia> substitute(x * a' * a, Dict(x => 2))
2 * a' * aSee also change_index.
SymbolicUtils.simplify — Function
simplify(expr::QField) -> QAddNormal-order expr, then simplify each coefficient symbolically and drop summation indices that no surviving term depends on.
The operator-level work (commutation, same-site composition, like-term collection) all happens inside normal_order. What simplify adds on top is purely at the symbolic-coefficient layer: Symbolics.expand followed by SymbolicUtils.simplify runs on each surviving prefactor, and any term whose coefficient simplifies to zero is dropped. A final pass removes summation indices that no remaining term references.
That symbolic step is expensive, so reach for simplify as a finalizer when cancellations or accumulated symbolic factors need to be folded; use normal_order for intermediate steps.
Examples
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> @variables x y;
julia> expr = (x^2 + 2x*y + y^2) * a' * a - (x + y)^2 * a' * a;
julia> simplify(expr)
0See also normal_order, expand, expand_completeness.
SymbolicUtils.expand — Function
expand(expr::QField) -> QAddExpand the symbolic prefactor of each term via Symbolics.expand.
Examples
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> @variables x y;
julia> expand((x + y)^2 * a)
(x^2 + 2x*y + y^2) * aSee also simplify.
SecondQuantizedAlgebra.@qnumbers — Macro
@qnumbers ops...Convenience macro for constructing named quantum operators.
Each argument has the form name::OperatorType(hilbert_space, args...). The macro calls OperatorType(hilbert_space, :name, args...) and binds the result to name in the calling scope. Multiple operators can be declared in one call.
Examples
julia> h = FockSpace(:fock);
julia> @qnumbers a::Destroy(h)
(a,)See also Destroy, Transition, CollectiveTransition, Pauli, Spin.
SecondQuantizedAlgebra.Destroy — Function
Destroy(h::FockSpace, name::Symbol) -> OpBosonic annihilation operator $a$ on a FockSpace. Satisfies the canonical commutation relation $[a, a^\dagger] = 1$. The adjoint a' returns the corresponding Create operator. Returns an Op tagged OP_DESTROY.
Examples
julia> h = FockSpace(:cavity);
julia> @qnumbers a::Destroy(h);
julia> a * a'
1 + a' * aSecondQuantizedAlgebra.Transition — Function
Transition(h::NLevelSpace, name::Symbol, i, j) -> OpTransition operator $|i\rangle\langle j|$ on an NLevelSpace. Satisfies the composition rule $|i\rangle\langle j| \cdot |k\rangle\langle l| = \delta_{jk} |i\rangle\langle l|$, with adjoint $|i\rangle\langle j|^\dagger = |j\rangle\langle i|$. Returns an Op tagged OP_TRANSITION, packing i, j, the ground state, and the number of levels into l1, l2, g, nlev.
The carried ground-state level and level count let the eager arithmetic keep $\sigma^{gg}$ atomic in canonical form (use expand_completeness to materialise $|g\rangle\langle g| = 1 - \sum_{k \neq g}|k\rangle\langle k|$ when needed).
Examples
julia> h = NLevelSpace(:atom, 3);
julia> σ = Transition(h, :σ, 1, 2);
julia> σ * σ'
σ₁₁
julia> σ' * σ
σ₂₂See also NLevelSpace, expand_completeness, @qnumbers.
SecondQuantizedAlgebra.CollectiveTransition — Function
CollectiveTransition(h::CollectiveNLevelSpace, name::Symbol, i, j) -> OpCollective transition $S^{ij} = \sum_k |i\rangle_k\langle j|$. These operators obey $[S^{ij},S^{kl}] = \delta_{jk}S^{il}-\delta_{li}S^{kj}$ and do not compose like single-site Transition operators.
SecondQuantizedAlgebra.Pauli — Function
Pauli(h::PauliSpace, name::Symbol, axis::Int) -> OpPauli operator $\sigma_x, \sigma_y, \sigma_z$ on a PauliSpace. The axis argument selects the component: 1 = x, 2 = y, 3 = z. Hermitian (σ' == σ) and satisfies $\sigma_j \sigma_k = \delta_{jk} I + i \epsilon_{jkl} \sigma_l$. Returns an Op tagged OP_PAULI with the axis stored in l1.
Examples
julia> h = PauliSpace(:s);
julia> σx = Pauli(h, :σ, 1); σy = Pauli(h, :σ, 2);
julia> σx * σy
im * σz
julia> σx * σx
1See also PauliSpace, Spin.
SecondQuantizedAlgebra.Spin — Function
Spin(h::SpinSpace, name::Symbol, axis::Int) -> OpAngular momentum operator $S_x, S_y, S_z$ on a SpinSpace. The axis argument selects the component: 1 = x, 2 = y, 3 = z. Hermitian (S' == S) and satisfies $[S_j, S_k] = i \epsilon_{jkl} S_l$ (applied eagerly by *). Returns an Op tagged OP_SPIN with the axis stored in l1.
Examples
julia> h = SpinSpace(:S);
julia> Sx = Spin(h, :S, 1); Sy = Spin(h, :S, 2);
julia> Sy * Sx
-im * Sz + Sx * SySecondQuantizedAlgebra.Position — Function
Position(h::PhaseSpace, name::Symbol) -> OpPosition (quadrature) operator $x$ on a PhaseSpace. Hermitian (x' == x) and related to Fock operators by $x = (a + a^\dagger)/\sqrt{2}$. Canonical pair with Momentum: $[p, x] = -i$. Returns an Op tagged OP_POSITION.
Examples
julia> h = PhaseSpace(:osc);
julia> @qnumbers x::Position(h) p::Momentum(h);
julia> p * x
-im + x * pSee also Momentum, PhaseSpace.
SecondQuantizedAlgebra.Momentum — Function
Momentum(h::PhaseSpace, name::Symbol) -> OpMomentum (quadrature) operator $p$ on a PhaseSpace. Hermitian (p' == p) and related to Fock operators by $p = i(a^\dagger - a)/\sqrt{2}$. Canonical pair with Position: $[p, x] = -i$. Returns an Op tagged OP_MOMENTUM.
See also Position, PhaseSpace.
SecondQuantizedAlgebra.optype — Function
optype(o::Op) -> OpKindReturn the OpKind tag of o.
SecondQuantizedAlgebra.operator_name — Function
operator_name(op::Op) -> SymbolThe display name of op (recovered from the intern table).
SecondQuantizedAlgebra.operator_index — Function
operator_index(o::Op) -> IndexThe site Index attached to o, or the sentinel NO_INDEX when o carries no index (test with has_index). Public accessor for the index field; pairs with operator_name and acts_on.
SecondQuantizedAlgebra.is_destroy — Function
is_destroy(o::Op) -> BoolTrue iff o is a Destroy (annihilation) operator. One of the role predicates that replace isa on the collapsed Op type; see also is_create, is_transition, is_pauli, is_spin, is_position, is_momentum, optype.
SecondQuantizedAlgebra.is_create — Function
is_create(o::Op) -> BoolTrue iff o is a Create (creation) operator. See is_destroy.
SecondQuantizedAlgebra.is_transition — Function
is_transition(o::Op) -> BoolTrue iff o is a Transition operator. See is_destroy.
SecondQuantizedAlgebra.is_collective_transition — Function
is_collective_transition(o::Op) -> BoolTrue iff o is a CollectiveTransition operator. See is_destroy.
SecondQuantizedAlgebra.is_pauli — Function
is_pauli(o::Op) -> BoolTrue iff o is a Pauli operator. See is_destroy.
SecondQuantizedAlgebra.is_spin — Function
is_spin(o::Op) -> BoolTrue iff o is a Spin operator. See is_destroy.
SecondQuantizedAlgebra.is_position — Function
is_position(o::Op) -> BoolTrue iff o is a Position operator. See is_destroy.
SecondQuantizedAlgebra.is_momentum — Function
is_momentum(o::Op) -> BoolTrue iff o is a Momentum operator. See is_destroy.
Mean field
QuantumCumulants.meanfield — Function
meanfield(ops, H, J=QField[]; kwargs...)
meanfield(op, H, J=QField[]; kwargs...)Equations of motion for the averages of ops under the Hamiltonian H and the collapse operators J. The result is the Heisenberg (quantum Langevin) equation with noise neglected: each jump adds a Lindblad term $\sum_i r_i \left(J_i^\dagger O J_i - \tfrac{1}{2}\{J_i^\dagger J_i, O\}\right)$. Returns a MeanfieldEquations, or a NoiseMeanfieldEquations when efficiencies is supplied (measurement backaction).
The returned right-hand sides are left unsimplified; call simplify! (or SymbolicUtils.simplify) on the equations you want to inspect.
Arguments
ops: the operator(s) whose equations of motion are derived.H: the operator defining the system Hamiltonian.J: the collapse (jump) operators of the system; defaults to none.
Keyword arguments
Jdagger=adjoint.(J): the adjoints of the jump operators.rates=ones(length(J)): decay rates forJ. A squareMatrixinstead selects collective dissipation across the jump vector.efficiencies=nothing: detector efficiencies per jump. When given, the result is aNoiseMeanfieldEquationscarrying a measurement-backaction noise drift.direction=Forward():Forwardfor ordinary evolution,Backwardfor retrodiction.order=nothing: if set, acumulant_expansionto this order is applied. AnIntcaps every subspace; aVector{Int}caps each Hilbert subspace separately.mix_choice=maximum: for aVectororder, how to combine the per-subspace caps on a term that acts on several subspaces.iv=ModelingToolkitBase.t_nounits: the independent (time) variable of the system.
SecondQuantizedAlgebra.commutator — Function
commutator(a, b) -> QAddReturn the commutator $[a, b] = a\,b - b\,a$ as a QAdd.
Examples
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> commutator(a, a')
1See also anticommutator, normal_order.
SecondQuantizedAlgebra.acts_on — Function
acts_on(expr) -> Vector{Int}Sorted unique space_index values that expr acts on. Works on QSym, QAdd, averaged BasicSymbolic expressions, and Numbers (Int[]).
julia> h = FockSpace(:a) ⊗ NLevelSpace(:b, 2);
julia> @qnumbers a::Destroy(h, 1) σ::Transition(h, 1, 2, 2);
julia> acts_on(a' * a), acts_on(a' * σ)
([1], [1, 2])QuantumCumulants.AbstractMeanfieldEquations — Type
AbstractMeanfieldEquationsSupertype of MeanfieldEquations and NoiseMeanfieldEquations.
QuantumCumulants.MeanfieldEquations — Type
MeanfieldEquationsConcrete equation set produced by meanfield when no measurement backaction is requested. The authoritative cumulant hierarchy is graph; the vector fields are a derived view regenerated from it.
Fields
graph: the source of truth (moments, drifts, treatments,sys,ctx).equations: the averaged differential equations (derived view).operator_equations: the same equations at the operator level.states: the averages on the left-hand sides.operators: the operators on the left-hand sides.hamiltonian: the system Hamiltonian.jumps,jumps_dagger: the collapse operators and their adjoints.rates: the decay rates corresponding tojumps.iv: the independent (time) variable.order: the cumulant-expansion order, ornothing.direction:ForwardorBackwardevolution.
Average
SecondQuantizedAlgebra.average — Function
average(expr) -> BasicSymbolic | NumberBuild the symbolic average $\langle \mathrm{expr} \rangle$. Distributes over sums, pulls c-number prefactors out, leaves scalars unchanged. Displayed as ⟨…⟩.
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> avg = average(a' * a);
julia> is_average(avg)
true
julia> avg + 1
1 + ⟨a' * a⟩See also undo_average, numeric_average.
QuantumCumulants.cumulant_expansion — Function
cumulant_expansion(expr, order; mix_choice=maximum)Expand every average in expr whose order exceeds order into lower-order moments via the joint-cumulant identity, neglecting the joint cumulant above order. order is an Int (one cap for all subspaces) or a Vector{Int} (a cap per Hilbert subspace, combined by mix_choice on mixed terms). The output is raw; apply SymbolicUtils.simplify for a canonical form.
See also: https://en.wikipedia.org/wiki/Cumulant#Joint_cumulants
Examples
julia> h = FockSpace(:a) ⊗ FockSpace(:b);
julia> @qnumbers a::Destroy(h, 1) b::Destroy(h, 2);
julia> cumulant_expansion(average(a * b), 1)
⟨b⟩*⟨a⟩cumulant_expansion(eqs::MeanfieldEquations, order)Cumulant-expand every RHS of eqs to order, returning a new MeanfieldEquations of the same shape with that order stored. The system's mix_choice (held in eqs.graph.sys) drives the mixed-order truncation. Noise systems take their order through meanfield, not here.
QuantumCumulants.cumulant — Function
cumulant(op, n=get_order(op))The n-th joint cumulant of op (either an operator or an average), the signed sum over partitions of its operator factors into n blocks. The result is raw and unsimplified; apply SymbolicUtils.simplify for a canonical form.
Examples
julia> h = FockSpace(:a) ⊗ FockSpace(:b);
julia> @qnumbers a::Destroy(h, 1) b::Destroy(h, 2);
julia> cumulant(a * b)
⟨a * b⟩ - ⟨b⟩*⟨a⟩
julia> cumulant(a * b, 1)
⟨a * b⟩
julia> cumulant(a * b, 3)
0QuantumCumulants.get_order — Function
get_order(expr)Highest moment order appearing in expr, the order used to decide whether a term is expanded by cumulant_expansion. Numbers have order 0, a bare operator order 1, and a product the number of its operator factors.
Examples
julia> h = FockSpace(:a) ⊗ FockSpace(:b);
julia> @qnumbers a::Destroy(h, 1) b::Destroy(h, 2);
julia> get_order(a)
1
julia> get_order(a * b)
2
julia> get_order(1)
0Introspection
QuantumCumulants.states — Function
states(eqs::AbstractMeanfieldEquations)The average variables $⟨…⟩$ evolved by eqs, i.e. the dynamical unknowns, one per tracked moment. Paired with operators by position.
QuantumCumulants.moments — Function
moments(eqs::AbstractMeanfieldEquations)Ordered operator$↔$moment map: each tracked operator product to its average $⟨op⟩$. The correspondence the cumulant hierarchy is built on.
QuantumCumulants.moment_variable_map — Function
moment_variable_map(eqs::AbstractMeanfieldEquations)Ordered map from each moment average $⟨op⟩$ to the time-dependent MTK unknown u(t) that System assigns it. The bridge initial_values, get_solution and parameter_map resolve moments against.
QuantumCumulants.closure_report — Function
closure_report(eqs::AbstractMeanfieldEquations; filter_func=nothing)Summarise the closure status of eqs:
n_states: number of tracked moments.by_order: tracked-moment count per cumulant order.missing: moments still needed to close the system (find_missing).closed: whether nothing is missing.
filter_func mirrors find_missing / complete!.
QuantumCumulants.noise_channels — Function
noise_channels(eqs::AbstractMeanfieldEquations)The monitored measurement channels: one entry per jump with nonzero efficiency, each an independent Brownian in the SDE System builds. Each entry is (; index, jump, rate, efficiency); a deterministic system has none.
Correlation functions
QuantumCumulants.CorrelationFunction — Type
CorrelationFunctionTwo-time correlation function ⟨op1(t+τ) op2(t)⟩ of a solved mean-field system, constructed via the quantum regression theorem. Build one with the CorrelationFunction(op1, op2, eqs0) constructor, then feed it to ModelingToolkitBase.System together with correlation_u0 and correlation_p0 to solve the τ-evolution, or to Spectrum for the power spectrum.
Fields
op1,op2: the operators whose correlation is computed.op2_ancilla:op2re-embedded on the ancilla subspaceaon_ancilla(internal).aon_ancilla: Hilbert-subspace index of the ancilla carryingop2(internal).eqs: the closed equations of motion in the delayτ.eqs0: the steady-state system the correlation is built on.τ: the delay independent variable.steady_state: iftrue, ambient averages are held constant; iffalse, they are evolved alongside the τ-states.ambient: the τ-system's ambient averages (steady-state coefficients, not τ-states), computed once from the closedeqsso theu0/System/spectrum paths need not re-walk it.
ModelingToolkitBase.System — Type
ModelingToolkitBase.System(eqs::MeanfieldEquations; name, kwargs...)
ModelingToolkitBase.System(eqs::NoiseMeanfieldEquations; name, kwargs...)Build an MTK System from the equation set: one u(t) variable per state, the drift resolved to those variables. A NoiseMeanfieldEquations becomes an SDE with one independent Brownian column per monitored channel, each carrying that channel's noise drift (sign +1 Forward, -1 Backward). RHS leaves not matching a state become parameters.
Any extra kwargs are forwarded to the underlying ModelingToolkitBase.System constructor. In particular bindings = [p => expr, ...] marks a parameter as derived from others, so it is computed from the supplied values rather than set directly (see the effective model in the unique-squeezing example).
ModelingToolkitBase.System(c::CorrelationFunction; name)MTK System for the τ-evolution. Ambient steady-state averages become parameters (values supplied via correlation_p0).
QuantumCumulants.Spectrum — Type
Spectrum(c::CorrelationFunction, ps)The Laplace-transformed correlation as a callable: S(ω, u_end, p0) returns the symmetric power spectrum 2·Re{∫₀^∞ g(τ)e^{-iωτ}dτ}. Solves (iω·I - A)X̃ = x̃(0) where A is the τ-system Jacobian at steady state and x̃ = x - x_∞ is centred so the one-sided transform converges. The linear/anti-linear split (the conjugate columns from get_adjoints=false) is handled by the 2n augmentation.
Examples
julia> h = FockSpace(:cavity);
julia> @qnumbers a::Destroy(h);
julia> eqs = meanfield([a' * a], a' * a, [a]; rates = [1.0], order = 2);
julia> c = CorrelationFunction(a', a, eqs);
julia> Spectrum(c)
ℱ(⟨a' * a_0⟩)(ω)QuantumCumulants.correlation_u0 — Function
correlation_u0(c, u_end)Initial values for the τ-evolution of the CorrelationFunction c. Each ancilla state ⟨X(τ) op2ancilla⟩ starts from the steady-state value ⟨X op2⟩ (with op2 on its original subspace), looked up in `uend`.
See also: CorrelationFunction, correlation_p0.
QuantumCumulants.correlation_p0 — Function
correlation_p0(c, u_end, ps_p0)Parameter dictionary for the τ-ODE of the CorrelationFunction c: the user parameters ps_p0 together with the numeric values of the ambient steady-state averages, looked up in u_end.
See also: CorrelationFunction, correlation_u0.
Symbolic Summations
SecondQuantizedAlgebra.Index — Type
Index(h::HilbertSpace, name::Symbol, range, space)
Index(h::HilbertSpace, name::Symbol, range, space_index::Int)Symbolic summation index for site- or mode-resolved operator expressions.
Represents labels such as i, j, or k that attach to operators and parameters. Pass a subspace type or integer position to specify which factor of a ProductSpace the index ranges over. Use with IndexedOperator to build objects like $a_i$ and with Σ to build sums $\sum_i a_i^\dagger a_i$.
Examples
julia> h = FockSpace(:site) ⊗ FockSpace(:cavity);
julia> i = Index(h, :i, 10, FockSpace(:site));
julia> j = Index(h, :j, 10, 1); # same subspace via integer position
julia> i == j
falseSee also has_index, IndexedOperator, Σ, change_index.
SecondQuantizedAlgebra.IndexedOperator — Function
IndexedOperator(op::Op, i::Index) -> OpReturn the indexed version of an operator.
IndexedOperator keeps the operator kind and quantum numbers, and only changes the symbolic index label. Use it to build objects such as a_i, σ_j₁₂, or x_k before forming sums with Σ.
Use IndexedOperator(op, NO_INDEX) to remove an index.
Examples
julia> h = FockSpace(:cavity);
julia> @qnumbers a::Destroy(h);
julia> i = Index(h, :i, 5, h);
julia> IndexedOperator(a, i)
a_iSee also Index, Σ, change_index.
SecondQuantizedAlgebra.IndexedVariable — Function
IndexedVariable(name::Symbol, i::Index) -> NumCreate a symbolic single-indexed parameter $\mathrm{name}(i)$.
Use this for site-dependent quantities in Hamiltonians, for example detunings or local couplings.
Examples
julia> h = FockSpace(:site) ⊗ FockSpace(:cavity);
julia> i = Index(h, :i, 10, 1);
julia> ω = IndexedVariable(:ω, i)
ω(i)See also DoubleIndexedVariable.
SecondQuantizedAlgebra.DoubleIndexedVariable — Function
DoubleIndexedVariable(name::Symbol, i::Index, j::Index; identical::Bool=true) -> NumCreate a symbolic two-index parameter $\mathrm{name}(i, j)$.
Use this for pairwise interactions such as hopping amplitudes or coupling matrices.
Keyword arguments
identical::Bool = true— iffalse, the variable evaluates to zero wheni == j, enforcing that the parameter is only defined for distinct sites.
Examples
julia> h = FockSpace(:site) ⊗ FockSpace(:cavity);
julia> i = Index(h, :i, 10, 1);
julia> j = Index(h, :j, 10, 1);
julia> J = DoubleIndexedVariable(:J, i, j; identical = false)
J(i, j)See also IndexedVariable.
SecondQuantizedAlgebra.Σ — Function
Σ(expr, i::Index, non_equal::Vector{Index} = Index[])
Σ(expr, i::Index, j::Index, rest::Index...)
∑(expr, i::Index, ...)Build symbolic sums over indices, for example $\sum_i expr$.
Returns a QAdd carrying i in its indices field. If expr does not depend on i, the sum is evaluated eagerly as i.range * expr.
The optional non_equal records pairwise inequality constraints i ≠ j per term. Diagonal splitting is performed automatically: if expr carries another free index j on the same Hilbert subspace as i, the contribution at i = j is emitted as a separate diagonal term and the off-diagonal term gains the constraint (i, j).
Multiple positional indices create nested sums: Σ(expr, i, j) is equivalent to Σ(Σ(expr, i), j). The Unicode alias ∑ is also exported.
Examples
julia> h = FockSpace(:site);
julia> @qnumbers a::Destroy(h);
julia> i = Index(h, :i, 3, h);
julia> Σ(IndexedOperator(a', i) * IndexedOperator(a, i), i)
Σ(i=1:3) a_i' * a_iSee also Index, IndexedOperator, constraint_pairs.
SecondQuantizedAlgebra.change_index — Function
change_index(expr, from::Index, to::Index)Rename an index in an expression by replacing from with to everywhere.
Operator indices are swapped directly. Symbolic prefactors (e.g. IndexedVariable, DoubleIndexedVariable) are substituted via Symbolics.substitute using the sym fields of the indices. DoubleIndexedVariable nodes with identical=false automatically evaluate to zero if the substitution makes both arguments equal.
Examples
julia> h = FockSpace(:site);
julia> @qnumbers a::Destroy(h);
julia> i = Index(h, :i, 5, h); j = Index(h, :j, 5, h);
julia> expr = IndexedVariable(:ω, i) * IndexedOperator(a, i);
julia> change_index(expr, i, j)
ω(j) * a_jSee also get_indices.
change_index(expr, pairs::AbstractDict{Index, Index})Simultaneous (batched) variant of change_index: substitute every key in pairs by its mapped value in one pass.
Unlike a sequence of single-pair change_index calls, this never produces an intermediate state where a renamed-into index temporarily collides with another encountered index. Use it when the rename describes a permutation or any map whose image overlaps its domain, e.g. swapping two indices:
julia> change_index(expr, Dict(i => j, j => i)) # swap, not fuseA pair (idx => idx) is a no-op; change_index(expr, Dict()) returns expr unchanged.
SecondQuantizedAlgebra.get_indices — Function
get_indices(expr) -> Vector{Index}Return all symbolic indices that appear in expr.
Returns a Vector{Index} of unique indices found in operator fields and summation metadata. Excludes the sentinel NO_INDEX.
Examples
julia> h = FockSpace(:site);
julia> @qnumbers a::Destroy(h);
julia> i = Index(h, :i, 5, h);
julia> get_indices(IndexedOperator(a, i))
1-element Vector{Index}:
iSecondQuantizedAlgebra.has_index — Function
has_index(idx::Index) -> BoolReturn true when idx is an actual symbolic index.
Returns false for the sentinel NO_INDEX, used for operators without an attached index.
SecondQuantizedAlgebra.assume_distinct_index — Function
assume_distinct_index(q::QAdd, pairs::Vector{Tuple{Index, Index}}) -> QAddRe-canonicalize q under the declared pairwise ≠ constraints on free indices, then run expand_completeness so any ground-state projectors that emerge from same-site composition are expanded.
Use this when two free indices semantically range over distinct atoms or modes but no Σ supplies the constraint. SQA cannot infer "different symbol implies different site" automatically: two operators carrying different free indices on the same Hilbert subspace remain in their physical order, and no same-site collapse fires between them. Declaring the pair distinct here lets the canonical sort resolve their relationship and triggers any composition or completeness rewriting it unlocks.
Examples
julia> h = NLevelSpace(:atom, 2);
julia> j = Index(h, :j, 5, h); k = Index(h, :k, 5, h);
julia> σ(i, m, idx) = IndexedOperator(Transition(h, :σ, i, m), idx);
julia> q = σ(2, 1, k) * σ(1, 2, j);
julia> assume_distinct_index(q, [(j, k)])
σ_j₁₂ * σ_k₂₁See also expand_completeness.
SecondQuantizedAlgebra.index_slot — Function
index_slot(x) -> Union{Int, Nothing}
index_slot(idx::Index) -> Union{Int, Nothing}Concrete slot integer of a per-slot index, or nothing for an abstract index. On an Index reads the slot field directly; on a symbol reads the IndexSlot metadata minted by (i::Index)(k), so consumers can recover the position k without parsing the symbol's name.
SecondQuantizedAlgebra.index_range — Function
index_range(idx::Index) -> NumThe summation range of idx (the user's Num, recovered from the intern table). Returns Num(0) for the sentinel NO_INDEX.
SecondQuantizedAlgebra.index_name — Function
index_name(idx::Index) -> SymbolThe display name of idx (recovered from the intern table).
SecondQuantizedAlgebra.index_sym — Function
index_sym(idx::Index) -> NumThe symbolic variable for idx, reconstructed from its interned name (plus the IndexSlot metadata for a per-slot index). SymbolicUtils hashconsing makes the reconstruction identical (===) to the originally minted symbol, so substitution and get_variables are unaffected. An anonymous concrete site (name_id == 0, slot == k) reconstructs to the integer Num(k), matching to_numeric's resolved-site convention.
SecondQuantizedAlgebra.get_sum_indices — Function
get_sum_indices(x::BasicSymbolic) -> Vector{Index}Summation indices carried by the indexed-sum node x. Only valid when has_sum_metadata(x) is true.
julia> h = FockSpace(:site);
julia> @qnumbers a::Destroy(h);
julia> i = Index(h, :i, 3, h);
julia> x = average(Σ(IndexedOperator(a', i) * IndexedOperator(a, i), i));
julia> SecondQuantizedAlgebra.get_sum_indices(x) == [i]
trueSee also get_sum_non_equal, has_sum_metadata.
SecondQuantizedAlgebra.get_sum_non_equal — Function
get_sum_non_equal(x::BasicSymbolic) -> Vector{Tuple{Index, Index}}Pairwise index-inequality constraints carried by the indexed-sum node x. An empty vector means no constraints. Only valid when has_sum_metadata(x) is true.
julia> h = FockSpace(:site);
julia> @qnumbers a::Destroy(h);
julia> i = Index(h, :i, 3, h);
julia> x = average(Σ(IndexedOperator(a', i) * IndexedOperator(a, i), i));
julia> isempty(SecondQuantizedAlgebra.get_sum_non_equal(x))
trueSee also get_sum_indices, has_sum_metadata.
SecondQuantizedAlgebra.has_sum_metadata — Function
has_sum_metadata(x) -> BoolWhether x is a moment-layer indexed-sum node (see is_indexed_sum) created by average on a summed expression. Retained as the public predicate name; equivalent to is_indexed_sum(x).
julia> h = FockSpace(:site);
julia> @qnumbers a::Destroy(h);
julia> i = Index(h, :i, 3, h);
julia> x = average(Σ(IndexedOperator(a', i) * IndexedOperator(a, i), i));
julia> SecondQuantizedAlgebra.has_sum_metadata(x)
trueSee also get_sum_indices, get_sum_non_equal.
QuantumCumulants.evaluate — Function
evaluate(eqs::AbstractMeanfieldEquations; limits=nothing, h=Int[])
evaluate(c::CorrelationFunction; limits=nothing, h=Int[])Materialise a symbolic indexed system into a concrete, fixed-size one by inserting the index ranges and unrolling the sums. The result is ready for ModelingToolkitBase.System; its operators carry no remaining sum scope.
Keyword arguments
limits=nothing: the concrete size of each symbolic index range, given as aPair(N => 3), a tuple of pairs, or aDict. Required whenever a range bound is symbolic.h=Int[]: restrict unrolling to these Hilbert subspaces (byacts_onindex). Empty unrolls every subspace covered bylimits.
QuantumCumulants.scale — Function
scale(eqs::AbstractMeanfieldEquations; h=Int[])
scale!(eqs::AbstractMeanfieldEquations; h=Int[])Reduce a permutation-symmetric indexed system to one representative per symmetry class, exploiting that every atom of an indexed family acts on the system identically. scale! mutates eqs in place; scale returns a new system.
Keyword arguments
h=Int[]: the Hilbert subspaces to scale, given by theiracts_onindices. Empty scales every symmetric (indexed) subspace and leaves the others untouched.
scale(c::CorrelationFunction)Scale both the τ-equations and the underlying eqs0 so the spectrum's steady-state lookup resolves against the same scaled state names.
QuantumCumulants.scale! — Function
scale(eqs::AbstractMeanfieldEquations; h=Int[])
scale!(eqs::AbstractMeanfieldEquations; h=Int[])Reduce a permutation-symmetric indexed system to one representative per symmetry class, exploiting that every atom of an indexed family acts on the system identically. scale! mutates eqs in place; scale returns a new system.
Keyword arguments
h=Int[]: the Hilbert subspaces to scale, given by theiracts_onindices. Empty scales every symmetric (indexed) subspace and leaves the others untouched.
Measurement Backaction
QuantumCumulants.NoiseMeanfieldEquations — Type
NoiseMeanfieldEquationsConcrete equation set produced by meanfield when efficiencies is supplied, adding a measurement-backaction noise drift to the deterministic equations. The direction tag (Forward or Backward) drives compile-time dispatch of the noise assembly. Like MeanfieldEquations it is graph-backed: graph is the source of truth and the vector fields are a derived view.
Fields
graph: the source of truth.equations: the deterministic averaged differential equations (derived view).noise_equations: the averaged noise (Brownian) drift per state.operator_equations,operator_noise_equations: the operator-level counterparts.states: the averages on the left-hand sides.operators: the operators on the left-hand sides.hamiltonian: the system Hamiltonian.jumps,jumps_dagger: the collapse operators and their adjoints.rates: the decay rates corresponding tojumps.efficiencies: the detector efficiencies per jump.iv: the independent (time) variable.order: the cumulant-expansion order, ornothing.direction:ForwardorBackwardevolution.
QuantumCumulants.translate_W_to_Y — Function
translate_W_to_Y(eqs::NoiseMeanfieldEquations; mix_choice=maximum)Rewrite an SDE whose noise drift is parametrised by the underlying Wiener process dW into one parametrised by the measurement record dY instead. The substitution dW = dY - sqrt(2η)·⟨J + J†⟩·dt adds a deterministic correction to the drift; the noise drift itself is unchanged.
For each equation the RHS is augmented by cumulant_expansion(-_dY_dS_extra_term(lhs_op, J, Jdagger, rates .* efficiencies)), cumulant-expanded to eqs.order when set. Returns a fresh NoiseMeanfieldEquations of the same direction. The augmented RHS is left unsimplified; apply simplify yourself for a canonical form.
QuantumCumulants.EvolutionDirection — Type
EvolutionDirectionSupertype of the singleton tags Forward and Backward used to dispatch the noise/retrodiction code path at compile time.
QuantumCumulants.Forward — Type
Forward Heisenberg evolution (positive sign on i[H, ·]).
QuantumCumulants.Backward — Type
Backward Heisenberg evolution (negative sign), used for retrodiction.
Utility functions
QuantumCumulants.find_missing — Function
find_missing(eqs::AbstractMeanfieldEquations; filter_func=nothing)Return the averages that appear on a right-hand side of eqs but are not yet among its tracked states: the equations still needed to close the system. A moment and its conjugate count as one: if either is already a state, neither is reported. filter_func mirrors complete!.
The report is representation-invariant: conjugate pairs are always folded onto one key, so the answer is the same whether eqs was closed with get_adjoints true or false.
SecondQuantizedAlgebra.find_operators — Function
find_operators(h::HilbertSpace, order::Int; names=nothing) -> VectorGenerate all unique operator products up to order factors for Hilbert space h.
Starts from fundamental_operators(h) and their adjoints, forms all products with up to order factors, then filters out zero terms and adjoint-duplicates. Useful for constructing the operator basis needed for cumulant expansions or moment equations. Pass names to override auto-generated operator names.
Examples
julia> h = FockSpace(:f);
julia> length(find_operators(h, 1))
1See also fundamental_operators, unique_ops.
ModelingToolkitBase.complete — Function
complete(eqs::AbstractMeanfieldEquations; order=nothing, kw...)Non-mutating complete!: derive equations of motion for every average that appears on a right-hand side but is not yet a state, returning a new closed system.
Keyword arguments
order=nothing: if given, the copy is first cumulant-expanded to this order (deterministic systems only) before closing.kw...: forwarded tocomplete!(max_iter,filter_func,get_adjoints).
See also: complete!, find_missing, meanfield.
QuantumCumulants.complete! — Function
complete!(eqs::AbstractMeanfieldEquations; max_iter=100_000, filter_func=nothing,
get_adjoints=false)Close eqs in place by repeatedly deriving equations of motion for every average that appears on a right-hand side but is not yet a state, until the set is self-contained. Closure reads and rewrites eqs.graph, so user RHS modifications and filter substitutions already recorded there survive. Right-hand sides are left unsimplified.
Keyword arguments
max_iter=100_000: runaway backstop; an error is raised if closure has not converged within this many iterations.filter_func=nothing: a predicatefilter_func(avg); rejected averages are dropped (substituted by 0), e.g. to discard phase-invariant terms.get_adjoints=false: by default only one representative per conjugate pair is tracked, with the partner recovered byconjat code generation (the minimal state set). Passtrueto integrate each conjugate as its own (redundant) state.
See also: complete, find_missing, meanfield.
SecondQuantizedAlgebra.unique_ops — Function
unique_ops(ops) -> VectorReturn unique operators from ops, treating op and op' (adjoint) as the same degree of freedom. Only the first occurrence of each operator/adjoint pair is kept.
Examples
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> length(unique_ops([a, a', a]))
1See also unique_ops!, fundamental_operators.
SecondQuantizedAlgebra.unique_ops! — Function
unique_ops!(ops) -> VectorIn-place version of unique_ops.
Examples
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> v = [a, a'];
julia> SecondQuantizedAlgebra.unique_ops!(v);
julia> length(v)
1QuantumCumulants.simplify! — Function
simplify!(eqs::AbstractMeanfieldEquations; kwargs...)
simplify(eqs::AbstractMeanfieldEquations; kwargs...)Run SymbolicUtils.simplify on every RHS in eqs (and on the noise drift RHSs of a NoiseMeanfieldEquations). simplify! mutates; simplify returns a fresh struct. The derivation pipeline leaves expressions raw so the cost of SymbolicUtils.simplify is paid only when explicitly requested.
SecondQuantizedAlgebra.fundamental_operators — Function
fundamental_operators(h::HilbertSpace; names=nothing) -> Vector{Op}Return the minimal generating set of operators for Hilbert space h.
Returns one Destroy per FockSpace; n(n+1)/2 - 1 Transition operators per NLevelSpace (upper-triangular plus diagonals, excluding the ground-state projector); all n² CollectiveTransition operators per CollectiveNLevelSpace; three Pauli or Spin operators per PauliSpace/SpinSpace; one Position and one Momentum per PhaseSpace; and the concatenation of the above for a ProductSpace. Pass names to override the auto-generated operator names.
Examples
julia> h = FockSpace(:cavity) ⊗ NLevelSpace(:atom, 2);
julia> length(fundamental_operators(h))
3See also find_operators, unique_ops.
SecondQuantizedAlgebra.to_numeric — Function
to_numeric(op, h::HilbertSpace, dims; backend, kwargs...)
to_numeric(op, basis [, d]; kwargs...)
to_numeric(op, state [, d]; kwargs...)
to_numeric(ops::AbstractVector, ...; kwargs...)Convert a symbolic operator expression to a numeric operator on the chosen backend.
The first form is backend-neutral and is recommended for portable code. Load QuantumOpticsBase or QuantumToolbox and pass QuantumOpticsBackend() or QuantumToolboxBackend(); backend may be omitted when exactly one bundled backend is loaded. Backend-native basis, state, and dimension forms are convenience overloads.
The representation contract is independent of the expression shape:
| Input | op_type | Result |
|---|---|---|
| static | nothing (default) | ordinary eager backend operator |
| static | identity | backend's lazy assembly |
| static | another callable | backend-defined representation |
| time-dependent | nothing or identity | native time-dependent operator |
Both bundled backends use sparse storage for the default static result. Examples of explicit conversions are dense for QuantumOptics and QuantumToolbox.to_dense for QuantumToolbox. Other backends may choose a different eager representation. Time-dependent conversion cannot apply a one-time materializer, so other op_type values are rejected.
Keyword arguments:
parameter=Dict(): substitutions for scalar symbolic coefficients.time_parameter=Dict(): scalar values or functions of time. A non-empty mapping selects time-dependent conversion (TimeDependentSumfor QuantumOptics,QobjEvofor QuantumToolbox). A value may also be a two-argument function(p, t) -> valueof the solver parameter vectorpand timet; the resultingQobjEvois then differentiable with respect top(forsesolve/mesolveoptimal control). This(p, t)form is QuantumToolbox-only; QuantumOptics has no solver parameter and raises anArgumentError. If any value function readsp, one-argument entriest -> f(t)and scalars are lifted to(p, t)form automatically.operators=Dict(): replacements for individual symbolic operators. Missing adjoints are inferred whenadjoint_ops=true(the default). Positionaldsupplies exact replacements without this inference.op_type=nothing: representation selection as described above.
dims follows the symbolic space convention: a Fock value is a cutoff, so 5 creates a six-dimensional space. In contrast, QuantumToolbox's native to_numeric(op, dims) convenience form accepts raw matrix dimensions. Conversion throws ArgumentError when a coefficient cannot be reduced to ComplexF64.
Examples
julia> using QuantumOpticsBase
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> b = FockBasis(5); ψ = fockstate(b, 2);
julia> real(expect(to_numeric(a' * a, b), ψ)) ≈ 2
trueSee also numeric_average.
SecondQuantizedAlgebra.numeric_average — Function
numeric_average(op, state[, d::AbstractDict{<:QSym}]) -> ComplexF64
numeric_average(op, states::AbstractVector[, d::AbstractDict{<:QSym}]) -> VectorExpectation value $\langle \psi | \hat{O} | \psi \rangle$ of a symbolic operator expression. Averaged BasicSymbolic expressions (from average) are unwrapped automatically. d substitutes symbolic operators with custom numeric operators before evaluation.
Examples
julia> using QuantumOpticsBase
julia> h = FockSpace(:f);
julia> @qnumbers a::Destroy(h);
julia> b = FockBasis(5); ψ = fockstate(b, 2);
julia> real(numeric_average(a' * a, ψ)) ≈ 2
trueSee also to_numeric, average.
QuantumCumulants.initial_values — Function
initial_values(eqs::AbstractMeanfieldEquations; defaults=Dict())Map every state's u(t) variable to its initial value, defaulting to zero(ComplexF64) for averages absent from defaults.
initial_values(eqs, state) # from a numeric quantum state
initial_values(eqs, u0::AbstractVector) # from a value vector aligned with eqs.statesQuantumCumulants.get_solution — Function
get_solution(sol, avg_or_op, eqs)Query an ODE/SDE solution for the trajectory of an average (or a QField, averaged internally). Resolves the user's operator to a tracked state through the single structural matcher match_moment, which folds Hermitian conjugation (the side bit recovers a stored conjugate) and the system's index/symmetry treatment, so a query posed in any equivalent symbolic form hits the same state.
QuantumCumulants.parameter_map — Function
parameter_map(sys::MTK.System, pairs)Keep only the entries of pairs whose key is a live unknown or parameter of the compiled system sys (MTK rejects superfluous entries).
parameter_map(eqs::AbstractMeanfieldEquations, pairs) -> DictTranslate a user-facing Pair/Dict of parameter assignments into an MTK-compatible map. An IndexedVariable callable key (e.g. g from g(i) = IndexedVariable(:g, i)) is matched by name to the Symbolics array parameter that evaluate synthesised from the per-atom references; a scalar value is broadcast to fill that array, an AbstractArray value passes through as the explicit per-atom values. Scalar (non-array) parameters pass through.
QuantumCumulants.modify_equations — Function
modify_equations(eqs::AbstractMeanfieldEquations, f::Function)Return a copy of eqs whose RHS for each equation has been rewritten by f. The function is called as f(lhs_op, rhs), receiving the operator form of the LHS (undo_average(eq.lhs)) and the symbolic RHS, and returning a new RHS.
f(lhs, rhs) = rhs + cumulant_expansion(average(commutator(1im * Hadd, lhs)), 2)
eqs_mod = modify_equations(eqs, f)See also modify_equations!.
QuantumCumulants.modify_equations! — Function
modify_equations!(eqs::AbstractMeanfieldEquations, f::Function)In-place version of modify_equations. Rewrites each drift in eqs.graph with f(lhs_op, rhs), where lhs_op is the node key (the operator form of the LHS).
QuantumCumulants.substitute! — Function
substitute(eqs::AbstractMeanfieldEquations, dict::AbstractDict)
substitute!(eqs::AbstractMeanfieldEquations, dict::AbstractDict)Substitute dict into every RHS of eqs (and into the noise drift RHSs of a NoiseMeanfieldEquations). Keys of dict are symbolic expressions to replace: parameters, or averages known to vanish (e.g. by symmetry), so that zeroing them closes the hierarchy onto the remaining moments. substitute! mutates eqs; substitute returns a fresh struct.
Only the drifts are rewritten; the moment set (the LHS states and operators) is unchanged. To also drop the substituted moments as unknowns, complete onto the reduced subset (e.g. with a filter_func) or rebuild meanfield on it.
# close a phase-invariant model onto ⟨σ⁺ᵢσ⁻ⱼ⟩ by asserting the single-operator averages vanish
eqs = meanfield(ops, H, J; rates, order = 2)
eqs = substitute(eqs, Dict(average(σ(2, 1, i)) => 0 for i in 1:N))SecondQuantizedAlgebra.make_time_dependent — Function
make_time_dependent(expr, iv) -> exprLift every iv-free average leaf in expr into a time-dependent variable name(iv) (typed Real when the operator is Hermitian, else Number) carrying the operator in AverageOperator metadata (and the VariableSource set by @variables, so it reads as a ModelingToolkit unknown). Non-average structure is rebuilt only where a child changed. The lifted node is a leaf: the walk does not descend into iv. The default name is uniqueness-only; downstream code may rename for display without changing identity.
Numeric backends
Symbolic-to-numeric conversion (to_numeric, numeric_average, initial_values) runs through SecondQuantizedAlgebra's pluggable backend interface. Loading a backend (using QuantumOpticsBase or using QuantumToolbox) selects the concrete implementation; the conversion details are documented in SQA's numeric conversion section. The re-exported types and hooks below are the interface for selecting and implementing a backend.
SecondQuantizedAlgebra.NumericBackend — Type
NumericBackendAbstract supertype for numeric-conversion backends. A backend is normally a concrete singleton whose methods implement the exported numeric hooks. See Adding a numeric backend for the interface.
Bundled implementations are selected with QuantumOpticsBackend or QuantumToolboxBackend after loading the corresponding package.
SecondQuantizedAlgebra.QuantumOpticsBackend — Type
QuantumOpticsBackend()Select QuantumOpticsBase.jl. Active after using QuantumOpticsBase.
SecondQuantizedAlgebra.QuantumToolboxBackend — Type
QuantumToolboxBackend()Select QuantumToolbox.jl. Active after using QuantumToolbox.
SecondQuantizedAlgebra.numeric_backend — Function
numeric_backend(state) -> NumericBackendReturn the numeric backend owning state. Third-party backends must implement this hook for their state types to support numeric_average and state-based to_numeric.
SecondQuantizedAlgebra.numeric_basis — Function
numeric_basis(be, h::HilbertSpace, dims)Build the full numeric basis for Hilbert space h with user dimensions dims (Fock cutoff / spin number; ignored for spaces that carry their own level count). Returns a Basis (QuantumOptics) or integer dims (QuantumToolbox).
SecondQuantizedAlgebra.numeric_subbasis — Function
numeric_subbasis(be, basis, slot::Int)The subsystem basis for slot of a composite basis (the basis itself for a simple one).
SecondQuantizedAlgebra.numeric_operator — Function
numeric_operator(be, op::Op, subbasis)Map a single-site operator op to its numeric matrix on subbasis (the subsystem basis for op's slot). The main extension method is a closed k === OP_* value-branch on op.kind; the separate extension point numeric_operator(be, ::Val{kind}, op, subbasis) is the fallthrough for adding backend support for an existing role and throws by default.
SecondQuantizedAlgebra.numeric_identity — Function
numeric_identity(be, basis)The identity operator on basis (lazy on a composite basis).
SecondQuantizedAlgebra.numeric_embed — Function
numeric_embed(be, basis, slot::Int, leaf)Place the single-site numeric operator leaf at slot of basis, returning an operator on the full space (the leaf unchanged for a simple basis, a lazy/concrete tensor for a composite one).
SecondQuantizedAlgebra.numeric_num_subsystems — Function
numeric_num_subsystems(be, basis) -> IntNumber of tensor-product subsystems represented by basis. Backends use this hook to validate indexed-site layouts before emitting any numeric operators.
SecondQuantizedAlgebra.numeric_assemble — Function
numeric_assemble(be, basis, terms)Assemble a static lazy operator from a backend-neutral terms list. Each entry is (c::ComplexF64, factors::Vector{<leaf>}) with factors already embedded at their slots. The result is one concrete vector-backed lazy type for any term count.
SecondQuantizedAlgebra.numeric_assemble_td — Function
numeric_assemble_td(be, basis, td_terms)Like numeric_assemble but each coefficient is Union{ComplexF64, Function} (a constant or a t -> ComplexF64), producing the backend's native time-dependent operator.
SecondQuantizedAlgebra.numeric_materialize — Function
numeric_materialize(be, op, op_type)Materialize a static lazily assembled operator op according to op_type, applied exactly once at the public boundary. Implementations must treat nothing as a request for their ordinary eager operator and identity as a request for op unchanged. Other callables are backend-defined. The eager representation need not be sparse, although both bundled backends choose sparse storage.
This hook is not used by numeric_average, which consumes the lazy assembly, or by time-dependent conversion, which returns the backend's native time-dependent operator.
SecondQuantizedAlgebra.numeric_expect — Function
numeric_expect(be, numop, state)Expectation value ⟨state| numop |state⟩ (a ComplexF64), or a Vector{ComplexF64} when state is a vector of states.