Atomic configurations

We define a configuration to be a set of orbitals with their associated occupation (i.e. the number of electron on that orbital). We can represent a particular configuration with an instance of the Configuration type. The orbitals of a configuration can be unsorted (default) or sorted according to the canonical ordering (first by $n$, then by $\ell$, &c). It is important to allow for arbitrary order, since permutation of the orbitals in a configuration, in general incurs a phase shift of matrix elements, &c.

AtomicLevels.ConfigurationType
struct Configuration{<:AbstractOrbital}

Represents a configuration – a set of orbitals and their associated occupation number. Furthermore, each orbital can be in one of the following states: :open, :closed or :inactive.

Constructors

Configuration(orbitals :: Vector{<:AbstractOrbital},
              occupancy :: Vector{Int},
              states :: Vector{Symbol}
              [; sorted=false])

Configuration(orbitals :: Vector{Tuple{<:AbstractOrbital, Int, Symbol}}
              [; sorted=false])

In the first case, the parameters of each orbital have to be passed as separate vectors, and the orbitals and occupancy have to be of the same length. The states vector can be shorter and then the latter orbitals that were not explicitly specified by states are assumed to be :open.

The second constructor allows you to pass a vector of tuples instead, where each tuple is a triplet (orbital :: AbstractOrbital, occupancy :: Int, state :: Symbol) corresponding to each orbital.

In all cases, all the orbitals have to be distinct. The orbitals in the configuration will be sorted (if sorted) according to the ordering defined for the particular AbstractOrbital.

source

The @c_str and @rc_str string macros can be used to conveniently construct configurations:

AtomicLevels.@c_strMacro
@c_str -> Configuration{Orbital}

Construct a Configuration, representing a non-relativistic configuration, out of a string. With the added string macro suffix s, the configuration is sorted.

Examples

julia> c"1s2 2s"
1s² 2s

julia> c"1s² 2s"
1s² 2s

julia> c"1s2.2s"
1s² 2s

julia> c"[Kr] 4d10 5s2 4f2"
[Kr]ᶜ 4d¹⁰ 5s² 4f²

julia> c"[Kr] 4d10 5s2 4f2"s
[Kr]ᶜ 4d¹⁰ 4f² 5s²
source
AtomicLevels.@rc_strMacro
@rc_str -> Configuration{RelativisticOrbital}

Construct a Configuration representing a relativistic configuration out of a string. With the added string macro suffix s, the configuration is sorted.

Examples

julia> rc"[Ne] 3s 3p- 3p"
[Ne]ᶜ 3s 3p- 3p

julia> rc"[Ne] 3s 3p-2 3p4"
[Ne]ᶜ 3s 3p-² 3p⁴

julia> rc"[Ne] 3s 3p-² 3p⁴"
[Ne]ᶜ 3s 3p-² 3p⁴

julia> rc"2p- 1s"s
1s 2p-
source

Interface

For example, it is possible to index into a configuration, including with a range of indices, returning a sub-configuration consisting of only those orbitals. With an integer index, an (orbital, occupancy, state) tuple is returned.

julia> config = c"1s2c 2si 2p3"
[He]ᶜ 2sⁱ 2p³

julia> config[2]
(2s, 1, :inactive)

julia> config[1:2]
[He]ᶜ 2sⁱ

julia> config[[3,1]]
[He]ᶜ 2p³

The configuration can also be iterated over. Each item is a (orbital, occupancy, state) tuple.

julia> for (o, nelec, s) in config
           @show o, nelec, s
       end
(o, nelec, s) = (1s, 2, :closed)
(o, nelec, s) = (2s, 1, :inactive)
(o, nelec, s) = (2p, 3, :open)

Various other methods exist to manipulate or transform configurations or to query them for information.

AtomicLevels.issimilarFunction
issimilar(a::Configuration, b::Configuration)

Compares the electronic configurations a and b, only considering the constituent orbitals and their occupancy, but disregarding their ordering and states (:open, :closed, &c).

Examples

julia> a = c"1s 2s"
1s 2s

julia> b = c"2si 1s"
2sⁱ 1s

julia> issimilar(a, b)
true

julia> a==b
false
source
Base.:==Method
==(a::Configuration, b::Configuration)

Tests if configurations a and b are the same, considering orbital occupancy, ordering, and states.

Examples

julia> c"1s 2s" == c"1s 2s"
true

julia> c"1s 2s" == c"1s 2si"
false

julia> c"1s 2s" == c"2s 1s"
false
source
AtomicLevels.num_electronsMethod
num_electrons(c::Configuration) -> Int

Return the number of electrons in the configuration.

julia> num_electrons(c"1s2")
2

julia> num_electrons(rc"[Kr] 5s2 5p-2 5p2")
42
source
AtomicLevels.num_electronsMethod
num_electrons(c::Configuration, o::AbstractOrbital) -> Int

Returns the number of electrons on orbital o in configuration c. If o is not part of the configuration, returns 0.

julia> num_electrons(c"1s 2s2", o"2s")
2

julia> num_electrons(rc"[Rn] Qf-5 Pf3", ro"Qf-")
5

julia> num_electrons(c"[Ne]", o"3s")
0
source
AtomicLevels.orbitalsMethod
orbitals(c::Configuration{O}) -> Vector{O}

Access the underlying list of orbitals

julia> orbitals(c"1s2 2s2")
2-element Vector{Orbital{Int64}}:
 1s
 2s

julia> orbitals(rc"1s2 2p-2")
2-element Vector{RelativisticOrbital{Int64}}:
 1s
 2p-
source
Base.delete!Function
delete!(c::Configuration, o::AbstractOrbital)

Remove the entire subshell corresponding to orbital o from configuration c.

julia> delete!(c"[Ar] 4s2 3d10 4p2", o"4s")
[Ar]ᶜ 3d¹⁰ 4p²
source
Base.:+Function
+(::Configuration, ::Configuration)

Add two configurations together. If both configuration have an orbital, the number of electrons gets added together, but in this case the status of the orbitals must match.

julia> c"1s" + c"2s"
1s 2s

julia> c"1s" + c"1s"
1s²
source
Base.:-Function
-(configuration::Configuration, orbital::AbstractOrbital[, n=1])

Remove n electrons in the orbital orbital from the configuration configuration. If the orbital had previously been :closed or :inactive, it will now be :open.

source
Base.closeFunction
close(c::Configuration)

Return a corresponding configuration where where all the orbitals are marked :closed.

See also: close!

source
Base.fillFunction
fill(c::Configuration)

Returns a corresponding configuration where the orbitals are completely filled (as determined by degeneracy).

See also: fill!

source
Base.fill!Function
fill!(c::Configuration)

Sets all the occupancies in configuration c to maximum, as determined by the degeneracy function.

See also: fill

source
Base.inFunction
in(o::AbstractOrbital, c::Configuration) -> Bool

Checks if orbital o is part of configuration c.

julia> in(o"2s", c"1s2 2s2")
true

julia> o"2p" ∈ c"1s2 2s2"
false
source
Base.filterFunction
filter(f, c::Configuration) -> Configuration

Filter out the orbitals from configuration c for which the predicate f returns false. The predicate f needs to take three arguments: orbital, occupancy and state.

julia> filter((o,occ,s) -> o.ℓ == 1, c"[Kr]")
2p⁶ᶜ 3p⁶ᶜ 4p⁶ᶜ
source
Base.replaceFunction
replace(conf, a => b[; append=false])

Substitute one electron in orbital a of conf by one electron in orbital b. If conf is unsorted the substitution is performed in-place, unless append, in which case the new orbital is appended instead.

Examples

julia> replace(c"1s2 2s", o"1s" => o"2p")
1s 2p 2s

julia> replace(c"1s2 2s", o"1s" => o"2p", append=true)
1s 2s 2p

julia> replace(c"1s2 2s"s, o"1s" => o"2p")
1s 2s 2p
source
AtomicLevels.coreFunction
core(::Configuration) -> Configuration

Return the core configuration (i.e. the sub-configuration of all the orbitals that are marked :closed).

julia> core(c"1s2c 2s2c 2p6c 3s2")
[Ne]ᶜ

julia> core(c"1s2 2s2")
∅

julia> core(c"1s2 2s2c 2p6c")
2s²ᶜ 2p⁶ᶜ
source
AtomicLevels.peelFunction
peel(::Configuration) -> Configuration

Return the non-core part of the configuration (i.e. orbitals not marked :closed).

julia> peel(c"1s2c 2s2c 2p3")
2p³

julia> peel(c"[Ne] 3s 3p3")
3s 3p³
source
AtomicLevels.activeFunction
active(::Configuration) -> Configuration

Return the part of the configuration marked :open.

julia> active(c"1s2c 2s2i 2p3i 3s2")
3s²
source
AtomicLevels.inactiveFunction
inactive(::Configuration) -> Configuration

Return the part of the configuration marked :inactive.

julia> inactive(c"1s2c 2s2i 2p3i 3s2")
2s²ⁱ 2p³ⁱ
source
AtomicLevels.boundFunction
bound(::Configuration) -> Configuration

Return the bound part of the configuration (see also isbound).

julia> bound(c"1s2 2s2 2p4 Ks2 Kp1")
1s² 2s² 2p⁴
source
AtomicLevels.continuumFunction
continuum(::Configuration) -> Configuration

Return the non-bound (continuum) part of the configuration (see also isbound).

julia> continuum(c"1s2 2s2 2p4 Ks2 Kp1")
Ks² Kp
source
AtomicLevels.parityMethod
parity(::Configuration) -> Parity

Return the parity of the configuration.

julia> parity(c"1s 2p")
odd

julia> parity(c"1s 2p2")
even

See also: Parity

source
AtomicLevels.nonrelconfigurationFunction
nonrelconfiguration(c::Configuration{<:RelativisticOrbital}) -> Configuration{<:Orbital}

Reduces a relativistic configuration down to the corresponding non-relativistic configuration.

julia> c = rc"1s2 2p-2 2s 2p2 3s2 3p-"s
1s² 2s 2p-² 2p² 3s² 3p-

julia> nonrelconfiguration(c)
1s² 2s 2p⁴ 3s² 3p
source

Generating configuration lists

The operator can be used to easily generate lists of configurations from existing pieces. E.g. to create all the valence configurations on top of an closed core, you only need to write

julia> c"[Ne]" ⊗ [c"3s2", c"3s 3p", c"3p2"]
3-element Vector{Configuration{Orbital{Int64}}}:
 [Ne]ᶜ 3s²
 [Ne]ᶜ 3s 3p
 [Ne]ᶜ 3p²

That can be combined with the @rcs_str string macro to easily generate all possible relativistic configurations from a non-relativistic definition:

julia> rc"[Ne] 3s2" ⊗ rcs"3p2"
3-element Vector{Configuration{RelativisticOrbital{Int64}}}:
 [Ne]ᶜ 3s² 3p-²
 [Ne]ᶜ 3s² 3p- 3p
 [Ne]ᶜ 3s² 3p²
AtomicLevels.:⊗Function
⊗(::Union{Configuration, Vector{Configuration}}, ::Union{Configuration, Vector{Configuration}})

Given two collections of Configurations, it creates an array of Configurations with all possible juxtapositions of configurations from each collection.

Examples

julia> c"1s" ⊗ [c"2s2", c"2s 2p"]
2-element Vector{Configuration{Orbital{Int64}}}:
 1s 2s²
 1s 2s 2p

julia> [rc"1s", rc"2s"] ⊗ [rc"2p-", rc"2p"]
4-element Vector{Configuration{RelativisticOrbital{Int64}}}:
 1s 2p-
 1s 2p
 2s 2p-
 2s 2p
source
AtomicLevels.@rcs_strMacro
@rcs_str -> Vector{Configuration{RelativisticOrbital}}

Construct a Vector of all Configurations corresponding to the non-relativistic nℓ orbital with the given occupancy from the input string.

The string is assumed to have the following syntax: $(n)$(ℓ)$(occupancy), where n and occupancy are integers, and is in spectroscopic notation.

Examples

julia> rcs"3p2"
3-element Vector{Configuration{var"#s00"} where var"#s00"<:RelativisticOrbital}:
 3p-²
 3p- 3p
 3p²
source

Spin configurations

AtomicLevels.spin_configurationsFunction
spin_configurations(configuration)

Generate all possible configurations of spin-orbitals from configuration, i.e. all permissible values for the quantum numbers n, , mℓ, ms for each electron. Example:

julia> spin_configurations(c"1s2")
1-element Vector{SpinConfiguration{SpinOrbital{Orbital{Int64}, Tuple{Int64, HalfIntegers.Half{Int64}}}}}:
 1s₀α 1s₀β

julia> spin_configurations(c"1s2"s)
1-element Vector{SpinConfiguration{SpinOrbital{Orbital{Int64}, Tuple{Int64, HalfIntegers.Half{Int64}}}}}:
 1s₀α 1s₀β

julia> spin_configurations(c"1s ks")
4-element Vector{SpinConfiguration{SpinOrbital{var"#s00", Tuple{Int64, HalfIntegers.Half{Int64}}} where var"#s00"<:Orbital}}:
 1s₀α ks₀α
 1s₀β ks₀α
 1s₀α ks₀β
 1s₀β ks₀β
source
spin_configurations(configurations)

For each configuration in configurations, generate all possible configurations of spin-orbitals.

source
AtomicLevels.substitutionsFunction
substitutions(src::SpinConfiguration, dst::SpinConfiguration)

Find all orbital substitutions going from spin-configuration src to configuration dst.

source
AtomicLevels.@sc_strMacro
@sc_str -> SpinConfiguration{<:SpinOrbital{<:Orbital}}

A string macro to construct a non-relativistic SpinConfiguration.

Examples

julia> sc"1s₀α 2p₋₁β"
1s₀α 2p₋₁β

julia> sc"ks(0,-1/2) l[4](-3,1/2)"
ks₀β lg₋₃α
source
AtomicLevels.@rsc_strMacro
@rsc_str -> SpinConfiguration{<:SpinOrbital{<:RelativisticOrbital}}

A string macro to construct a relativistic SpinConfiguration.

Examples

julia> rsc"1s(1/2) 2p(-1/2)"
1s(1/2) 2p(-1/2)

julia> rsc"ks(-1/2) l[4]-(-5/2)"
ks(-1/2) lg-(-5/2)
source
AtomicLevels.@scs_strMacro
@scs_str -> Vector{<:SpinConfiguration{<:Orbital}}

Generate all possible spin-configurations out of a string. With the added string macro suffix s, the configuration is sorted.

Examples

julia> scs"1s2 2p2"
15-element Vector{SpinConfiguration{SpinOrbital{Orbital{Int64}, Tuple{Int64, HalfIntegers.Half{Int64}}}}}:
 1s₀α 1s₀β 2p₋₁α 2p₋₁β
 1s₀α 1s₀β 2p₋₁α 2p₀α
 1s₀α 1s₀β 2p₋₁α 2p₀β
 1s₀α 1s₀β 2p₋₁α 2p₁α
 1s₀α 1s₀β 2p₋₁α 2p₁β
 1s₀α 1s₀β 2p₋₁β 2p₀α
 1s₀α 1s₀β 2p₋₁β 2p₀β
 1s₀α 1s₀β 2p₋₁β 2p₁α
 1s₀α 1s₀β 2p₋₁β 2p₁β
 1s₀α 1s₀β 2p₀α 2p₀β
 1s₀α 1s₀β 2p₀α 2p₁α
 1s₀α 1s₀β 2p₀α 2p₁β
 1s₀α 1s₀β 2p₀β 2p₁α
 1s₀α 1s₀β 2p₀β 2p₁β
 1s₀α 1s₀β 2p₁α 2p₁β
source
AtomicLevels.@rscs_strMacro
@rscs_str -> Vector{<:SpinConfiguration{<:RelativisticOrbital}}

Generate all possible relativistic spin-configurations out of a string. With the added string macro suffix s, the configuration is sorted.

Examples

julia> rscs"1s2 2p2"
6-element Vector{SpinConfiguration{SpinOrbital{RelativisticOrbital{Int64}, Tuple{HalfIntegers.Half{Int64}}}}}:
 1s(-1/2) 1s(1/2) 2p(-3/2) 2p(-1/2)
 1s(-1/2) 1s(1/2) 2p(-3/2) 2p(1/2)
 1s(-1/2) 1s(1/2) 2p(-3/2) 2p(3/2)
 1s(-1/2) 1s(1/2) 2p(-1/2) 2p(1/2)
 1s(-1/2) 1s(1/2) 2p(-1/2) 2p(3/2)
 1s(-1/2) 1s(1/2) 2p(1/2) 2p(3/2)
source

Excited configurations

AtomicLevels.jl provides an easy interface for generating lists of configurations which are the result of exciting one or more orbitals of a reference set to a set of substitution orbitals. This is done with excited_configurations, which provides various parameters for controlling which excitations are generated. A very simple example could be

julia> excited_configurations(c"1s2", os"2[s-p]"...)
4-element Vector{Configuration{Orbital{Int64}}}:
 1s²
 1s 2s
 2s²
 2p²

which as we see contains all configurations generated by at most exciting two orbitals 1s² and keeping the overall parity. By lifting these restrictions, more configurations can be generated:

julia> excited_configurations(c"1s2 2s", os"3[s-p]"...,
                              keep_parity=false, max_excitations=2)
14-element Vector{Configuration{Orbital{Int64}}}:
 1s² 2s
 1s 2s²
 1s 2s 3s
 1s 2s 3p
 1s² 3s
 1s² 3p
 2s² 3s
 2s² 3p
 2s 3s²
 2s 3s 3p
 1s 3s²
 1s 3s 3p
 2s 3p²
 1s 3p²

julia> excited_configurations(c"1s2 2s", os"3[s-p]"...,
                              keep_parity=false, max_excitations=3)
17-element Vector{Configuration{Orbital{Int64}}}:
 1s² 2s
 1s 2s²
 1s 2s 3s
 1s 2s 3p
 1s² 3s
 1s² 3p
 2s² 3s
 2s² 3p
 2s 3s²
 2s 3s 3p
 1s 3s²
 1s 3s 3p
 2s 3p²
 1s 3p²
 3s² 3p
 3s 3p²
 3p³

Since configurations by default are unsorted, when exciting from SpinConfigurations, the substitutions are performed in-place:

julia> excited_configurations(first(scs"1s2"), sos"2[s-p]"...)
21-element Vector{SpinConfiguration{SpinOrbital{Orbital{Int64}, Tuple{Int64, HalfIntegers.Half{Int64}}}}}:
 1s₀α 1s₀β
 2s₀α 1s₀β
 2s₀β 1s₀β
 1s₀α 2s₀α
 1s₀α 2s₀β
 2s₀α 2s₀β
 2p₋₁α 2p₋₁β
 2p₋₁α 2p₀α
 2p₋₁α 2p₀β
 2p₋₁α 2p₁α
 ⋮
 2p₋₁β 2p₀β
 2p₋₁β 2p₁α
 2p₋₁β 2p₁β
 2p₀α 2p₀β
 2p₀α 2p₁α
 2p₀α 2p₁β
 2p₀β 2p₁α
 2p₀β 2p₁β
 2p₁α 2p₁β
AtomicLevels.excited_configurationsFunction
excited_configurations([fun::Function, ] cfg::Configuration,
                       orbitals::AbstractOrbital...
                       [; min_excitations=0, max_excitations=:doubles,
                        min_occupancy=[0, 0, ...], max_occupancy=[..., g_i, ...],
                        keep_parity=true])

Generate all excitations from the reference set cfg by substituting at least min_excitations and at most max_excitations of the substitution orbitals. min_occupancy specifies the minimum occupation number for each of the source orbitals (default 0) and equivalently max_occupancy specifies the maximum occupation number (default is the degeneracy for each orbital). keep_parity controls whether the excited configuration has to have the same parity as cfg. Finally, fun allows modification of the substitution orbitals depending on the source orbitals, which is useful for generating ionized configurations. If fun returns nothing, that particular substitution will be rejected.

Examples

julia> excited_configurations(c"1s2", o"2s", o"2p")
4-element Vector{Configuration{Orbital{Int64}}}:
 1s²
 1s 2s
 2s²
 2p²

julia> excited_configurations(c"1s2 2p", o"2p")
2-element Vector{Configuration{Orbital{Int64}}}:
 1s² 2p
 2p³

julia> excited_configurations(c"1s2 2p", o"2p", max_occupancy=[2,2])
1-element Vector{Configuration{Orbital{Int64}}}:
 1s² 2p

julia> excited_configurations(first(scs"1s2"), sos"k[s]"...) do dst,src
           if isbound(src)
               # Generate label that indicates src orbital,
               # i.e. the resultant hole
               SpinOrbital(Orbital(Symbol("[$(src)]"), dst.orb.ℓ), dst.m)
           else
               dst
           end
       end
9-element Vector{SpinConfiguration{SpinOrbital{var"#s00", Tuple{Int64, HalfIntegers.Half{Int64}}} where var"#s00"<:Orbital}}:
 1s₀α 1s₀β
 [1s₀α]s₀α 1s₀β
 [1s₀α]s₀β 1s₀β
 1s₀α [1s₀β]s₀α
 1s₀α [1s₀β]s₀β
 [1s₀α]s₀α [1s₀β]s₀α
 [1s₀α]s₀β [1s₀β]s₀α
 [1s₀α]s₀α [1s₀β]s₀β
 [1s₀α]s₀β [1s₀β]s₀β

julia> excited_configurations((a,b) -> a.m == b.m ? a : nothing,
                              spin_configurations(c"1s"), sos"k[s-d]"..., keep_parity=false)
8-element Vector{SpinConfiguration{SpinOrbital{var"#s00", Tuple{Int64, HalfIntegers.Half{Int64}}} where var"#s00"<:Orbital}}:
 1s₀α
 ks₀α
 kp₀α
 kd₀α
 1s₀β
 ks₀β
 kp₀β
 kd₀β
source

Index