Code Monkey home page Code Monkey logo

cbsyst's Introduction

CBsyst

A Python module for calculating seawater carbon and boron chemistry.

This will be particularly useful for anyone thinking about oceans in the distant past, when Mg and Ca concentrations were different. I use Mathis Hain's MyAMI model to adjust speciation constants for Mg and Ca concentration.

Tested in the modern ocean against GLODAPv2 data (see below). Performs as well as Matlab CO2SYS.

Work in Progress:

If anyone wants to help with any of this, please do contribute! A full list of bite-sized tasks that need doing is available on the Issues page.

Acknowledgement

The development of cbsyst has been greatly aided by CO2SYS, and the Matlab conversion of CO2SYS. In particular, these programs represent a gargantuan effort to find the most appropriate coefficient formulations and parameterisations from typo-prone literature. CO2SYS has also provided an invaluable benchmark throughout development.

Data Comparison

I have used the GLODAPv2 data set to test how well cbsyst works with modern seawater.

Method:

Import the entire GLODAPv2 data set, remove all data where flag != 2 (2 = good data), and exclude all rows that don't have all of (salinity, temperature, pressure, tco2, talk, phosphate, silicate and phtsinsitutp) - i.e. salinity, temperature, pressure, nutrients and all three measured carbonate parameters. The resulting dataset contains 79,896 bottle samples. The code used to process the raw GLODAPv2 data is available here.

Next, calculate the carbonate system from sets of two of the measured carbonate parameters, and compare the calculated third parameter to the measured third parameter (i.e. calculate Alkalinity from pH and DIC, then compared calculated vs. measured Alkalinities). The code for making these comparison plots is here.

Results:

Calculated pH (from DIC and Alkalinity) is offset from measured values by -0.00061 (-0.029/+0.029). Calculated vs Measured pH

Calculated Alkalinity (from pH and DIC) is offset from measured values by 0.23 (-12/+11) umol/kg. Calculated vs Measured TA

Calculated DIC (from pH and Alkalinity) is offset from measured values by -0.22 (-11/+11) umol/kg. Calculated vs Measured DIC

Reported statistics are median ±95% confidence intervals extracted from the residuals (n = 79,896).

Data are idential to within rouding errors as values calculated by Matlab CO2SYS (v1.1).

Conclusions:

cbsyst does a good job of fitting the GLODAPv2 dataset!

Technical Details

Constants

Constants calculated by an adaptation of Mathis Hain's MyAMI model. The original MyAMI code is available on GitHub. A stripped-down version of MyAMI is packaged with cbsyst. It has been modified to make it faster (by vectorising) and more 'Pythonic'. All the Matlab interface code has been removed.

Constants not provided by MyAMI (KP1, KP2, KP3, KSi, KF) are formulated following Dickson, Sabine & Christian's (2007) 'Guide to best practices for ocean CO2 measurements.'.

Pressure corrections are applied to the calculated constants following Eqns. 38-40 of Millero et al (2007), using (typo-corrected) constants in their Table 5. All constants are on the pH Total scale.

Calculations

Speciation calculations follow Zeebe and Wolf-Gladrow (2001). Carbon speciation calculations are described in Appendix B, except where Alkalinity is involved, in which cases the formulations of Ernie Lewis' CO2SYS are used. Boron speciation calculations in Eqns. 3.4.43 - 3.4.46.

Boron isotopes are calculated in terms of fractional abundances instead of delta values, as outlines here. Delta values can be provided as an input, and are given as an output.

Installation

Requires Python 3.5+. Does not work in 2.7. Sorry.

PyPi

pip install cbsyst

Conda-Forge

conda install cbsyst -c conda-forge

Example Usage

import cbsyst as cb
import numpy as np

# Create pH master variable for demo
pH = np.linspace(7,11,100)  # pH on Total scale

# Example Usage
# -------------
# The following functions can be used to calculate the
# speciation of C and B in seawater, and the isotope
# fractionation of B, given minimal input parameters.
#
# See the docstring for each function for info on
# required minimal parameters.

# Carbon system only
Csw = cb.Csys(pHtot=pH, DIC=2000.)

# Boron system only
Bsw = cb.Bsys(pHtot=pH, BT=433., dBT=39.5)

# Carbon and Boron systems
CBsw = cb.CBsys(pHtot=pH, DIC=2000., BT=433., dBT=39.5)

# NOTE:
# At present, each function call can only be used to
# calculate a single minimal-parameter combination -
# i.e. you can't pass it multiple arrays of parameters
# with different combinations of parameters, as in
# the Matlab CO2SYS code.

# Example Output
# --------------
# The functions return a Bunch (modified dict with '.' 
# attribute access) containing all system parameters
# and constants.
#
# Output for a single input condition shown for clarity:

out = cb.CBsys(pHtot=8.1, DIC=2000., BT=433., dBT=39.5)
out

>>> {'ABO3': array([ 0.80882931]),
     'ABO4': array([ 0.80463763]),
     'ABT': array([ 0.80781778]),
     'BO3': array([ 328.50895695]),
     'BO4': array([ 104.49104305]),
     'BT': array([ 433.]),
     'CO2': array([ 9.7861814]),
     'CO3': array([ 238.511253]),
     'Ca': array([ 0.0102821]),
     'DIC': array([ 2000.]),
     'H': array([  7.94328235e-09]),
     'HCO3': array([ 1751.7025656]),
     'Ks': {'K0': array([ 0.02839188]),
      'K1': array([  1.42182814e-06]),
      'K2': array([  1.08155475e-09]),
      'KB': array([  2.52657299e-09]),
      'KS': array([ 0.10030207]),
      'KW': array([  6.06386369e-14]),
      'KspA': array([  6.48175907e-07]),
      'KspC': array([  4.27235093e-07])},
     'Mg': array([ 0.0528171]),
     'S': array([ 35.]),
     'T': array([ 25.]),
     'TA': array([ 2333.21612227]),
     'alphaB': array([ 1.02725]),
     'dBO3': array([ 46.30877684]),
     'dBO4': array([ 18.55320208]),
     'dBT': array([ 39.5]),
     'deltas': True,
     'fCO2': array([ 344.68238018]),
     'pCO2': array([ 345.78871573]),
     'pHtot': array([ 8.1]),
     'pdict': None}

# All of the calculated output arrays will be the same length as the longest
# input array.

# Access individual parameters by:
out.CO3

>>> array([ 238.511253])

# Output data for external use:
df = cb.data_out(out, 'example_export.csv')

# This returns a pandas.DataFrame object with all C and B parameters.
# It also saves the data to the specified file. The extension of the
# file determined the format it is saved in (see data_out docstring).

Technical Note: Whats is a Bunch?

For code readability and convenience, I've used Bunch objects instead of traditional dicts. A Bunch is a modification of a dict, which allows attribute access via the dot (.) operator. Apart from that, it works exactly like a normal dict (all the usual methods are available transparrently).

Example:

from cbsyst.helpers import Bunch

# Make a bunch
bun = Bunch({'a': 1,
             'b': 2})

# Access items of bunch...
# as a dict:
bun['a']

>>> 1

# as a Bunch:
bun.a

>>> 1

cbsyst's People

Contributors

danielgaskell avatar douglascoenen avatar ocefpaf avatar oscarbranson avatar rossidae avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cbsyst's Issues

Failure in kgen dependency when temperature/salinity/pressure are not specified

cbsyst errors out when some inputs are set to None due to a bug in the current version of Kgen.

Steps to reproduce:
Install cbsyst

python3.10 -m venv .environment
source .environment/bin/activate
pip install cbsyst

Run a test

python
from cbsyst import Csys

result = Csys(pHtot=8.0,DIC=2000/1e6,unit="mol") // Produces error in kgen

Alternative Constants?

Would other constants be useful?

At present, there are no options for different constants in cbsyst. Do people want/need choices?

Request constants in the comments below. If they get traction (i.e. lots of +1's), we'll implement them.

At present:

Constants as a function of Temperature and Salinity:

Pressure Corrections
All pressure correction parameters from Millero (2007), with modifications:

  • TYPO: Third parameter for KB was positive, should have been negative
  • TYPO: All 'b' parameters were missing a factor of 10-3
  • TYPO: KW parameters in original were for fresh water - replaced with seawater values from Millero (1979)

Conserved Seawater Compositions:
As a function of salinity:

Temperature sensitivity of Ks?

Suspect that slight negative trend in DIC predicted from TA and pH could be because of incorrect K temperature sensitivity.

Check this!

Circular import

Dear developers,

thank you for your help and your work!
I have following issue, when I am running the script(s).

partially initialized module 'cbsyst' has no attribute 'Bsys' (most likely due to a circular import)

Do you have any clou, how to solve that problem?

best regards, Matthias

Separate T/S/P input & output conditions.

Currently, input and output conditions are the same.
It would be good to be able to separate input/output T, S, P conditions.

This should be relatively simple and inexpensive, and involve a second call to MyAMI_get_Ks and re-calculating the final speciations.
Suggest wrapping this in a separate function which takes Ks, and outputs all conditions from a single parameter combination (e.g. DIC and H for Csys).

Fails under high pH + high TA

CO2 can become negative under high pH, high TA:

cb.Csys(pHtot=10.5, TA=2300).CO2

# array([-9.01237887e-06])

Csys hangs if pH is too high:

cb.Csys(pHtot=11, TA=2300)

# does not run

Incorrect calculated variables

Current vectorised fsolve calls in carbon_fns produce incorrect values when multiple parameters are passed. Identified from Luecker et al (2000) test data. Unclear why.

Solution:

  • Cast all inputs into ndarray and iterate over rows of inputs. This gives correct results, but is slower.
  • When to do this?
    • At [N]sys level, after Ks calculation? Brings problems with identification of None params?
    • But these problems need to be fixed anyway, because at the moment doesn't allow for simultaneous calculation of different input calculations.

Vectorising

A lot of the solvers in carbon_fns.py would be sped up by vectorising.
At the moment, most serially apply zero_finder functions to solve for H.

The following functions in cbsyst/carbon_fns.py need to be vecotrised:

  • CO2SYS-style zero finder for HCO3_TA (case 11)
  • CO2SYS-style zero finder for CO3_TA (case 13)
  • Vectorise CO2_HCO3 (case 2)
  • Vectorise CO2_CO3 (case 3)
  • Vectorise CO2_DIC (case 5)
  • Vectorise HCO3_CO3 (case 10)
  • Vectorise HCO3_DIC (case 12)
  • Vectorise CO3_DIC (case 14)

Python 2.7 Compatability

This is low priority but would be nice.
Current incompatibility stems from repeated use of 'Extended Tuple Unpacking' (using *) in numerous function inputs throughout.
Can be solved with a generator wrapper (as described here).

  • Replace all instances of Extended Tuple Unpacking with generators.
  • Make import structure python 2 & 3 compatible.

Note: To run tests in python 2.x, you need to remove the python 2 incompatability warning in setup.py

Boron fractionation factor (alpha_B) ignored when used as input

In the list of input from the Bsys or CBsys functions, the alphaB parametre can be manually typed. However, this parametre is never really used. The ps Bunch object is automatically updated using the alphaB_calc function (dependant on temperature, based on the Hoenisch book) and simply ignores the alphaB parametre (i.e. cbsyst.py, line 692 and 1037).
Would it be possible to make a simple statement checking if the alphaB is given, and if not calculate it?

Error when using Bsys function

Hello,
Firstly, thank you very much for this great work! I have quickly compared the outputs to pyCO2sys and not only is a near 1:1, it is also about 100 times faster! Amazing!

However, I ran into a small issue when trying to execute the given example in the README.
cbsyst.Csys and cbsyst.CBsys appear to run just fine, but when I try to run the following from you example:

  import cbsyst as cb
  import numpy as np

  pH = np.linspace(7,11,100)
  Bsw = cb.Bsys(pHtot=pH, BT=433., dBT=39.5)

It gives me the following error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.8/dist-packages/cbsyst/cbsyst.py", line 324, in Bsys
    ps.update(ABsys(pdict=ps))
  File "/usr/local/lib/python3.8/dist-packages/cbsyst/cbsyst.py", line 435, in ABsys
    ps.alphaB = alphaB_calc(ps.T)
AttributeError: 'Bunch' object has no attribute 'T'

So I went in the ABsys function and printed the Bunch object just before this line and found that there was indeed no attribute "T", but instead there was "T_in". So if I change the line from what it was to:

ps.alphaB = alphaB_calc(ps.T_in)

It works, but then the same kind of error arises when hitting this loop:

for k in ['ABO3', 'ABO4', 'ABT', 'Ca',
              'H', 'Mg', 'S', 'T', 'alphaB',
              'dBO3', 'dBO4', 'dBT', 'pHtot']:
        if not isinstance(ps[k], np.ndarray):
            ps[k] = np.array(ps[k], ndmin=1)

As in the current Bunch, there is no T and S, but T_in and S_in. So if you change them to give:

for k in ['ABO3', 'ABO4', 'ABT', 'Ca',
              'H', 'Mg', 'S_in', 'T_in', 'alphaB',
              'dBO3', 'dBO4', 'dBT', 'pHtot']:
        if not isinstance(ps[k], np.ndarray):
            ps[k] = np.array(ps[k], ndmin=1)

Then it works flawlessly.
I do not know if it wise to change it like this, because I do not know how you treat the "in" and "out" of the physical properties of water samples. That is why I posted it here.

By the way, I run python 3.8.5.

Thanks,
Douglas

Parameter I/O

This is shelved for the moment - revisit later if it becomes an issue

At the moment, the Xsys functions can only take single combinations of input parameters.
It might be useful to allow for the calculation of multiple combinations of input combinations at once (a la CO2SYS.m).
However, algorithms currently identify parameters to be calculated by whether they are 'None' or not, so this would require a major re-tooling.

I reckon the effort involved here would be much greater than the inconvenience for the user having to run the function twice for different sets of input parameters.
How often do people actually use the ability of CO2SYS.m to take more than one combination of input parameters?

MyAMI Inconsistencies

Clarification

cbsyst should not be used to calculate C system at different [Mg] and [Ca] conditions until this issue is resolved. Modern ocean calculations are unaffected by this problem.

Issue

Output of MyAMI_K_calc is incorrect at non-ambient [Ca] and [Mg] (c.v. sensitivies listed in Hain et al, 2015).

This is an underlying issue in MyAMI. Hain's original code produces different K values when using PyMyAMI.m compared to MyAMI_V1.py.

Main difference in code is that PyMyAMI.m calculates correction factor for experimental condition directly, while MyAMI_V1.py calculates a grid of corrected Ks at different (T,S) conditions, fits a K_conditional function to it, and then returns parameters to be used for K calculation across a temperature range. Uncertain why these produce different results.

Resolution

  • Contact Hain
  • Either: Implement direct experimental correction method (as perPyMyAMI.m), or fix grid calculation method in MyAMI_K_calc function.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.