API

Hilbert Spaces

SecondQuantizedAlgebra.ProductSpaceType
ProductSpace{T} <: HilbertSpace

Composite 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)

See also , tensor.

source
SecondQuantizedAlgebra.NLevelSpaceType
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.

source
SecondQuantizedAlgebra.PauliSpaceType
PauliSpace(name::Symbol) <: HilbertSpace

Hilbert 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)

See also Pauli, SpinSpace.

source
SecondQuantizedAlgebra.SpinSpaceType
SpinSpace(name::Symbol) <: HilbertSpace

Hilbert 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.

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

source

Operators

SecondQuantizedAlgebra.QTermType
QTerm

A 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 product
  • ne::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.

source
SecondQuantizedAlgebra.QTermDictType
QTermDict

Alias 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.

source
SecondQuantizedAlgebra.@qnumbersMacro
@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, Pauli, Spin.

source
SecondQuantizedAlgebra.DestroyFunction
Destroy(h::FockSpace, name::Symbol) -> Op

Bosonic 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' * a

See also Create, FockSpace, @qnumbers.

source
SecondQuantizedAlgebra.TransitionFunction
Transition(h::NLevelSpace, name::Symbol, i, j) -> Op

Transition 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.

source
SecondQuantizedAlgebra.PauliFunction
Pauli(h::PauliSpace, name::Symbol, axis::Int) -> Op

Pauli 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
1

See also PauliSpace, Spin.

source
SecondQuantizedAlgebra.SpinFunction
Spin(h::SpinSpace, name::Symbol, axis::Int) -> Op

Angular 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 * Sy

See also SpinSpace, Pauli.

source
SecondQuantizedAlgebra.PositionFunction
Position(h::PhaseSpace, name::Symbol) -> Op

Position (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 * p

See also Momentum, PhaseSpace.

source
SecondQuantizedAlgebra.OpType
Op <: QSym

The single concrete leaf operator. A kind::OpKind tag selects the physical role (annihilation, creation, transition, Pauli, spin, position, momentum); the remaining fields are shared storage interpreted per kind:

  • name::Symbol: display name
  • space_index::Int: position in a ProductSpace
  • index::Index: symbolic site index, or NO_INDEX
  • l1, l2, g, nlev::Int32: packed level/axis data. Transition uses all four (i, j, ground state, number of levels); Pauli/Spin store the axis in l1; Fock/PhaseSpace leave them zero.

Construct via the role-named functions Destroy, Create, Transition, 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.

source
SecondQuantizedAlgebra.OpKindType
OpKind

Runtime tag distinguishing the seven 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).

source

Algebra

SymbolicUtils.simplifyFunction
simplify(expr::QField) -> QAdd

Normal-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)
0

See also normal_order, expand, expand_completeness.

source
SecondQuantizedAlgebra.normal_orderFunction
normal_order(expr::QField) -> QAdd

Route 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' * a

See also simplify, expand_completeness, normal_to_symmetric, symmetric_to_normal.

source
SecondQuantizedAlgebra.symmetric_to_normalFunction
symmetric_to_normal(expr) -> QAdd

Convert 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 * p

See also normal_to_symmetric, normal_order.

source
SymbolicUtils.expandFunction
expand(expr::QField) -> QAdd

Expand 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) * a

See also simplify.

source
SecondQuantizedAlgebra.expand_completenessFunction
expand_completeness(q) -> QAdd

Rewrite 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.

source
SecondQuantizedAlgebra.assume_distinct_indexFunction
assume_distinct_index(q::QAdd, pairs::Vector{Tuple{Index, Index}}) -> QAdd

Re-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.

source
SecondQuantizedAlgebra.qadjointFunction
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.

source
SecondQuantizedAlgebra.inner_adjointFunction
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.

source

Average

SecondQuantizedAlgebra.averageFunction
average(expr) -> BasicSymbolic | Number

Build 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.

source
SecondQuantizedAlgebra.undo_averageFunction
undo_average(expr) -> QAdd

Recursively 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
true
source
SecondQuantizedAlgebra.make_time_dependentFunction
make_time_dependent(expr, iv) -> expr

Lift every iv-free average leaf in expr into a time-dependent Number variable name(iv) 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.

source
SecondQuantizedAlgebra.is_averageFunction
is_average(x) -> Bool

Whether 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)
source

Utility Functions

SecondQuantizedAlgebra.acts_onFunction
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])
source
SecondQuantizedAlgebra.find_operatorsFunction
find_operators(h::HilbertSpace, order::Int; names=nothing) -> Vector

Generate 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))
1

See also fundamental_operators, unique_ops.

source
SecondQuantizedAlgebra.unique_opsFunction
unique_ops(ops) -> Vector

Return 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]))
1

See also unique_ops!, fundamental_operators.

source
SecondQuantizedAlgebra.unique_ops!Function
unique_ops!(ops) -> Vector

In-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)
1
source
SecondQuantizedAlgebra.fundamental_operatorsFunction
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); 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))
3

See also find_operators, unique_ops.

source
SecondQuantizedAlgebra.prefactorFunction
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)
2

See also operators, sorted_arguments.

source
SecondQuantizedAlgebra.operatorsFunction
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))
2

See also prefactor, sorted_arguments.

source
SecondQuantizedAlgebra.constraint_pairsFunction
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))
1
source
SecondQuantizedAlgebra.sorted_argumentsFunction
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'))
2
source
SecondQuantizedAlgebra.order_keyFunction
order_key(op::Op) -> Tuple

Total, identity-faithful ordering key: a comparable tuple that orders operators reproducibly and ties two exactly when they are isequal. The leading kind value gives the cross-family order; the packed l1..nlev fields keep distinct levels/axes distinct.

source
SecondQuantizedAlgebra.term_order_keyFunction
term_order_key(t::QTerm) -> Tuple

Total ordering key for an operator product, built from order_key: length, then operator-by-operator, then the canonical-sorted non-equal constraints.

source
SecondQuantizedAlgebra.qadd_order_keyFunction
qadd_order_key(q::QAdd) -> Vector

Total, reproducible ordering key for a sum: its term/coefficient pairs in sorted order, so two QAdds compare with < on their keys and tie exactly when they are isequal. The coefficient contributes its printed form (a reproducible tiebreak, not a numeric order).

source
SymbolicUtils.substituteFunction
substitute(expr, d::Dict)

Substitute symbolic parameters and/or operators in expr using dictionary d.

Supports both symbolic coefficient replacement (for example x => 2) and operator replacement. The result is re-canonicalized and returned as a QAdd.

Examples

julia> h = FockSpace(:f);

julia> @qnumbers a::Destroy(h);

julia> @variables x;

julia> substitute(x * a' * a, Dict(x => 2))
2 * a' * a

See also change_index.

source
SecondQuantizedAlgebra.transition_superscriptFunction
transition_superscript(x::Bool) -> Bool

Set whether Transition level indices are rendered as superscripts (true, default) or subscripts (false) in LaTeX output via Latexify.jl.

  • true: ${\sigma}^{{ij}}$
  • false: ${\sigma}_{{ij}}$

Examples

julia> SecondQuantizedAlgebra.transition_superscript(false)
false

julia> SecondQuantizedAlgebra.transition_superscript(true)
true
source
SecondQuantizedAlgebra.to_numericFunction
to_numeric(op, basis [, d::AbstractDict{<:QSym}])
to_numeric(op, state [, d::AbstractDict{<:QSym}])

Convert a symbolic operator expression to a numeric QuantumOpticsBase operator. d substitutes individual QSyms with custom numeric operators. Throws ArgumentError if any prefactor cannot be reduced to a concrete ComplexF64.

Examples

julia> using QuantumOpticsBase

julia> h = FockSpace(:f);

julia> @qnumbers a::Destroy(h);

julia> b = FockBasis(5); ψ = fockstate(b, 2);

julia> real(QuantumOpticsBase.expect(to_numeric(a' * a, ψ), ψ)) ≈ 2
true

See also numeric_average.

source
SecondQuantizedAlgebra.numeric_averageFunction
numeric_average(op, state[, d::AbstractDict{<:QSym}]) -> ComplexF64
numeric_average(op, states::AbstractVector[, d::AbstractDict{<:QSym}]) -> Vector

Expectation 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
true

See also to_numeric, average.

source

Symbolic Summations

SecondQuantizedAlgebra.IndexType
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
false

See also has_index, IndexedOperator, Σ, change_index.

source
SecondQuantizedAlgebra.IndexedOperatorFunction
IndexedOperator(op::Op, i::Index) -> Op

Return 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_i

See also Index, Σ, change_index.

source
SecondQuantizedAlgebra.IndexedVariableFunction
IndexedVariable(name::Symbol, i::Index) -> Num

Create 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.

source
SecondQuantizedAlgebra.DoubleIndexedVariableFunction
DoubleIndexedVariable(name::Symbol, i::Index, j::Index; identical::Bool=true) -> Num

Create 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 — if false, the variable evaluates to zero when i == 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.

source
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_i

See also Index, IndexedOperator, constraint_pairs.

source
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_i

See also Index, IndexedOperator, constraint_pairs.

source
SecondQuantizedAlgebra.change_indexFunction
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_j

See also get_indices.

source
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 fuse

A pair (idx => idx) is a no-op; change_index(expr, Dict()) returns expr unchanged.

source
SecondQuantizedAlgebra.get_indicesFunction
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}:
 i
source
SecondQuantizedAlgebra.has_indexFunction
has_index(idx::Index) -> Bool

Return true when idx is an actual symbolic index.

Returns false for the sentinel NO_INDEX, used for operators without an attached index.

source
SecondQuantizedAlgebra.index_slotFunction
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.

source
SecondQuantizedAlgebra.index_rangeFunction
index_range(idx::Index) -> Num

The summation range of idx (the user's Num, recovered from the intern table). Returns Num(0) for the sentinel NO_INDEX.

source
SecondQuantizedAlgebra.index_symFunction
index_sym(idx::Index) -> Num

The 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.

source
SecondQuantizedAlgebra.has_sum_metadataFunction
has_sum_metadata(x) -> Bool

Whether 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)
true

See also get_sum_indices, get_sum_non_equal.

source
SecondQuantizedAlgebra.get_sum_non_equalFunction
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))
true

See also get_sum_indices, has_sum_metadata.

source