Databases
ChemistryLab.DERIVED_DATABASESChemistryLab.HKF_SI_CONVERSIONSChemistryLab.LITERATURE_SCHEMAChemistryLab.THIRD_PARTY_DATABASESChemistryLab.AbstractLogKChemistryLab.DatabaseUnavailableChemistryLab.DerivedDatabaseChemistryLab.FormationLogKChemistryLab.LiteratureRecordChemistryLab.PhreeqcBlockChemistryLab.PhreeqcLogKChemistryLab.SorptionModelChemistryLab.SorptionReactionChemistryLab.SorptionSiteChemistryLab.ThirdPartyDatabaseChemistryLab._cp_intervalsChemistryLab._parse_sorption_stoichiometryChemistryLab._reference_cp_intervalChemistryLab.asr_enthalpyChemistryLab.asr_logK_checkChemistryLab.asr_recordsChemistryLab.available_literatureChemistryLab.build_reactionsChemistryLab.build_solid_solutionsChemistryLab.build_speciesChemistryLab.cashplus_entriesChemistryLab.complete_reaction_with_thermo_model!ChemistryLab.complete_species_with_thermo_model!ChemistryLab.cshq_chloride_entryChemistryLab.database_activity_modelChemistryLab.database_cacheChemistryLab.database_infoChemistryLab.database_pathChemistryLab.datapathChemistryLab.display_data_pathChemistryLab.extract_primary_speciesChemistryLab.extract_unitChemistryLab.extract_valueChemistryLab.fe_friedel_entryChemistryLab.fe_friedel_logK_checkChemistryLab.fetch_databasesChemistryLab.get_compatible_speciesChemistryLab.import_databaseChemistryLab.install_databaseChemistryLab.literatureChemistryLab.literature_pathChemistryLab.literature_rowChemistryLab.literature_tableChemistryLab.literature_table_infoChemistryLab.literature_valueChemistryLab.log_constantsChemistryLab.phreeqc_blocksChemistryLab.phreeqc_equationChemistryLab.phreeqc_gamma_parametersChemistryLab.reactions_involvingChemistryLab.read_eq36_databaseChemistryLab.read_gwb_databaseChemistryLab.read_literatureChemistryLab.read_phreeqc_databaseChemistryLab.read_reaktoro_databaseChemistryLab.read_sorption_modelChemistryLab.read_thermofun_databaseChemistryLab.resolve_data_pathChemistryLab.temperature_rangeChemistryLab.zeolite_logK_checkChemistryLab.zeolite_records
ChemistryLab.datapath Method
datapath(parts::AbstractString...) -> StringAbsolute path to a data file of ChemistryLab. Called without argument, returns the package's data/ directory.
A thermodynamic database is named by its file name alone, datapath("cemdata18-thermofun.json"), and resolved by database_path: from ENV["CHEMISTRYLAB_DATABASE_DIR"] if it is set and holds the file, else from the package's cache, else downloaded from its publisher on first use, or built from it for a derived database. Any other name is a file under data/: the solid-solution and gel models, the published values of data/literature/, the experimental datasets.
This is the recommended way to name a database, because it depends neither on the working directory nor on where the file happens to be stored: a script written this way runs identically from the package root, from an editor whose REPL started elsewhere, inside a documentation build and on a machine that has never used ChemistryLab before.
Examples
substances = build_species(datapath("cemdata18-thermofun.json"))
ss_phases = build_solid_solutions(datapath("solid_solutions.toml"), dict)
datapath("experimental", "README.md") # subdirectories of data/ work tooSee also database_info, fetch_databases, 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 database is shown by its file name (cemdata18-thermofun.json), wherever the cache or the user's directory put it; another file inside the package is shown relative to the package root (data/solid_solutions.toml); 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 underdata/;datapath(basename(path))— a data file or a database of that name, whatever directory prefix the caller wrote (a database is obtained asdatapathdescribes);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 package's data files or databases, so a mistyped path to a file of one's own still fails loudly instead of silently loading something else.
Throws ArgumentError listing the data files and databases when nothing matches, and DatabaseUnavailable when the name is a database that cannot be obtained.
ChemistryLab.THIRD_PARTY_DATABASES Constant
THIRD_PARTY_DATABASESThe databases ChemistryLab obtains from their publishers, by file name. See database_info for where each one currently resolves.
ChemistryLab.DatabaseUnavailable Type
DatabaseUnavailable <: ExceptionThrown when a database cannot be obtained. The message names the file, says where it was looked for and what was tried, and lists the ways to provide it.
ChemistryLab.ThirdPartyDatabase Type
ThirdPartyDatabaseA thermodynamic database published outside ChemistryLab: its file name, what it is, where it is obtained, the SHA-256 of the validated version, and the license and citation its publisher states. urls is empty for a file that has to be downloaded by hand from page.
ChemistryLab.database_cache Method
database_cache() -> StringThe directory where ChemistryLab keeps the databases it has downloaded or built: a Scratch space of the Julia depot, created on first use. Deleting it is safe; the files are obtained again when next needed.
ChemistryLab.database_info Function
database_info([io]) -> Vector{NamedTuple}Where each database ChemistryLab knows currently resolves, without downloading or building anything: :local (in CHEMISTRYLAB_DATABASE_DIR), :cached, :downloadable, :manual (to be downloaded by hand) or, for a derived database, :buildable. Printed as a table, each database with its source and the license its publisher states, and returned as one (; name, status, path, source, license) per database.
ChemistryLab.database_path Method
database_path(name; download = true) -> StringPath to the database file name — a database obtained from its publisher (see THIRD_PARTY_DATABASES) or one ChemistryLab builds from such a database (see DERIVED_DATABASES) — obtaining or building it when needed. datapath calls this for every database name, so code written datapath("cemdata18-thermofun.json") needs no change.
With download = false nothing is downloaded, and a file absent from the local directory and the cache throws DatabaseUnavailable.
ChemistryLab.fetch_databases Method
fetch_databases(; derived = true) -> Vector{NamedTuple}Obtain in advance every database that can be downloaded, and build the derived ones, so that later work needs no network access: before a documentation build, on a machine that will go offline, or in continuous integration. A database that has to be downloaded by hand is reported as missing, not as an error.
Returns one (; name, path, status) per database, status being :ready or :missing.
ChemistryLab.install_database Method
install_database(path; name = basename(path), force = false) -> StringPut a copy of a database the user downloaded into ChemistryLab's cache, and return its path there. The file is identified by name and its SHA-256 must be that of the version ChemistryLab was validated with; a different file is refused unless force = true, in which case it is installed and a warning says that results may differ from the documented ones.
This is how a database that ChemistryLab does not download is provided (one whose publisher distributes it through a web page, under its own terms), and how a machine without network access is prepared.
ChemistryLab.DERIVED_DATABASES Constant
DERIVED_DATABASESThe databases ChemistryLab builds from a published one, by file name: the Cemdata18 zeolite extension, the Cemdata18 chloride extension, the Cemdata18 extension to the products of the alkali-silica reaction, and Cemdata18 with the CASH+ model of C-S-H and its extension to the alkali and alkaline-earth metals.
ChemistryLab.DerivedDatabase Type
DerivedDatabaseA database ChemistryLab builds from a published one (base, a name of THIRD_PARTY_DATABASES) and data of its own (inputs, paths under the package's data/). build(base_path, out_path) writes it.
ChemistryLab.asr_enthalpy Method
asr_enthalpy(record, base_path) -> Float64The enthalpy of formation (kJ/mol) of an ASR product of asr_records that agrees with its Gibbs energy and entropy at 298.15 K, data/literature/Jin2023.json), so neither is used: the database entry carries this one.
ChemistryLab.asr_logK_check Method
asr_logK_check(base_path) -> Vector{NamedTuple}For each ASR product of asr_records, the published log Ksp at 25 °C and the one recomputed from its Gibbs energy through the aqueous species of the base database, as for the zeolites: their agreement within 0.05 log units shows the product shares the reference state of the base. The build refuses otherwise.
ChemistryLab.asr_records Method
asr_records() -> Vector{NamedTuple}K-shlykovite and Na-shlykovite, the crystalline products of the alkali-silica reaction, as Jin et al. (2023) estimate them at 25 °C (Table 1, from data/literature/Jin2023.json): formula, standard Gibbs energy, entropy, heat capacity, volume, solubility product and its dissolution products, and the two enthalpies of formation published for each, the article's and its corrigendum's (Jin et al., 2024).
ChemistryLab.cashplus_entries Method
cashplus_entries() -> Vector{Dict}The ThermoFun records of the thirty-three end-members of the CASH+ model of C-S-H with its extension to the alkali and alkaline-earth metals, the heat capacity taken as constant:
the six of the core model of Kulik et al. (2022), from
data/literature/Kulik2022.json: G° and H° of its Table 8, S°, Cp° and V° of its Table 4, except the H° of TSvh, which is the value Miron et al. (2022) give;the six with sodium or potassium, from
data/literature/Miron2022a.json(Table A1), with the G° and H° of TCNh and TCKh fine-tuned by Miron et al. (2022), Table 5 for cement pore solutions;the twenty-one with Li, Rb, Cs, Mg, Sr, Ba or Ra, from the same Table A1.
ChemistryLab.cshq_chloride_entry Method
cshq_chloride_entry(db, δ; provenance = nothing) -> DictThe ThermoFun record of CSHQ-Cl = (CaCl2)0.5, the chloride end member of CSHQ, built on the pattern of the NaSiOH record of db (a parsed Cemdata18 database). δ (J/mol) is the Gibbs energy of forming it from ½ Ca²⁺ and Cl⁻ at 293.15 K and 1 bar, the one parameter fitted by data/chloride/regenerate.jl. Its entropy and volume are those of the ions it is made of, so that its dissolution has neither; its heat capacity is zero.
ChemistryLab.fe_friedel_entry Method
fe_friedel_entry(db) -> DictThe ThermoFun record of Fe-Friedel's salt, Ca₄Fe₂Cl₂(OH)₁₂·4H₂O, from Table 1 of (Lothenbach et al., 2019) as transcribed in data/literature/Lothenbach2019.json (table solid_standard_properties), on the pattern of the C4AClH10 record of db, a parsed Cemdata18 database: ΔfG⁰, ΔfH⁰, S⁰ and V⁰ at 298.15 K and 1 bar, and the heat capacity Cp = a₀ + a₁T + a₂T⁻² + a₃T^(-1/2).
ChemistryLab.fe_friedel_logK_check Method
fe_friedel_logK_check(base_path) -> NamedTupleThe log Ks0 of Fe-Friedel's salt at 25 °C printed in Table 2 of (Lothenbach et al., 2019) and the one recomputed from its ΔfG⁰ of Table 1 through the aqueous Gibbs energies of the base database, over the products of data/literature/Lothenbach2019.json (table dissolution_products), the water recovered from the hydrogen balance.
ChemistryLab.zeolite_logK_check Method
zeolite_logK_check(base_path) -> Vector{NamedTuple}For every zeolite of zeolite_records, the published log Ksp at 25 °C and the one recomputed from its published ΔfG⁰ through the aqueous Gibbs energies of the base database. Their agreement, within 0.05 log units, is what shows the two datasets share a reference state; the build refuses otherwise.
ChemistryLab.zeolite_records Method
zeolite_records() -> Vector{NamedTuple}The 28 zeolites of Ma and Lothenbach (2020), Ma and Lothenbach (2021) (Na and K series) as the two papers publish them, read from data/literature/MaLothenbach2020.json and MaLothenbach2021.json: energies in kJ/mol, S⁰ and Cp⁰ in J/(mol K), V⁰ in cm³/mol, and the products of their congruent dissolution.
ChemistryLab.HKF_SI_CONVERSIONS Constant
HKF_SI_CONVERSIONSHardcoded conversion factors from SUPCRT (Johnson et al., 1992) (cal, bar) to SI (J, Pa) for eos_hkf_coeffs. The JSON unit metadata for a3 and a4 omit the /bar the SUPCRT units carry, so the conversion factors are set here explicitly.
| 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._cp_intervals Method
_cp_intervals(methods) -> Vector{NamedTuple}The heat-capacity intervals of a ThermoFun substance, sorted by temperature: lower and upper (K), coeffs (the aᵢ with their units, as cp_ft_equation takes them) and transition, nothing or the (T, dS, dH) of the phase transition the record places at the top of the interval (m_phase_trans_props). A method without temperature limits makes the list empty: it applies everywhere.
ChemistryLab._reference_cp_interval Method
_reference_cp_interval(methods, Tref) -> method or nothingThe heat-capacity method of a ThermoFun substance that applies at the reference temperature Tref (K).
A substance whose heat capacity changes form at a phase transition, such as quartz or hematite, lists one cp_ft_equation per temperature interval. The thermodynamic functions are anchored at Tref, so the interval containing it is the one retained; a method without temperature limits applies everywhere, and when no interval contains Tref the first one listed is kept. The functions are anchored there and followed into the other intervals by complete_thermo_functions! (see _cp_intervals).
ChemistryLab.build_reactions Function
build_reactions(df_reactions::AbstractDataFrame, species_list=[], 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, instances=Dict()) -> 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", "regular", "sublattice", "compound_energy"A Redlich-Kister (Redlich and Kister, 1948) entry gives its parameters in J/mol as a0, a1, a2, or names published dimensionless Guggenheim parameters with guggenheim = "<literature key>:<pair>", a row of the guggenheim_parameters table of data/literature/<key>.json; they become a = α R T at 298.15 K, so that no published value is copied into the file. The sign of a1 follows the order of end_members. A model that unmixes needs instances = 2, the number of coexisting compositions SolidSolutionPhase may give the phase, or instances = "auto", which gives it the second only when a solve finds it wanting to split; without either the phase is refused at construction, which is what makes a gap visible. The keyword instances, a Dict from a phase name to a number or :auto, replaces what the file declares for the phases it names.
[[solid_solution]]
name = "AFm_SO4_OH"
end_members = ["C4AH13", "monosulphate12"]
model = "redlich_kister"
guggenheim = "Lothenbach2019:AFm SO4/OH"
instances = 2A sublattice entry names the published model it follows with sublattice = "<literature key>:<model>", read by sublattice_model, which also checks each end-member's formula against the one the source prints:
[[solid_solution]]
name = "CNASH_ss"
end_members = ["T2C-CNASHss", "T5C-CNASHss", "TobH-CNASHss",
"5CA", "5CNA", "INFCA", "INFCN", "INFCNA"]
model = "sublattice"
sublattice = "Myers2014:cnash"An entry whose end members exist in one database only names it with database = "<file>". Where they are absent, that database was not loaded, which is expected rather than an anomaly: the entry is skipped without a warning, whatever skip_missing says. CSHQ_Cl, whose chloride end member is in the database cemdata18-chloride.json alone, is declared so.
Example
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)
# The file holds alternatives (`CSHQ` or `CNASH_ss`, `Ettringite_ss` or
# `AFt_SO4_CO3`): a system takes the phases it needs by name.
chosen = [p for p in ss_phases if name(p) in ("CSHQ", "C3(AF)S0.84H", "Ettringite_ss")]
cs = ChemicalSystem(species, CEMDATA_PRIMARIES; solid_solutions = chosen)ChemistryLab.build_species Function
build_species(df_substances::AbstractDataFrame, list_symbols=nothing; verbose=false) -> Vector{Species}Build Species objects from a substance DataFrame: that of a ThermoFun file (read_thermofun_database), of a database of reactions (read_phreeqc_database, read_gwb_database, read_eq36_database) or of a database of Reaktoro (read_reaktoro_database). The substances named in list_symbols are looked up by their symbol, and in a database of reactions or of Reaktoro also by their name in the database.
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.
Substances defined by a reaction
A substance the database defines by a reaction (defining_reaction, see read_thermofun_database) gets its standard properties at any temperature from that reaction, as ThermoFun computes them: with the reaction list_symbols.
Methods of aq17
The volume of a mineral of Holland and Powell (mv_eos_murnaghan_hp98, the Murnaghan equation with a thermal expansion) and its order-disorder transition (landau_holland_powell98, tricritical Landau theory) are computed as (Holland and Powell, 1998) defines them, the volume integrated from the reference pressure; a mineral whose record gives no bulk modulus keeps the volume of its record, its Gibbs energy moving by solute_aknifiev_diamond03) is computed as (Akinfiev and Diamond, 2003) defines it, with the water and the water vapor of the same table. ThermoFun integrates the volume of a mineral from zero pressure and leaves it out at the reference state: away from that state its energies exceed these by the molar volume times one bar, a few J/mol.
Methods that are not computed
A substance whose record computes it by a method the package does not implement (a method ThermoFun does not list, or a dissolved gas without the water of its database) is built for its reference state only: its standard properties are those of its record at the reference temperature, and within one bar of the reference pressure; anywhere else they raise an error naming the method. A warning lists such substances.
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 of a ThermoFun database, asread_thermofun_databasereturns it: itsaggregate_statecolumn holds ThermoFun's one-entry dictionaries, which the filter reads.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). A substance the database defines by a reaction carries that reaction's record in the columndefining_reaction, from whichbuild_speciescomputes it.df_reactions: DataFrame of chemical reactions.
ChemistryLab.temperature_range Method
temperature_range(s::AbstractSpecies) -> (lower, upper)The temperatures, in K, over which the record of s declares its heat capacity: the limitsTP of its cp_ft_equation methods in a ThermoFun database, from the lowest bound of its first interval to the highest of its last. (-Inf, Inf) for a species whose record declares none, which includes every solute described by the HKF equations (Helgeson et al., 1981), whose domain is a density of water rather than a temperature (see Where the HKF equations hold), and the solvent.
Nothing refuses a temperature outside the range: the heat capacity is carried past it by the nearest interval's function. The ranges are narrow in places. Cemdata18 declares its AFm phases to 50 °C, its ettringites to 60 °C and its clinker phases from 25 °C only, so that a calculation at 80 °C extrapolates ettringite and one at 5 °C extrapolates alite; a warning at every such evaluation would fire in most calculations below 25 °C, and the function is there for the calculation that needs to know.
julia> s = Species("CaCO3"; aggregate_state = AS_CRYSTAL);
julia> temperature_range(s)
(-Inf, Inf)ChemistryLab.AbstractLogK Type
AbstractLogKThe log K of one reaction of a database of reactions, as a function of temperature, in the form its format gives: _log10K(k, T) returns it with its first two derivatives.
ChemistryLab.FormationLogK Type
FormationLogKThe log K of formation of a species of a database of reactions from its master species, as the column formation of its table of substances holds it: a combination Σ cₖ log Kₖ(T) of the constants of the database, each in the form its format gives (AbstractLogK).
ChemistryLab.PhreeqcBlock Type
PhreeqcBlockOne keyword data block of a PHREEQC file: its keyword, the line it starts on, and its logical lines as (line number, text), comments removed and the lines a semicolon joins split apart.
ChemistryLab.PhreeqcLogK Type
PhreeqcLogKThe log K of one reaction as a PHREEQC database states it: at 25 °C, with an enthalpy of reaction for the van 't Hoff equation, or an analytical expression of the temperature, which takes precedence over both (Parkhurst and Appelo 2013, SOLUTION_SPECIES and PHASES). add holds the named expressions the constant adds (-add_logk), as (index, coefficient).
ChemistryLab.database_activity_model Method
database_activity_model(df_substances) -> AbstractActivityModelThe activity model PHREEQC applies with the database whose table of substances is df_substances (read_phreeqc_database), which its blocks decide: Pitzer's with a PITZER block (PitzerActivityModel, the parameters of the block, a pair it leaves out taken as zero), the specific ion interaction theory with a SIT block (SITActivityModel), the aqueous model of Lawrence Livermore with LLNL_AQUEOUS_MODEL_PARAMETERS (LLNLActivityModel), and the WATEQ and Davies equations otherwise (TruesdellJonesActivityModel).
Each is built with the parameters the database gives its species, its Debye–Hückel parameters following temperature, and the activity of water PHREEQC computes: the osmotic coefficient of the model for Pitzer and SIT, and a_w = 1 − 0.017 Σᵢ mᵢ for the other two (Parkhurst and Appelo, 1999; Eq. 25). The species are keyed by their ChemistryLab symbols, those of build_species.
Example
_, substances, _ = read_phreeqc_database(datapath("llnl.dat"))
model = database_activity_model(substances) # an LLNLActivityModelChemistryLab.extract_primary_species Method
extract_primary_species(path) -> DataFrameThe primary species of a PHREEQC database: the master species its SOLUTION_SPECIES define by an identity reaction (Ca+2 = Ca+2), every other species being a combination of them, and the electron, written Zz, last.
One row per species, with the columns species (the PHREEQC name), symbol, formula, aggregate_state, atoms, charge and gamma (the (å, b) of -gamma, empty without one).
ChemistryLab.phreeqc_blocks Method
phreeqc_blocks(path) -> Vector{PhreeqcBlock}The keyword data blocks of a PHREEQC file, in order, up to its first END.
ChemistryLab.phreeqc_equation Method
phreeqc_equation(text) -> (lhs, rhs)The two sides of a PHREEQC equation, each a vector of (coefficient, species).
ChemistryLab.phreeqc_gamma_parameters Method
phreeqc_gamma_parameters(path) -> Dict{String, Tuple{Float64, Float64}}The WATEQ activity-coefficient parameters (å, b) that the -gamma option of a PHREEQC database gives its aqueous species, by species symbol, for TruesdellJonesActivityModel.
Every SOLUTION_SPECIES block is read, the master species as well as the others, without resolving the species against the master species: a fragment of a database is read as well as a whole one. The species a reaction defines is the first one to the right of its equal sign, as the PHREEQC manual requires, and its name becomes a ChemistryLab symbol by the rule of build_species: a neutral species gets @ appended (CO2 is CO2@), and H2O is the solvent.
A second -gamma for a species replaces the first, as PHREEQC reads the options of a species in order. A -gamma that does not give two numbers is reported with its line and skipped.
ChemistryLab.read_phreeqc_database Method
read_phreeqc_database(path) -> (df_elements, df_substances, df_reactions)Read a PHREEQC database into three tables, as read_thermofun_database reads a ThermoFun file: its master species (df_elements: element, master), its species and phases (df_substances), and its reactions as written, each with its constant (df_reactions: symbol, kind, equation, logk, line).
Every species is resolved against the master species, its composition by the balance of its reaction and its energy by the log K of its formation from them. df_substances has one row per species or phase: symbol (ChemistryLab's), name (PHREEQC's), formula, aggregate_state, class, charge, atoms, formation (a FormationLogK), the activity parameters gamma (the (å, b) of -gamma), ion_size (-llnl_gamma) and co2_gamma, the molar_volume of a phase (m³/mol), the critical constants T_c (K), P_c (Pa) and omega of a gas, gauge and line; build_species builds from it, and database_activity_model finds in it the activity model of the database. Its metadata (metadata(df_substances, "notes")) hold format, path, source, gauge, the parameters of the LLNL model, the keywords of the blocks, and the notes: what is read and not used, with its line — transport data (-dw, -Millero), an option the manual does not document, a species whose equation does not balance (-mole_balance, -no_check), and the blocks that are not thermodynamic data (RATES).
Any PHREEQC database is read the same way. The ones PHREEQC distributes are obtained by datapath: datapath("phreeqc.dat"), "llnl.dat", "minteq.v4.dat", "wateq4f.dat", "sit.dat", "pitzer.dat".
ChemistryLab.read_gwb_database Method
read_gwb_database(path) -> (df_elements, df_substances, df_reactions)Read a thermo dataset of The Geochemist's Workbench (.tdat, format "jan19" or later, and the earlier formats whose sections are the same): its elements, basis species, redox couples, aqueous species, free electron, minerals and gases, each with its reaction and its log K, as a table at the principal temperatures or as a polynomial (Bethke and Farrell, 2026; chapter 3).
The species are resolved against the basis species as those of a PHREEQC database are against its master species, and laid out in the same three tables as read_phreeqc_database lays them out, from which build_species builds: their energies are counted from the basis species, which are at zero, a gauge recorded under :gauge. A log K table is evaluated as the dataset's applications evaluate it between principal temperatures, by the polynomial of degree four fitted to it. What is read and not used is listed in notes: the solid solutions, the oxide components, the virial coefficients.
No thermo dataset is distributed with the package or downloaded by it: the datasets of The Geochemist's Workbench come under the terms of their publishers, and a copy is installed by hand (install_database).
ChemistryLab.read_eq36_database Method
read_eq36_database(path) -> (df_elements, df_substances, df_reactions)Read a data0 file of EQ3/6, of the com archetype (an extended Debye-Hückel activity model): its parameters, its elements, and its species, auxiliary basis species, minerals, liquids and gases, each with its composition, its reaction and its log K on the temperature grid (Daveler and Wolery, 1992; chapter 4).
The species are resolved against the strict basis species as those of a PHREEQC database are against its master species, and laid out in the same three tables as read_phreeqc_database lays them out, from which build_species builds: their energies are counted from the strict basis species, which are at zero, a gauge recorded under :gauge. Their composition is the one each block states, checked against the balance of its reaction. A log K is evaluated between the temperatures of the grid as EQPT evaluates it, by an interpolating polynomial through the valid points of each part of the grid. The solid solutions and the Pitzer parameters of the hmw archetype are noted and not read.
No data0 file is distributed with the package or downloaded by it; a copy is installed by hand (install_database).
ChemistryLab.read_reaktoro_database Method
read_reaktoro_database(path) -> (df_elements, df_substances, df_reactions)Read a database of Reaktoro, a YAML file whose Species map gives each species with its standard thermodynamic model, into the three tables read_thermofun_database returns for a ThermoFun file. df_substances has one row per species: symbol (ChemistryLab's), name, formula, aggregate_state, class, charge, atoms, standard_model and its parameters (in SI units, as the file gives them), and gauge; the file's reactions are not read, and df_reactions is empty.
build_species builds from df_substances the models the package computes: HKF (the revised Helgeson-Kirkham-Flowers equations of the aqueous species, as solute_hkf88_reaktoro computes them), MaierKelley (a heat capacity a + bT + c/T² and a constant volume), HollandPowell (a heat capacity a + bT + c/T² + d/√T and the volume of the modified Tait equation of state of (Holland and Powell, 2011), integrated from the reference pressure; a constant volume without kappa0), and water (WaterHKF, WaterHGK, WaterWagnerPruss), the solvent of the package's own equation of state. Reaktoro integrates the volume of HollandPowell from zero pressure, as the article writes it: its energies exceed these by the molar volume times one bar, a few J/mol. A species of HollandPowell with a Landau transition (Tcr, Smax, Vmax), for which the article states no equations of its own, is used at its reference state only. Any other model is left out, with a warning naming it.
No database of Reaktoro is distributed with the package or downloaded by it: the data of its files come from several sources, under their own terms, and a copy is installed by hand (install_database).
ChemistryLab.import_database Method
import_database(path; format = :auto) -> (df_elements, df_substances, df_reactions)Read a thermodynamic database of any of the formats the package reads, into the three tables read_thermofun_database returns for a ThermoFun file:
format | files | read by |
|---|---|---|
:phreeqc | PHREEQC databases (.dat) | read_phreeqc_database |
:gwb | thermo datasets of The Geochemist's Workbench (.tdat) | read_gwb_database |
:eq36 | data0 files of EQ3/6 | read_eq36_database |
:thermofun | ThermoFun databases (.json) | read_thermofun_database |
:reaktoro | Reaktoro databases (.yaml) | read_reaktoro_database |
:auto decides by the name of the file, then by its first lines. build_species builds the species of df_substances in every case. path is a file, or the name of a database datapath knows.
Example
_, substances, _ = import_database(datapath("phreeqc.dat"))
species = build_species(substances, ["H2O", "H+", "OH-", "Ca+2", "CO3-2", "HCO3-", "CO2", "Calcite"])Published values
The values taken from articles, one file per source under data/literature/; see Published values live in data files.
ChemistryLab.LITERATURE_SCHEMA Constant
LITERATURE_SCHEMAIdentifier of the format of the files in data/literature/, written in each of them as "schema". See literature for the fields.
ChemistryLab.LiteratureRecord Type
struct LiteratureRecordThe content of one data/literature/<key>.json file, as returned by literature.
Fields
key: the bibliography key, which is also the file name.path: the file it was read from.source: the"source"object of the file (key, DOI, citation, location).transcription: how the values were transcribed and checked.quantities: named scalars, each aTracedvalue carrying its provenance kind, its source and, when the source gives one, its uncertainty.tables: named tables, each aNamedTupleof column vectors.table_info: for each table, its provenancekind, itslocationin the source and, for values read off a figure, itsdigitization; seeliterature_table_info.notes: free remarks recorded with the data.
record[name] returns the quantity name; literature_value and literature_table are the shortcuts most callers need.
ChemistryLab.available_literature Method
available_literature() -> Vector{String}The keys of every source with a file in data/literature/.
ChemistryLab.literature Method
literature(key) -> LiteratureRecordThe values taken from the published source key, read from data/literature/<key>.json and checked against the format LITERATURE_SCHEMA as they are read. A malformed file is an error that names the file and the field, never a silently different number.
The format
{
"schema": "chemistrylab-literature/1",
"source": {"key": "Powers1948", "doi": null,
"citation": "…", "location": "p. 185"},
"transcription": {"from": "…", "checked_against_source": false,
"note": "…"},
"quantities": {
"w_c_sealed": {"value": 0.42, "unit": "1", "kind": "published",
"uncertainty": null, "location": "…",
"description": "…"}
},
"tables": {
"name": {"columns": ["m", "phi"], "units": ["mol/kg", "1"],
"kind": "published", "location": "Table 16",
"rows": [[0.1, 0.932], [0.2, 0.925]]}
},
"notes": ["…"]
}source.keymust equal the file name and be an entry ofrefs.bib, whose DOI, when it has one,source.doirepeats.kindis one ofpublished,measured,fitted,estimated,placeholder,unstated, the names ofProvenanceKind.A table whose values were read off a figure, not printed as numbers, says so in
"digitization":{"method": "vector drawing" | "raster image", "tool": "…", "uncertainty": …, "note": "…"}.uncertaintyis the error the reading adds, on top of the source's own:{"relative": 0.002},{"value": 0.2, "unit": "K"}, ornullwhen the reading was not quantified (the note then says what bounds it). Itskindstays what the values are (a measurement read off a plot is stillmeasured); the field records how they reached the file.unitis unit arithmetic read byDynamicQuantities("J/mol","m^2/g");"1"returns a plain number. A column whose unit isnullholds text (a phase name, a label) and is returned as such.A cell the source leaves empty (printed "n.d.", "n.a." or a dash) is
null, and reads asmissing: a wide table keeps its shape without a number the source does not give. A table in long format leaves the row out instead.
Records are read once and cached. Reading one while the package precompiles registers the file as a dependency, so that editing it recompiles the package instead of leaving a stale value in the compiled image.
See also: literature_value, literature_table, literature_row, Traced.
ChemistryLab.literature_path Method
literature_path(key) -> StringThe file that holds the values taken from the source key: data/literature/<key>.json in the package directory.
ChemistryLab.literature_row Method
literature_row(key, table, label) -> NamedTupleThe row of the table table of the source key whose first column is label, as a NamedTuple of its entries: the phase, mix or species a row is about is what a caller knows, not its position.
co = literature_row("BaroghelBouny1999", "retention_fit", "CO")
VanGenuchten(; a = co.a, m = 1 / co.b)ChemistryLab.literature_table Method
literature_table(key, name; column = value, ...) -> NamedTupleThe table name taken from the source key, as a NamedTuple of columns. Each column carries the unit the file declares for it, and a column declared without a unit holds text.
Keywords keep the rows whose columns equal the values given, all of them, which is how a table in long format is read — one row per measurement, the conditions of each in its other columns:
literature_table("Durdzinski2017", "degree_of_reaction";
technique = "SEM-IA", material = "S1", curing = "sealed", age_days = 28)A keyword that names no column is an error rather than a filter that keeps nothing.
ChemistryLab.literature_table_info Method
literature_table_info(key, name) -> NamedTupleWhat the file says about the table name of the source key beyond its values: kind (a ProvenanceKind), location in the source, and digitization, nothing for values printed as numbers and otherwise (; method, tool, uncertainty, relative, note) for values read off a figure. uncertainty is the error the reading adds, relative (relative = true) or absolute with its unit (relative = false); both are nothing when the reading was not quantified.
literature_table_info("Deschner2012", "pore_solution").digitization.uncertaintyChemistryLab.literature_value Method
literature_value(key, name)The value of the quantity name taken from the source key, with its unit (a DynamicQuantities quantity) or as a plain number when it is dimensionless. Its provenance is dropped here, which is where a value enters a calculation; literature(key)[name] keeps it.
ChemistryLab.read_literature Method
read_literature(path) -> LiteratureRecordRead and check one file of the format LITERATURE_SCHEMA. Most callers want literature, which finds the file from the source key and caches the result; this reads an arbitrary path and caches nothing.
Published sorption models
Reading a sorption model written in the PHREEQC format (Parkhurst and Appelo, 2013) — 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 (Marinich et al., 2025), the model these were written against, is CC-BY-4.0 and freely available from its Zenodo deposit (Marinich et al., 2025); 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 (Marinich et al., 2025) 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 SorptionReaction{T}One 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, as a rational number (0.5 Xis1//2).log_K: aTracedwhose source is theref:tag and whose uncertainty is theerror:tag, when the entry carries them, in the number typeTit is given in: alog Kbeing fitted carries its derivative.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._parse_sorption_stoichiometry Method
_parse_sorption_stoichiometry(equation) -> Dict{String,Rational{Int}}species => coefficient for a PHREEQC reaction line, negative on the left of the = and positive on the right, a species on both sides counted once: the equation is read by phreeqc_equation, as every PHREEQC reaction is, and its coefficients as written, exactly (0.5 is 1//2).
ChemistryLab.log_constants Method
log_constants(m::SorptionModel) -> Vector{<:Traced}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 (Marinich et al., 2025); 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.