Skip to content

(0.21.0) Fix the pH scales of the CarbonChemistry outputs - #457

Merged
ali-ramadhan merged 2 commits into
mainfrom
ali/fix-ph-scales
Oct 27, 2026
Merged

ali-ramadhan merged 2 commits into
mainfrom
ali/fix-ph-scales

Conversation

@ali-ramadhan

Copy link
Copy Markdown
Collaborator

Closes #455.

The three pH scales

In seawater, some hydrogen ions are free and some are bound in bisulfate (HSO₄⁻) and hydrogen fluoride (HF). The three pH scales differ in which of them they count:

scale counts the H⁺ that is output
free free Val(:pHᶠ)
total free or in HSO₄⁻ Val(:pHᵗ)
seawater free, in HSO₄⁻, or in HF Val(:pHˢ)

Counting more ions gives a larger [H⁺] and so a lower pH. The free pH is 0.04–0.13 above the total pH (more in warm, salty water), and the seawater pH is about 0.01 below it.

What was wrong

CarbonChemistry solves for the total [H⁺]. Its equilibrium constants (e.g. K₁ and K₂ from Lueker et al. (2000), K_B from Dickson (1990)) are total-scale constants, following Dickson et al. (2007). The pH outputs, however, treated that [H⁺] as the free one:

  • Val(:pHᶠ) returned -log10(H), which is the total pH.
  • Val(:pHᵗ) added [HSO₄⁻] to an [H⁺] that already includes it. HSO₄⁻ was counted twice, so the pH was 0.04–0.13 too low.
  • Val(:pHˢ) counted HSO₄⁻ twice too.
  • A pH = input was documented as the free pH, but it has always been used as the total pH.

The fix

Treat the solved [H⁺] as the total [H⁺] in all three outputs:

pHᵗ = -log10(H)                       # H is already the total [H⁺]
pHᶠ = -log10(H / (1 + sulfate / KS))  # take out the H⁺ in HSO₄⁻
pHˢ = -log10(H + HF)                  # add the H⁺ in HF

These use the same free [H⁺] and [HF] as the alkalinity balance in alkalinity_residual.jl. The docstrings and docs now say that a pH = input is the total pH, and the docs explain the three scales.

What this changes for users

  1. Val(:pHᵗ) now returns exactly what Val(:pHᶠ) used to return.
  2. Val(:pHᶠ) now returns the actual free pH, which is 0.04–0.13 higher.
  3. Observational products like GLODAP and CODAP-NA report the total pH, so Val(:pHᵗ) is usually the one to compare against.

Val(:pHᶠ) now returns a different number, so this is a breaking change.

fCO₂, pCO₂, Val(:CO₂) (and so the air–sea CO₂ flux) and calcium_carbonate_saturation use [H⁺] directly, so they don't change.

CarbonChemistry solves for the total-scale [H⁺] (its K₁, K₂, K_B, ...
are total-scale constants), but its pH outputs treated it as the free
[H⁺]: `Val(:pHᶠ)` returned the total pH, and `Val(:pHᵗ)` and
`Val(:pHˢ)` added [HSO₄⁻] to an [H⁺] that already includes it, so they
were 0.04 to 0.13 too low. A `pH` input was documented as the free pH
but has always been used as the total pH.

The outputs now treat [H⁺] as the total [H⁺]: `Val(:pHᵗ)` is
-log10(H), `Val(:pHᶠ)` takes out the H⁺ in HSO₄⁻, H / (1 + S_T / K_S),
and `Val(:pHˢ)` adds [HF], using the same free [H⁺] and [HF] as the
alkalinity balance. `Val(:pHᵗ)` returns exactly what `Val(:pHᶠ)` used
to, so the CODAP-NA test, the GLODAP validation and the MARBL test,
which used `Val(:pHᶠ)` as the total pH, now use `Val(:pHᵗ)`. The
docstrings and docs now say a `pH` input is the total pH.

Adds a "pH scales" testset: the free and total pH match PyCO2SYS with
the same constants, the seawater pH is the total pH plus [HF], a total
pH input reproduces the fCO₂ and calcite saturation from DIC and Alk,
and the outputs agree inside a kernel on the architecture. The CODAP-NA
mean pH error check is now two-sided.

Fixes #455

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ali-ramadhan
ali-ramadhan requested a review from jagoosw October 26, 2026 20:21
@ali-ramadhan ali-ramadhan changed the title Fix the pH scales of the CarbonChemistry outputs (0.21.0) Fix the pH scales of the CarbonChemistry outputs Oct 26, 2026
`Val(:pHᶠ)` now returns the free pH rather than the total pH (#455).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@jagoosw jagoosw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@ali-ramadhan

Copy link
Copy Markdown
Collaborator Author

Thanks for the quick review @jagoosw! I'll wait a bit until a few more PRs are merged before tagging v0.21.0 if that's okay. Hoping to merge in the customer replicate tracer names (+ associated bug fixes).

@ali-ramadhan
ali-ramadhan merged commit d37dcde into main Oct 27, 2026
14 checks passed
@ali-ramadhan
ali-ramadhan deleted the ali/fix-ph-scales branch October 27, 2026 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CarbonChemistry: Val(:pHᵗ) and Val(:pHˢ) convert to the total scale twice, and Val(:pHᶠ) returns the total pH

2 participants