Skip to content

Commit

Permalink
Add string and chemical formulae utility functions
Browse files Browse the repository at this point in the history
"""
    parse_number_name(nname::AbstractString) -> (number, name)

Parse a string of form "-1*A"

"""

"""
    ChemistryUtils.parse_chemical_formula(formula::AbstractString) -> element_counts::OrderedDict{Symbol, Int64}

Parse a chemical formula into a Dict of element counts
"""

"""
    ChemistryUtils.calc_molar_mass(element_counts::AbstractDict) -> molar_mass::Float64

Add up element masses to get molar mass (g)
"""

"""
    Constants.STANDARD_ATOMIC_WEIGHTS

IUPAC recommended values of relative atomic masses of sources in the local environment of the Earth's crust and atmosphere
(ie with Earth-specific isotope composition)
"""
  • Loading branch information
sjdaines committed Nov 30, 2024
1 parent a23314f commit b770312
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 11 deletions.
3 changes: 2 additions & 1 deletion src/Model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,7 @@ function _dispatch_methodlist_methoderror(reactionmethod)
println(io, "dispatch_methodlist: a ReactionMethod failed:")
show(io, MIME"text/plain"(), reactionmethod)
@warn String(take!(io))
return
return nothing
end

function dispatch_methodlist(
Expand Down Expand Up @@ -906,6 +906,7 @@ end
function _dispatch_methodlist_nomethoderroravailable()
@warn "dispatch_methodlist: a ReactionMethod failed. To get a more detailed error report "*
"including the YAML name of the failed Reaction, rerun with 'generated_dispatch=false' argument added to PALEOmodel.initialize!"
return nothing
end

#################################
Expand Down
2 changes: 2 additions & 0 deletions src/PALEOboxes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ include("utils/TestUtils.jl")
include("utils/SIMDutils.jl")
include("utils/IteratorUtils.jl")
include("utils/DocUtils.jl")
include("utils/StringUtils.jl")
include("utils/ChemistryUtils.jl")

include("variableaggregators/VariableAggregator.jl")

Expand Down
3 changes: 2 additions & 1 deletion src/Types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ end
Output accumulated log messages in io, then raise `ErrorException` with message
"""
function infoerror(io::IOBuffer, message::AbstractString)
@info String(take!(io))
s = String(take!(io))
isempty(s) || @info s
error(message)
end
27 changes: 27 additions & 0 deletions src/reactioncatalog/Constants.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,32 @@ const k_preindCO2atm = 280e-6 # pre-industrial pCO2 (atm)
const k_atmmixrN2 = 0.78084 # N2 atmospheric mixing ratio (moles / moles dry air)
const k_atmmixrO2 = 0.20946 # O2 atmospheric mixing ratio (moles / moles dry air)

"""
STANDARD_ATOMIC_WEIGHTS
IUPAC recommended values of relative atomic masses of sources in the local environment of the Earth's crust and atmosphere
(ie with Earth-specific isotope composition)
"""
const STANDARD_ATOMIC_WEIGHTS = (
H = 1.0080,
He = 4.0026,
Li = 6.94,
C = 12.011,
N = 14.007,
O = 15.999,
F = 18.998,
Na = 22.990,
P = 30.974,
S = 32.06,
Cl = 35.45,
Ar = 39.95,
K = 39.098,
Ti = 47.867,
V = 50.942,
Cr = 51.996,
Fe = 55.845,
Rb = 85.468,
Cs = 132.91,
)

end
11 changes: 2 additions & 9 deletions src/reactioncatalog/VariableStats.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,8 @@ function PB.register_methods!(rj::ReactionSum)
empty!(rj.var_multipliers)

for varmultname in rj.pars.vars_to_add
# parse multiplier
svmn = split(varmultname, ['*', ' '], keepempty=false)
if length(svmn) == 1
mult, varname = (1.0, rj.pars.vars_prefix[]*svmn[1])
elseif length(svmn) == 2
mult, varname = (parse(Float64, svmn[1]), rj.pars.vars_prefix[]*svmn[2])
else
PB.infoerror(io, "reaction ", fullname(rj), "invalid field in vars_to_add ", varmultname)
end
mult, varname = PB.parse_number_name(varmultname; io, errmsg="reaction $(PB.fullname(rj)) invalid field in vars_to_add ")
varname = rj.pars.vars_prefix[]*varname
println(io, " add $mult * $varname")
push!(rj.var_multipliers, mult)

Expand Down
64 changes: 64 additions & 0 deletions src/utils/ChemistryUtils.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
module ChemistryUtils

import PALEOboxes as PB
import OrderedCollections

"""
parse_chemical_formula(formula::AbstractString) -> element_counts::OrderedDict{Symbol, Int64}
Parse a chemical formula into a Dict of element counts
# Examples
```jldoctest; setup = :(import PALEOboxes as PB)
julia> PB.ChemistryUtils.parse_chemical_formula("HO2NO2")
OrderedCollections.OrderedDict{Symbol, Float64} with 3 entries:
:H => 1.0
:N => 1.0
:O => 4.0
```
"""
function parse_chemical_formula(formula::AbstractString)
elementcounts = OrderedCollections.OrderedDict{Symbol, Float64}()
for m in eachmatch(r"([A-Z][a-z]*)(\d*)", formula)
elementstr, countstr = m.captures
element = Symbol(elementstr)
count = isempty(countstr) ? 1 : parse(Int, countstr)
elementcounts[element] = get(elementcounts, element, 0) + count
end

sort!(elementcounts)

return elementcounts
end



"""
calc_molar_mass(element_counts::AbstractDict) -> molar_mass::Float64
calc_molar_mass(element_counts::AbstractVector{<:Pair}) -> molar_mass::Float64
calc_molar_mass(element_counts::NamedTuple) -> molar_mass::Float64
Add up element masses to get molar mass (g)
# Examples
```jldoctest; setup = :(import PALEOboxes as PB)
julia> PB.ChemistryUtils.calc_molar_mass([:C=>1, :O=>2])
44.009
```
"""
function calc_molar_mass(element_counts)

molar_mass = 0.0
for (element, count) in element_counts
element isa Symbol || error("element $element is not a Symbol eg :O, :C")
haskey(PB.Constants.STANDARD_ATOMIC_WEIGHTS, element) || error("unknown element $element")
count isa Number || error("element count $count is not a Number")
molar_mass += count*PB.Constants.STANDARD_ATOMIC_WEIGHTS[element]
end

return molar_mass
end

calc_molar_mass(element_counts::NamedTuple) = calc_molar_mass(pairs(element_counts))

end # module
38 changes: 38 additions & 0 deletions src/utils/StringUtils.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""
parse_number_name(nname::AbstractString) -> (number, name)
Parse a string of form "-1*A"
"""
function parse_number_name(
nname::AbstractString;
sep=['*', ' '],
number_first=true,
io=IOBuffer(),
errmsg="invalid field in nname, not of form number*name: ",
)

# parse multiplier
svmn = split(nname, sep, keepempty=false)
mult = nothing
if length(svmn) == 1
mult, name = 1, svmn[1]
elseif length(svmn) == 2
if !number_first
tmp = svmn[1]
svmn[1] = svmn[2]
svmn[2] = tmp
end
mult = tryparse(Int64, svmn[1])
if isnothing(mult)
mult = tryparse(Float64, svmn[1])
end
name = svmn[2]
end

!isnothing(mult) || infoerror(io, errmsg*nname)

return (mult, name)
end

parse_name_to_power_number(nname::AbstractString) = parse_number_name(nname; sep=['^', ' '], number_first=false)

0 comments on commit b770312

Please sign in to comment.