diff --git a/src/Model.jl b/src/Model.jl index 6567107..2f0499d 100644 --- a/src/Model.jl +++ b/src/Model.jl @@ -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( @@ -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 ################################# diff --git a/src/PALEOboxes.jl b/src/PALEOboxes.jl index 93db06d..ee3aea6 100644 --- a/src/PALEOboxes.jl +++ b/src/PALEOboxes.jl @@ -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") diff --git a/src/Types.jl b/src/Types.jl index 02574e4..334903d 100644 --- a/src/Types.jl +++ b/src/Types.jl @@ -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 \ No newline at end of file diff --git a/src/reactioncatalog/Constants.jl b/src/reactioncatalog/Constants.jl index fe35920..76ce3c8 100644 --- a/src/reactioncatalog/Constants.jl +++ b/src/reactioncatalog/Constants.jl @@ -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 \ No newline at end of file diff --git a/src/reactioncatalog/VariableStats.jl b/src/reactioncatalog/VariableStats.jl index efc2c5a..1289859 100644 --- a/src/reactioncatalog/VariableStats.jl +++ b/src/reactioncatalog/VariableStats.jl @@ -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) diff --git a/src/utils/ChemistryUtils.jl b/src/utils/ChemistryUtils.jl new file mode 100644 index 0000000..deac0a5 --- /dev/null +++ b/src/utils/ChemistryUtils.jl @@ -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 \ No newline at end of file diff --git a/src/utils/StringUtils.jl b/src/utils/StringUtils.jl new file mode 100644 index 0000000..f2dd5b2 --- /dev/null +++ b/src/utils/StringUtils.jl @@ -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)