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.Configuration
— Typestruct 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
.
The @c_str
and @rc_str
string macros can be used to conveniently construct configurations:
AtomicLevels.@c_str
— Macro@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²
AtomicLevels.@rc_str
— Macro@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-
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.issimilar
— Functionissimilar(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
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
AtomicLevels.num_electrons
— Methodnum_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
AtomicLevels.num_electrons
— Methodnum_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
AtomicLevels.orbitals
— Methodorbitals(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-
Base.delete!
— Functiondelete!(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²
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²
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
.
Base.close
— Functionclose(c::Configuration)
Return a corresponding configuration where where all the orbitals are marked :closed
.
See also: close!
AtomicLevels.close!
— FunctionBase.fill
— Functionfill(c::Configuration)
Returns a corresponding configuration where the orbitals are completely filled (as determined by degeneracy
).
See also: fill!
Base.fill!
— Functionfill!(c::Configuration)
Sets all the occupancies in configuration c
to maximum, as determined by the degeneracy
function.
See also: fill
Base.in
— Functionin(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
Base.filter
— Functionfilter(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⁶ᶜ
Base.replace
— Functionreplace(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
AtomicLevels.core
— Functioncore(::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⁶ᶜ
AtomicLevels.peel
— Functionpeel(::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³
AtomicLevels.active
— Functionactive(::Configuration) -> Configuration
Return the part of the configuration marked :open
.
julia> active(c"1s2c 2s2i 2p3i 3s2")
3s²
AtomicLevels.inactive
— Functioninactive(::Configuration) -> Configuration
Return the part of the configuration marked :inactive
.
julia> inactive(c"1s2c 2s2i 2p3i 3s2")
2s²ⁱ 2p³ⁱ
AtomicLevels.bound
— Functionbound(::Configuration) -> Configuration
Return the bound part of the configuration (see also isbound
).
julia> bound(c"1s2 2s2 2p4 Ks2 Kp1")
1s² 2s² 2p⁴
AtomicLevels.continuum
— Functioncontinuum(::Configuration) -> Configuration
Return the non-bound (continuum) part of the configuration (see also isbound
).
julia> continuum(c"1s2 2s2 2p4 Ks2 Kp1")
Ks² Kp
AtomicLevels.parity
— Methodparity(::Configuration) -> Parity
Return the parity of the configuration.
julia> parity(c"1s 2p")
odd
julia> parity(c"1s 2p2")
even
See also: Parity
AtomicLevels.nonrelconfiguration
— Functionnonrelconfiguration(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
AtomicLevels.relconfigurations
— Functionrelconfigurations(c::Configuration{<:Orbital}) -> Vector{<:Configuration{<:RelativisticOrbital}}
Generate all relativistic configurations from the non-relativistic configuration c
, by applying rconfigurations_from_orbital
to each subshell and combining the results.
AtomicLevels.multiplicity
— Methodmultiplicity(::Configuration)
Calculates the number of Slater determinants corresponding to the configuration.
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 Configuration
s, it creates an array of Configuration
s 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
AtomicLevels.@rcs_str
— Macro@rcs_str -> Vector{Configuration{RelativisticOrbital}}
Construct a Vector
of all Configuration
s 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{<:RelativisticOrbital}}:
3p-²
3p- 3p
3p²
Spin configurations
AtomicLevels.SpinConfiguration
— TypeSpinConfiguration
Specialization of Configuration
for configurations consisting of SpinOrbital
s.
AtomicLevels.spin_configurations
— Functionspin_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{<:Orbital, Tuple{Int64, HalfIntegers.Half{Int64}}}}}:
1s₀α ks₀α
1s₀β ks₀α
1s₀α ks₀β
1s₀β ks₀β
spin_configurations(configurations)
For each configuration in configurations
, generate all possible configurations of spin-orbitals.
AtomicLevels.substitutions
— Functionsubstitutions(src::SpinConfiguration, dst::SpinConfiguration)
Find all orbital substitutions going from spin-configuration src
to configuration dst
.
AtomicLevels.@sc_str
— Macro@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₋₃α
AtomicLevels.@rsc_str
— Macro@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)
AtomicLevels.@scs_str
— Macro@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₁β
AtomicLevels.@rscs_str
— Macro@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)
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 SpinConfiguration
s, 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_configurations
— Functionexcited_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{<:Orbital, Tuple{Int64, HalfIntegers.Half{Int64}}}}}:
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{<:Orbital, Tuple{Int64, HalfIntegers.Half{Int64}}}}}:
1s₀α
ks₀α
kp₀α
kd₀α
1s₀β
ks₀β
kp₀β
kd₀β
Index
AtomicLevels.Configuration
AtomicLevels.SpinConfiguration
AtomicLevels.:⊗
AtomicLevels.active
AtomicLevels.bound
AtomicLevels.close!
AtomicLevels.continuum
AtomicLevels.core
AtomicLevels.excited_configurations
AtomicLevels.inactive
AtomicLevels.issimilar
AtomicLevels.multiplicity
AtomicLevels.nonrelconfiguration
AtomicLevels.num_electrons
AtomicLevels.num_electrons
AtomicLevels.orbitals
AtomicLevels.parity
AtomicLevels.peel
AtomicLevels.relconfigurations
AtomicLevels.spin_configurations
AtomicLevels.substitutions
Base.:+
Base.:-
Base.:==
Base.close
Base.delete!
Base.fill
Base.fill!
Base.filter
Base.in
Base.replace
AtomicLevels.@c_str
AtomicLevels.@rc_str
AtomicLevels.@rcs_str
AtomicLevels.@rsc_str
AtomicLevels.@rscs_str
AtomicLevels.@sc_str
AtomicLevels.@scs_str