Skip to content

Commit

Permalink
Reading/writing orbitals to/from file (#75)
Browse files Browse the repository at this point in the history
* Implemented write/read of {,Relativistic}Orbital to file (#73)

* Implemented reading/writing SpinOrbitals to/from file (#73)

* Use Base.parse to generate various AbstractOrbitals (closes #56)

* String macro for spin-configurations (closes #41)

* Support Unicode subscripts when parsing spin-{orbitals,configurations} (#41)

* Support Unicode occupations/states when parsing configurations (#36, #73)

* Support Unicode in term symbols (#36)

* Implemented serialization of Configurations (closes #73)

* Unicode docstrings
  • Loading branch information
jagot authored Mar 24, 2021
1 parent 07120dc commit 17ab948
Show file tree
Hide file tree
Showing 14 changed files with 443 additions and 53 deletions.
2 changes: 2 additions & 0 deletions docs/src/configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ julia> rc"[Ne] 3s2" ⊗ rcs"3p2"
SpinConfiguration
spin_configurations
substitutions
@sc_str
@rsc_str
@scs_str
```

Expand Down
1 change: 1 addition & 0 deletions src/AtomicLevels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ using HalfIntegers
using Combinatorics

include("common.jl")
include("unicode.jl")
include("parity.jl")
include("orbitals.jl")
include("relativistic_orbitals.jl")
Expand Down
146 changes: 136 additions & 10 deletions src/configurations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,9 @@ function get_noble_core_name(config::Configuration{O}) where O
end

function state_sym(state::AbstractString)
if state == "c"
if state == "c" || state == ""
:closed
elseif state == "i"
elseif state == "i" || state == ""
:inactive
else
:open
Expand All @@ -337,19 +337,27 @@ function core_configuration(::Type{O}, element::AbstractString, state::AbstractS
sorted=sorted)
end

function parse_orbital(::Type{O}, orb_str) where {O<:AbstractOrbital}
function parse_orbital_occupation(::Type{O}, orb_str) where {O<:AbstractOrbital}
m = match(r"^(([0-9]+|.)([a-z]|\[[0-9]+\])[-]{0,1})([0-9]*)([*ci]{0,1})$", orb_str)
orbital_from_string(O, m[1]) , (m[4] == "") ? 1 : parse(Int, m[4]), state_sym(m[5])
m2 = match(r"^(([0-9]+|.)([a-z]|\[[0-9]+\])[-]{0,1})([¹²³⁴⁵⁶⁷⁸⁹⁰]*)([ᶜⁱ]{0,1})$", orb_str)
orb,occ,state = if !isnothing(m)
m[1], m[4], m[5]
elseif !isnothing(m2)
m2[1], from_superscript(m2[4]), m2[5]
else
throw(ArgumentError("Unknown subshell specification $(orb_str)"))
end
parse(O, orb) , (occ == "") ? 1 : parse(Int, occ), state_sym(state)
end

function Base.parse(::Type{Configuration{O}}, conf_str::AbstractString; sorted=false) where {O<:AbstractOrbital}
function Base.parse(::Type{Configuration{O}}, conf_str; sorted=false) where {O<:AbstractOrbital}
isempty(conf_str) && return Configuration{O}(sorted=sorted)
orbs = split(conf_str, r"[\. ]")
core_m = match(r"\[([a-zA-Z]+)\]([*ci]{0,1})", first(orbs))
if core_m != nothing
core_m = match(r"\[([a-zA-Z]+)\]([*ciᶜⁱ]{0,1})", first(orbs))
if !isnothing(core_m)
core_config = core_configuration(O, core_m[1], core_m[2], sorted)
if length(orbs) > 1
peel_config = Configuration(parse_orbital.(Ref(O), orbs[2:end]))
peel_config = Configuration(parse_orbital_occupation.(Ref(O), orbs[2:end]))
Configuration(vcat(core_config.orbitals, peel_config.orbitals),
vcat(core_config.occupancy, peel_config.occupancy),
vcat(core_config.states, peel_config.states),
Expand All @@ -358,7 +366,7 @@ function Base.parse(::Type{Configuration{O}}, conf_str::AbstractString; sorted=f
core_config
end
else
Configuration(parse_orbital.(Ref(O), orbs), sorted=sorted)
Configuration(parse_orbital_occupation.(Ref(O), orbs), sorted=sorted)
end
end

Expand All @@ -380,6 +388,12 @@ configuration, out of a string. With the added string macro suffix
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²
Expand Down Expand Up @@ -407,6 +421,9 @@ julia> rc"[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-
```
Expand Down Expand Up @@ -459,6 +476,56 @@ Base.length(conf::Configuration) = length(conf.orbitals)
Base.lastindex(conf::Configuration) = length(conf)
Base.eltype(conf::Configuration{O}) where O = (O,Int,Symbol)

function Base.write(io::IO, conf::Configuration{O}) where O
write(io, 'c')
if O <: Orbital
write(io, 'n')
elseif O <: RelativisticOrbital
write(io, 'r')
elseif O <: SpinOrbital
write(io, 's')
end
write(io, length(conf))
for (orb,occ,state) in conf
write(io, orb, occ, first(string(state)))
end
write(io, conf.sorted)
end

function Base.read(io::IO, ::Type{Configuration})
read(io, Char) == 'c' || throw(ArgumentError("Failed to read a Configuration from stream"))
kind = read(io, Char)
O = if kind == 'n'
Orbital
elseif kind == 'r'
RelativisticOrbital
elseif kind == 's'
SpinOrbital
else
throw(ArgumentError("Unknown orbital type $(kind)"))
end
n = read(io, Int)
occupations = Vector{Int}(undef, n)
states = Vector{Symbol}(undef, n)
orbitals = map(1:n) do i
orb = read(io, O)
occupations[i] = read(io, Int)
s = read(io, Char)
states[i] = if s == 'o'
:open
elseif s == 'c'
:closed
elseif s == 'i'
:inactive
else
throw(ArgumentError("Unknown orbital state $(s)"))
end
orb
end
sorted = read(io, Bool)
Configuration(orbitals, occupations, states, sorted=sorted)
end

function Base.isless(a::Configuration{<:O}, b::Configuration{<:O}) where {O<:AbstractOrbital}
a = sorted(a)
b = sorted(b)
Expand Down Expand Up @@ -1023,6 +1090,65 @@ consisting of [`SpinOrbital`](@ref)s.
"""
const SpinConfiguration{O<:SpinOrbital} = Configuration{O}

function Base.parse(::Type{SpinConfiguration{SO}}, conf_str; sorted=false) where {O<:AbstractOrbital,SO<:SpinOrbital{O}}
isempty(conf_str) && return SpinConfiguration{SO}(sorted=sorted)
orbs = split(conf_str, r"[ ]")
core_m = match(r"\[([a-zA-Z]+)\]([*ci]{0,1})", first(orbs))
if !isnothing(core_m)
core_config = first(spin_configurations(core_configuration(O, core_m[1], core_m[2], sorted)))
if length(orbs) > 1
peel_orbitals = parse.(Ref(SO), orbs[2:end])
orbitals = vcat(core_config.orbitals, peel_orbitals)
Configuration(orbitals,
ones(Int, length(orbitals)),
vcat(core_config.states, fill(:open, length(peel_orbitals))),
sorted=sorted)
else
core_config
end
else
Configuration(parse.(Ref(SO), orbs), ones(Int, length(orbs)), sorted=sorted)
end
end

"""
@sc_str -> SpinConfiguration{<:SpinOrbital{<:Orbital}}
A string macro to construct a non-relativistic [`SpinConfiguration`](@ref).
# Examples
```jldoctest
julia> sc"1s₀α 2p₋₁β"
1s₀α 2p₋₁β
julia> sc"ks(0,-1/2) l[4](-3,1/2)"
ks₀β lg₋₃α
```
"""
macro sc_str(conf_str, suffix="")
parse(SpinConfiguration{SpinOrbital{Orbital}}, conf_str, sorted=suffix=="s")
end

"""
@rsc_str -> SpinConfiguration{<:SpinOrbital{<:RelativisticOrbital}}
A string macro to construct a relativistic [`SpinConfiguration`](@ref).
# Examples
```jldoctest
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)
```
"""
macro rsc_str(conf_str, suffix="")
parse(SpinConfiguration{SpinOrbital{RelativisticOrbital}}, conf_str, sorted=suffix=="s")
end

function Base.show(io::IO, c::SpinConfiguration)
ascii = get(io, :ascii, false)
nc = length(c)
Expand Down Expand Up @@ -1162,7 +1288,7 @@ Calculates the number of Slater determinants corresponding to the configuration.
"""
multiplicity(c::Configuration) = prod(binomial.(degeneracy.(c.orbitals), c.occupancy))

export Configuration, @c_str, @rc_str, @scs_str, issimilar,
export Configuration, @c_str, @rc_str, @sc_str, @rsc_str, @scs_str, issimilar,
num_electrons, core, peel, active, inactive, bound, continuum, parity, , @rcs_str,
SpinConfiguration, spin_configurations, substitutions, close!,
nonrelconfiguration, relconfigurations
38 changes: 27 additions & 11 deletions src/orbitals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ struct Orbital{N<:MQ} <: AbstractOrbital
end
end

Orbital{N}(n::N, ℓ::Int) where {N<:MQ} = Orbital(n, ℓ)

Base.:(==)(a::Orbital, b::Orbital) =
a.n == b.n && a.== b.

Expand Down Expand Up @@ -209,6 +211,26 @@ julia> angular_momentum_ranges(o"4f")
angular_momentum_ranges(orbital::AbstractOrbital) =
map(j -> -j:j, angular_momenta(orbital))

# ** Saving/loading

Base.write(io::IO, o::Orbital{Int}) = write(io, 'i', o.n, o.ℓ)
Base.write(io::IO, o::Orbital{Symbol}) = write(io, 's', sizeof(o.n), o.n, o.ℓ)

function Base.read(io::IO, ::Type{Orbital})
kind = read(io, Char)
n = if kind == 'i'
read(io, Int)
elseif kind == 's'
b = Vector{UInt8}(undef, read(io, Int))
readbytes!(io, b)
Symbol(b)
else
error("Unknown Orbital type $(kind)")
end
= read(io, Int)
Orbital(n, ℓ)
end

# * Orbital construction from strings

parse_orbital_n(m::RegexMatch,i=1) =
Expand All @@ -225,18 +247,12 @@ function parse_orbital_ℓ(m::RegexMatch,i=2)
end
end

function orbital_from_string(::Type{O}, orb_str::AbstractString) where {O<:AbstractOrbital}
m = match(r"^([0-9]+|.)([a-z]|\[[0-9]+\])([-]{0,1})$", orb_str)
m === nothing && throw(ArgumentError("Invalid orbital string: $(orb_str)"))
function Base.parse(::Type{<:Orbital}, orb_str)
m = match(r"^([0-9]+|.)([a-z]|\[[0-9]+\])$", orb_str)
isnothing(m) && throw(ArgumentError("Invalid orbital string: $(orb_str)"))
n = parse_orbital_n(m)
= parse_orbital_ℓ(m)
if O == RelativisticOrbital
j =+ (m[3] == "-" ? -1 : 1)*1//2
O(n, ℓ, j)
else
m[3] == "" || throw(ArgumentError("Non-relativistic orbitals cannot have their spins explicitly specified"))
O(n, ℓ)
end
Orbital(n, ℓ)
end

"""
Expand All @@ -253,7 +269,7 @@ Fd
```
"""
macro o_str(orb_str)
:(orbital_from_string(Orbital, $orb_str))
:(parse(Orbital, $orb_str))
end

function orbitals_from_string(::Type{O}, orbs_str::AbstractString) where {O<:AbstractOrbital}
Expand Down
33 changes: 32 additions & 1 deletion src/relativistic_orbitals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,37 @@ julia> angular_momenta(ro"3d")
angular_momenta(orbital::RelativisticOrbital) = (orbital.j,)
angular_momentum_labels(::RelativisticOrbital) = ("j",)

# ** Saving/loading

Base.write(io::IO, o::RelativisticOrbital{Int}) = write(io, 'i', o.n, o.κ)
Base.write(io::IO, o::RelativisticOrbital{Symbol}) = write(io, 's', sizeof(o.n), o.n, o.κ)

function Base.read(io::IO, ::Type{RelativisticOrbital})
kind = read(io, Char)
n = if kind == 'i'
read(io, Int)
elseif kind == 's'
b = Vector{UInt8}(undef, read(io, Int))
readbytes!(io, b)
Symbol(b)
else
error("Unknown RelativisticOrbital type $(kind)")
end
κ = read(io, Int)
RelativisticOrbital(n, κ)
end

# * Orbital construction from strings

function Base.parse(::Type{<:RelativisticOrbital}, orb_str)
m = match(r"^([0-9]+|.)([a-z]|\[[0-9]+\])([-]{0,1})$", orb_str)
isnothing(m) && throw(ArgumentError("Invalid orbital string: $(orb_str)"))
n = parse_orbital_n(m)
= parse_orbital_ℓ(m)
j =+ (m[3] == "-" ? -1 : 1)*1//2
RelativisticOrbital(n, ℓ, j)
end

"""
@ro_str -> RelativisticOrbital
Expand All @@ -206,7 +237,7 @@ Kf-
```
"""
macro ro_str(orb_str)
:(orbital_from_string(RelativisticOrbital, $orb_str))
:(parse(RelativisticOrbital, $orb_str))
end

"""
Expand Down
Loading

0 comments on commit 17ab948

Please sign in to comment.