Databases
ChemistryLab.HKF_SI_CONVERSIONSChemistryLab.SorptionModelChemistryLab.SorptionReactionChemistryLab.SorptionSiteChemistryLab._blockChemistryLab._parse_sorption_stoichiometryChemistryLab.build_reactionsChemistryLab.build_solid_solutionsChemistryLab.build_speciesChemistryLab.complete_reaction_with_thermo_model!ChemistryLab.complete_species_with_thermo_model!ChemistryLab.datapathChemistryLab.display_data_pathChemistryLab.extract_primary_speciesChemistryLab.extract_unitChemistryLab.extract_valueChemistryLab.get_compatible_speciesChemistryLab.log_constantsChemistryLab.merge_jsonChemistryLab.merge_reactionsChemistryLab.parse_float_arrayChemistryLab.parse_phasesChemistryLab.parse_reaction_stoich_cemdataChemistryLab.reactions_involvingChemistryLab.read_sorption_modelChemistryLab.read_thermofun_databaseChemistryLab.resolve_data_pathChemistryLab.write_reaction
ChemistryLab.datapath Method
datapath(parts::AbstractString...) -> StringAbsolute path to a file shipped in ChemistryLab's data/ directory. Called without argument, returns the directory itself.
This is the recommended way to name a bundled database, because it does not depend on the working directory: a script written this way runs identically from the package root, from an editor whose REPL started elsewhere, and inside a documentation build.
Examples
substances = build_species(datapath("cemdata18-thermofun.json"))
ss_phases = build_solid_solutions(datapath("solid_solutions.toml"), dict)
readdir(datapath()) # every bundled data file
datapath("experimental", "README.md") # subdirectories work tooSee also read_thermofun_database, build_species.
ChemistryLab.display_data_path Method
display_data_path(path::AbstractString) -> StringShort, machine-independent label for path, meant for the banner a reader sees rather than for opening a file.
A file inside the package is shown relative to the package root (data/cemdata18-thermofun.json); anything else is shown unchanged. This matters because these banners are captured verbatim into the documentation: printing the absolute path would bake the build machine's directories (/home/runner/work/...) into every page that loads a database.
Whether a file is inside the package is decided by a prefix test and not by asking relpath and reading .. off its answer. relpath compares two paths component by component, and on Windows two paths on different drives share no component at all: it returns a chain of .. and the target, which starts with .. only by accident, or a path that starts with the drive letter and does not. A GitHub Windows runner checks out on one drive and puts tempdir() on another, so a file plainly outside the package could come back rewritten. The prefix test has no such case.
ChemistryLab.resolve_data_path Method
resolve_data_path(path::AbstractString) -> StringResolve path to an existing file, trying in order:
pathas given — relative to the working directory, or absolute;datapath(path)— the same relative path under the bundleddata/;datapath(basename(path))— a bundled data file of that name, whatever directory prefix the caller wrote;joinpath(pkgdir(ChemistryLab), path)— relative to the package root.
The working directory comes first, so a call that already resolves keeps resolving to exactly the same file: the fallbacks can only turn a failure into a success, never change an existing answer. And steps 2–3 can only succeed for a name that is one of the bundled data files, so a mistyped path to a file of one's own still fails loudly instead of silently loading something else.
Throws ArgumentError listing the bundled files when nothing matches.
ChemistryLab.HKF_SI_CONVERSIONS Constant
HKF_SI_CONVERSIONSHardcoded conversion factors from SUPCRT (cal, bar) to SI (J, Pa) for eos_hkf_coeffs. The JSON unit metadata for a3 and a4 is incorrect (missing /bar), hence this explicit table.
| Symbol | SUPCRT unit | SI unit | Factor |
|---|---|---|---|
| a1 | cal/(mol·bar) | J/(mol·Pa) | 4.184e-5 |
| a2 | cal/mol | J/mol | 4.184 |
| a3 | (cal·K)/(mol·bar) | (J·K)/(mol·Pa) | 4.184e-5 |
| a4 | cal·K/mol | J·K/mol | 4.184 |
| c1 | cal/(mol·K) | J/(mol·K) | 4.184 |
| c2 | cal·K/mol | J·K/mol | 4.184 |
| wref | cal/mol | J/mol | 4.184 |
ChemistryLab.build_reactions Function
build_reactions(df_reactions::AbstractDataFrame, dict_species=Dict(), list_symbols=nothing; verbose=false) -> Vector{Reaction}Build Reaction objects from a reaction DataFrame.
Arguments
df_reactions: DataFrame containing reaction data.species_list: vector of existingSpeciesobjects to use in reactions.list_symbols: optional list of reaction symbols to filter (default: nothing, process all).verbose: if true, print details during processing (default: false).
Returns
- Vector of
Reactionobjects.
ChemistryLab.build_solid_solutions Method
build_solid_solutions(toml_file, dict_species; skip_missing=true) -> Vector{SolidSolutionPhase}Load solid solution phase definitions from a TOML file and assemble SolidSolutionPhase objects from an existing species dictionary.
Each end-member species is automatically requalified to SC_SSENDMEMBER via with_class, regardless of the class stored in the database.
Arguments
toml_file: path to a TOML file with[[solid_solution]]entries (seedata/solid_solutions.tomlfor the format).dict_species:Dict{String, <:AbstractSpecies}mapping symbol → species (typically built fromDict(symbol(s) => s for s in build_species(...))).skip_missing: iftrue(default), silently skip phases whose end-members are not all present indict_species; iffalse, throw an error.
TOML format
[[solid_solution]]
name = "CSHQ"
end_members = ["CSHQ-TobD", "CSHQ-TobH", "CSHQ-JenH", "CSHQ-JenD", "KSiOH", "NaSiOH"]
model = "ideal" # or "redlich_kister"
# For redlich_kister only:
a0 = 3000.0 # J/mol
a1 = 500.0 # J/mol
a2 = 0.0 # J/molExample
substances = build_species(datapath("cemdata18-thermofun.json"))
dict = Dict(symbol(s) => s for s in substances)
ss_phases = build_solid_solutions(datapath("solid_solutions.toml"), dict)
cs = ChemicalSystem(species, CEMDATA_PRIMARIES; solid_solutions = ss_phases)ChemistryLab.build_species Function
build_species(df_substances::AbstractDataFrame, list_symbols=nothing; verbose=false) -> Vector{Species}Build Species objects from a substance DataFrame.
Arguments
df_substances: DataFrame containing substance data.list_symbols: optional list of symbols to filter (default: nothing, process all).verbose: if true, print details during processing (default: false).
Returns
- Vector of
Species.
ChemistryLab.complete_reaction_with_thermo_model! Method
complete_reaction_with_thermo_model!(reaction, row; verbose=false)Populate thermodynamic reference values and build thermodynamic functions on reaction from a ThermoFun reaction DataFrame row. Mutates reaction.properties in place.
ChemistryLab.complete_species_with_thermo_model! Method
complete_species_with_thermo_model!(species, row; verbose=false)Populate thermodynamic reference values and build thermodynamic functions on species from a ThermoFun substance DataFrame row. Mutates species.properties in place.
ChemistryLab.extract_unit Function
extract_unit(v, default_unit=u"1") -> AbstractQuantityParse unit arithmetic (including powers, roots, and Constants names). Reject executable syntax before calling uparse. Returns default_unit for unsupported expressions, unknown units, or malformed input.
ChemistryLab.extract_value Method
extract_value(row, field; verbose=false, default_unit=u"1", with_units=true) -> Union{AbstractQuantity, Number, Missing}Extract a scalar value (optionally with units) from a nested ThermoFun DataFrame row. Returns missing when the field is absent, missing, or cannot be parsed.
ChemistryLab.get_compatible_species Method
get_compatible_species(df_substances::AbstractDataFrame, species_list; aggregate_states=[AS_AQUEOUS], exclude_species=[], union=false) -> DataFrameFind species in the database compatible with a given list of species (sharing atoms).
Arguments
df_substances: substance DataFrame.species_list: list of target species symbols.aggregate_states: filter for specific aggregate states (default:[AS_AQUEOUS]).exclude_species: list of species symbols to exclude.union: if true, includes the originalspecies_listin the result (default: false).
Returns
- DataFrame of compatible substances.
ChemistryLab.read_thermofun_database Method
read_thermofun_database(filename::AbstractString) -> (DataFrame, DataFrame, DataFrame)Read a ThermoFun database from a JSON file.
Arguments
filename: path to the JSON database file.
Returns
df_elements: DataFrame of chemical elements.df_substances: DataFrame of chemical substances (species).df_reactions: DataFrame of chemical reactions.
ChemistryLab.extract_primary_species Method
extract_primary_species(file_path::AbstractString) -> DataFrameExtract primary aqueous species from a PHREEQC database file.
Arguments
file_path: path to PHREEQC .dat file.
Returns
- DataFrame with columns: species, symbol, formula, aggregate_state, atoms, charge, gamma.
Parses the SOLUTION_SPECIES section to extract master species and their properties. The "Zz" charge placeholder is handled specially. Gamma coefficients for activity models are extracted from "-gamma" lines.
ChemistryLab.parse_float_array Method
parse_float_array(line::AbstractString) -> Vector{Float64}Parse a line containing space-separated floats, skipping the first token and any comments.
Arguments
line: input string with format "keyword value1 value2 ...".
Returns
- Vector of successfully parsed Float64 values.
Examples
julia> parse_float_array("-analytical_expression 1.5 2.3 4.7")
3-element Vector{Float64}:
1.5
2.3
4.7
julia> parse_float_array("-log_K 5.2 # comment")
1-element Vector{Float64}:
5.2ChemistryLab.parse_phases Method
parse_phases(dat_content::AbstractString) -> Dict{String,Any}Extract phase information from PHREEQC .dat file content.
Arguments
dat_content: full text content of a PHREEQC .dat file.
Returns
- Dictionary mapping phase names to their properties (equation, log_K, analytical_expression, V⁰).
Parses the PHASES section and extracts reaction equations, equilibrium constants, analytical expressions, and molar volumes for each phase.
ChemistryLab.parse_reaction_stoich_cemdata Method
parse_reaction_stoich_cemdata(reaction_line::AbstractString) -> (Vector, String, String)Parse a reaction line from CEMDATA format and extract stoichiometric information.
Arguments
reaction_line: reaction string in CEMDATA format, optionally with a comment after '#'.
Returns
reactants: vector of dictionaries with "symbol" and "coefficient" keys.modified_equation: equation string with added "@" markers for aqueous species.comment: extracted comment string (empty if none).
The function automatically adds "@" suffixes to aqueous species without explicit charges (except for the first reactant).
ChemistryLab.merge_json Method
merge_json(json_path::AbstractString, dat_path::AbstractString, output_path::AbstractString)Merge PHREEQC .dat phase data into a ThermoFun JSON database file.
Arguments
json_path: path to input ThermoFun JSON file.dat_path: path to PHREEQC .dat file containing phase definitions.output_path: path for output merged JSON file.
Reads both files, extracts phases from the .dat file, merges them into the JSON database structure, and writes the result preserving the original JSON formatting.
ChemistryLab.merge_reactions Method
merge_reactions(json_data::Dict, new_reactions::Dict) -> DictMerge new reactions from PHREEQC .dat into existing ThermoFun JSON data.
Arguments
json_data: existing ThermoFun database as a dictionary.new_reactions: dictionary of new phase reactions to add.
Returns
- Updated
json_datawith new reactions appended.
Only adds reactions that don't already exist (by symbol) and that have complete required fields (logKr, analytical_expression, equation).
ChemistryLab.write_reaction Method
write_reaction(f::IO, reaction::Dict)Write a single reaction dictionary to an IO stream in JSON format.
Arguments
f: output IO stream.reaction: reaction dictionary with all required ThermoFun fields.
Helper function used by merge_json to write formatted JSON output.
Published sorption models
Reading a sorption model written in the PHREEQC format — its site families, its exchangers, and the log K of each reaction with the reference and the uncertainty the compilation states beside it.
None ships here. ClaySor 2023, the model these were written against, is CC-BY-4.0 and freely available from its Zenodo deposit; a published model is also written against a particular aqueous database, and its constants are that database's, so importing the reactions is not the same as being able to reproduce the model.
ChemistryLab.SorptionModel Type
struct SorptionModelA published sorption model read from a PHREEQC-format database: its surface site families, its exchangers, and where it came from.
Reading one is not the same as being able to solve it. A model is written against a particular aqueous thermodynamic database — ClaySor 2023 says so in its own first lines, naming PSI/Nagra TDB 2020 — and its constants are that database's constants. Using them over a different one is a different model, in exactly the way a surface constant fitted with a diffuse layer is a different constant from one fitted without.
header keeps the file's own comment block, which is where such a statement lives and where the reference list usually is.
ChemistryLab.SorptionReaction Type
struct SorptionReactionOne reaction of a published sorption model: its equation as written, its stoichiometry, and its log K with provenance and uncertainty.
Fields
equation: the line as the database writes it, kept verbatim so a reader can check the parse.stoichiometry:species => coefficient, negative for reactants and positive for products.log_K: aTracedwhose source is theref:tag and whose uncertainty is theerror:tag, when the entry carries them.comment: the rest of the comment, which usually says what the reaction is in words.
ChemistryLab.SorptionSite Type
struct SorptionSiteOne site of a sorption model — a surface site family or an exchanger — with the reactions written on it.
master: the master species,"Mnt_s".reference: the reference form,"Mnt_sOH"or"Mntx-", which is what a site family's free member corresponds to.reactions: every reaction whose products name this site.comment: what the database calls it, for exampleEdgeSite_S_mont.
ChemistryLab._block Method
_block(text, keyword) -> Vector{String}The lines of one PHREEQC keyword block, up to the next keyword at column one.
ChemistryLab._parse_sorption_stoichiometry Method
_parse_sorption_stoichiometry(equation) -> Dict{String,Int}species => coefficient for a PHREEQC reaction line, negative on the left of the = and positive on the right.
Handles the two spacings a database uses interchangeably, 2 Na+ and 2Na+, and refuses a coefficient it cannot read rather than silently taking it as one.
ChemistryLab.log_constants Method
log_constants(m::SorptionModel) -> Vector{Traced{Float64}}Every log K of the model, for provenance_report.
ChemistryLab.reactions_involving Method
reactions_involving(m::SorptionModel, species) -> Vector{SorptionReaction}Every reaction of m in which species appears, on either side.
The way to take a subset of a published model: a compilation covering thirty elements is not something to import whole, and naming which part was taken is part of saying what was reproduced.
ChemistryLab.read_sorption_model Method
read_sorption_model(path) -> SorptionModelRead the SURFACE_MASTER_SPECIES, SURFACE_SPECIES, EXCHANGE_MASTER_SPECIES and EXCHANGE_SPECIES blocks of a PHREEQC-format database.
Reading rather than transcribing, for the reason every generator in test/reference/ exists: a hand-copied compilation is a transcription, and this package has already found standard energies that had drifted that way.
No sorption model ships with this package. ClaySor 2023 is CC-BY-4.0 and freely available from its Zenodo deposit; point this at your own copy.
Example
m = read_sorption_model("claysor23_v0.7.dat")
keys(m.surfaces) # "Mnt_s", "Mnt_v", "Mnt_w", "Ilt_s", …
provenance_report(log_constants(m)) # how much of it is known how wellSee also: log_constants, reactions_involving.