# CHNOSZ FAQ

#### Jeffrey M. Dick

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

## Where do the names CHNOSZ, OBIGT, and subcrt come from?

CHNOSZ has the symbols for five elements (carbon, hydrogen, nitrogen, oxygen, and sulfur) and charge (Z). These are the main elements in proteins, which were the subject of JMD’s PhD thesis.

OBIGT started as an abbreviation for the OrganoBioGeoTherm software package written by John Donovan at UC Berkeley. This was a Windows interface to SUPCRT92 that was probably first cited by Richard and Helgeson (1998). That package held a database that was the seed for the current OBIGT database.

The name of the `subcrt()` function is loosely based on the SUPCRT92 software package. SUPCRT is an abbreviation for supercritical (Johnson et al., 1992). `subcrt()` is just a different name and is not limited to subcritical conditions.

During a meeting at the 2007 Goldschmidt conference, Jan Amend suggested using a unique name (not SUPCRT) and vocalized CHNOSZ while looking at the program output. That interaction resulted in `subcrt()` and CHNOSZ for the function and package names.

> The author pronounces CHNOSZ as one syllable that starts with an sh sound and rhymes with Oz. In this pronunciation, CHNOSZ and schnoz are homophones.

Added on 2025-06-05.

## How should CHNOSZ be cited?

- This paper is the general reference for CHNOSZ: Dick (2019).
- This paper describes diagrams with multiple metals: Dick (2021).
- This paper describes metastable equilibrium calculations for proteins: Dick (2008).
- The OBIGT thermodynamic database represents the work of many researchers. If you publish results that depend on any of these data, please cite the primary sources. Use `info()` to show the reference keys for particular species and `thermo.refs()` to list the bibliographic details. The following example shows the sources of data for aqueous alanine:

    info(info("alanine"))[c("ref1", "ref2")]

    ##       ref1    ref2
    ## 1027 AH97b DLH06.1

    thermo.refs(info("alanine"))

    ##         key                                      author year
    ## 87    AH97b              J. P. Amend and H. C. Helgeson 1997
    ## 180 DLH06.1 J. M. Dick, D. E. LaRowe and H. C. Helgeson 2006
    ##                                        citation                       note
    ## 87  J. Chem. Soc., Faraday Trans. 93, 1927-1941            amino acids GHS
    ## 180                   Biogeosciences 3, 311-336 amino acids HKF parameters
    ##                                       URL
    ## 87       https://doi.org/10.1039/A608126F
    ## 180 https://doi.org/10.5194/bg-3-311-2006

- Mineral data in OBIGT are based on Berman (1988) together with sulfides and other non-conflicting minerals from Helgeson et al. (1978). For a reaction such as the pyrite-pyrrhotite-magnetite (PPM) oxygen fugacity buffer, all the sources of data can be listed as follows:

    basis(c("pyrite", "pyrrhotite", "oxygen"))

    ##      Fe O S ispecies logact state
    ## FeS2  1 0 2     1470      0    cr
    ## FeS   1 0 1     1471      0    cr
    ## O2    0 2 0     2288      0   gas

    sres <- subcrt("magnetite", 1)
    info(sres$reaction$ispecies)[, 1:6]

    ##            name abbrv formula state   ref1   ref2
    ## 1367  magnetite   Mag   Fe3O4    cr  Ber88   <NA>
    ## 1470     pyrite    Py    FeS2    cr HDNB78 RH95.7
    ## 1471 pyrrhotite    Po     FeS    cr HDNB78   <NA>
    ## 2288     oxygen    O2      O2   gas WEP+82  Kel60

    thermo.refs(sres)

    ##       key                              author year
    ## 34  Ber88                        R. G. Berman 1988
    ## 13 HDNB78 H. C. Helgeson, J. M. Delany et al. 1978
    ## 23 WEP+82    D. D. Wagman, W. H. Evans et al. 1982
    ## 80 RH95.7     R. A. Robie and B. S. Hemingway 1995
    ## 1   Kel60                        K. K. Kelley 1960
    ##                                        citation                         note
    ## 34                       J. Petrol. 29, 445-522                     minerals
    ## 13                      Am. J. Sci. 278A, 1-229                     minerals
    ## 23 J. Phys. Chem. Ref. Data 11, Suppl. 2, 1-392                    gases GHS
    ## 80           U. S. Geological Survey Bull. 2131 phase transition temperature
    ## 1               U. S. Bureau of Mines Bull. 584                     gases Cp
    ##                                            URL
    ## 34  https://doi.org/10.1093/petrology/29.2.445
    ## 13  https://search.worldcat.org/title/13594862
    ## 23 https://srd.nist.gov/JPCRD/jpcrdS2Vol11.pdf
    ## 80               https://doi.org/10.3133/b2131
    ## 1  https://search.worldcat.org/title/693388901

    reset()

- Additional minerals from Helgeson et al. (1978), that were available in SUPCRT92 but may conflict with the Berman (1988) compilation, can be loaded from an optional database with `add.OBIGT("SUPCRT92")`. When using these data, it is appropriate to cite Helgeson et al. (1978) rather than SUPCRT92.

Added on 2023-05-27; PPM example added on 2023-11-15.

## What thermodynamic models are used in CHNOSZ?

- The thermodynamic properties of liquid water are calculated using Fortran code from SUPCRT92 (Johnson et al., 1992) or optionally an implementation in R of the IAPWS-95 formulation (Wagner and Pruß, 2002).
- Thermodynamic properties of other species are taken from a database for minerals and inorganic and organic aqueous species including biomolecules, or from amino acid group additivity for proteins (Dick et al., 2006).
- The corresponding high-temperature properties are calculated using the Berman and Brown (1985) equations for minerals and the revised (Tanger and Helgeson, 1988; Shock and Helgeson, 1988) Helgeson-Kirkham-Flowers (Helgeson et al., 1981) equations for aqueous species.
- The revised HKF equations are augmented with the Deep Earth Water (DEW) model (Sverjensky et al., 2014) and estimates of parameters in the extended Debye-Hückel equation (Manning et al., 2013) to calculate standard-state properties and activity coefficients for given ionic strength at high pressure (to 6 GPa).
- Activity coefficients are implemented via adjusted standard Gibbs energies at specified ionic strength (Alberty, 1996), which converts all activity variables in the workflow to molalities.
- A related adjustment is available to convert standard Gibbs energies for gases from the 1 bar standard state used in SUPCRT92 to a variable-pressure standard state (Anderson and Crerar, 1993: Ch.12).

Added to https://chnosz.net/ website on 2018-11-13; moved to FAQ on 2023-05-27; added references for revised HKF on 2023-11-17.

## What are the main limitations of CHNOSZ?

- No phase diagrams: CHNOSZ does not implement phase diagrams (*).
  - This includes diagrams with different numbers of coexisting phases and/or phases that don’t all have an element in common.
  - Some types of diagrams that CHNOSZ can’t make: binary mixing diagrams, ternary phase diagrams, pseudosections.
  - Suggested terminology for diagrams that CHNOSZ can make: stability diagrams for minerals, predominance or speciation diagrams for aqueous species, activity or fugacity diagrams to refer to the axis variables, solubility diagrams made using the `solubility()` function.
- No Gibbs energy minimization: CHNOSZ does not implement Gibbs energy minimization or other general-purpose equilibrium speciation.
- Shared element limitation:
  - CHNOSZ makes diagrams for minerals and/or aqueous species that are balanced on an element, so every point on the diagram has one mineral or aqueous species with that element.
  - CHNOSZ can calculate equilibrium concentrations of aqueous species balanced on an element, but not for multicomponent systems in general.
  - Diagrams with multiple metals are possible but are limited by data availability and have not been tested for more than two metals.
- Limitations imposed by the revised HKF model (or DEW for higher pressures) and extended Debye-Hückel equation. Highly concentrated solutions (brines) or temperatures below about -20 °C are not supported.

(*) Any diagram with different minerals is technically a phase diagram, but “phase diagram” usually has a more specific meaning depending on the context.

Added on 2025-06-07.

## When and why do equal-activity boundaries depend on total activity?

Short answer: When the species have the same number of the conserved element (let’s take C for example), their activities are raised to the same exponent in reaction quotient, so the activity ratio in the law of mass action becomes unity. But when the species have different numbers of the conserved element (for example, propanoate with 3 C and bicarbonate with 1 C), their activities are raised to different exponents, and the activity ratio does not become unity even when the activities are equal (except for the specific case where the activities themselves are equal to 1). Therefore, in general, the condition of “equal activity” is not sufficient to define boundaries on a relative stability diagram; instead, we need to say “activity of each species equal to x” or alternatively “total activity equal to y”.

Long answer: First, consider a reaction between formate and bicarbonate: HCO2− + 0.5 O2 ⇌ HCO3−. The law of mass action (LMA) is logK = log (aHCO3− / aHCO2−) − 0.5 logfO2. The condition of equal activity is aHCO2− = aHCO3−. Then, the LMA simplifies to logK = − 0.5 logfO2. The total activity of C is given by Ctot = aHCO2− + aHCO3−. According to the LMA, logfO2 is a function only of logK, so dlogfO2/dlog Ctot = 0. In other words, the position of the equal-activity boundary is independent of the value of Ctot.

Next, consider a reaction between propanoate and bicarbonate: C3H5O2− + 7⁄2 O2 ⇌ 3 HCO3− + 2 H+. The LMA is logK = log (a3HCO3− / aC3H5O2−) − pH − 7⁄2 logfO2. The condition of equal activity is aC3H5O2− = aHCO3−. Then, the LMA simplifies to logK = loga2HCO3− − pH − 7⁄2 logfO2. The total activity of C is given by Ctot = 3 aC3H5O2− + aHCO3−; combined with the condition of equal activity, this gives Ctot = 4 aHCO3−. Substituting this into the LMA gives logK = log (Ctot / 4)2 − pH − 7⁄2 logfO2, which can be rearranged to write logfO2 = 2⁄7 (2 log Ctot − logK − log 16 − pH). It follows that dlogfO2/dlog Ctot = 4⁄7, and the position of the equal-activity boundary depends on Ctot.

According to this analysis, increasing Ctot from 0.03 to 3 molal (a 2 log-unit increase) would have no effect on the location of the formate-bicarbonate equal-activity boundary, but would raise the propanoate-bicarbonate equal-activity boundary by 8⁄7 units on the logfO2 scale. Because the reaction between bicarbonate and CO2 does not involve O2 (but rather H2O and H+), the same effect should occur on the propanoate-CO2 equal-activity boundary. The plots below, which are made using `equilibrate()` for species in the Deep Earth Water (DEW) model, illustrate this effect.

    ### Based on demo/DEW.R in CHNOSZ

    # Set up subplots
    par(mfrow = c(1, 2), mar = c(3.0, 3.5, 2.5, 1.0), mgp = c(1.7, 0.3, 0), las = 1, tcl = 0.3, xaxs = "i", yaxs = "i")

    # Activate DEW model
    water("DEW")

    ###########
    ## logfO2-pH diagram for aqueous inorganic and organic carbon species at high pressure
    ## After Figure 1b of Sverjensky et al., 2014b [SSH14]
    ## (Nature Geoscience, https://doi.org/10.1038/NGEO2291)
    ###########

    # Define system
    basis("CHNOS+")
    inorganic <- c("CO2", "HCO3-", "CO3-2", "CH4")
    organic <- c("formic acid", "formate", "acetic acid", "acetate", "propanoic acid", "propanoate")
    species(c(inorganic, organic), 0)
    fill <- c(rep("aliceblue", length(inorganic)), rep("aquamarine", length(organic)))

    # A function to make the diagrams
    dfun <- function(T = 600, P = 50000, res = 200, Ctot = 0.03) {
      a <- affinity(pH = c(0, 10, res), O2 = c(-24, -12, res), T = T, P = P)
      # Set total C concentration to 0.03 molal
      # (from EQ3NR model for eclogite [Supporting Information of SSH14])
      e <- equilibrate(a, loga.balance = log10(Ctot))
      diagram(e, fill = fill)
      dp <- describe.property(c("     T", "     P"), c(T, P), digits = 0)
      legend("bottomleft", legend = dp, bty = "n")
    }

    water("DEW")
    add.OBIGT("DEW")
    dfun(Ctot = 0.03)
    mtitle(c("Inorganic and organic species", "C[total] = 0.03 molal"))
    dfun(Ctot = 3)
    mtitle(c("Inorganic and organic species", "C[total] = 3 molal"))

    # Restore default settings for the questions below
    reset()

Added on 2023-05-17.

## How can minerals with polymorphic transitions be added to the database?

The different crystal forms of a mineral are called polymorphs. Many minerals undergo polymorphic transitions upon heating. Each polymorph for a given mineral should have its own entry in OBIGT, including values of the standard thermodynamic properties (ΔG°f, ΔH°f, and S°) at 25 °C. The 25 °C (or 298.15 K) values for high-temperature polymorphs are often not listed in thermodynamic tables, but they can be calculated. This thermodynamic cycle shows how: we calculate the changes of a thermodyamic property (pictured here as `DS1` and `DS2`) between 298.15 K and the transition temperature (`Ttr`) for two polymorphs, then combine those with the property of the polymorphic transition (`DStr`) to obtain the difference of the property between the polymorphs at 298.15 K (`DS298`).

                   DStr              DStr: entropy of transition between polymorphs 1 and 2
          Ttr  o---------->o          Ttr: temperature of transition
               ^           |
               |           |
           DS1 |           | DS2      DS1: entropy change of polymorph 1 from 298.15 K to Ttr
               |           |          DS2: entropy change of polymorph 2 from 298.15 K to Ttr
               |           v
     298.15 K  o==========>o    DS298: entropy difference between polymorphs 1 and 2 at 298.15 K
                   DS298        DS298 = DS1 + DStr - DS2
    Polymorph  1           2

As an example, let’s add pyrrhotite (Fe0.877S) from Pankratz et al. (1987). The formula and thermodynamic properties of this pyrrhotite differ from those of FeS from Helgeson et al. (1978), which is already in OBIGT. We begin by defining all the input values in the next code block. In addition to `G`, `H`, `S`, and the heat capacity coefficients, non-NA values of volume (`V`) must be provided for the polymorph transitions to be calculated correctly by `subcrt()`.

    # The formula of the new mineral and literature reference
    formula <- "Fe0.877S"
    ref1 <- "PMW87"
    # Because the properties from Pankratz et al. (1987) are listed in calories,
    # we need to change the output of subcrt() to calories (the default is Joules)
    E.units("cal")
    # Use temperature in Kelvin for the calculations below
    T.units("K")
    # Thermodynamic properties of polymorph 1 at 25 °C (298.15 K)
    G1 <- -25543
    H1 <- -25200
    S1 <- 14.531
    Cp1 <- 11.922
    # Heat capacity coefficients for polymorph 1
    a1 <- 7.510
    b1 <- 0.014798
    # For volume, use the value from Helgeson et al. (1978)
    V1 <- V2 <- 18.2
    # Transition temperature
    Ttr <- 598
    # Transition enthalpy (cal/mol)
    DHtr <- 95
    # Heat capacity coefficients for polymorph 2
    a2 <- -1.709
    b2 <- 0.011746
    c2 <- 3073400
    # Maximum temperature of polymorph 2
    T2 <- 1800

Use the temperature (`Ttr`) and enthalpy of transition (`DHtr`) to calculate the entropy of transition (`DStr`). Note that the Gibbs energy of transition (`DGtr`) is zero at `Ttr`.

    DGtr <- 0  # DON'T CHANGE THIS
    TDStr <- DHtr - DGtr  # TΔS° = ΔH° - ΔG°
    DStr <- TDStr / Ttr

Start new database entries that include basic information, volume, and heat capacity coefficients for each polymorph. Pankratz et al. (1987) don’t list Cp° of the high-temperature polymorph extrapolated to 298.15 K, so leave it out. If the properties were in Joules, we would omit `E_units = "cal"` or change it to `E_units = "J"`.

    mod.OBIGT("pyrrhotite_new", formula = formula, state = "cr", ref1 = ref1,
      E_units = "cal", G = 0, H = 0, S = 0, V = V1, Cp = Cp1,
      a = a1, b = b1, c = 0, d = 0, e = 0, f = 0, lambda = 0, T = Ttr)
    mod.OBIGT("pyrrhotite_new", formula = formula, state = "cr2", ref1 = ref1,
      E_units = "cal", G = 0, H = 0, S = 0, V = V2,
      a = a2, b = b2, c = c2, d = 0, e = 0, f = 0, lambda = 0, T = T2)

For the time being, we set `G`, `H`, and `S` (i.e., the properties at 25 °C) to zero in order to easily calculate the temperature integrals of the properties from 298.15 K to `Ttr`. Values of zero are placeholders that don’t satisfy ΔG°f = ΔH°f − TΔS°f for either polymorph (the subscript f represents formation from the elements), as indicated by the following messages. We will check again for consistency of the thermodynamic parameters at the end of the example.

    info(info("pyrrhotite_new", "cr"))

check.GHS: calculated ΔG°f of pyrrhotite_new(cr) differs by 3989 cal mol-1 from database value

    info(info("pyrrhotite_new", "cr2"))

check.GHS: calculated ΔG°f of pyrrhotite_new(cr2) differs by 3989 cal mol-1 from database value

info.numeric: Cp° of pyrrhotite_new(cr2) is NA; set by EOS parameters to 36.37 cal K-1 mol-1

In order to calculate the temperature integral of ΔG°f, we need not only the heat capacity coefficients but also actual values of S°. Therefore, we start by calculating the entropy changes of each polymorph from 298.15 to 598 K (`DS1` and `DS2`) and combining those with the entropy of transition to obtain the difference of entropy between the polymorphs at 298.15 K. For polymorph 1 (with `state = "cr"`) it’s advisable to include `use.polymorphs = FALSE` to prevent `subcrt()` from trying to identify the most stable polymorph at the indicated temperature.

    DS1 <- subcrt("pyrrhotite_new", "cr", P = 1, T = Ttr, use.polymorphs = FALSE)$out[[1]]$S
    DS2 <- subcrt("pyrrhotite_new", "cr2", P = 1, T = Ttr)$out[[1]]$S
    DS298 <- DS1 + DStr - DS2

Put the values of S° at 298.15 into OBIGT, then calculate the changes of all thermodynamic properties of each polymorph between 298.15 K and `Ttr`.

    mod.OBIGT("pyrrhotite_new", state = "cr", S = S1)
    mod.OBIGT("pyrrhotite_new", state = "cr2", S = S1 + DS298)
    D1 <- subcrt("pyrrhotite_new", "cr", P = 1, T = Ttr, use.polymorphs = FALSE)$out[[1]]
    D2 <- subcrt("pyrrhotite_new", "cr2", P = 1, T = Ttr)$out[[1]]

It’s a good idea to check that the entropy of transition is calculated correctly.

    stopifnot(all.equal(D2$S - D1$S, DStr))

Now we’re ready to add up the contributions to ΔG°f and ΔH°f from the left, top, and right sides of the cycle. This gives us the differences between the polymorphs at 298.15 K, which we use to make the final changes to the database.

    DG298 <- D1$G + DGtr - D2$G
    DH298 <- D1$H + DHtr - D2$H
    mod.OBIGT("pyrrhotite_new", state = "cr", G = G1, H = H1)
    mod.OBIGT("pyrrhotite_new", state = "cr2", G = G1 + DG298, H = H1 + DH298)

It’s a good idea to check that the values of `G`, `H`, and `S` at 25 °C for a given polymorph are consistent with each other. Here we use `check.GHS()` to calculate the difference between the value given for `G` and the value calculated from `H` and `S`. The difference of less than 1 cal/mol can probably be attributed to small differences in the entropies of the elements used by Pankratz et al. (1987) and in CHNOSZ. We still get a message that the database value of Cp° at 25 °C for the high-temperature polymorph is NA; this is OK because the (extrapolated) value can be calculated from the heat capacity coefficients.

    cr_parameters <- info(info("pyrrhotite_new", "cr"))
    stopifnot(abs(check.GHS(cr_parameters)) < 1)
    cr2_parameters <- info(info("pyrrhotite_new", "cr2"))

info.numeric: Cp° of pyrrhotite_new(cr2) is NA; set by EOS parameters to 36.37 cal K-1 mol-1

    stopifnot(abs(check.GHS(cr2_parameters)) < 1)

For the curious, here are the parameter values:

    cr_parameters

    ##                name abbrv  formula state  ref1 ref2 date model E_units      G
    ## 3100 pyrrhotite_new  <NA> Fe0.877S    cr PMW87 <NA> <NA>   CGL     cal -25543
    ##           H      S     Cp    V    a        b c d e f lambda   T
    ## 3100 -25200 14.531 11.922 18.2 7.51 0.014798 0 0 0 0      0 598

    cr2_parameters

    ##                name abbrv  formula state  ref1 ref2 date model E_units
    ## 3101 pyrrhotite_new  <NA> Fe0.877S   cr2 PMW87 <NA> <NA>   CGL     cal
    ##              G        H        S       Cp    V      a        b       c d e f
    ## 3101 -25802.75 -27099.4 9.031592 36.36706 18.2 -1.709 0.011746 3073400 0 0 0
    ##      lambda    T
    ## 3101      0 1800

Finally, let’s look at the thermodynamic properties of the newly added pyrrhotite as a function of temperature around `Ttr`. Here, we use the feature of `subcrt()` that identifies the stable polymorph at each temperature. Note that ΔG°f is a continuous function – visual confirmation that the parameters yield zero for `DGtr` – but ΔH°f, S°, and Cp° are discontinuous at the transition temperature.

    T <- seq(550, 650, 1)
    sout <- subcrt("pyrrhotite_new", T = T, P = 1)$out[[1]]
    par(mfrow = c(1, 4), mar = c(4, 4.2, 1, 1))
    labels <- c(G = "DG0f", H = "DH0f", S = "S0", Cp = "Cp0")
    for(property in c("G", "H", "S", "Cp")) {
      plot(sout$T, sout[, property], col = sout$polymorph,
        xlab = axis.label("T"), ylab = axis.label(labels[property])
      )
      abline(v = Ttr, lty = 3, col = 8)
      if(property == "G")
        legend("bottomleft", c("1", "2"), pch = 1, col = c(1, 2), title = "Polymorph")
    }

    # Restore default settings for the questions below
    reset()

For additional polymorphs, we could repeat the above procedure using polymorph 2 as the starting point to calculate `G`, `H`, and `S` of polymorph 3, and so on.

Added on 2023-06-23.

## How can I make a diagram with the trisulfur radical ion (S3-)?

A logfO2–pH plot for aqueous sulfur species including S3- was first presented by Pokrovski and Dubrovinsky (2011). Later, Pokrovski and Dubessy (2015) reported parameters in the revised HKF equations of state for S3-, which are available in OBIGT.

The blocks of code are described here:

1. Set temperature, pressure, and resolution.
2. Calculate molality of S from given weight percent [this is rather tedious and could be condensed to fewer lines of code].
   - Define the given weight percent (10 wt% S).
   - Calculate weight permil S.
   - Divide by molar mass to calculate moles of S in 1 kg of solution.
   - Calculate grams of H2O in 1 kg of solution.
   - Calculate molality (moles of S per kg of H2O, not kg of solution).
   - Calculate decimal logarithm of molality.
3. Define the basis species and formed species, calculate affinity, equilibrate activities, and make the diagram.
   - If we didn’t want to plot the buffer lines, we could just use `basis(c("H2S", "H2O", "oxygen", "H+"))`.
   - Basis species with Fe, Si, and Ni are needed for the HM, QFM, and NNO buffers.
   - Note that “oxygen” matches O2(gas), not O2(aq), so the variable on the diagram is logfO2.
4. Define Ni-NiO (NNO) buffer and plot buffer lines for HM, QFM, and NNO.
   - QFM (quartz-fayalite-magnetite) is also known as FMQ.
5. Calculate and plot pH of neutrality for water.
6. Add a legend and title.

    ## BLOCK 1
    T <- 350
    P <- 5000
    res <- 200

    ## BLOCK 2
    wt_percent_S <- 10
    wt_permil_S <- wt_percent_S * 10
    molar_mass_S <- mass("S") # 32.06
    moles_S <- wt_permil_S / molar_mass_S
    grams_H2O <- 1000 - wt_permil_S
    molality_S <- 1000 * moles_S / grams_H2O
    logm_S <- log10(molality_S)

    ## BLOCK 3
    basis(c("Ni", "SiO2", "Fe2O3", "H2S", "H2O", "oxygen", "H+"))
    species(c("H2S", "HS-", "S3-", "SO2", "HSO4-", "SO4-2"))
    a <- affinity(pH = c(2, 10, res), O2 = c(-34, -22, res), T = T, P = P)
    e <- equilibrate(a, loga.balance = logm_S)
    diagram(e)

    ## BLOCK 4
    mod.buffer("NNO", c("nickel", "bunsenite"), state = "cr", logact = 0)
    for(buffer in c("HM", "QFM", "NNO")) {
      basis("O2", buffer)
      logfO2_ <- affinity(return.buffer = TRUE, T = T, P = P)$O2
      abline(h = logfO2_, lty = 3, col = 2)
      text(8.5, logfO2_, buffer, adj = c(0, 0), col = 2, cex = 0.9)
    }

    ## BLOCK 5
    pH <- subcrt(c("H2O", "H+", "OH-"), c(-1, 1, 1), T = T, P = P)$out$logK / -2
    abline(v = pH, lty = 2, col = 4)

    ## BLOCK 6
    lTP <- describe.property(c("T", "P"), c(T, P))
    lS <- paste(wt_percent_S, "wt% S(aq)")
    ltext <- c(lTP, lS)
    legend("topright", ltext, bty = "n", bg = "transparent")
    title(quote("Parameters for"~S[3]^"-"~"from Pokrovski and Dubessy (2015)"), xpd = NA)

### Why does the published diagram have a much larger stability field for S3-?

Let’s calculate logK for the reaction 2 H2S(aq) + SO4-2 + H+ = S3- + 0.75 O2(gas) + 2.5 H2O.

    species <- c("H2S", "SO4-2", "H+", "S3-", "oxygen", "H2O")
    coeffs <- c(-2, -1, -1, 1, 0.75, 2.5)
    (calclogK <- subcrt(species, coeffs, T = seq(300, 450, 50), P = 5000)$out$logK)

    ## [1] -16.669685 -14.013206 -11.686722  -9.622529

By using the thermodynamic parameters for S3- in OBIGT that are taken from Pokrovski and Dubessy (2015), logK is calculated to be -16.67, -14.01, -11.69, and -9.62 at 300, 350, 400, and 450 °C and 5000 bar. In contrast, ref. 22 of Pokrovski and Dubrovinsky (2011) lists -9.6 for logK at 350 °C; this is 4.41 log units higher than the calculated value of -14.01. This corresponds to a difference of Gibbs energy of -2.303 * 1.9872 * (350 + 273.15) * 4.4 = -12586 cal/mol.

In the code below, we use the difference of Gibbs energy to temporarily update the OBIGT entry for S3-. Then, we make a new diagram that is more similar to that from Pokrovski and Dubrovinsky (2011). Finally, we reset the OBIGT database so that the temporary parameters don’t interfere with later calculations.

    oldG <- info(info("S3-"))$G
    newG <- oldG - 12586
    mod.OBIGT("S3-", G = newG)
    a <- affinity(pH = c(2, 10, res), O2 = c(-34, -22, res), T = T, P = P)
    e <- equilibrate(a, loga.balance = logm_S)
    diagram(e)
    legend("topright", ltext, bty = "n", bg = "transparent")
    title(quote("Modified"~log*italic(K)~"after Pokrovski and Dubrovinsky (2011)"), xpd = NA)
    OBIGT()

### Can I make the diagram using the Deep Earth Water (DEW) model?

Yes! Just set a new temperature and pressure and activate the DEW water model and load the DEW aqueous species. You can also use `info()` to see which species are affected by loading the DEW parameters; it turns out that SO4-2 isn’t. Then, use similar commands as above to make the diagram. At the end, reset the water model and OBIGT database.

    T <- 700
    P <- 10000 # 10000 bar = 1 GPa
    oldwat <- water("DEW")
    add.OBIGT("DEW")
    info(species()$ispecies)[, 1:8]
    ##      name   abbrv formula state   ref1     ref2       date model
    ## 64    H2S  H2S(0)     H2S    aq  SHS89  DEW17.3 2017-09-26   DEW
    ## 22    HS-   HS(-)     HS-    aq SSWS97  DEW17.3 2017-09-26   DEW
    ## 577   S3-   S3(-)     S3-    aq   PD15 DEW17.36 2017-09-26   DEW
    ## 72    SO2  SO2(0)     SO2    aq  SHS89 DEW17.38 2017-09-26   DEW
    ## 25  HSO4- HSO4(-)   HSO4-    aq SSWS97  DEW17.3 2017-09-26   DEW
    ## 24  SO4-2   SO4-2   SO4-2    aq   SH88     <NA> 1997-11-07   HKF
    a <- affinity(pH = c(2, 10, res), O2 = c(-18, -10, res), T = T, P = P)
    e <- equilibrate(a, loga.balance = logm_S)
    diagram(e)
    lTP <- describe.property(c("T", "P"), c(T, P))
    lS <- paste(wt_percent_S, "wt% S(aq)")
    ltext <- c(lTP, lS)
    legend("topright", ltext, bty = "n", bg = "transparent")
    title(quote("Deep Earth Water (DEW)"~"model"))
    water(oldwat)
    OBIGT()

Here are the three plots that we made:

Added on 2023-09-08.

## In OBIGT, what is the meaning of `T` for solids, liquids, and gases?

The value in this column can be one of the following:

1. The temperature of transition to the next polymorph of a mineral;
2. For the highest-temperature (or only) polymorph, if `model == "CGL_Ttr"`, then `T` is the phase transition temperature (e.g., the temperature of melting or decomposition of a solid or vaporization of a liquid);
3. For the highest-temperature polymorph, if `model == "CGL"`, then `T` is the Tmax for the Cp equation.

These cases are handled by `subcrt()` as follows. The units of `T` in OBIGT are Kelvin, but `subcrt()` by default uses °C:

1. For polymorphic transitions, the properties of specific polymorphs are returned:

    subcrt("pyrrhotite", T = c(25, 150, 350), property = "G")$out

info.character: found pyrrhotite(cr) with 2 polymorphic transitions

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

subcrt: 3 polymorphs for pyrrhotite … polymorphs 1,2,3 are stable

    ## $pyrrhotite
    ##     T          P         G polymorph
    ## 1  25   1.000000 -100767.5         1
    ## 2 150   4.757169 -109734.3         2
    ## 3 350 165.211289 -129984.5         3

Note: In both SUPCRT92 and OBIGT, tin, sulfur, and selenium are listed as minerals with one or more polymorphic transitions, but the highest-temperature polymorph actually represents the liquid state. Furthermore, quicksilver is listed as a mineral whose polymorphs actually correspond to the liquid and gaseous states.

2. For species with phase transitions, extrapolated values of ΔG° are returned and a warning is produced:

    subcrt("pyrite", T = seq(200, 1000, 200), P = 1)

Warning in subcrt(“pyrite”, T = seq(200, 1000, 200), P = 1): above transition

temperature of 1015 K for pyrite(cr)

    ## $species
    ##        name formula state ispecies   model
    ## 1470 pyrite    FeS2    cr     1470 CGL_Ttr
    ##
    ## $out
    ## $out$pyrite
    ##      T P     logK         G          H         S     V       Cp
    ## 1  200 1 19.02722 -172355.1 -159662.59  84.11482 23.94 71.72282
    ## 2  400 1 14.89151 -191911.2 -144868.84 110.15205 23.94 75.71142
    ## 3  600 1 12.92264 -216018.0 -129487.09 130.14642 23.94 77.95838
    ## 4  800 1 11.86275 -243722.5 -113722.56 146.39738 23.94 79.62872
    ## 5 1000 1 11.25823 -274409.9  -97651.55 160.12626 23.94 81.05409
    ##
    ##
    ## $warnings
    ## [1] "above transition temperature of 1015 K for pyrite(cr)"

The warning alerts when the temperature is above the transition temperature for species with `model = "CGL_Ttr"`. To silence the warning, add `warn.Ttr = FALSE` to the call to `subcrt()`.

`CGL_Ttr` in OBIGT is used for species where the T limit represent melting, decomposition, or other phase change. These minerals are listed below:

    file <- system.file("extdata/OBIGT/inorganic_cr.csv", package = "CHNOSZ")
    dat <- read.csv(file)
    dat$name[dat$model == "CGL_Ttr"]
    ##  [1] "anhydrite"       "bunsenite"       "bromellite"      "chalcocite"
    ##  [5] "chlorargyrite"   "cinnabar"        "copper"          "covellite"
    ##  [9] "cuprite"         "galena"          "gold"            "halite"
    ## [13] "iron"            "nickel"          "pyrite"          "pyrrhotite"
    ## [17] "silver"          "sodium oxide"    "sphalerite"      "strontianite"
    ## [21] "sylvite"         "zincite"         "sulfur"          "selenium"
    ## [25] "gypsum"          "manganosite"     "wustite"         "cobalt monoxide"
    ## [29] "zinc"            "carrollite"      "cassiterite"     "aluminum"
    ## [33] "EuO"             "beta-Ta2O5"      "beta-Nb2O5"      "GeS2"
    ## [37] "stibnite"        "petalite"        "La2O3-A"         "Ce2O3-A"
    ## [41] "Pr2O3-A"         "Nd2O3-A"         "Pm2O3-A"         "Pm2O3-B"
    ## [45] "Sm2O3-B"         "Eu2O3-B"         "Gd2O3-B"         "Cm2O3-B"
    ## [49] "Sm2O3-C"         "Eu2O3-C"         "Gd2O3-C"         "Tb2O3-C"
    ## [53] "Dy2O3-C"         "Ho2O3-C"         "Er2O3-C"         "Tm2O3-C"
    ## [57] "Yb2O3-C"         "Lu2O3-C"         "Y2O3-C"          "CeO2"
    ## [61] "PrO2"            "TbO2"            "LaCl3"           "CeCl3"
    ## [65] "PrCl3"           "NdCl3"           "SmCl3"           "EuCl3"
    ## [69] "GdCl3"           "TbCl3"           "DyCl3"           "HoCl3"
    ## [73] "ErCl3"           "TmCl3"           "YbCl3"           "LuCl3"
    ## [77] "YCl3"            "NdCl3*6H2O"      "LaF3"            "CeF3"
    ## [81] "PrF3"            "NdF3"            "SmF3"            "EuF3"
    ## [85] "GdF3"            "TbF3"            "DyF3"            "HoF3"
    ## [89] "ErF3"            "TmF3"            "YbF3"            "LuF3"
    ## [93] "YF3"             "bastnäsite-(Ce)" "parisite-(Ce)"

OBIGT uses the decomposition temperature of covellite (780.5 K from Robie and Hemingway, 1995) in contrast to the value of Tmax from SUPCRT92 (1273 K, which is referenced to a equation described as “estimated” on p. 62 of Kelley, 1960). Selected other organic solids and liquids have melting or vaporization temperatures listed. However, no melting temperatures are listed for minerals that use the `Berman` model.

3. For a Cp equation limit, extrapolated values of ΔG° are returned and a message is emitted:

    add.OBIGT("SUPCRT92")

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

    subcrt("muscovite", T = 850, P = 4500)

subcrt: 1 species at 850 ºC and 4500 bar [energy units: J]

subcrt: above T limit of 1000 K for the Cp equation for muscovite(cr)

    ## $species
    ##           name             formula state ispecies model
    ## 1372 muscovite KAl2(AlSi3)O10(OH)2    cr     1372   CGL
    ##
    ## $out
    ## $out$muscovite
    ##     T    P     logK        G        H        S      V       Cp
    ## 1 850 4500 280.7978 -6037836 -5533722 864.6487 140.71 523.7196

    reset()

reset: resetting “thermo” object

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

The message is similar to that produced by SUPCRT92 (“CAUTION: BEYOND T LIMIT OF CP COEFFS FOR A MINERAL OR GAS”) at temperatures above maximum temperature of validity of the Maier-Kelley equation (Tmax). Similar to the behavior of SUPCRT92, CHNOSZ outputs ΔG° and other standard thermodynamic properties at temperatures higher than Tmax despite the message.

4. Finally, if `T` is NA or 0, then no message or warning is produced.

Added on 2023-11-15. Updated on 2024-12-29 for use of `CGL_Ttr`.

## How can mineral pH buffers be plotted?

Unlike mineral redox buffers, the K-feldspar–muscovite–quartz (KMQ) and muscovite–kaolinite (MC) pH buffers are known as “sliding scale” buffers because they do not determine pH but rather the activity ratio of K+ to H+ (Holm and Andersson, 2005). To add these buffers to a logfO2–pH diagram in CHNOSZ, choose basis species that include Al+3 (the least mobile element, which the reactions are balanced on), quartz (this is needed for the KMQ buffer), the mobile ions K+ and H+, and the remaining elements in H2O and O2; `oxygen` denotes the gas in OBIGT. The formation reactions for these minerals don’t involve O2, but it must be present so that the number of basis species equals the number of elements +1 (i.e. elements plus charge).

    basis(c("Al+3", "quartz", "K+", "H+", "H2O", "oxygen"))

    ##      Al H K O Si Z ispecies logact state
    ## Al+3  1 0 0 0  0 3      473      0    aq
    ## SiO2  0 0 0 2  1 0     1382      0    cr
    ## K+    0 0 1 0  0 1        6      0    aq
    ## H+    0 1 0 0  0 1        3      0    aq
    ## H2O   0 2 0 1  0 0        1      0   liq
    ## O2    0 0 0 2  0 0     2288      0   gas

    species(c("kaolinite", "muscovite", "K-feldspar"))

    ##   Al+3 SiO2 K+  H+ H2O O2 ispecies logact state       name
    ## 1    2    2  0  -6   5  0     1362      0    cr  kaolinite
    ## 2    3    3  1 -10   6  0     1372      0    cr  muscovite
    ## 3    1    3  1  -4   2  0     1376      0    cr K-feldspar

We could go right ahead and make a logfO2–pH diagram, but the implied assumption would be that the K+ activity is unity, which may not be valid. Instead, we can obtain an independent estimate for K+ activity based on 1) the activity ratio of Na+ to K+ for the reaction between albite and K-feldspar and 2) charge balance among Na+, K+, and Cl- for a given activity of the latter (Heinrich and Candela, 2014). Using the variables defined below, those conditions are expressed as `K_AK = m_Na / m_K` and `m_Na + m_K = m_Cl`, which combine to give `m_K = m_Cl / (K_AK + 1)`. The reason for writing the equations with molality instead of activity is that ionic strength (`IS`) is provided in the arguments to `subcrt()`, so the function returns a value of logK adjusted for ionic strength. Furthermore, it is assumed that this is a chloride-dominated solution, so ionic strength is taken to be equal to the molality of Cl-.

    # Define temperature, pressure, and molality of Cl- (==IS)
    T <- 150
    P <- 500
    IS <- m_Cl <- 1
    # Calculate equilibrium constant for Ab-Kfs reaction, corrected for ionic strength
    logK_AK <- subcrt(c("albite", "K+", "K-feldspar", "Na+"), c(-1, -1, 1, 1),
      T = T, P = P, IS = IS)$out$logK
    K_AK <- 10 ^ logK_AK
    # Calculate molality of K+
    (m_K <- m_Cl / (K_AK + 1))

    ## [1] 0.07194125

This calculation gives a molality of K+ that is lower than unity and accordingly makes the buffers less acidic (Heinrich and Candela, 2014). Now we can apply the calculated molality of K+ to the basis species and add the buffer lines to the diagram. The `IS` argument is also used for `affinity()` so that activities are replaced by molalities (that is, affinity is calculated with standard Gibbs energies adjusted for ionic strength; this has the same effect as calculating activity coefficients).

    basis("K+", log10(m_K))
    a <- affinity(pH = c(2, 10), O2 = c(-55, -38), T = T, P = P, IS = IS)
    diagram(a, srt = 90)
    lTP <- as.expression(c(lT(T), lP(P)))
    legend("topleft", legend = lTP, bty = "n", inset = c(-0.05, 0), cex = 0.9)
    ltxt <- c(quote("Unit molality of Cl"^"-"), "Quartz saturation")
    legend("topright", legend = ltxt, bty = "n", cex = 0.9)
    title("Mineral data from Berman (1988)\nand Sverjensky et al. (1991) (OBIGT default)",
      font.main = 1, cex.main = 0.9)

The gray area, which is automatically drawn by `diagram()`, is below the reducing stability limit of water; that is, this area is where the equilibrium fugacity of H2 exceeds unity. NOTE: Although the muscovite–kaolinite (MC) buffer was mentioned by Heinrich and Candela (2014) in the context of “clay-rich but feldspar-free sediments”, this example uses the feldspathic Ab–Kfs reaction for calculating K+ molality for both the KMQ and MC buffers. A more appropriate reaction to constrain the Na/K ratio with the MC buffer may be that between paragonite and muscovite (e.g., Yardley, 2005).

The diagram in Fig. 4 of Heinrich and Candela (2014) shows the buffer lines at somewhat higher pH values of ca. 5 and 6. Removing `IS` from the code moves the lines to lower rather than higher pH (not shown – try it yourself!), so the calculation of activity coefficients does not explain the differences. One possible reason for these differences is the use of different thermodynamic data for the minerals. The parameters for these minerals in the default OBIGT database come from Berman (1988) and Sverjensky et al. (1991).

    thermo.refs(species()$ispecies)

    ##      key                                            author year
    ## 34 Ber88                                      R. G. Berman 1988
    ## 52 SHD91 D. A. Sverjensky, J. J. Hemley and W. M. D'Angelo 1991
    ##                                 citation
    ## 34                J. Petrol. 29, 445-522
    ## 52 Geochim. Cosmochim. Acta 55, 989-1004
    ##                                                 note
    ## 34                                          minerals
    ## 52 G and H revisions for K- and Al-bearing silicates
    ##                                             URL
    ## 34   https://doi.org/10.1093/petrology/29.2.445
    ## 52 https://doi.org/10.1016/0016-7037(91)90157-Z

CHNOSZ doesn’t implement the thermodynamic model for minerals from Holland and Powell (1998), which is one of the sources cited by Heinrich and Candela (2014). If we use the thermodynamic parameters for minerals from Helgeson et al. (1978; these do not include the revisions for aluminosilicates described by Sverjensky et al., 1991), we get the lines shown in the second plot above, representing a larger stability field for muscovite. This moves the KMQ buffer closer to the value shown by Heinrich and Candela (2014), but the MC buffer further away, so this still doesn’t explain why we get a different result.

    add.OBIGT("SUPCRT92")
    a <- affinity(pH = c(2, 10), O2 = c(-55, -38), T = T, P = P, IS = IS)
    diagram(a, srt = 90)
    title("Mineral data from Helgeson et al. (1978)\n(as used in SUPCRT92)",
      font.main = 1, cex.main = 0.9)

Added on 2023-11-28.

## Why are mineral stability boundaries curved on mosaic diagrams?

The reason they are curved has to do with mass balance of elements in aqueous solution. For example, take two reactions between pyrite (FeS2) and pyrrhotite (FeS), one with H2S and the other with HS-:

1. FeS2 + H2O = FeS + 0.5 O2 + H2S
2. FeS2 + H2O = FeS + 0.5 O2 + HS- + H+

If a pH 4 solution at 150 °C has 0.001 mol/kg H2S, then raising the pH to 8 would give 0.001 mol/kg of HS- and essentially no H2S. For the remainder of this discussion we will assume that mol/kg is equivalent to activity (i.e., that activity cofficients are unity). If we use the same value (0.001) for H2S and HS- in reactions 1 and 2 (the constant activity constraint), then we will get straight lines on a logfO2–pH diagram. However, this is inconsistent with a constant sum constraint of activities that is sometimes attributed to these diagrams.

The constant activity constraint is compatible with the constant sum constraint only well inside the predominance field of a given aqueous species. The equivalence breaks down near the transitions between aqueous species. For instance, if the total activity of S is 0.001, then at the pKa of H2S (about 6.5 at 150 °C), the activities of H2S and HS- are equal to each other and by mass balance are both 0.0005. The position of the stability boundary should be calculated with these activities to satisfy the constant sum constraint.

The following code defines functions to calculate logfO2 for these two reactions.

    # Constants
    a_S <- 0.001
    T <- 150

    # Get the pKa of H2S (note the minus sign!)
    pKa_H2S <- - subcrt(c("H2S", "HS-", "H+"), c(-1, 1, 1), T = T)$out$logK

    # Reaction 1 between pyrite and pyrrhotite with H2S
    basis(c("pyrite", "H2S", "oxygen", "H2O", "H+"))
    PyPo_H2S <- subcrt("pyrrhotite", 1, T = T)

    # Reaction 1 law of mass action (LMA)
    # logf_O2 = 2 logK_1 - 2 loga_H2S
    logf_O2_1 <- function(loga_H2S) 2 * PyPo_H2S$out$logK - 2 * loga_H2S

    # Reaction 2 between pyrite and pyrrhotite with HS-
    basis(c("pyrite", "HS-", "oxygen", "H2O", "H+"))
    PyPo_HS <- subcrt("pyrrhotite", 1, T = T)

    # Reaction 2 LMA
    # logf_O2 = 2 pH + 2 logK_2 - 2 loga_HS-
    logf_O2_2 <- function(pH, loga_HS) 2 * pH + 2 * PyPo_HS$out$logK - 2 * loga_HS

    # The logf_O2 for each reaction is the same at the pKa of H2S
    stopifnot(all.equal(logf_O2_1(log10(a_S)), logf_O2_2(pKa_H2S, log10(a_S))))
    stopifnot(all.equal(logf_O2_1(log10(a_S / 2)), logf_O2_2(pKa_H2S, log10(a_S / 2))))

At 150 °C and the pKa of H2S, we get logfO2 = -54.68 for aH2S = aHS- = 0.001 but logfO2 = -54.08 for aH2S + aHS- = 0.001. In other words, the constant activity and constant sum constraints produce different results; the former yields two straight lines while the latter yields a curve. This is shown graphically in the plots below.

    par(mfrow = c(1, 2))

    # Let's draw this out
    thermo.plot.new(c(2, 10), c(-56, -46), xlab = "pH", ylab = axis.label("O2"))
    lines(c(2, pKa_H2S), c(logf_O2_1(log10(a_S)), logf_O2_1(log10(a_S))), col = 2)
    lines(c(pKa_H2S, 10), c(logf_O2_2(pKa_H2S, log10(a_S)), logf_O2_2(10, log10(a_S))), col = 4)
    abline(v = pKa_H2S, lty = 2, col = 8)
    text(6, -46.5, expr.species("H2S"), adj = c(0.5, 1))
    text(7, -46.5, expr.species("HS-"), adj = c(0.5, 1))
    text(4, -54, "pyrite")
    text(4, -55.5, "pyrrhotite")
    points(pKa_H2S, logf_O2_1(log10(a_S)), pch = 0)
    points(pKa_H2S, logf_O2_1(log10(a_S / 2)), pch = 19)
    legend("bottomright", c("Yes", "No"), pch = c(19, 0), title = as.expression(quote(sum(S) == "constant")), bty = "n")
    legend("topleft", legend = lT(T), bty = "n")
    title(quote("Straight lines for" ~ italic(a)[H[2]*S] ~ "=" ~ italic(a)[HS^"-"] == "0.001"), font.main = 1)

    # Do it with mosaic()
    basis(c("pyrite", "H2S", "oxygen", "H2O", "H+"))
    # This sets the activity of H2S, which is used for total S when mosaic is called with blend = TRUE
    basis("H2S", -3)
    # This defines the basis species to swap through
    bases <- c("H2S", "HS-")
    species(c("pyrite", "pyrrhotite"))
    m <- mosaic(bases, pH = c(2, 10, 500), O2 = c(-56, -46, 500), T = T)
    diagram(m$A.species, dx = c(-1, -3), dy = c(-3, -1.5), col = 6)
    abline(v = pKa_H2S, lty = 2, col = 8)
    text(6, -46.5, expr.species("H2S"), adj = c(0.5, 1))
    text(7, -46.5, expr.species("HS-"), adj = c(0.5, 1))
    points(pKa_H2S, logf_O2_1(log10(a_S)), pch = 0)
    points(pKa_H2S, logf_O2_1(log10(a_S / 2)), pch = 19)
    legend("bottomright", c("Yes", "No"), pch = c(19, 0), title = as.expression(quote(sum(S) == "constant")), bty = "n")
    legend("topleft", legend = lT(T), bty = "n")
    title(quote("Curved line for" ~ italic(a)[H[2]*S] + italic(a)[HS^"-"] == "0.001"), font.main = 1)

The plot on the left is made “by hand” (using equilibrium constants calculated with `subcrt()`) while the plot on the right is made with the `mosaic()` and `diagram()` functions. The gray area is where water is unstable and is automatically added by `diagram()`. If you’d like to make a plot without curved lines (i.e., for constant activity instead of constant sum), then set `blend = FALSE` in `mosaic()`.

There are relatively few published logfO2–pH diagrams with curved mineral stability lines. An example of one is in Figure 5 of Cooke et al. (2000). The code below makes a diagram for the minerals shown in that figure:

    basis(c("FeO", "H2S", "CO3-2", "H2O", "H+", "oxygen"))
    basis("H2S", -3)
    basis("CO3-2", -0.6)
    species(c("hematite", "pyrite", "pyrrhotite", "magnetite", "siderite"))
    bases <- list( c("H2S", "HS-", "SO2", "HSO4-", "SO4-2"), c("CO3-2", "HCO3-", "CO2") )
    # Uncomment to include the trisulfur radical ion (slower)
    # bases <- list( c("H2S", "HS-", "S3-", "SO2", "HSO4-", "SO4-2"), c("CO3-2", "HCO3-", "CO2") )
    m <- mosaic(bases, pH = c(0, 14, 500), O2 = c(-57, -35, 500), T = 150, IS = 0.77)
    d <- diagram(m$A.species, fill = "terrain", dx = c(0, 0, 0, 0, 0.3))
    water.lines(d)

This result suggests two improvements to Fig. 5A in Cooke et al. (2000). First, the triangular area above the water stability limit should be labeled as part of the siderite field (which is interrupted by the pyrite wedge), not as pyrrhotite. Second, the boundary between pyrite and magnetite should have one bend, not two.

Added on 2024-04-01.

## How is the sum of activities of basis species defined for mosaic diagrams?

Short answer:

- `basis(..., log_activity)` before `mosaic()` defines the sum of activities of the set of basis species starting with `...` in the `bases` argument to `mosaic()`.
- `basis(..., log_activity)` before `affinity()` defines the activity of the single basis species identified by `...`.

Longer explanation:

The purpose of `mosaic()` is to speciate the basis species; that is, to allow an element such as S to be distributed among different candidate basis species, which in turn are used to construct the formation reactions of species for relative stability calculations. Let’s take H2S as the starting basis species for S. If we define a constant activity of H2S, then the distribution of S-bearing basis species looks like the plot on the left. If we define a constant sum of activities of S-bearing species, we get the plot on the right.

    par(mfrow = c(1, 2))

    # The basis species we are speciating
    bases <- c("H2S", "HS-", "HSO4-", "SO4-2")
    # The basis species defining the system
    # Note 1: for mosaic(), the first species in 'bases' must be in this list
    # Note 2: for solubility(), the first species in this list must contain S
    basis(c("H2S", "CO2", "H2O", "O2", "H+"))
    # Define a low fO2 so reduced sulfur dominates over most of the pH range
    basis("O2", -75)
    # The pH range we'll look at
    pH <- c(0, 14)
    # The logarithm of activity used for aH2S or sum(S)
    log_activity <- -2

    # Left-hand plot: Constant log aH2S, variable sum(S)
    # Set it up as a solubility calculation
    # Define a single log aH2S
    species("H2S", log_activity)
    # Specify the aqueous species in equilibrium with H2S
    iaq <- info(bases)
    # Run the calculation to calculate all species' activities
    s <- solubility(iaq, pH = c(0, 14))
    # Diagram individual activities then total activity
    diagram(s, type = "loga.equil", ylim = c(-15, 10))
    diagram(s, lwd = 2, col = 4, add = TRUE)
    title(quote("Constant"~italic(a)*H[2]*S))

    # Right-hand plot: Constant sum(S), variable log aH2S
    # Start by loading all the candidate species with preset activities
    species(bases)
    # Calculate affinities of formation reactions
    a <- affinity(pH = pH)
    # Equilibrate the species for a total activity of S
    e <- equilibrate(a, loga.balance = log_activity)
    # Diagram individual activities then total activity
    diagram(e, ylim = c(-15, 10))
    diagram(e, lwd = 2, col = 4, add = TRUE, type = "loga.balance")
    title(quote("Constant total activity of S ("*sum(italic(a)*S)*")"))

The right-hand plot is how `mosaic()` interprets the value defined in `basis("H2S", log_activity)`. If `log_activity` is -2, then the formation reactions see these activities of H2S:

- log aH2S = -2 at pH 0
- log aH2S = -11 at pH 14

H2S practically disappears at pH 14 and is replaced by SO4-2 with a log activity of -2. Thus, for the purposes of `mosaic()`, `basis("H2S", -2)` defines a constant sum of activity of all S-bearing basis species, not a constant activity of H2S.

What does this mean in practice? It means that the affinities to form minerals or aqueous species are sensitive to the speciation of basis species. Let’s set up a simple system to compare the affinities of forming calcite (CaCO3) and anhydrite (CaSO4) as a function of pH. If we define constant activities of carbonate (CO3−2) and sulfate (SO4-2), we get the plot on the left. The flat lines are suspicious; the protonated forms of these ions are are stabilized by acidic conditions, and changing the basis species should change the affinities.

    par(mfrow = c(1, 2))

    # Look at the stabilities of calcite and anhydrite
    basis(c("Ca+2", "CO3-2", "SO4-2", "H2O", "O2", "H+"))
    basis("Ca+2", -3)
    # For afffinity(), these are single activities
    # For mosaic(), these are total activities for groups of species in the 'bases' list
    basis("CO3-2", -3)
    basis("SO4-2", -3)
    species(c("calcite", "anhydrite"))

    # affinity() calculation
    a <- affinity(pH = pH)
    diagram(a, ylim = c(-5, 3))
    title(quote("affinity(): constant"~italic(a)*CO[3]^"-2"~and~italic(a)*SO[4]^"-2"))

    # mosaic() calculation
    bases <- list(
      c("CO3-2", "HCO3-", "CO2"),
      c("SO4-2", "HSO4-")
    )
    m <- mosaic(bases, pH = pH)
    diagram(m$A.species, ylim = c(-5, 3))
    title(quote("mosaic(): constant"~sum(italic(a)*C)~and~sum(italic(a)*S)))

The key idea is to speciate the basis species by listing the candatate basis species in the `bases` argument of `mosaic()`:

    bases <- list(
      c("CO3-2", "HCO3-", "CO2"),
      c("SO4-2", "HSO4-")
    )
    m <- mosaic(bases, pH = pH)

This underlies the diagram on the right. The lower affinity at lower pH makes sense because the activities of CO3−2 and SO4-2 are becoming depleted as the protonated forms become more stable. In both cases the total activity of C and S in the sets of basis species defined by `bases` is constant.

Added on 2026-06-02.

## References

Alberty RA. 1996. Recommendations for nomenclature and tables in biochemical thermodynamics. European Journal of Biochemistry 240(1): 1–14. doi: 10.1111/j.1432-1033.1996.0001h.x
Anderson GM, Crerar DA. 1993. Thermodynamics in Geochemistry: The Equilibrium Model. New York: Oxford University Press. doi: 10.1093/oso/9780195064643.001.0001
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
Berman RG, Brown TH. 1985. Heat capacity of minerals in the system Na2O–K2O–CaO–MgO–FeO–Fe2O3–Al2O3–SiO2–TiO2–H2O–CO2: Representation, estimation, and high temperature extrapolation. Contributions to Mineralogy and Petrology 89(2): 168–183. doi: 10.1007/BF00379451
Cooke DR, Bull SW, Large RR, McGoldrick PJ. 2000. The importance of oxidized brines for the formation of Australian Proterozoic stratiform sediment-hosted Pb-Zn (Sedex) deposits. Economic Geology 95(1): 1–17. doi: 10.2113/gsecongeo.95.1.1
Dick JM. 2008. Calculation of the relative metastabilities of proteins using the CHNOSZ software package. Geochemical Transactions 9: 10. doi: 10.1186/1467-4866-9-10
Dick JM. 2019. CHNOSZ: Thermodynamic calculations and diagrams for geochemistry. Frontiers in Earth Science 7: 180. doi: 10.3389/feart.2019.00180
Dick JM. 2021. Diagrams with multiple metals in CHNOSZ. Applied Computing and Geosciences 10: 100059. doi: 10.1016/j.acags.2021.100059
Dick JM, LaRowe DE, Helgeson HC. 2006. Temperature, pressure, and electrochemical constraints on protein speciation: Group additivity calculation of the standard molal thermodynamic properties of ionized unfolded proteins. Biogeosciences 3(3): 311–336. doi: 10.5194/bg-3-311-2006
Heinrich CA, Candela PA. 2014. Fluids and ore formation in the Earth’s crust. In: Holland HD, Turekian KK, editors. Treatise on Geochemistry (Vol. 13) 2nd ed. Oxford: Elsevier. p. 1–28. doi: 10.1016/B978-0-08-095975-7.01101-3
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, Kirkham DH, Flowers GC. 1981. Theoretical prediction of the thermodynamic behavior of aqueous electrolytes at high pressures and temperatures: IV. Calculation of activity coefficients, osmotic coefficients, and apparent molal and standard and relative partial molal properties to 600°C and 5 Kb. American Journal of Science 281(10): 1249–1516. doi: 10.2475/ajs.281.10.1249
Holland TJB, Powell R. 1998. An internally consistent thermodynamic data set for phases of petrological interest. Journal of Metamorphic Geology 16(3): 309–343. doi: 10.1111/j.1525-1314.1998.00140.x
Holm NG, Andersson E. 2005. Hydrothermal simulation experiments as a tool for studies of the origin of life on Earth and other terrestrial planets: A review. Astrobiology 5(4): 444–460. doi: 10.1089/ast.2005.5.444
Johnson JW, Oelkers EH, Helgeson HC. 1992. SUPCRT92: A software package for calculating the standard molal thermodynamic properties of minerals, gases, aqueous species, and reactions from 1 to 5000 bar and 0 to 1000°C. Computers & Geosciences 18(7): 899–947. doi: 10.1016/0098-3004(92)90029-Q
Kelley KK. 1960. Contributions to the Data in Theoretical Metallurgy XIII: High Temperature Heat Content, Heat Capacities and Entropy Data for the Elements and Inorganic Compounds. U. S. Bureau of Mines. (Bulletin 584). Available at https://search.worldcat.org/title/693388901.
Manning CE, Shock EL, Sverjensky DA. 2013. The chemistry of carbon in aqueous fluids at crustal and upper-mantle conditions: Experimental and theoretical constraints. Reviews in Mineralogy and Geochemistry 75(1): 109–148. doi: 10.2138/rmg.2013.75.5
Pankratz LB, Mah AD, Watson SW. 1987. Thermodynamic Properties of Sulfides. United States Department of the Interior, Bureau of Mines. (Bulletin; Vol. 689). Available at https://search.worldcat.org/title/16131757.
Pokrovski GS, Dubessy J. 2015. Stability and abundance of the trisulfur radical ion in hydrothermal fluids. Earth and Planetary Science Letters 411: 298–309. doi: 10.1016/j.epsl.2014.11.035
Pokrovski GS, Dubrovinsky LS. 2011. The S3- ion is stable in geological fluids at elevated temperatures and pressures. Science 331(6020): 1052–1054. doi: 10.1126/science.1199911
Richard L, Helgeson HC. 1998. Calculation of the thermodynamic properties at elevated temperatures and pressures of saturated and aromatic high molecular weight solid and liquid hydrocarbons in kerogen, bitumen, petroleum, and other organic matter of biogeochemical interest. Geochimica et Cosmochimica Acta 62(23-24): 3591–3636. doi: 10.1016/S0016-7037(97)00345-1
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, Helgeson HC. 1988. Calculation of the thermodynamic and transport properties of aqueous species at high pressures and temperatures: Correlation algorithms for ionic species and equation of state predictions to 5 kb and 1000°C. Geochimica et Cosmochimica Acta 52(8): 2009–2036. doi: 10.1016/0016-7037(88)90181-0
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, Hemley JJ, D’Angelo WM. 1991. Thermodynamic assessment of hydrothermal alkali feldspar-mica-aluminosilicate equilibria. Geochimica et Cosmochimica Acta 55(4): 989–1004. doi: 10.1016/0016-7037(91)90157-Z
Tanger JC IV, Helgeson HC. 1988. Calculation of the thermodynamic and transport properties of aqueous species at high pressures and temperatures: Revised equations of state for the standard partial molal properties of ions and electrolytes. American Journal of Science 288(1): 19–98. doi: 10.2475/ajs.288.1.19
Wagner W, Pruß A. 2002. The IAPWS formulation 1995 for the thermodynamic properties of ordinary water substance for general and scientific use. Journal of Physical and Chemical Reference Data 31(2): 387–535. doi: 10.1063/1.1461829
Yardley BWD. 2005. 100th Anniversary Special Paper: Metal concentrations in crustal fluids and their relationship to ore formation. Economic Geology 100(4): 613–632. doi: 10.2113/gsecongeo.100.4.613