SpinAdaptedSecondQuantization.jl

SpinAdaptedSecondQuantization.jl is a Julia package for doing symbolic second quantization mainly targeted at quantum chemistry methods, such as post Hartree-Fock methods.

To install the package, run

(v1.8) pkg> add SpinAdaptedSecondQuantization

Julia version 1.8 or higher is required.

The package can then be loaded

using SpinAdaptedSecondQuantization

Basic Structure

The core type of the package is the Expression type. This contains an array of Term types. A term is a product that can contain a combination of various elements. These are:

  • A scalar multiplier
  • An array of operators
  • An array of tensors
  • An array of Kronecker deltas
  • An array of summation indices
  • A dictionary of orbital constraints

Orbital Indices

Most types will contain orbital indices. For convenience these are represented as integers, but they are purely symbolic. When printed, they will be given names according to the semi-standard names for general molecular orbital (MO) indices pqrstuvw. This means that index 1 will be printed as 'p', 2 as 'q' and 8 as 'w'. For indices larger than 8 the names wrap around and are numbered with subscripts so index 9 will be printed as 'p₁' and index 78 as 'u₉'.

Operators

A small variety of operator types is supported, see (REF TO LIST) for full list.

A few examples are:

julia> E(1, 2)E_pq
julia> E(1, 2) * E(3, 4)E_pq E_rs
julia> e(1, 2, 3, 4)- δ_qr E_ps + E_pq E_rs
julia> fermion(1, α)a⁻_pα
julia> fermiondag(2, β)a†_qβ

Tensors

The simplest tensor type is the RealTensor which has a name and an array of MO-indices.

Example:

julia> real_tensor("h", 1, 2)h_pq
julia> real_tensor("g", 1, 2, 3, 4)g_pqrs

Some other types of tensors are supported with various symmetries in the indices. See (REF TO LIST) for full list.

Kronecker Deltas

Kronecker deltas constrain indices to be equal or else the term would be zero. They can have two or more indices each.

Example:

julia> delta(1, 2)δ_pq
julia> δ(1, 2) # Unicode version equivalent to the one aboveδ_pq
julia> d1 = δ(1, 2)δ_pq
julia> d2 = δ(2, 3)δ_qr
julia> d1 * d2 # Compacts delta expression since all are equalδ_pqr

Summation Indices

A term can represent a sum over all values of a specific (or many) MO-indices.

Example:

julia> a = summation(E(1, 2), [1])∑_p(E_pq)
julia> b = ∑(E(1, 2), 1:2) # Unicode∑_pq(E_pq)
julia> a * b # Automatically renames summation indices to not collide∑_prs(E_pq E_rs)

Orbital Constraints

An important feature is the ability to tell a term whether an MO-index belongs to a particular subset of all orbitals, specifically whether it belongs to the occupied or virtual set.

Example:

julia> occupied(1)C(p∈o)
julia> virtual(2)C(q∈v)

here the constraints are printed explicitly as that is all that exists in the term, but if the index shows up other places in the term, the indices are colored to indicate the constraints instead:

julia> a = E(1, 2) * virtual(1) * occupied(2)E_pq

Where occupied orbitals are colored green and virtual are colored blue. If you would rather not have colored prints (printing to file, no color support in terminal, preferance, ...) you can easily turn this off:

julia> SpinAdaptedSecondQuantization.disable_color()
julia> aE_pq C(p∈v, q∈o)
julia> SASQ.enable_color() # Can use acronym SASQ instead of full module name
julia> aE_pq

Constraints will transfer to other indices through Kronecker deltas and will produce a zero-term if they are unsatisfiable:

julia> a = δ(1, 2) * occupied(1) # Now q is also occupiedδ_pq
julia> b = δ(2, 3) * virtual(3)δ_qr
julia> a * b # gives 0 as they cannot be occupied and virtual0

Hartree-Fock energy expression

julia> h = ∑(rsym_tensor("h", 1, 2) * E(1, 2), 1:2)∑_pq(h_pq E_pq)
julia> g = simplify(∑(rsym_tensor("g", 1:4...) * e(1:4...), 1:4))- ∑_pqr(g_prqr E_pq) + ∑_pqrs(g_pqrs E_pq E_rs)
julia> H = h + g∑_pq(h_pq E_pq) - ∑_pqr(g_prqr E_pq) + ∑_pqrs(g_pqrs E_pq E_rs)
julia> E_hf = simplify_heavy(act_on_ket(H, 0))2 ∑_i(h_ii) + 4 ∑_ij(g_iijj) - 2 ∑_ij(g_ijij)