---
title: "CHNOSZ FAQ"
author: "Jeffrey M. Dick"
output:
  html_vignette:
    mathjax: null
    toc: true
vignette: >
  %\VignetteIndexEntry{CHNOSZ FAQ}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
bibliography: vig.bib
csl: elementa.csl
link-citations: true
---

<style>

/* https://gomakethings.com/how-to-break-an-image-out-of-its-parent-container-with-css/ */
@media (min-width: 700px) {
  .full-width {
    left: 50%;
    margin-left: -50vw;
    margin-right: -50vw;
    max-width: 100vw;
    position: relative;
    right: 50%;
    width: 100vw;
  }
}
@media (min-width: 1100px) {
  .full-width {
    left: 50vw; /* fallback if needed */
    left: calc(50vw - 200px);
    width: 1100px;
    position: relative;
  }
}
/* zero margin around pre blocks (looks more like R console output) */
pre {
  margin-top: 0;
  margin-bottom: 0;
}

/* CSS snippet from boostrap.css modified by Jeffrey Dick on 2023-11-14 */
/*!
 * Bootstrap  v5.3.2 (https://getbootstrap.com/)
 * Copyright 2011-2023 The Bootstrap Authors
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
 */
:root,
[data-bs-theme=light] {
  --bs-info-text-emphasis: #055160;
  --bs-warning-text-emphasis: #664d03;
  --bs-info-bg-subtle: #cff4fc;
  --bs-warning-bg-subtle: #fff3cd;
  --bs-info-border-subtle: #9eeaf9;
  --bs-warning-border-subtle: #ffe69c;
  --bs-border-width: 1px;
  --bs-border-radius: 0.375rem;
}
.alert {
  --bs-alert-bg: transparent;
  --bs-alert-padding-x: 0.5rem;
  --bs-alert-padding-y: 0.5rem;
  --bs-alert-margin-bottom: 1rem;
  --bs-alert-color: inherit;
  --bs-alert-border-color: transparent;
  --bs-alert-border: var(--bs-border-width) solid var(--bs-alert-border-color);
  --bs-alert-border-radius: var(--bs-border-radius);
  --bs-alert-link-color: inherit;
  position: relative;
  padding: var(--bs-alert-padding-y) var(--bs-alert-padding-x);
  margin-bottom: var(--bs-alert-margin-bottom);
  color: var(--bs-alert-color);
  background-color: var(--bs-alert-bg);
  border: var(--bs-alert-border);
  border-radius: var(--bs-alert-border-radius);
}
.alert-info {
  --bs-alert-color: var(--bs-info-text-emphasis);
  --bs-alert-bg: var(--bs-info-bg-subtle);
  --bs-alert-border-color: var(--bs-info-border-subtle);
  --bs-alert-link-color: var(--bs-info-text-emphasis);
}
.alert-warning {
  --bs-alert-color: var(--bs-warning-text-emphasis);
  --bs-alert-bg: var(--bs-warning-bg-subtle);
  --bs-alert-border-color: var(--bs-warning-border-subtle);
  --bs-alert-link-color: var(--bs-warning-text-emphasis);
}

</style>

<script>
function ToggleDiv(ID) {
  var D = document.getElementById("D-" + ID);
  var B = document.getElementById("B-" + ID);
  if (D.style.display === "none") {
    // open the div and change button text
    D.style.display = "block";
    B.innerText = "Hide code";
  } else {
    // close the div and change button text
    D.style.display = "none";
    B.innerText = "Show code";
  }
}
</script>

```{r setup, include = FALSE}
library(CHNOSZ)
options(width = 80)
# Use pngquant to optimize PNG images
library(knitr)
knit_hooks$set(pngquant = hook_pngquant)
pngquant <- if(nzchar(Sys.getenv("CHNOSZ_BUILD_LARGE_VIGNETTES"))) "--speed=1 --quality=0-25" else "--speed=1 --quality=0-10"
if (!nzchar(Sys.which("pngquant"))) pngquant <- NULL
# To make warnings appear in text box 20230619
# https://selbydavid.com/2017/06/18/rmarkdown-alerts/
knitr::knit_hooks$set(
   error = function(x, options) {
     paste('\n\n<div class="alert alert-danger">',
           gsub('##', '\n', gsub('^##\ Error', '**Error:**', x)),
           '</div>', sep = '\n')
   },
   warning = function(x, options) {
     paste('\n\n<div class="alert alert-warning">',
           gsub('##', '\n', gsub('^##\ Warning:', '**Warning:**', x)),
           '</div>', sep = '\n')
   },
   message = function(x, options) {
     paste('\n\n<div class="alert alert-info">',
           gsub('##', '\n', x),
           '</div>', sep = '\n')
   }
)

# Set dpi 20231129
knitr::opts_chunk$set(
  dpi = if(nzchar(Sys.getenv("CHNOSZ_BUILD_LARGE_VIGNETTES"))) 100 else 72
)
```

```{r echo = F, cache = F}
# Merge consecutive messages into a single div 20231114
knitr::knit_hooks$set(document = function(x){

  # Not sure why this is needed, but simply computing on 'x' doesn't work
  file <- tempfile()
  writeLines(x, file)
  x <- readLines(file)

  # Line numbers of the document with </div>
  enddiv <- which(x == "</div>")
  # Line numbers with <div class="alert alert-info">
  beginalert <- which(x == '<div class="alert alert-info">')
  # Find </div> followed <div class="alert alert-info"> (skip empty lines)
  removediv <- (enddiv + 2) %in% beginalert
  if(any(removediv)) {
    # Lines to remove
    rmlines1 <- enddiv[removediv]
    rmlines2 <- enddiv[removediv] + 1
    rmlines3 <- enddiv[removediv] + 2
    # Do the removing
    x <- x[-c(rmlines1, rmlines2, rmlines3)]
  }

  x

})
```

```{r HTML, include = FALSE}
NOTE <- '<span style="background-color: yellow;">NOTE</span>'
# CHNOSZ functions
equilibrate_ <- '<code>equilibrate()</code>'
info_ <- '<code>info()</code>'
thermo.refs_ <- '<code>thermo.refs()</code>'
# Math stuff
logK <- "log&thinsp;<i>K</i>"
Hplus <- "H<sup>+</sup>"
HCO2_ <- "HCO<sub>2</sub><sup>−</sup>"
HCO3_ <- "HCO<sub>3</sub><sup>−</sup>"
O2 <- "O<sub>2</sub>"
S2 <- "S<sub>2</sub>"
log <- "log&thinsp;"
aHCO2_ <- "<i>a</i>HCO<sub>2</sub><sup>−</sup>"
aHCO3_ <- "<i>a</i>HCO<sub>3</sub><sup>−</sup>"
logfO2 <- "log&thinsp;<i>f</i>O<sub>2</sub>"
Ctot <- "C<sub>tot</sub>"
C3H5O2_ <- "C<sub>3</sub>H<sub>5</sub>O<sub>2</sub><sup>−</sup>"
a3HCO3_ <- "<i>a</i><sup>3<sub>HCO<sub>3</sub><sup>−</sup>"
aC3H5O2_ <- "<i>a</i>C<sub>3</sub>H<sub>5</sub>O<sub>2</sub><sup>−</sup>"
a2HCO3_ <- "<i>a</i><sup>2</sup>HCO<sub>3</sub><sup>−</sup>"
logCtot <- "log&thinsp;C<sub>tot</sub>"
CO2 <- "CO<sub>2</sub>"
H2O <- "H<sub>2</sub>O"
S3minus <- "S<sub>3</sub><sup>-</sup>"
H2S <- "H<sub>2</sub>S"
SO4__ <- "SO<sub>4</sub><sup>-2</sup>"
Kplus <- "K<sup>+</sup>"
Naplus <- "Na<sup>+</sup>"
Clminus <- "Cl<sup>-</sup>"
H2 <- "H<sub>2</sub>"
Tmax <- "<i>T</i><sub>max</sub>"
```

This vignette was compiled on `r Sys.Date()` with CHNOSZ version `r sessionInfo()$otherPkgs$CHNOSZ$Version`.

## How is CHNOSZ pronounced?

As one syllable that starts with an *sh* sound and [rhymes with *Oz*](https://en.wiktionary.org/wiki/Rhymes:English/%C9%92z).
CHNOSZ and [schnoz](https://en.wiktionary.org/wiki/schnozz) are homophones.

*Added on 2023-05-22.*

## 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 letters appear as the column names in the stoichiometric matrix of basis species for ionized proteins, which were the subject of JMD's PhD thesis.

OBIGT was originally an abbreviation for the OrganoBioGeoTherm software package written by John Donovan,
a Windows interface to SUPCRT92 that was first mentioned in print by @RH98.
That package incorporated a database that was used as the starting point 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 [@JOH92], but `subcrt()` is just a different name and is not limited to subcritical conditions.

I would like to acknowledge Jan Amend for the suggestion to use a different function name than SUPCRT
and for being possibly the first person to try to pronounce CHNOSZ while looking at the program's output.
That interaction at the 2007 Goldschmidt conference led me to use CHNOSZ as the name for the package.

*Added on 2025-06-05.*

## How should CHNOSZ be cited?

* This paper is the general reference for CHNOSZ: @Dic19.
* This paper describes diagrams with multiple metals: @Dic21b.
* This paper describes metastable equilibrium calculations for proteins: @Dic08.
* The [<span style="color:blue">*OBIGT thermodynamic database*</span>](OBIGT.html) represents the work of many researchers.
**If you publish results that depend on any of these data, please cite the primary sources.**
Use `r info_` to show the reference keys for particular species and `r thermo.refs_` to list the bibliographic details.
The following example shows the sources of data for aqueous alanine:
```{r alanine_refs, message = FALSE}
info(info("alanine"))[c("ref1", "ref2")]
thermo.refs(info("alanine"))
```

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

```{r PPM_refs, message = FALSE}
basis(c("pyrite", "pyrrhotite", "oxygen"))
sres <- subcrt("magnetite", 1)
info(sres$reaction$ispecies)[, 1:6]
thermo.refs(sres)
reset()
```

* Additional minerals from @HDNB78, that were available in SUPCRT92 but may conflict with the @Ber88 compilation, can be loaded from an optional database with `add.OBIGT("SUPCRT92")`. When using these data, it is appropriate to cite @HDNB78 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 [@JOH92] or optionally an implementation in R of the IAPWS-95 formulation [@WP02].
* 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 [@DLH06].
* The corresponding high-temperature properties are calculated using the @BB85 equations for minerals and the revised [@TH88;@SH88] Helgeson-Kirkham-Flowers [@HKF81] equations for aqueous species.
* The revised HKF equations are augmented with the Deep Earth Water (DEW) model [@SHA14] and estimates of parameters in the extended Debye-H&uuml;ckel equation [@MSS13] 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 [@Alb96], 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 [@AC93,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](multi-metal.html) 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&uuml;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: `r HCO2_` + 0.5 `r O2` $\rightleftharpoons$ `r HCO3_`.
The law of mass action (LMA) is <strong>`r logK` $=$ `r log`(`r aHCO3_` / `r aHCO2_`) $-$ 0.5 `r logfO2`</strong>.
The condition of equal activity is <font color="red">`r aHCO2_` $=$ `r aHCO3_`</font>.
Then, the LMA simplifies to <strong>`r logK` $=$ $-$ 0.5 `r logfO2`</strong>.
The total activity of C is given by <font color="blue">`r Ctot` $=$ `r aHCO2_` $+$ `r aHCO3_`</font>.
According to the LMA, `r logfO2` is a function only of `r logK`, so <font color="green">*d*`r logfO2`/*d*`r logCtot` $=$ 0</font>.
In other words, the position of the equal-activity boundary is independent of the value of `r Ctot`.

Next, consider a reaction between propanoate and bicarbonate: `r C3H5O2_` + <sup>7</sup>&frasl;<sub>2</sub> `r O2` $\rightleftharpoons$ 3 `r HCO3_` + 2 `r Hplus`.
The LMA is <strong>`r logK` $=$ `r log`(`r a3HCO3_` / `r aC3H5O2_`) $-$ pH $-$ <sup>7</sup>&frasl;<sub>2</sub> `r logfO2`</strong>.
The condition of equal activity is <font color="red">`r aC3H5O2_` $=$ `r aHCO3_`</font>.
Then, the LMA simplifies to <strong>`r logK` $=$ `r log``r a2HCO3_` $-$ pH $-$ <sup>7</sup>&frasl;<sub>2</sub> `r logfO2`</strong>.
The total activity of C is given by <font color="blue">`r Ctot` $=$ 3 `r aC3H5O2_` $+$ `r aHCO3_`</font>; combined with the condition of equal activity,
this gives <font color="blue">`r Ctot` $=$ 4 `r aHCO3_`</font>.
Substituting this into the LMA gives <strong>`r logK` $=$ `r log`(`r Ctot` / 4)<sup>2</sup> $-$ pH $-$ <sup>7</sup>&frasl;<sub>2</sub> `r logfO2`</strong>,
which can be rearranged to write `r logfO2` $=$ <sup>2</sup>&frasl;<sub>7</sub> (2 `r log``r Ctot` $-$ `r logK` $-$ `r log`16 $-$ pH).
It follows that <font color="green">*d*`r logfO2`/*d*`r logCtot` $=$ <sup>4</sup>&frasl;<sub>7</sub></font>,
and the position of the equal-activity boundary depends on `r Ctot`.

According to this analysis, increasing `r 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 <sup>8</sup>&frasl;<sub>7</sub> units on the `r logfO2` scale.
Because the reaction between bicarbonate and `r CO2` does not involve `r O2` (but rather `r H2O` and `r Hplus`),
the same effect should occur on the propanoate-`r CO2` equal-activity boundary.
The plots below, which are made using `r equilibrate_` for species in the Deep Earth Water (DEW) model, illustrate this effect.

<button id="B-DEW_Ctot" onclick="ToggleDiv('DEW_Ctot')">Show code</button>
<div id="D-DEW_Ctot" style="display: none">
```{r DEW_Ctot, eval = FALSE}
### 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()
```
</div>

```{r DEW_Ctot, echo = FALSE, message = FALSE, results = "hide", fig.width = 8, fig.height = 4, out.width = "100%", fig.align = "center", pngquant = pngquant, cache = TRUE}
```

*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`).

```text
               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 @PMW87.
The formula and thermodynamic properties of this pyrrhotite differ from those of FeS from @HDNB78, 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()`.

```{r pyrrhotite_values, message = FALSE}
# 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`.
```{r pyrrhotite_Ttr, message = FALSE}
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.
@PMW87 don't list *C~p~*° 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"`.
```{r pyrrhotite_Cp, results = "hide", message = FALSE}
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.
```{r pyrrhotite_info, results = "hide", collapse = TRUE}
info(info("pyrrhotite_new", "cr"))
info(info("pyrrhotite_new", "cr2"))
```

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 `r Ttr` 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.
```{r pyrrhotite_S, message = FALSE}
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`.
```{r pyrrhotite_D1_D2, message = FALSE, results = "hide"}
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.
```{r pyrrhotite_check_S, results = "hide"}
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.
```{r pyrrhotite_DG298_DH298, results = "hide", message = FALSE}
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 `r E.units()`/mol can probably be attributed to small differences in the entropies of the elements used by @PMW87 and in CHNOSZ.
We still get a message that the database value of *C~p~*° 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.
```{r pyrrhotite_info2, collapse = TRUE}
cr_parameters <- info(info("pyrrhotite_new", "cr"))
stopifnot(abs(check.GHS(cr_parameters)) < 1)
cr2_parameters <- info(info("pyrrhotite_new", "cr2"))
stopifnot(abs(check.GHS(cr2_parameters)) < 1)
```

For the curious, here are the parameter values:
```{r pyrrhotite_parameters}
cr_parameters
cr2_parameters
```

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 *C~p~*° are discontinuous at the transition temperature.

<button id="B-pyrrhotite_T" onclick="ToggleDiv('pyrrhotite_T')">Show code</button>
<div id="D-pyrrhotite_T" style="display: none">
```{r pyrrhotite_T, eval = FALSE}
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()
```
</div>
```{r pyrrhotite_T, echo = FALSE, message = FALSE, results = "hide", fig.width = 8, fig.height = 2.5, out.width = "100%", fig.align = "center", pngquant = pngquant}
```

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 (`r S3minus`)?

A `r logfO2`--pH plot for aqueous sulfur species including `r S3minus` was first presented by @PD11.
Later, @PD15 reported parameters in the revised HKF equations of state for `r S3minus`, which are available in OBIGT.

```{r trisulfur, eval = FALSE, echo = FALSE}
par(mfrow = c(1, 3))

## 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)

######## Plot 2: Modify Gibbs energy

oldG <- info(info("S3-"))$G
newG <- oldG - 12548
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()

######## Plot 3: Do it with DEW

T <- 700
P <- 10000 # 10000 bar = 1 GPa
oldwat <- water("DEW")
add.OBIGT("DEW")
info(species()$ispecies)
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))
ltext <- c(lTP, lS)
legend("topright", ltext, bty = "n", bg = "transparent")
title(quote("Deep Earth Water (DEW)"~"model"))
water(oldwat)
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 `r H2O` in 1 kg of solution.
   - Calculate molality (moles of S per kg of `r 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 `r O2`(gas), not `r O2`(aq), so the variable on the diagram is `r 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.

<button id="B-trisulfur-1" onclick="ToggleDiv('trisulfur-1')">Show code</button>
<div id="D-trisulfur-1" style="display: none">
```{r trisulfur, eval = FALSE, echo = 3:43}
```
</div>

### Why does the published diagram have a much larger stability field for `r S3minus`?

Let's calculate `r logK` for the reaction 2 `r H2S`(aq) + `r SO4__` + `r Hplus` = `r S3minus` + 0.75 `r O2`(gas) + 2.5 `r H2O`.

```{r trisulfur_logK, message = FALSE, echo = 1:3}
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)
fcalclogK <- formatC(calclogK, format = "f", digits = 1)
reflogK <- -9.6
dlogK <- calclogK[2] - reflogK
# Put in a test so that we don't get surprised by
# future database updates or changes to this vignette
stopifnot(round(dlogK, 4) == -4.4132)
```

By using the thermodynamic parameters for `r S3minus` in OBIGT that are taken from @PD15, `r logK` is calculated to be `r paste(paste(fcalclogK[1:3], collapse = ", "), fcalclogK[4], sep = ", and ")` at 300, 350, 400, and 450 °C and 5000 bar.
In contrast, ref. 22 of @PD11 lists `r reflogK` for `r logK` at 350 °C; this is `r round(-dlogK, 1)` log units higher than the calculated value of `r fcalclogK[2]`. 
This corresponds to a difference of Gibbs energy of -2.303 * 1.9872 * (350 + 273.15) * `r round(-dlogK, 1)` = `r formatC(-2.303 * 1.9872 * (350 + 273.15) * -dlogK, format = "f", digits = 0)` cal/mol.

In the code below, we use the difference of Gibbs energy to temporarily update the OBIGT entry for `r S3minus`.
Then, we make a new diagram that is more similar to that from @PD11.
Finally, we reset the OBIGT database so that the temporary parameters don't interfere with later calculations.

<button id="B-trisulfur-2" onclick="ToggleDiv('trisulfur-2')">Show code</button>
<div id="D-trisulfur-2" style="display: none">
```{r trisulfur, eval = FALSE, echo = 46:55}
```
</div>

### 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 `r info_` to see which species are affected by loading the DEW parameters; it turns out that `r SO4__` isn't.
Then, use similar commands as above to make the diagram.
At the end, reset the water model and OBIGT database.

<button id="B-trisulfur-DEW" onclick="ToggleDiv('trisulfur-DEW')">Show code</button>
<div id="D-trisulfur-DEW" style="display: none">
```{r trisulfur_DEW, message = FALSE, echo = 8:22, collapse = TRUE, fig.keep = "none"}
# The first four lines are stand-ins to make this block runnable and get the right output from info();
# the diagram actually shown in the vignette is made using the 'trisulfur' block above
b <- basis("CHNOS+")
s <- species(c("H2S", "HS-", "S3-", "SO2", "HSO4-", "SO4-2"))
res <- 10
wt_percent_S <- logm_S <- 0
######## End of stand-in code ########
T <- 700
P <- 10000 # 10000 bar = 1 GPa
oldwat <- water("DEW")
add.OBIGT("DEW")
info(species()$ispecies)[, 1:8]
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()
```
</div>

Here are the three plots that we made:

```{r trisulfur, echo = FALSE, message = FALSE, results = "hide", fig.width = 10, fig.height = 3.33, out.width = "100%", out.extra='class="full-width"', pngquant = pngquant, cache = TRUE}
```

*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_Ttr"`, then `T` is the `r Tmax` for the *C~p~* equation.

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

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

```{r pyrrhotite_polymorphs, collapse = TRUE}
subcrt("pyrrhotite", T = c(25, 150, 350), property = "G")$out
```

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 a phase transition temperature, Δ*G*° is set to NA at higher temperatures:**

```{r pyrite_limit}
subcrt("pyrite", T = seq(200, 1000, 200), P = 1)
```

This feature is intended to make it harder to obtain potentially unreliable results at temperatures where a mineral, or organic solid or liquid, is not stable.
If you want the extrapolated Δ*G*° above the listed phase transition temperature, then add `exceed.Ttr = TRUE` to the call to `subcrt()`.

OBIGT has a non-exhaustive list of temperatures of melting, decomposition, or other phase change, some of which were taken from SUPCRT92 while others were taken from @RH95.
These minerals are listed below:

```{r mineral_Ttr, collapse = TRUE}
file <- system.file("extdata/OBIGT/inorganic_cr.csv", package = "CHNOSZ")
dat <- read.csv(file)
dat$name[dat$model == "CGL_Ttr"]
```

<!-- 
     Use square brackets to suppress parentheses around citation 20231115
     https://stackoverflow.com/questions/64209134/r-markdown-suppress-parentheses-in-specific-citations
-->
OBIGT uses the decomposition temperature of covellite [780.5 K from @RH95] in contrast to the value of `r Tmax` from SUPCRT92 [1273 K, which is referenced to a \Cp equation described as "estimated" on p. 62 of @Kel60].
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 *C~p~* equation limit, extrapolated values of Δ*G*° are shown and a warning is produced:**

```{r muscovite_limit, message = FALSE}
add.OBIGT("SUPCRT92")
subcrt("muscovite", T = 850, P = 4500)
reset()
```

The warning 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 (`r Tmax`).
Similar to the behavior of SUPCRT92, CHNOSZ outputs Δ*G*° and other standard thermodynamic properties at temperatures higher than `r Tmax` despite the warning.

**4. Finally, if `T` is NA or 0, then no upper temerature limit is imposed by `subcrt()`.**

*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&ndash;muscovite&ndash;quartz (KMQ) and muscovite&ndash;kaolinite (MC) pH buffers are known as "sliding scale" buffers because they do not determine pH but rather the activity ratio of `r Kplus` to `r Hplus` [@HA05].
To add these buffers to a `r logfO2`&ndash;pH diagram in CHNOSZ, choose basis species that include Al<sup>+3</sup> (the least mobile element, which the reactions are balanced on), quartz (this is needed for the KMQ buffer), the mobile ions `r Kplus` and `r Hplus`, and the remaining elements in `r H2O` and `r O2`; `oxygen` denotes the gas in OBIGT.
The formation reactions for these minerals don't involve `r O2`, but it must be present so that the number of basis species equals the number of elements +1 (i.e. elements plus charge).

```{r KMQ_basis_species, message = FALSE}
basis(c("Al+3", "quartz", "K+", "H+", "H2O", "oxygen"))
species(c("kaolinite", "muscovite", "K-feldspar"))
```

We could go right ahead and make a `r logfO2`&ndash;pH diagram, but the implied assumption would be that the `r Kplus` activity is unity, which may not be valid.
Instead, we can obtain an independent estimate for `r Kplus` activity based on 1) the activity ratio of `r Naplus` to `r Kplus` for the reaction between albite and K-feldspar and 2) charge balance among `r Naplus`, `r Kplus`, and `r Clminus` for a given activity of the latter [@HC14].
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 `r 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 `r Clminus`.

```{r KMQ_m_K, message = FALSE}
# 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))
```

This calculation gives a molality of `r Kplus` that is lower than unity and accordingly makes the buffers less acidic [@HC14].
Now we can apply the calculated molality of `r Kplus` 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).
```{r KMQ_diagram, eval = FALSE, echo = 2:10}
par(mfrow = c(1, 2))
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)
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)
OBIGT()
```
```{r KMQ_diagram, message = FALSE, fig.width = 8, fig.height = 4, out.width = "100%", results = "hide", echo = FALSE}
```

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 `r H2` exceeds unity.
NOTE: Although the muscovite&ndash;kaolinite (MC) buffer was mentioned by @HC14 in the context of "clay-rich but feldspar-free sediments", this example uses the feldspathic Ab&ndash;Kfs reaction for calculating `r Kplus` 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., @Yar05].

The diagram in Fig. 4 of @HC14 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 @Ber88 and @SHD91.
```{r KMQ_refs, message = FALSE}
thermo.refs(species()$ispecies)
```

CHNOSZ doesn't implement the thermodynamic model for minerals from @HP98, which is one of the sources cited by @HC14.
If we use the thermodynamic parameters for minerals from @HDNB78 [these do not include the revisions for aluminosilicates described by @SHD91], 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 @HC14, but the MC buffer further away, so this still doesn't explain why we get a different result.

```{r KMQ_diagram, eval = FALSE, echo = 11:15}
```

*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 (FeS~2~) and pyrrhotite (FeS), one with H~2~S and the other with HS^-^:

1. FeS~2~ + H~2~O = FeS + 0.5 O~2~ + H~2~S
2. FeS~2~ + H~2~O = FeS + 0.5 O~2~ + HS^-^ + H^+^

If a pH 4 solution at 150 &deg;C has 0.001 mol/kg H~2~S, then raising the pH to 8 would give 0.001 mol/kg of HS^-^ and essentially no H~2~S.
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 H~2~S and HS^-^ in reactions 1 and 2 (the *constant activity* constraint), then we will get straight lines on a `r logfO2`&ndash;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 p*K*~a~ of H~2~S (about 6.5 at 150 &deg;C), the activities of H~2~S 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 `r logfO2` for these two reactions.

<button id="B-PyPo_logfO2" onclick="ToggleDiv('PyPo_logfO2')">Show code</button>
<div id="D-PyPo_logfO2" style="display: none">
```{r PyPo_logfO2, message = FALSE, results = "hide"}
# 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))))
```
</div>

At 150 &deg;C and the p*K*~a~ of H~2~S, we get `r logfO2` = `r round(logf_O2_1(log10(0.001)), 2)` for *a*<sub>H~2~S</sub> = *a*<sub>HS^-^</sub> = 0.001 but `r logfO2` = `r round(logf_O2_1(log10(0.0005)), 2)` for *a*<sub>H~2~S</sub> + *a*<sub>HS^-^</sub> = 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.

<button id="B-PyPo_plot" onclick="ToggleDiv('PyPo_plot')">Show code</button>
<div id="D-PyPo_plot" style="display: none">
```{r PyPo_plot, eval = FALSE}
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)
```
</div>

```{r PyPo_plot, echo = FALSE, message = FALSE, results = "hide", fig.width = 9, fig.height = 4.5, out.width = "100%", fig.align = "center", pngquant = pngquant, cache = TRUE}
```

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 `r logfO2`&ndash;pH diagrams with curved mineral stability lines.
An example of one is in Figure 5 of @CBLM00.
The code below makes a diagram for the minerals shown in that figure:

```{r Fe-S-O-C, message = FALSE, results = "hide", fig.width = 5, fig.height = 5, out.width = "60%", fig.align = "center", pngquant = pngquant, cache = TRUE}
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-", "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 @CBLM00.
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.*

## References
