diff --git a/docs/src/configurations.md b/docs/src/configurations.md index b0c974c..2f69b91 100644 --- a/docs/src/configurations.md +++ b/docs/src/configurations.md @@ -126,6 +126,8 @@ julia> rc"[Ne] 3s2" ⊗ rcs"3p2" SpinConfiguration spin_configurations substitutions +@sc_str +@rsc_str @scs_str ``` diff --git a/src/AtomicLevels.jl b/src/AtomicLevels.jl index de40060..cfa72fc 100644 --- a/src/AtomicLevels.jl +++ b/src/AtomicLevels.jl @@ -9,6 +9,7 @@ using HalfIntegers using Combinatorics include("common.jl") +include("unicode.jl") include("parity.jl") include("orbitals.jl") include("relativistic_orbitals.jl") diff --git a/src/configurations.jl b/src/configurations.jl index a6e26da..7ad0957 100644 --- a/src/configurations.jl +++ b/src/configurations.jl @@ -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 @@ -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), @@ -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 @@ -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² @@ -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- ``` @@ -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) @@ -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) @@ -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 diff --git a/src/orbitals.jl b/src/orbitals.jl index 8eaf792..0e279d6 100644 --- a/src/orbitals.jl +++ b/src/orbitals.jl @@ -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.ℓ @@ -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) = @@ -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 """ @@ -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} diff --git a/src/relativistic_orbitals.jl b/src/relativistic_orbitals.jl index 875337a..508b698 100644 --- a/src/relativistic_orbitals.jl +++ b/src/relativistic_orbitals.jl @@ -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 @@ -206,7 +237,7 @@ Kf- ``` """ macro ro_str(orb_str) - :(orbital_from_string(RelativisticOrbital, $orb_str)) + :(parse(RelativisticOrbital, $orb_str)) end """ diff --git a/src/spin_orbitals.jl b/src/spin_orbitals.jl index 47f802f..e0dd96e 100644 --- a/src/spin_orbitals.jl +++ b/src/spin_orbitals.jl @@ -107,11 +107,44 @@ Base.promote_type(::Type{SpinOrbital}, ::Type{SpinOrbital{O}}) where O = SpinOrb Base.promote_type(::Type{SpinOrbital{A,M}}, ::Type{SpinOrbital{B,M}}) where {A,B,M} = SpinOrbital{<:promote_type(A,B),M} -function spin_orbital_from_string(::Type{O}, orb_str) where {O<:AbstractOrbital} +# ** Saving/loading + +Base.write(io::IO, o::SpinOrbital{<:Orbital}) = write(io, 'n', o.orb, o.m[1], o.m[2].twice) +Base.write(io::IO, o::SpinOrbital{<:RelativisticOrbital}) = write(io, 'r', o.orb, o.m[1].twice) + +function Base.read(io::IO, ::Type{SpinOrbital}) + kind = read(io, Char) + if kind == 'n' + orb = read(io, Orbital) + mℓ = read(io, Int) + ms = half(read(io, Int)) + SpinOrbital(orb, (mℓ, ms)) + elseif kind == 'r' + orb = read(io, RelativisticOrbital) + mj = half(read(io, Int)) + SpinOrbital(orb, mj) + else + error("Unknown SpinOrbital type $(kind)") + end +end + +# * Orbital construction from strings + +function Base.parse(::Type{O}, orb_str) where {OO<:AbstractOrbital,O<:SpinOrbital{OO}} m = match(r"^(.*)\((.*)\)$", orb_str) - m === nothing && throw(ArgumentError("Invalid spin-orbital string: $(orb_str)")) - o = orbital_from_string(O, m[1]) - SpinOrbital(o, (split(m[2], ",")...,)) + # For non-relativistic spin-orbitals, we also support specifying + # the m_ℓ quantum number using Unicode subscripts and the spin + # label using α/β + m2 = match(r"^(.*?)([₋]{0,1}[₁₂₃₄₅₆₇₈₉₀]+)([αβ])$", orb_str) + if !isnothing(m) + o = parse(OO, m[1]) + SpinOrbital(o, (split(m[2], ",")...,)) + elseif !isnothing(m2) && OO <: Orbital + o = parse(OO, m2[1]) + SpinOrbital(o, from_subscript(m2[2]), m2[3]) + else + throw(ArgumentError("Invalid spin-orbital string: $(orb_str)")) + end end """ @@ -138,7 +171,7 @@ julia> so"2p(1,-1/2)" ``` """ macro so_str(orb_str) - :(spin_orbital_from_string(Orbital, $orb_str)) + :(parse(SpinOrbital{Orbital}, $orb_str)) end """ @@ -162,7 +195,7 @@ julia> rso"3d(2.5)" ``` """ macro rso_str(orb_str) - :(spin_orbital_from_string(RelativisticOrbital, $orb_str)) + :(parse(SpinOrbital{RelativisticOrbital}, $orb_str)) end """ diff --git a/src/terms.jl b/src/terms.jl index 768b056..595d60f 100644 --- a/src/terms.jl +++ b/src/terms.jl @@ -58,14 +58,24 @@ Parses a string into a [`Term`](@ref) object. ```jldoctest julia> parse(Term, "4Po") ⁴Pᵒ + +julia> parse(Term, "⁴Pᵒ") +⁴Pᵒ ``` See also: [`@T_str`](@ref) """ function Base.parse(::Type{Term}, s::AbstractString) m = match(r"([0-9]+)([A-Z]|\[[0-9/]+\])([oe ]{0,1})", s) - isnothing(m) && throw(ArgumentError("Invalid term string $s")) - L = lowercase(m[2]) + m2 = match(r"([¹²³⁴⁵⁶⁷⁸⁹⁰]+)([A-Z]|\[[0-9/]+\])([ᵒᵉ]{0,1})", s) + Sstr,Lstr,Pstr = if !isnothing(m) + m[1],m[2],m[3] + elseif !isnothing(m2) + from_superscript(m2[1]),m2[2],m2[3] + else + throw(ArgumentError("Invalid term string $s")) + end + L = lowercase(Lstr) L = if L[1] == '[' L = strip(L, ['[',']']) if occursin("/", L) @@ -80,8 +90,8 @@ function Base.parse(::Type{Term}, s::AbstractString) findfirst(L, spectroscopic)[1]-1 end denominator(L) ∈ [1,2] || throw(ArgumentError("L must be integer or half-integer")) - S = (parse(Int, m[1]) - 1)//2 - Term(L, S, m[3] == "o" ? p"odd" : p"even") + S = (parse(Int, Sstr) - 1)//2 + Term(L, S, Pstr == "o" || Pstr == "ᵒ" ? p"odd" : p"even") end """ @@ -96,6 +106,9 @@ julia> T"1S" julia> T"4Po" ⁴Pᵒ +julia> T"⁴Pᵒ" +⁴Pᵒ + julia> T"2[3/2]o" # jK coupling, common in noble gases ²[3/2]ᵒ ``` diff --git a/src/unicode.jl b/src/unicode.jl new file mode 100644 index 0000000..d956f84 --- /dev/null +++ b/src/unicode.jl @@ -0,0 +1,17 @@ +function from_subscript(s) + inverse_subscript_map = Dict('₋' => '-', '₊' => '+', '₀' => '0', + '₁' => '1', '₂' => '2', '₃' => '3', + '₄' => '4', '₅' => '5', '₆' => '6', + '₇' => '7', '₈' => '8', '₉' => '9', + '₀' => '0') + map(Base.Fix1(getindex, inverse_subscript_map), s) +end + +function from_superscript(s) + inverse_superscript_map = Dict('⁻' => '-', '⁺' => '+', '⁰' => '0', + '¹' => '1', '²' => '2', '³' => '3', + '⁴' => '4', '⁵' => '5', '⁶' => '6', + '⁷' => '7', '⁸' => '8', '⁹' => '9', + '⁰' => '0') + map(Base.Fix1(getindex, inverse_superscript_map), s) +end diff --git a/test/atsp/csfparser.jl b/test/atsp/csfparser.jl index ce32213..7562bdc 100644 --- a/test/atsp/csfparser.jl +++ b/test/atsp/csfparser.jl @@ -1,4 +1,4 @@ -using AtomicLevels: CSF, orbital_from_string +using AtomicLevels: CSF p_suffix(p::Parity) = isodd(p) ? "o" : "" p_suffix(cfg::Configuration) = p_suffix(parity(cfg)) diff --git a/test/configurations.jl b/test/configurations.jl index 4172eba..a831b14 100644 --- a/test/configurations.jl +++ b/test/configurations.jl @@ -42,6 +42,36 @@ @test_throws ArgumentError parse(Configuration{RelativisticOrbital}, "1s3") @test_throws ArgumentError parse(Configuration{Orbital}, "1s2 2p-2") + @testset "Unicode occupations/states" begin + for c in [c"1s2 2p6", c"[He]c 2p6", c"[He]i 2p6", c"1s 3d4", c"[Xe]*"] + @test parse(Configuration{Orbital}, string(c)) == c + end + + for c in [rc"1s2 2p6", rc"1s 2p- 2p3", rc"[He]c 2p6", rc"[He]i 2p6", rc"1s 3d4", rc"[Xe]*"] + @test parse(Configuration{RelativisticOrbital}, string(c)) == c + end + end + + @testset "Spin-configurations" begin + a = sc"" + @test a isa SpinConfiguration{<:SpinOrbital{<:Orbital}} + @test isempty(a) + + b = rsc"" + @test b isa SpinConfiguration{<:SpinOrbital{<:RelativisticOrbital}} + @test isempty(b) + + @test sc"1s(0,α) 2p(1,β)" == Configuration([so"1s(0,α)", so"2p(1,β)"], ones(Int, 2)) + @test sc"[He]c 2p(1,β)" == Configuration([so"1s(0,α)", so"1s(0,β)", so"2p(1,β)"], ones(Int, 3), [:closed, :closed, :open]) + @test rsc"[He]c 2p(-1/2)" == Configuration([rso"1s(-1/2)", rso"1s(1/2)", rso"2p(-1/2)"], ones(Int, 3), [:closed, :closed, :open]) + + # Test parsing of Unicode m_ℓ quantum numbers + @test sc"1s₀α 2p₁β" == sc"1s(0,α) 2p(1,β)" + + c = rsc"[Xe]*" + @test parse(SpinConfiguration{SpinOrbital{RelativisticOrbital}}, string(c)) == c + end + @test fill(c"1s 2s 2p") == c"1s2 2s2 2p6" @test fill(rc"1s 2s 2p- 2p") == rc"1s2 2s2 2p-2 2p4" @test close(c"1s2") == c"1s2c" @@ -66,21 +96,38 @@ @test c"10s2" == Configuration([o"10s"], [2], [:open]) @test c"9999l32" == Configuration([o"9999l"], [32], [:open]) - # Hashing - let c1a = c"1s 2s", c1b = c"1s 2s", c2 = c"1s 2p" - @test c1a == c1b - @test isequal(c1a, c1b) - @test c1a != c2 - @test !isequal(c1a, c2) - - @test hash(c1a) == hash(c1a) - @test hash(c1a) == hash(c1b) - - # If hashing is not properly implemented, unique fails to detect all equal pairs - @test unique([c1a, c1b]) == [c1a] - @test unique([c1a, c1a]) == [c1a] - @test unique([c1a, c1a, c1b]) == [c1a] - @test length(unique([c1a, c2, c1a, c1b, c2])) == 2 + @testset "Hashing" begin + let c1a = c"1s 2s", c1b = c"1s 2s", c2 = c"1s 2p" + @test c1a == c1b + @test isequal(c1a, c1b) + @test c1a != c2 + @test !isequal(c1a, c2) + + @test hash(c1a) == hash(c1a) + @test hash(c1a) == hash(c1b) + + # If hashing is not properly implemented, unique fails to detect all equal pairs + @test unique([c1a, c1b]) == [c1a] + @test unique([c1a, c1a]) == [c1a] + @test unique([c1a, c1a, c1b]) == [c1a] + @test length(unique([c1a, c2, c1a, c1b, c2])) == 2 + end + end + + @testset "Serialization" begin + cs = [sc"1s(0,α) 2p(1,β)", sc"[He]c 2p(1,β)", rsc"[He]c 2p(-1/2)", sc"1s₀α 2p₁β", rsc"[Xe]*", + c"1s2 2p6", c"[He]c 2p6", c"[He]i 2p6", c"1s 3d4", c"[Xe]*", + rc"1s2 2p6", rc"1s 2p- 2p3", rc"[He]c 2p6", rc"[He]i 2p6", rc"1s 3d4", rc"[Xe]*"] + + ncs = let io = IOBuffer() + foreach(Base.Fix1(write, io), cs) + + seekstart(io) + [read(io, Configuration) + for i in eachindex(cs)] + end + + @test cs == ncs end end diff --git a/test/grasp/rcsfparser.jl b/test/grasp/rcsfparser.jl index 32a65ae..47a3c82 100644 --- a/test/grasp/rcsfparser.jl +++ b/test/grasp/rcsfparser.jl @@ -1,4 +1,4 @@ -using AtomicLevels: CSF, orbital_from_string +using AtomicLevels: CSF using HalfIntegers angularmomentum(o::RelativisticOrbital) = AtomicLevels.kappa_to_j(o.κ) @@ -116,7 +116,7 @@ function parse_csflines(line1, line2, line3) for i = 1:norbitals orb = line1[9*(i-1)+1:9*i] @assert orb[6]=='(' && orb[9]==')' - orbital = orbital_from_string(RelativisticOrbital, strip(orb[1:5])) + orbital = parse(RelativisticOrbital, strip(orb[1:5])) # n = parse(Int, orb[1:3]) # kappa = parse_j(orb[4:5]) nelec = parse(Int, orb[7:8]) @@ -174,7 +174,7 @@ function parse_cores(line) orbstrings = split(line) orbs = RelativisticOrbital{Int}[] for os in orbstrings - push!(orbs, orbital_from_string(RelativisticOrbital, strip(os))) + push!(orbs, parse(RelativisticOrbital, strip(os))) end orbs end diff --git a/test/orbitals.jl b/test/orbitals.jl index 9c0a55f..b4fb548 100644 --- a/test/orbitals.jl +++ b/test/orbitals.jl @@ -56,6 +56,10 @@ using Random @test o"2p" == Orbital(2, 1) @test o"2[1]" == Orbital(2, 1) + @test parse(Orbital, "1s") == Orbital(1, 0) + @test parse(Orbital{Int}, "1s") == Orbital(1, 0) + @test parse(Orbital{Symbol}, "ks") == Orbital(:k, 0) + @test ro"1s" == RelativisticOrbital(1, -1) # κ=-1 => s orbital @test ro"2p-" == RelativisticOrbital(2, 1, half(1)) @test ro"2p-" == RelativisticOrbital(2, 1, 1//2) @@ -68,8 +72,8 @@ using Random @test ro"kp" == RelativisticOrbital(:k, 1, 3//2) @test ro"ϵd-" == RelativisticOrbital(:ϵ, 2, 3//2) - @test_throws ArgumentError AtomicLevels.orbital_from_string(Orbital, "2p-") - @test_throws ArgumentError AtomicLevels.orbital_from_string(Orbital, "sdkfl") + @test_throws ArgumentError parse(Orbital, "2p-") + @test_throws ArgumentError parse(Orbital, "sdkfl") @test_throws ArgumentError Orbital(0, 0) @test_throws ArgumentError Orbital(1, 1) @@ -303,6 +307,30 @@ using Random @test_throws ArgumentError rso"3d-(..)" @test rso"3d-(-3/2)" == SpinOrbital(ro"3d-", (-3/2)) @test rso"3d(5/2)" == SpinOrbital(ro"3d", (5/2)) + + @test parse(SpinOrbital{Orbital}, "1s(0,α)") == SpinOrbital(Orbital(1, 0), (0, up)) + @test parse(SpinOrbital{Orbital{Int}}, "1s(0,α)") == SpinOrbital(Orbital(1, 0), (0, up)) + @test parse(SpinOrbital{Orbital{Symbol}}, "ks(0,α)") == SpinOrbital(Orbital(:k, 0), (0, up)) + + @test so"1s₀β" == so"1s(0,β)" + @test so"2p₋₁α" == so"2p(-1,α)" + @test so"k[31]₋₁₃α" == so"k[31](-13,α)" + + for o in [SpinOrbital(o"1s", (0,-1/2)), + SpinOrbital(o"1s", (0,1/2)), + SpinOrbital(o"2p", (1,-1/2)), + SpinOrbital(ro"1s", (1/2)), + SpinOrbital(ro"2p", (3/2)), + SpinOrbital(ro"3d", (5/2)), + SpinOrbital(ro"3d-", (-3/2)), + SpinOrbital(ro"3p", (-1/2)), + SpinOrbital(ro"3p", (-3/2)), + SpinOrbital(ro"3p", (1/2)), + SpinOrbital(ro"3p-", (-1/2)), + SpinOrbital(ro"3p-", (1/2))] + O = typeof(o.orb) + @test parse(SpinOrbital{O}, string(o)) == o + end end end @@ -323,4 +351,70 @@ using Random @test hash(o"3s") == hash(o"3s") @test hash(ro"3p-") == hash(ro"3p-") end + + @testset "Serialization" begin + @testset "Orbitals" begin + o = o"1s" + p = o"kg" + q = Orbital(:k̃, 14) + oo = SpinOrbital(o, (0, 1/2)) + r = Orbital(Symbol("[$(oo)]"), 14) + + no,np,nq,nr = let io = IOBuffer() + foreach(Base.Fix1(write, io), (o,p,q,r)) + + seekstart(io) + [read(io, Orbital) + for i = 1:4] + end + + @test no == o + @test np == p + @test nq == q + @test nr == r + end + + @testset "Relativistic orbitals" begin + o = ro"1s" + p = ro"kg" + q = RelativisticOrbital(:k̃, 14) + oo = SpinOrbital(o, (1/2)) + r = RelativisticOrbital(Symbol("[$(oo)]"), 14) + + no,np,nq,nr = let io = IOBuffer() + foreach(Base.Fix1(write, io), (o,p,q,r)) + + seekstart(io) + [read(io, RelativisticOrbital) + for i = 1:4] + end + + @test no == o + @test np == p + @test nq == q + @test nr == r + end + + @testset "Spin-orbitals" begin + a = so"1s(0,α)" + b = rso"2p-(1/2)" + c = rso"kd-(-1.5)" + d = so"3d(-2,-0.5)" + e = SpinOrbital(Orbital(Symbol("[$(d)]"), 14), (-13, -0.5)) + + na,nb,nc,nd,ne = let io = IOBuffer() + foreach(Base.Fix1(write, io), (a,b,c,d,e)) + + seekstart(io) + [read(io, SpinOrbital) + for i = 1:5] + end + + @test na == a + @test nb == b + @test nc == c + @test nd == d + @test ne == e + end + end end diff --git a/test/runtests.jl b/test/runtests.jl index 9db9814..dcb8f52 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,6 +3,11 @@ using WignerSymbols using HalfIntegers using Test +@testset "Unicode super-/subscripts" begin + @test AtomicLevels.from_subscript("₋₊₁₂₃₄₅₆₇₈₉₀") == "-+1234567890" + @test AtomicLevels.from_superscript("⁻⁺¹²³⁴⁵⁶⁷⁸⁹⁰") == "-+1234567890" +end + include("parity.jl") include("orbitals.jl") include("configurations.jl") diff --git a/test/terms.jl b/test/terms.jl index af8533e..5c09105 100644 --- a/test/terms.jl +++ b/test/terms.jl @@ -20,6 +20,11 @@ using Test @test T"2[3/2]o" == Term(3//2, 1//2, p"odd") @test T"2Z" == Term(20, 1//2, p"even") + for T in [T"1S", T"1Se", T"1So", T"2So", T"4P", T"3D", T"3Do", + T"1[54]", T"1[3/2]", T"2[3/2]o", T"2Z"] + @test parse(Term, string(T)) == T + end + @test_throws DomainError Term(HalfInteger(-1//2), HalfInteger(1//2), p"even") @test_throws DomainError Term(3//2, -1//2, p"odd") @test_throws DomainError Term(-2, 1//2, 1)