Internals

Docstrings of internal types and functions, for reading next to the code. They describe the current implementation and may change without notice. The rules they implement are on the Architecture page.

Terms and canonicalization

A QAdd stores its terms in a QTermDict. Every operation that changes an operator product ends in canonicalize!, which sorts with partial_sort! and then streams the product through the passes of stream!.

SecondQuantizedAlgebra.QTermDict — Type
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.SiteCmp — Type
SiteCmp

Result of site_compare, the site relation of two operators in the canonical sort.

Less and Greater order operators on distinct sites. Equal marks a provably shared site, where the reduce and commute passes may act. Undetermined marks two free indices that may or may not coincide, where no rule may act. Both of the latter leave the pair in place; they are separate values so that a comparison cannot return one where the other is meant.

source
SecondQuantizedAlgebra.partial_sort! — Function
partial_sort!(ops::Vector{Op}, ne::Vector{NonEqualPair})

In-place stable partial-sort using site_compare. Distinct-site adjacents are placed in canonical order; Equal and Undetermined pairs are left in physical order so the sibling passes can interpret them.

source
SecondQuantizedAlgebra.stream! — Function
stream!(out::QTermDict, ops::Vector{Op}, c::CNum, ne::Vector{NonEqualPair})

The default canonicalization pipeline: reduce, commute, reduce, then store in out.

The first reduce composes same-site Transition and Pauli pairs, which compose rather than commute. Commute then swaps the remaining same-site pairs (Fock, Spin, PhaseSpace and CollectiveTransition) and emits their commutator residuals. The trailing reduce catches a composition that a residual creates next to a same-site neighbour, such as the contracted operator of a Spin commutator.

source

Exact coefficients

A Coeff holds an exact number, a polynomial in the parameters, or a raw SymbolicUtils expression. The exact numbers and polynomials are built from the following types.

SecondQuantizedAlgebra.GaussianRational — Type
GaussianRational{T}

An exact Gaussian rational (re + im*i) / den, an element of ℚ(i). The denominator is positive and gcd(re, im, den) == 1, so every value has exactly one representation and isequal compares fields. A Gaussian integer has den == 1, and its arithmetic needs no gcd.

It is a storage type, not a Number: methods of a new Number subtype on ==, hash and the arithmetic operators invalidate SymbolicUtils code that dispatches on abstract Number arguments, which slowed its simplify by about a fifth. An exact value enters a symbolic expression as a Rational or Complex{Rational} instead.

T = Int is the small tier. Its arithmetic is checked and throws OverflowError, like Rational{Int}; the kernels mul_checked/add_checked report the overflow as a flag instead. No field of a small-tier value is typemin(Int), so negation and conjugation never overflow. T = BigInt is the big tier.

source
SecondQuantizedAlgebra.ExactComplex — Type
ExactComplex
BigExactComplex

The two tiers of exact numbers, GaussianRational{Int} and GaussianRational{BigInt}.

Exactness is a property of the type: an exact value is a GaussianRational, and a ComplexF64 is always inexact. Every function that can produce an exact scalar takes the exact type E of its tier. Arithmetic in ExactComplex is checked and throws OverflowError; tiered (cnum.jl) catches that and redoes the whole computation in BigExactComplex, so the steps must not mutate their inputs. A result returns to ExactComplex whenever every scalar fits.

This file also keeps radicals canonical. A radical atom is the hashconsed SymbolicUtils.Const{SymReal}(p) of a prime p with an exponent in (0, 1). Radicands factor by trial division below 2^16; a larger cofactor is tested with Primes.isprime and otherwise split by Pollard-Brent rho under a fixed step budget. A radicand that cannot be split within the budget stays an unevaluated symbolic leaf, which has no canonical form.

source
SecondQuantizedAlgebra.Monomial — Type
Monomial{E}

One term of a parameter polynomial: scalar * ∏ symᵢ^expᵢ. Factors are sorted by objectid and deduplicated; Rational{Int} exponents let radicals of a single atom merge (sqrt(p)*sqrt(p) = p). An exact scalar has the type E of its tier, ExactComplex or BigExactComplex, and every term of one Poly shares that tier. The slot field stores the scalar as an E, with a float packed into it (pack_scalar), so the layout is concrete; read it with monomial_scalar. A radical atom (a hashconsed Const{SymReal} of a prime, see radical_atom) always keeps its exponent in (0, 1): the constructor folds any integer part into scalar, so √2·√2 normalizes to the scalar 2, and √2·√3/√6/√24/2 all reduce to the same two prime atoms Const(2)^(1/2), Const(3)^(1/2) and compare isequal.

source
SecondQuantizedAlgebra.Poly — Type
Poly

A sparse multivariate polynomial over named parameters (a sum of distinct Monomial terms in canonical order), kept off SymbolicUtils hashconsing and lowered to Complex{Num} only at the symbolic boundaries (see poly_to_num).

source

Orderings

The total, identity-faithful ordering used by downstream packages to choose canonical representatives.

SecondQuantizedAlgebra.order_key — Function
order_key(op::Op) -> Tuple

Total ordering key of an operator that ties two operators exactly when they are isequal.

The tuple is (space_index, kind, name rank, index, l1, l2, g, nlev): operators group by subspace, then by kind, then alphabetically by name, and the packed level and axis fields keep the remaining operators apart. Unlike hash, the key is reproducible across sessions and Julia versions.

source
SecondQuantizedAlgebra.qadd_order_key — Function
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

Averages and sums

An average is a SymbolicUtils term with the operation AvgFunc, and the average of an indexed sum is a term whose scope is a SumScope argument. The accessors read that scope.

SecondQuantizedAlgebra.AvgFunc — Type
AvgFunc

Singleton callable used as the operation of average Term nodes; defining show_call on it gives the ⟨…⟩ display without type piracy.

source
SecondQuantizedAlgebra.SumFunc — Type
SumFunc

Singleton callable used as the operation of moment-layer indexed-sum Term nodes (cf. AvgFunc). A node is sym_sum(body, scope): the summed body is an arbitrary averaged expression and the SumScope rides as a plain Term argument, so it participates in isequal/hash (metadata would be ignored, letting two differently-scoped sums over the same body wrongly cancel). Because the operation is neither + nor *, Add/Mul keep the node opaque, so the body and scope survive SymbolicUtils canonicalization.

source
SecondQuantizedAlgebra.SumScope — Type
SumScope

Opaque carrier for an indexed-sum's summation scope (indices::Vector{Index} and ne::Vector{NonEqualPair}). Stored as a single scalar argument of a SumFunc node: SymbolicUtils array-ifies a bare Vector argument (losing the Index objects), but a scalar struct is kept opaque and recoverable, exactly like the QField argument of an average node. Custom ==/isequal/hash compare it by value, so two sums cancel only when their scopes are equal.

source
SecondQuantizedAlgebra.is_indexed_sum — Function
is_indexed_sum(x) -> Bool

Whether x is a sum of averages over an index, as made by average of a Σ.

The indices and constraints of the sum are read with get_sum_indices and get_sum_non_equal, and its summand with get_sum_body.

Examples

julia> h = NLevelSpace(:atom, 2); i = Index(h, :i, 10, h);julia> x = average(Σ(IndexedOperator(Transition(h, :σ, 2, 2), i), i))Σ(i=1:10) ⟨σ_i₂₂⟩julia> is_indexed_sum(x)true

See also indexed_sum, average.

source
SecondQuantizedAlgebra.get_sum_indices — Function
get_sum_indices(q::QAdd) -> Vector{Index}
get_sum_indices(q::QSym) -> Vector{Index}

The summation indices bound by q, in the order of its sum scope. A QSym and an unsummed QAdd bind none. The result is a copy, so mutating it does not change q.

julia> h = FockSpace(:site);julia> @qnumbers a::Destroy(h);julia> i = Index(h, :i, 3, h);julia> SecondQuantizedAlgebra.get_sum_indices(Σ(IndexedOperator(a, i), i)) == [i]true

See also get_indices, constraint_pairs.

source
get_sum_indices(x) -> Vector{Index}

Indices summed over in the sum of averages x.

The function is not exported.

Examples

julia> using SecondQuantizedAlgebra: get_sum_indicesjulia> h = NLevelSpace(:atom, 2); i = Index(h, :i, 10, h);julia> get_sum_indices(average(Σ(IndexedOperator(Transition(h, :σ, 2, 2), i), i)))1-element Vector{Index}: i

See also is_indexed_sum, get_sum_non_equal, get_sum_body.

source
SecondQuantizedAlgebra.get_sum_non_equal — Function
get_sum_non_equal(x) -> Vector{Tuple{Index, Index}}

Pairs of indices required to differ in the sum of averages x.

The function is not exported.

Examples

julia> using SecondQuantizedAlgebra: get_sum_non_equaljulia> h = NLevelSpace(:atom, 2); i = Index(h, :i, 10, h); j = Index(h, :j, 10, h);julia> get_sum_non_equal(average(Σ(IndexedOperator(Transition(h, :σ, 2, 2), i), i, [j])))1-element Vector{Tuple{Index, Index}}: (i, j)

See also is_indexed_sum, get_sum_indices.

source
SecondQuantizedAlgebra.indexed_sum — Function
indexed_sum(body, indices::Vector{Index}; non_equal = Tuple{Index, Index}[])

Sum of the averaged expression body over indices, the inverse of get_sum_body.

non_equal lists pairs of indices that must differ. This rebuilds a sum after its summand has been rewritten, for instance when a cumulant expansion factorizes the averages in it. The function is not exported.

Examples

julia> using SecondQuantizedAlgebra: indexed_sum, get_sum_bodyjulia> h = NLevelSpace(:atom, 2); i = Index(h, :i, 10, h);julia> x = average(Σ(IndexedOperator(Transition(h, :σ, 2, 2), i), i));julia> isequal(indexed_sum(get_sum_body(x), [i]), x)true

See also get_sum_body, is_indexed_sum.

source