# Customizing the thermodynamic database

#### Jeffrey M. Dick

This vignette was compiled on 2026-08-31 with CHNOSZ version 2.3.0.

This vignette will cover some topics about using custom thermodynamic data in CHNOSZ. The two main functions to remember are `add.OBIGT()` to add data from a CSV file and `mod.OBIGT()` to add or modify data through a function interface. A third function, `logK.to.OBIGT()`, is provided to fit thermodynamic parameters to experimental formation constants (log β).

Before describing the methods to add or modify data, some notes on the basic structure of the database and data entry conventions are given. Column names (or parts thereof) are formatted in blue (e.g. `formula`), and important notes are highlighted in yellow (NOTE). Function names in CHNOSZ are colored red for functions that have side effects (including those that modify the database; e.g. `add.OBIGT()`) and green for functions that don’t have side effects. Note that `info()` is used for querying the OBIGT thermodynamic database, and `subcrt()` is the main function in CHNOSZ for calculating standard thermodynamic properties as a function of temperature and pressure from the parameters in the database.

## Basic structure of OBIGT

OBIGT is the name of the thermodynamic database in CHNOSZ. The data are distributed in CSV files in the `inst/extdata/OBIGT` directory of the CHNOSZ package. When the package is installed, the files are copied to the `exdata/OBIGT` directory of the installed package location. To find out where this is on your computer, run the following command.

    system.file("extdata/OBIGT", package = "CHNOSZ")

    ## [1] "/home/tmp/RtmpXLddU6/Rinst6797717e42ef/CHNOSZ/extdata/OBIGT"

The directory path on your computer will be different. Although possible, it is NOT recommended to edit the data files at that location. This is because they will be overwritten by package updates; moreover, it is good practice to keep all the files needed for your project in a project directory.

This lists the files in the installation directory:

    dir(system.file("extdata/OBIGT", package = "CHNOSZ"))

    ##  [1] "AD.csv"            "Berman_cr.csv"     "DEW.csv"
    ##  [4] "H2O_aq.csv"        "SLOP98-a.csv"      "SLOP98-b.csv"
    ##  [7] "SUPCRT92.csv"      "inorganic_aq.csv"  "inorganic_cr.csv"
    ## [10] "inorganic_gas.csv" "inorganic_liq.csv" "organic_aq.csv"
    ## [13] "organic_cr.csv"    "organic_gas.csv"   "organic_liq.csv"
    ## [16] "testing"

Some of these files are used to build the default OBIGT database that is created when CHNOSZ starts up. There are also a number of additional data files that have optional datasets. The OBIGT thermodynamic database vignette summarizes the contents of the default and optional data files. The files can also be opened by a spreadsheet program and used as templates for adding data yourself.

`thermo()$OBIGT` (hereafter, just OBIGT) is the “live” version of the database that is assembled from the CSV data files when CHNOSZ starts up or by using the `reset()` or `OBIGT()` functions. The OBIGT data frame is stored in an environment named `CHNOSZ` that is part of the namespace of the CHNOSZ package. More specifically, it is part of a list named `thermo`, which has the OBIGT database and other parameters and settings used by CHNOSZ. `reset()` restores the entire `thermo` object to default values; `OBIGT()` restores just the OBIGT data frame. The latter is useful for seeing the effects of changing the thermodynamic database on on chemical affinities calculated with `affinity()`, without changing the chemical species.

OBIGT can be modified during an R session; if it couldn’t, some of the examples in this vignette would not be possible! When you quit R, it offers the option of saving your workspace so it can be reloaded it when R is restarted. I always say “no” here; my preference is to load data into a fresh session every time I start R. This “load saved workspace” feature means that OBIGT might not be the default database in any given R session. To ensure that this vignette is run using the default database, we start by running `reset()` to reset OBIGT and the other settings used by CHNOSZ.

    reset()

    ## reset: resetting "thermo" object

    ## OBIGT: loading default database with 1327 aqueous, 3099 total species

`thermo()` is a convenience function to access or modify parts of the `thermo` list object. To see the first few entries in OBIGT, do this:

    head(thermo()$OBIGT)

    ##    name abbrv formula state    ref1  ref2       date model E_units      G
    ## 1 water  <NA>     H2O   liq   JOH92 HGK84 2006-10-25   H2O     cal     NA
    ## 2    e-  <NA>   (Z-1)    aq OBIGT06  <NA> 2006-10-28   HKF     cal      0
    ## 3    H+    H+      H+    aq   Den81  <NA> 1997-11-06   HKF     cal      0
    ## 4   Li+   Li+     Li+    aq    SH88  <NA> 1997-11-06   HKF     cal -69933
    ## 5   Na+   Na+     Na+    aq    SH88  <NA> 1997-11-06   HKF     cal -62591
    ## 6    K+    K+      K+    aq    SH88  <NA> 1997-11-06   HKF     cal -67510
    ##        H       S    Cp     V    a1.a   a2.b   a3.c    a4.d  c1.e   c2.f
    ## 1     NA      NA    NA    NA      NA     NA     NA      NA    NA     NA
    ## 2      0 15.6166  0.00  0.00  0.0000  0.000  0.000  0.0000  0.00  0.000
    ## 3      0  0.0000  0.00  0.00  0.0000  0.000  0.000  0.0000  0.00  0.000
    ## 4 -66552  2.7000 14.20 -0.87 -0.0237 -0.069 11.580 -2.7761 19.20 -0.240
    ## 5 -57433 13.9600  9.06 -1.11  1.8390 -2.285  3.256 -2.7260 18.18 -2.981
    ## 6 -60270 24.1500  1.98  9.06  3.5590 -1.473  5.435 -2.7120  7.40 -1.791
    ##   omega.lambda z.T
    ## 1           NA  NA
    ## 2       0.0000   0
    ## 3       0.0000   0
    ## 4       0.4862   1
    ## 5       0.3306   1
    ## 6       0.1927   1

## Conventions for data entry in OBIGT

The format of OBIGT is described in the CHNOSZ manual: see `thermo()`. Next, we point out some particular conventions including types of data, required and optional data, order-of-magnitude scaling. Here are the numbered column names for reference:

    ##  [1] "1 name"          "2 abbrv"         "3 formula"       "4 state"
    ##  [5] "5 ref1"          "6 ref2"          "7 date"          "8 model"
    ##  [9] "9 E_units"       "10 G"            "11 H"            "12 S"
    ## [13] "13 Cp"           "14 V"            "15 a1.a"         "16 a2.b"
    ## [17] "17 a3.c"         "18 a4.d"         "19 c1.e"         "20 c2.f"
    ## [21] "21 omega.lambda" "22 z.T"

### Types of data

- Columns 1–9 have character data.
  - Column 8 is named model; here are the two models that are most frequent in the default database:
    - `HKF`: Revised Helgeson-Kirkham-Flowers “equation-of-state” parameters for aqueous species
    - `CGL`: Heat capacity coefficients for crystalline, gaseous, and liquid species. The first three terms in the CGL heat capacity equation correspond to the Maier-Kelley equation for heat capacity (Maier and Kelley, 1932); the additional terms are useful for representing heat capacities of minerals (Robie and Hemingway, 1995) and organic gases and liquids (Helgeson et al., 1998).
- Columns 10–22 have numeric data.
  - Columns 10–14 have standard-state thermodynamic properties at 25 °C and 1 bar.
  - Columns 15–21 have parameters for calculating thermodynamic properties at other temperatures and pressures.
    - The columns are named by combining the the names of the HKF and CGL coefficients, separated by a dot.
  - Column 22 has the charge used in the HKF EOS or the maximum temperature for CGL species.
    - NOTE: The value of charge used in the HKF EOS (in particular, the g function for the temperature derivatives of the ω parameter (Shock et al., 1992)) is taken from this column and not from the chemical formula of the species.

### Ranges of HKF and CGL models

To a first approximation, the revised HKF equations of state are applicable within the stability region of liquid water or the supercritical fluid with a density greater than 0.35 g/cm3, and not exceeding the ranges of 0 to 1000 °C and 1 to 5000 bar (see Shock et al., 1992 for details). There are two ways in which these limits are enforced in CHNOSZ:

- The default source of water properties (H2O92D Fortran subroutine modified from SUPCRT92) yields NA values beyonds these T and P ranges. Note that the Deep Earth Water (DEW) model is available to extend the applicable range to pressures of up to 60 kbar (6 GPa) (Sverjensky et al., 2014).
- `subcrt()` generates NA values beyond the density limit; the `exceed.rhomin` argument can be used to enable calculations at lower density in the supercritical region.

The upper temperature limit for validity of the CGL heat-capacity equation is a species-dependent parameter. This value is stored as a negative value in the `T` column of OBIGT and is used by `subcrt()` to issue a warning at temperatures beyond this limit.

### Required and optional data

REQUIRED:

- All species need a `name` and a `state`.
  - The state can be one of `aq`, `gas`, or `cr`.
  - For minerals with higher-temperature polymorphs, they are named `cr2`, `cr3`, etc.
  - NOTE: `cr` stands for “crystalline”; this naming convention (which was inherited from SUPCRT92 data files) refers to any solid phases including amorphous SiO2 and other minerals.
- A chemical `formula` is required to do almost anything useful in CHNOSZ (e.g. check reaction balancing with `subcrt()` and add species with `basis()` or `species()`).
  - NOTE: The `name` of inorganic aqueous species and CH4 in OBIGT is the same as the chemical formula.
  - Most minerals, gases, liquids, and organic aqueous species have a `name` that is a common name. This permits a shortcut to identify commonly used species in `subcrt()`.
  - For example, `info("O2")` refers to dissolved oxygen, while `info("oxygen")` or `info("O2", "gas")` refers to the gas.
- `E_units` needs to be defined to perform any calculations of thermodynamic properties.
  - The value can be `J` for Joules or `cal` for calories.

OPTIONAL: Everything else. Really, it depends on what you need. For instance, if you just want to use `subcrt()` to calculate log β of a reaction from ΔG° of species at 25 °C, then `G` is the only parameter that is needed.

OPTIONAL but useful:

- `abbrv` may be an abbreviation (e.g. Qtz for quartz). It is used by `info()` (together with `name` and `formula`) to look up species in the database.
- `date` is a timestamp for the data entry (YYYY-MM-DD format in the default OBIGT database).
- `ref1` and `ref2` are bibliographic reference keys. They have matching entries in `extdata/thermo/refs.csv`, which is used by `thermo.refs()` to display references, and in `vignettes/OBIGT.bib`, which is used in the OBIGT thermodynamic database vignette to produce a reference list.

NOTE: Other functions in CHNOSZ do not depend on `date`, `ref1`, and `ref2`, so you can put anything there that is convenient for you.

### NA or 0?

If a character value (in Columns 1–9) or thermodynamic parameter (in Columns 10–14) is unknown, use `NA`. Note that a missing (blank) value in the file is treated as NA.

- Unknown values for character values (usually `abbrv`, `date`, `ref1`, or `ref2`) should be NA.
- If you have only two of `G`, `H`, and `S`, then the missing one should be NA.
  - Do NOT set a missing value of `G`, `H`, or `S` to 0. Zero is a numeric value that is incorrect except for very special cases.
  - NOTE: `info()` – and, by extension, `subcrt()` – “know” about the equation ΔG°f = ΔH°f - TΔS°f and the entropies of the elements needed to calculate ΔS°f from values of `S` in OBIGT. This equation is used to compute a missing value of `G`, `H`, or `S` from the other two, or to cross-check the values if all three are present for any species.
- If you don’t have `Cp` or `V`, then set it to NA.
  - If HKF or CGL parameters are present, they will be used to calculate `Cp`, so thermodynamic properties can be calculated at T > 25 °C.
  - If HKF or CGL parameters aren’t present, thermodynamic properties can’t be calculated at T > 25 °C (NAs will propagate to higher T).

If an “equation-of-state” parameter or heat capacity coefficient (Columns 15-21) is unknown, use 0.

- Furthermore, if you would like to assume that `Cp` or `V` is 0, then set it to 0.
  - Then, thermodynamic properties will be extrapolated to T > 25 °C and P > 1 bar assuming that `Cp` and `V` are 0.

More detail on the inner working of the functions: For both HKF and CGL, if at least one parameter for a species is provided, any NA values of the other parameters are taken to be zero. If all EOS parameters are NA, but values of `Cp` and/or `V` are present, they are assumed to be constants for extrapolating thermodynamic properties (e.g. ΔG°) as a function of temperature and pressure.

### OOM scaling and `info()`

HKF parameters in the the CSV files and OBIGT data frame are scaled by order-of-magnitude (OOM) factors. For these parameters, OOM scaling is nearly always used in published data tables. See `thermo()` for details of the OOM scaling.

`info()` provides a simple user interface to the OBIGT database and is called by other functions in CHNOSZ to retrieve unscaled values from the database. This is a summary of its main features:

- Remove OOM scaling. This is used primarily by other functions in CHNOSZ to get a set of unscaled model parameters for calculating thermodynamic properties as a function of T and P.
- Extract the HKF or CGL parts of column names (only if all matching species have the same model).
- Calculate a missing one of `G`, `H`, or `S` if two of them are present.
- Cross-check `G`, `H`, and `S` if all of them are present, and print a message if the difference is above a threshold (see `check.GHS()`).
- Calculate a missing `Cp` or `V` from the model parameters, if possible.
- Cross-check `Cp` or `V` (if present) against the model parameters, if possible, and print a message if the difference is above a threshold (see `check.EOS()`).

NOTE: `info()` does NOT change the units of energy; the values it displays (including possibly calculated ones) correspond to the `E_units` for that species in OBIGT. On the other hand, `subcrt()` outputs values in the units previously selected with the function `E.units()`.

## Case study: NA and 0 in the default database

Use the `info()` function to look at the database and `subcrt()` to calculate thermodynamic properties.

Let’s look at some minerals first. First use `info()` to get the species indices (i.e. rownumbers) in OBIGT, then pull out the “raw” data (including any NA values).

    icr <- info(c("orpiment,amorphous", "arsenic,alpha", "tin"))
    thermo()$OBIGT[icr, ]

    ##                    name abbrv formula state ref1     ref2       date model
    ## 1545 orpiment,amorphous  <NA>   As2S3    cr NA03     <NA> 2017-10-16   CGL
    ## 1538      arsenic,alpha  <NA>      As    cr NA03 ZZL+16.1 2017-10-16   CGL
    ## 1488                tin    Sn      Sn    cr JH85     <NA> 1985-08-00   CGL
    ##      E_units      G      H      S    Cp      V a1.a   a2.b a3.c a4.d c1.e c2.f
    ## 1545       J -76800 -66900 200.00    NA     NA   NA     NA   NA   NA   NA   NA
    ## 1538       J      0      0  35.63 24.43 12.960   NA     NA   NA   NA   NA   NA
    ## 1488     cal      0      0  12.24    NA 16.289 4.42 0.0063    0    0    0    0
    ##      omega.lambda    z.T
    ## 1545           NA     NA
    ## 1538           NA     NA
    ## 1488            0 505.06

Based on the values in the `Cp` column, would you predict that thermodynamic properties at T > 25 °C could be calculated for all of these minerals? Let’s see …

For conciseness we’ll consider a relatively small temperature range and display only the `out` part of the `subcrt()` output.

    subcrt("orpiment,amorphous", T = c(25, 50, 75))$out[[1]]

    ## subcrt: 1 species at 3 values of T (ºC) and P (bar) [energy units: J]

    ##    T P     logK      G      H   S  V Cp
    ## 1 25 1 13.45478 -76800 -66900 200 NA NA
    ## 2 50 1       NA     NA     NA  NA NA NA
    ## 3 75 1       NA     NA     NA  NA NA NA

That makes sense; integrating NA `Cp` to calculate Gibbs energy and other thermodynamic properties would propagate NA, and that is what appears in the output. Now let’s run the calculation for the alpha phase of arsenic.

    subcrt("arsenic,alpha", T = c(25, 50, 75))$out[[1]]

    ## subcrt: 1 species at 3 values of T (ºC) and P (bar) [energy units: J]

    ##    T P      logK          G       H        S     V    Cp
    ## 1 25 1 0.0000000     0.0000    0.00 35.63000 12.96 24.43
    ## 2 50 1 0.1480077  -915.6687  610.75 37.59710 12.96 24.43
    ## 3 75 1 0.2818552 -1878.6344 1221.50 39.41755 12.96 24.43

What happened here? Even though there are no heat capacity coefficients (see above), there is a non-NA value of `Cp`, and that value is used together with the entropy for calculating Gibbs energy at T > 25 °C. Note that zero for the 25 °C values of G and H in this case is not a placeholder for unknown values (as noted above, unknown values should be represented by NA). Instaed, this is the reference state for the element, for which G and H are by convention equal to zero.

Let’s look at another element in its reference state, tin:

    subcrt("tin", T = c(25, 50, 75))$out[[1]]

    ## info.character: found tin(cr) with 1 polymorphic transition

    ## subcrt: 1 species at 3 values of T (ºC) and P (bar) [energy units: J]

    ## subcrt: 2 polymorphs for tin ... polymorph 1 is stable

    ##    T P      logK         G         H        S      V       Cp polymorph
    ## 1 25 1 0.0000000     0.000    0.0000 51.21216 16.289 26.35228         1
    ## 2 50 1 0.2113275 -1307.404  667.0441 53.36022 16.289 27.01126         1
    ## 3 75 1 0.4001482 -2667.087 1350.5628 55.39726 16.289 27.67024         1

Are you surprised? You might be if you only noticed the NA value for `Cp` in OBIGT. However, there are non-NA values for the heat capacity coefficients, which are used to calculate Cp° as a function of temperature. When supplied with a numeric argument (a species index), `info()` actually does this to fill in missing 25 °C values of `Cp`, `V`, and `G`, `H`, or `S` if possible, in addition to simplifying column names:

    info(info("tin"))

    ## info.character: found tin(cr) with 1 polymorphic transition

    ## info.numeric: Cp° of tin(cr) is NA; set by EOS parameters to 6.3 cal K-1 mol-1

    ##      name abbrv formula state ref1 ref2       date model E_units G H     S
    ## 1488  tin    Sn      Sn    cr JH85 <NA> 1985-08-00   CGL     cal 0 0 12.24
    ##            Cp      V    a      b c d e f lambda      T
    ## 1488 6.298345 16.289 4.42 0.0063 0 0 0 0      0 505.06

## Examples of adding data from a file

Using `add.OBIGT()` to add data from optional data files for OBIGT or CSV files you make yourself.

### `add.OBIGT()` with optional data files

The default database has parameters for many minerals from Berman (1988); a notable exception is sulfide minerals, which are from Helgeson et al. (1978). Besides the different literature sources in `ref1`, the model column indicates that a different model is used for these minerals (Berman equations or CGL).

    info(info(c("quartz", "pyrite")))

    ## info.numeric: Cp° of pyrite(cr) is NA; set by EOS parameters to 14.84 cal K-1 mol-1

    ##        name abbrv formula state   ref1   ref2       date   model E_units
    ## 1382 quartz    Qz    SiO2    cr  Ber88   <NA> 2017-10-01  Berman       J
    ## 1470 pyrite    Py    FeS2    cr HDNB78 RH95.7 1978-05-05 CGL_Ttr     cal
    ##            G       H     S       Cp     V     a       b       c  d  e  f lambda
    ## 1382 -856288 -910700 41.46 44.74227 22.69    NA      NA      NA NA NA NA     NA
    ## 1470  -38293  -41000 12.65 14.84248 23.94 17.88 0.00132 -305000  0  0  0      0
    ##         T
    ## 1382   NA
    ## 1470 1015

Sometimes it is useful to load mineral data from the SUPCRT92 database, corresponding largely to the compilation by Helgeson et al. (1978). This can be done with `add.OBIGT()`. In this example we load SUPCRT92 data for just one mineral, quartz.

    add.OBIGT("SUPCRT92", "quartz")

    ## add.OBIGT: read 1 rows; made 1 replacements, 0 additions [energy units: cal]

    info(info("quartz"))

    ## info.numeric: Cp° of quartz(cr) is NA; set by EOS parameters to 10.63 cal K-1 mol-1

    ##        name abbrv formula state   ref1 ref2       date model E_units       G
    ## 1382 quartz   Qtz    SiO2    cr HDNB78 <NA> 1978-05-05   CGL     cal -204646
    ##            H    S       Cp      V     a      b       c d e f lambda   T
    ## 1382 -217650 9.88 10.62748 22.688 11.22 0.0082 -270000 0 0 0      0 848

Here we load all minerals available in the optional SUPCRT92 data file and then list the names. NOTE: `suppressMessages()` is used to suppress messages from `info()` about missing parameters, and `unique()` is used to list each mineral only once (because each polymorph has a separate entry).

    iSUPCRT92 <- add.OBIGT("SUPCRT92")

    ## add.OBIGT: read 177 rows; made 69 replacements, 108 additions [energy units: cal]

    unique(suppressMessages(info(iSUPCRT92))$name)

    ##   [1] "akermanite"          "albite"              "albite,high"
    ##   [4] "albite,low"          "almandine"           "andalusite"
    ##   [7] "andradite"           "annite"              "anorthite"
    ##  [10] "anthophyllite"       "antigorite"          "aragonite"
    ##  [13] "boehmite"            "brucite"             "Ca-Al-pyroxene"
    ##  [16] "cassiterite"         "calcite"             "chrysotile"
    ##  [19] "clinozoisite"        "coesite"             "corundum"
    ##  [22] "cristobalite,alpha"  "cristobalite,beta"   "diaspore"
    ##  [25] "diopside"            "dolomite"            "enstatite"
    ##  [28] "epidote"             "fayalite"            "ferrosilite"
    ##  [31] "ferrotremolite"      "fluorphlogopite"     "fluortremolite"
    ##  [34] "forsterite"          "gehlenite"           "gibbsite"
    ##  [37] "glaucophane"         "greenalite"          "grossular"
    ##  [40] "grunerite"           "hedenbergite"        "hematite"
    ##  [43] "jadeite"             "K-feldspar"          "kaolinite"
    ##  [46] "kyanite"             "lawsonite"           "lime"
    ##  [49] "magnesite"           "magnetite"           "margarite"
    ##  [52] "merwinite"           "minnesotaite"        "monticellite"
    ##  [55] "muscovite"           "paragonite"          "periclase"
    ##  [58] "phlogopite"          "prehnite"            "pyrope"
    ##  [61] "pyrophyllite"        "quartz"              "sillimanite"
    ##  [64] "spinel"              "talc"                "tremolite"
    ##  [67] "wollastonite"        "zoisite"             "rutile"
    ##  [70] "aegerine"            "amesite,14A"         "amesite,7A"
    ##  [73] "amorphous silica"    "analcime"            "analcime,dehydrated"
    ##  [76] "Ca-phillipsite"      "celadonite"          "chabazite"
    ##  [79] "chalcedony"          "chloritoid"          "clinochlore,14A"
    ##  [82] "clinochlore,7A"      "cordierite,dry"      "cordierite,hydrous"
    ##  [85] "cristobalite"        "cronstedtite,7A"     "cummingtonite"
    ##  [88] "daphnite,14A"        "daphnite,7A"         "dickite"
    ##  [91] "dolomite,disordered" "dolomite,ordered"    "edenite"
    ##  [94] "epidote,ordered"     "epistilbite"         "ferroedenite"
    ##  [97] "ferrogedrite"        "ferropargasite"      "fluoredenite"
    ## [100] "halloysite"          "hastingsite"         "heulandite"
    ## [103] "K-phillipsite"       "kalsilite"           "larnite"
    ## [106] "laumontite"          "leonhardite"         "magnesiohastingsite"
    ## [109] "magnesioriebeckite"  "microcline,maximum"  "Na-phillipsite"
    ## [112] "natrolite"           "nepheline"           "pargasite"
    ## [115] "PD-oxyannite"        "richterite"          "riebeckite"
    ## [118] "sanidine,high"       "sepiolite"           "spessartine"
    ## [121] "staurolite"          "stilbite"            "wairakite"
    ## [124] "titanite"

### `add.OBIGT()` with other CSV files

`add.OBIGT()` can also be used to add data from a user-specified file to the OBIGT database. The file must be a CSV (comma separated value) file with column headers that match those in the default database (i.e., `thermo()$OBIGT`). As an example, here are the contents of `BZA10.csv`, which has parameters taken from Bazarkina et al. (2010). Missing values are indicated by `NA`:

    file <- system.file("extdata/misc/BZA10.csv", package = "CHNOSZ")
    read.csv(file, as.is = TRUE)

    ##      name abbrv formula state  ref1 ref2       date model E_units       G  H
    ## 1   CdCl+    NA   CdCl+    aq BZA10   NA 2010-07-03   HKF     cal  -52629 NA
    ## 2   CdCl2    NA   CdCl2    aq BZA10   NA 2010-07-03   HKF     cal  -84883 NA
    ## 3  CdCl3-    NA  CdCl3-    aq BZA10   NA 2010-07-03   HKF     cal -115399 NA
    ## 4 CdCl4-2    NA CdCl4-2    aq BZA10   NA 2010-07-03   HKF     cal -145583 NA
    ##       S     Cp     V    a1.a    a2.b    a3.c    a4.d    c1.e    c2.f
    ## 1  7.06  11.12  2.20  2.2303 -2.3357  6.6681 -2.6824 16.6723 -0.7693
    ## 2 25.72 116.01 42.21  7.5221 10.5852  1.5895 -3.2166 73.7023 20.5956
    ## 3 45.15  97.78 63.47 10.8045 18.5994 -1.5605 -3.5479 72.0244 16.8832
    ## 4 50.61  42.52 81.35 13.8329 25.9938 -4.4669 -3.8536 53.6766  5.6267
    ##   omega.lambda z.T
    ## 1       0.4372   1
    ## 2      -0.0495   0
    ## 3       0.9378  -1
    ## 4       2.4766  -2

Loading the data with `add.OBIGT()` produces a message that the new data replace existing species. We can then use `subcrt()` to calculate the equilibrium constant for a reaction involving the new species. Note the decrease in the stepwise stability constant for the second cadmium chloride complex with increasing pressure (Bazarkina et al., 2010, Fig. 4).

    iCd <- add.OBIGT(file)

    ## add.OBIGT: read 4 rows; made 4 replacements, 0 additions [energy units: cal]

    subcrt(c("CdCl+", "Cl-", "CdCl2"), c(-1, -1, 1), T = 25, P = c(1, 2000))

    ## subcrt: 3 species at 2 values of T (ºC) and P (bar) (wet) [energy units: J]

    ## $reaction
    ##     coeff  name formula state ispecies model
    ## 645    -1 CdCl+   CdCl+    aq      645   HKF
    ## 29     -1   Cl-     Cl-    aq       29   HKF
    ## 646     1 CdCl2   CdCl2    aq      646   HKF
    ##
    ## $out
    ##    T    P       rho      logK          G        H        S        V       Cp
    ## 1 25    1 0.9970614 0.6413794 -3661.0000 2669.611 21.33840 22.68164 561.3063
    ## 2 25 2000 1.0717835 0.0618530  -353.0575 2732.393 10.45408 12.26316 539.2635

After running `reset()` we can look up the source of data in the default OBIGT database (Sverjensky et al., 1997). Running the reaction with thermodynamic parameters from the default database, we now see that the equilibrium constant is not as sensitive to pressure:

    reset()

    ## reset: resetting "thermo" object

    ## OBIGT: loading default database with 1327 aqueous, 3099 total species

    thermo.refs(iCd)[, 1:3]

    ##       key                                          author year
    ## 338  HB24                   D. L. Huston and E. Bastrakov 2024
    ## 207 BZA10 E. F. Bazarkina, A. V. Zotov and N. N. Akinfiev 2010

    subcrt(c("CdCl+", "Cl-", "CdCl2"), c(-1, -1, 1), T = 25, P = c(1, 2000))

    ## subcrt: 3 species at 2 values of T (ºC) and P (bar) (wet) [energy units: J]

    ## $reaction
    ##     coeff  name formula state ispecies model
    ## 645    -1 CdCl+   CdCl+    aq      645   HKF
    ## 29     -1   Cl-     Cl-    aq       29   HKF
    ## 646     1 CdCl2   CdCl2    aq      646   HKF
    ##
    ## $out
    ##    T    P       rho      logK          G        H        S        V       Cp
    ## 1 25    1 0.9970614 0.6413794 -3661.0000 2669.611 21.33840 22.68164 561.3063
    ## 2 25 2000 1.0717835 0.0618530  -353.0575 2732.393 10.45408 12.26316 539.2635

## Examples of adding and modifying data with a function

Use `mod.OBIGT()` to add or modify the database in the current session. The function requires the name of a species and one or more properties to change.

### `mod.OBIGT()` for aqueous species

Let’s add data for CoCl4-2 from Liu et al. (2011). The values are taken from Table 5 of that paper; note that they are reported in caloric units, which is rather common for the HKF model. The entry includes the date in ISO 8601 extended format (e.g. 2020-08-16); `Sys.Date()` is used in this example to get the current date.

    mod.OBIGT("CoCl4-2", formula = "CoCl4-2", state = "aq", ref1 = "LBT+11", E_units = "cal",
      date = as.character(Sys.Date()), G = -134150, H = -171558, S = 19.55, Cp = 72.09, V = 27.74)

    ## mod.OBIGT: updated CoCl4-2(aq)

    ## [1] 583

The function prints a message saying that the species was added and returns the species index of the new species. Now let’s modify the new species by adding the HKF coefficients including the OOM multipliers, as they are usually given in publications. The `z` at the end refers to the charge of the species, and is used only for calculating the “g function” in the revised HKF model, not for balancing reactions.

    mod.OBIGT("CoCl4-2", a1 = 6.5467, a2 = 8.2069, a3 = 2.0130, a4 = -3.1183,
      c1 = 76.3357, c2 = 11.6389, omega = 2.9159, z = -2)

    ## mod.OBIGT: no change for CoCl4-2(aq)

    ## [1] 583

Let us now calculate the equilibrium constant for the formation of CoCl4-2 from Co+2 and Cl-.

    T <- c(25, seq(50, 350, 50))
    sres <- subcrt(c("Co+2", "Cl-", "CoCl4-2"), c(-1, -4, 1), T = T)
    round(sres$out$logK, 2)

    ## [1] -3.20 -2.96 -2.02 -0.74  0.77  2.50  4.57  7.29

The calculated values of logK are identical to those in Table 9 of Liu et al. (2011), which provides a good indication that the thermodynamic parameters were entered correctly. Nevertheless, this isn’t a guarantee that the thermodynamic parameters are consistent with the provided values of CP° and V°. We can see this by running `info()` to cross-check the parameters for the new CoCl4-2 species:

    inew <- info("CoCl4-2")
    info(inew)

    ## check.EOS: calculated Cp° of CoCl4-2(aq) differs by 1.33 cal K-1 mol-1 from database value

    ## check.EOS: calculated V° of CoCl4-2(aq) differs by -1.19 cm3 mol-1 from database value

The messages indicate that the given values of CP° and V° differ slightly from those calculated using the HKF parameters.

### `mod.OBIGT()` for minerals

Let’s add data for magnesiochromite from Klemme et al. (2000). The parameters in this paper are reported in Joules, so we set the `E.units()` to J. The value for volume, in cm3 mol-1, is from Robie and Hemingway (1995).

    H <- -1762000
    S <- 119.6
    V <- 43.56
    mod.OBIGT("magnesiochromite", formula = "MgCr2O4", state = "cr", ref1 = "KOSG00",
              date = as.character(Sys.Date()), E_units = "J", H = H, S = S, V = V)

    ## mod.OBIGT: added magnesiochromite(cr) with CGL model and energy units of J

    ## [1] 3100

Here are the heat capacity parameters for the Haas-Fisher polynomial equation (C p=a+b T+c T−2+d T−0.5+e T2). As of CHNOSZ 2.0.0, OOM multipliers are not used for these coefficients. 1500 K is a generic value for the high-temperature limit; experimental heat capacities were only reported up to 340 K (Klemme et al., 2000).

    a <- 221.4
    b <- -0.00102030
    c <- -1757210
    d <- -1247.9
    mod.OBIGT("magnesiochromite", E_units = "J", a = a, b = b, c = c, d = d,
              e = 0, f = 0, lambda = 0, T = 1500)

    ## mod.OBIGT: updated magnesiochromite(cr)

    ## [1] 3100

NOTE: An additional `f` term is available, which can have any exponent given in `lambda`. This offers some flexibility for using heat capacity equations that are different from the Haas-Fisher polynomial.

Now we can use `subcrt()` to calculate the heat capacity of magnesiochromite. For this calculation, we set the temperature units to Kelvin. We also specify a pressure of 1 bar because the default setting of Psat (liquid-vapor saturation) causes an error below the freezing temperature of water.

    T.units("K")

    ## changed temperature units to K

    Tref <- c(250, 300, 340)
    (sres <- subcrt("magnesiochromite", property = "Cp", T = Tref, P = 1))

    ## subcrt: 1 species at 3 values of T (K) and P (bar) [energy units: J]

    ## $species
    ##                  name formula state ispecies model
    ## 3100 magnesiochromite MgCr2O4    cr     3100   CGL
    ##
    ## $out
    ## $out$magnesiochromite
    ##     T P       Cp
    ## 1 250 1 114.1054
    ## 2 300 1 129.5218
    ## 3 340 1 138.1754

Next we check that the calculated values are within 0.3 J K-1 mol-1 of reference values taken from Fig. 1 of Klemme et al. (2000).

    Cpref <- c(114.3, 129.8, 138.4)
    stopifnot(max(abs(sres$out[[1]]$Cp - Cpref)) < 0.3)

Finally, let’s restore the units setting for later calculations with `subcrt()`. (Another way would be to run `reset()`, which also resets the OBIGT database.)

    T.units("C")

    ## changed temperature units to C

## Case study: Formation constants for aqueous tungsten species

Here we use `logK.to.OBIGT()` to fit to thermodynamic parameters to experimental formation constants. Some additional steps are shown to refine a thermodynamic model to generate a speciation diagram as a function of pH.

### Fitting formation constants

`logK.to.OBIGT()` requires three things:

- Experimental decimal logarithms of formation constants (log β) as a function of temperature;
- The stoichiometry of the formation reaction in terms of known species (the new species must be last);
- The experimental temperature and pressure.

`logK.to.OBIGT()` does three things:

- Combines the formation constants with standard Gibbs energies (ΔG°) of the known species to calculate ΔG° of the new species;
- Fits ΔG° of the new species using 25 °C thermodynamic properties and selected HKF model parameters (i.e., `G`, `S`, `c1`, `c2`, and `omega` parameters in OBIGT);
- Adds the parameters to OBIGT for use by other functions in CHNOSZ.

First we set the pressure for all log β data.

    P <- "Psat"

Add first species: HWO4- (Wang et al., 2019).

    T <- c(250, 300, 350)
    logK <- c(5.58, 6.51, 7.99)
    species <- c("WO4-2", "H+", "HWO4-")
    coeff <- c(-1, -1, 1)
    logK.to.OBIGT(logK, species, coeff, T, P)

    ## mod.OBIGT: updated HWO4-(aq)

    ## logK.to.OBIGT: mean absolute difference between experimental and calculated logK is 0

    ## [1] 166

Add second species: H3WO4F2- (Wang et al., 2021).

    T <- seq(100, 250, 25)
    logK <- c(17.00, 17.11, 17.46, 17.75, 18.17, 18.71, 19.23)
    # Species and coefficients in the formation reaction
    species <- c("H+", "WO4-2", "F-", "H3WO4F2-")
    coeff <- c(-3, -1, -2, 1)
    logK.to.OBIGT(logK, species, coeff, T, P)

    ## mod.OBIGT: updated H3WO4F2-(aq)

    ## logK.to.OBIGT: mean absolute difference between experimental and calculated logK is 0.0307

    ## [1] 3101

Add third species: H2WO4 (Wang et al., 2021). Here we increase the tolerance because there is considerable scatter in the experimental values.

    logK <- c(7.12, 7.82, 7.07, 7.76, 7.59, 7.98, 8.28)
    species <- c("H+", "WO4-2", "H2WO4")
    coeff <- c(-2, -1, 1)
    logK.to.OBIGT(logK, species, coeff, T, P, tolerance = 0.3)

    ## mod.OBIGT: updated H2WO4(aq)

    ## logK.to.OBIGT: mean absolute difference between experimental and calculated logK is 0.2035

    ## [1] 622

After running, `logK.to.OBIGT()` returns the species indices; the low values for HWO4- (166) and H2WO4 (622) indicate that the function replaced parameters for these species that were already present in OBIGT.

### Diagram 1: Constant molality of F-

Now we’re ready to make a speciation diagram. Our aim is to reproduce Fig. 7b of Wang et al. (2021), which is made for 300 °C. A constant molality of F- is based on the assumption of complete dissociation of 0.1 m NaF (we’ll change this later). An ionic strength of 0.9 mol/kg is estimated for a solution with 1.8 m NaCl (use `NaCl(1.8, T = 300)`). NOTE: because the ionic strength is non-zero, the calculations here refer to molality instead of activity of species (see An Introduction to CHNOSZ).

    basis(c("H+", "WO4-2", "F-", "H2O", "O2"))
    basis("F-", log10(0.1))
    iaq <- retrieve("W", c("O", "H", "F"), "aq")
    species(iaq)
    a <- affinity(pH = c(2, 7), T = 300, IS = 0.9)
    e <- equilibrate(a)
    col <- c(1, 4, 5, 2)
    diagram(e, alpha = TRUE, col = col, lty = 1, lwd = 2, ylab = "Fraction total W")

This isn’t quite the diagram we were looking for. The published diagram shows a broad region of coexistence of H3WO4F2- and HWO4- at pH < 5 and increasing abundance of H2WO4 at lower pH.

### Diagram 2: Variable molality of F-

In reality, the molality of F- depends strongly on pH according to the reaction H+ + F- = HF. With a little algebra, we can calculate the molality of F- (`a_F` in the code below) from the equilbrium constant of this reaction for a given total F concentration (`F_tot`). NOTE: It is important to call `subcrt()` with a non-zero `IS` so that it returns effective equilibrium constants corrected for ionic strength (try setting `IS = 0` yourself and look at what happens to the diagram).

    T <- 300
    pH <- seq(2, 7, 0.1)
    logK_HF <- subcrt(c("H+", "F-", "HF"), c(-1, -1, 1), T = T, IS = 0.9)$out$logK

    ## info.character: found HF(aq); also available in gas

    ## subcrt: 3 species at 300 ºC and 85.84 bar (wet) [energy units: J]

    ## nonideal: calculations for F- (Bdot equation)

    ## nonideal: calculations for HF (Setchenow equation)

    F_tot <- 0.1
    a_F <- F_tot / (1 + 10^(logK_HF - pH))

Now that we have the molality of F- as a function of pH, we can provide it in the call to `affinity()`.

    basis(c("H+", "WO4-2", "F-", "H2O", "O2"))
    iaq <- retrieve("W", c("O", "H", "F"), "aq")
    species(iaq)
    a <- affinity(pH = pH, "F-" = log10(a_F), T = T, IS = 0.9)
    e <- equilibrate(a)
    diagram(e, alpha = TRUE, col = col, lty = 1, lwd = 2, ylab = "Fraction total W")

That’s more like it. We have captured the basic geometry of Fig. 7b in Wang et al. (2021). For instance, in accord with the published diagram, HWO4- plateaus at around 40% of total W, and H2WO4 and H3WO4F2- are nearly equally abundant at pH = 2.

The highest experimental temperature for the formation constants of H2WO4 and H3WO4F2- is 250 °C, but this diagram is drawn for 300 °C. Wang et al. (2021) used the modified Ryzhenko-Bryzgalin (MRB) model to extrapolate to 300 °C. In contrast, we used a different model but obtained quite similar results.

NOTE: The coefficients in the model used by `logK.to.OBIGT()` include 25 °C values of `G` and `S`. These should be conservatively treated only as fitting parameters and should not be used to compute thermodynamic properties close to 25 °C unless they were fit to experimental data in that temperature range.

## References

Bazarkina EF, Zotov AV, Akinfiev NN. 2010. Pressure-dependent stability of cadmium chloride complexes: Potentiometric measurements at 1-1000 bar and 25°C. Geology of Ore Deposits 52(2): 167–178. doi: 10.1134/S1075701510020054
Berman RG. 1988. Internally-consistent thermodynamic data for minerals in the system Na2 O–K 2 O–CaO–MgO–FeO–Fe2 O 3–Al2 O 3–SiO 2–TiO 2–H 2 O–CO 2. Journal of Petrology 29(2): 445–522. doi: 10.1093/petrology/29.2.445
Helgeson HC, Delany JM, Nesbitt HW, Bird DK. 1978. Summary and critique of the thermodynamic properties of rock-forming minerals. American Journal of Science 278A: 1–229. Available at https://search.worldcat.org/title/13594862.
Helgeson HC, Owens CE, Knox AM, Richard L. 1998. Calculation of the standard molal thermodynamic properties of crystalline, liquid, and gas organic molecules at high temperatures and pressures. Geochimica et Cosmochimica Acta 62(6): 985–1081. doi: 10.1016/S0016-7037(97)00219-6
Klemme S, O’Neill HStC, Schnelle W, Gmelin E. 2000. The heat capacity of MgCr2 O 4, FeCr2 O 4, and Cr2 O 3 at low temperatures and derived thermodynamic properties. American Mineralogist 85(11-12): 1686–1693. doi: 10.2138/am-2000-11-1212
Liu W, Borg SJ, Testemale D, Etschmann B, Hazemann J-L, Brugger J. 2011. Speciation and thermodynamic properties for cobalt chloride complexes in hydrothermal fluids at 35–440 °C and 600 bar: An in-situ XAS study. Geochimica et Cosmochimica Acta 75(5): 1227–1248. doi: 10.1016/j.gca.2010.12.002
Maier ChasG, Kelley KK. 1932. An equation for the representation of high-temperature heat content data. Journal of the American Chemical Society 54(8): 3243–3246. doi: 10.1021/ja01347a029
Robie RA, Hemingway BS. 1995. Thermodynamic Properties of Minerals and Related Substances at 298.15 K and 1 Bar (105 Pascals) Pressure and at Higher Temperatures. Washington, D.C.: U.S. Geological Survey. (Bulletin 2131). doi: 10.3133/b2131
Shock EL, Oelkers EH, Johnson JW, Sverjensky DA, Helgeson HC. 1992. Calculation of the thermodynamic properties of aqueous species at high pressures and temperatures: Effective electrostatic radii, dissociation constants, and standard partial molal properties to 1000 °C and 5 kbar. Journal of the Chemical Society, Faraday Transactions 88(6): 803–826. doi: 10.1039/FT9928800803
Sverjensky DA, Harrison B, Azzolini D. 2014. Water in the deep Earth: The dielectric constant and the solubilities of quartz and corundum to 60 kb and 1,200 °C. Geochimica et Cosmochimica Acta 129: 125–145. doi: 10.1016/j.gca.2013.12.019
Sverjensky DA, Shock EL, Helgeson HC. 1997. Prediction of the thermodynamic properties of aqueous metal complexes to 1000°C and 5 kb. Geochimica et Cosmochimica Acta 61(7): 1359–1412. doi: 10.1016/S0016-7037(97)00009-4
Wang X-S, Timofeev A, Williams-Jones AE, Shang L-B, Bi X-W. 2019. An experimental study of the solubility and speciation of tungsten in NaCl-bearing aqueous solutions at 250, 300, and 350 °C. Geochimica et Cosmochimica Acta 265: 313–329. doi: 10.1016/j.gca.2019.09.013
Wang X-S, Williams-Jones AE, Hu R-Z, Shang L-B, Bi X-W. 2021. The role of fluorine in granite-related hydrothermal tungsten ore genesis: Results of experiments and modeling. Geochimica et Cosmochimica Acta 292: 170–187. doi: 10.1016/j.gca.2020.09.032