"""
Conversions of fCO2 - pCO2
--------------------------
"""
[docs]def fCO2_to_pCO2(fCO2SW_uatm, tempSW_C, pres_hPa=1013.25, tempEQ_C=None, checks=True):
"""Convert fCO2 to pCO2 in sea water.
If equilibrator temperature is provided, we get a simple approximate for
equilibrator :math:`xCO_2` that allows for the virial expansion to be
calculated more accurately. If not, then a simple approximation is good
enough. See the examples for the differences.
.. math::
pCO_2^{sw} = fCO_2^{sw} \\div virial(xCO_2^{eq})
where :math:`xCO_2^{eq} = fCO_2^{sw} \\times \\Delta T^{(sw - eq)} \\div P^{eq}`
Args:
fCO2SW_uatm (array): seawater fugacity of CO2 in micro atmospheres
tempSW_C (array): sea water temperature in degrees C
pres_hPa (array, optional): equilibrator pressure in hecto Pascals. Defaults to 1013.25.
tempEQ_C (array, optional): equilibrator temperature in degrees C. Defaults to None.
Returns:
array:
partial pressure of CO2 in seawater
Note:
In FluxEngine, they account fully solve for the original xCO2 that is used
in the calculation of the virial expansion. I use the first estimate of
xCO2 (based on fCO2 rather than pCO2). The difference between the two
approaches is so small that it is not significant to be concerned. Their
correction is more precise, but the difference between their iterative
correction and our approximation is on the order of 1e-14 atm (1e-8 uatm).
Examples:
>>> fCO2_to_pCO2(380, 8)
381.50806485658234
>>> fCO2_to_pCO2(380, 8, pres_hPa=985)
381.4659553134281
>>> fCO2_to_pCO2(380, 8, pres_hPa=985, tempEQ_C=14)
381.466027968504
"""
from . import auxiliary_equations as eqs
# if equilibrator inputs are None, tempEQ=tempSW
if tempEQ_C is None:
tempEQ_was_None = True
tempEQ_C = tempSW_C
else:
tempEQ_was_None = False
# standardise the inputs and convert units
fCO2sw = fCO2SW_uatm * 1e-6
Tsw = tempSW_C + 273.15
Teq = tempEQ_C + 273.15
Peq = pres_hPa / 1013.25
# calculate the CO2 diff due to equilibrator and seawater temperatures
# if statement is there to save a bit of time
if tempEQ_was_None:
dT = 1.0
else:
dT = eqs.temperature_correction(Tsw, Teq)
# a best estimate of xCO2 - this is an approximation
# one would have to use pCO2 / Peq to get real xCO2
# Not getting the exact equilibrator xCO2
xCO2eq = fCO2sw * dT / Peq
pCO2SW = fCO2sw / virial_coeff(Tsw, Peq, xCO2eq, checks=checks)
pCO2SW_uatm = pCO2SW * 1e6
return pCO2SW_uatm
[docs]def pCO2_to_fCO2(pCO2SW_uatm, tempSW_C, pres_hPa=None, tempEQ_C=None, checks=False):
"""Convert pCO2 to fCO2 in sea water to account for non-ideal behaviour of CO2
If equilibrator temperature is provided, we get a simple approximate for
equilibrator :math:`xCO_2` that allows for the virial expansion to be
calculated more accurately. If not, then a simple approximation is probably
good enough. See the examples for the differences.
.. math::
pCO_2^{sw} = fCO_2^{sw} \\times virial(xCO_2^{eq})
where :math:`xCO_2^{eq} = fCO_2^{sw} \\times \\Delta T^{(sw - eq)} \\div P^{eq}`
Args:
fCO2SW_uatm (array): seawater fugacity of CO2 in micro atmospheres
tempSW_C (array): sea water temperature in degrees C
pres_hPa (array, optional): equilibrator pressure in hecto Pascals. Defaults to 1013.25.
tempEQ_C (array, optional): equilibrator temperature in degrees C. Defaults to None.
Returns:
array: fugacity of CO2 in seawater
Note:
In FluxEngine, they account for the change in xCO2. This error is so small
that it is not significant to be concerned about it. Their correction is
more precise, but the difference between their iterative correction and our
approximation is less than 1e-14 atm (or 1e-8 uatm).
Examples:
>>> pCO2_to_fCO2(380, 8)
378.49789637942064
>>> pCO2_to_fCO2(380, 8, pres_hPa=985)
378.53967828231225
>>> pCO2_to_fCO2(380, 8, pres_hPa=985, tempEQ_C=14)
378.53960618459695
"""
from . import auxiliary_equations as eqs
# if equilibrator inputs are None then make defaults Patm=1, tempEQ=tempSW
if tempEQ_C is None:
tempEQ_C = tempSW_C
if pres_hPa is None:
pres_hPa = 1013.25
# standardise the inputs and convert units
pCO2sw = pCO2SW_uatm * 1e-6
Tsw = tempSW_C + 273.15
Teq = tempEQ_C + 273.15
Peq = pres_hPa / 1013.25
# calculate the CO2 diff due to equilibrator and seawater temperatures
dT = eqs.temperature_correction(Tsw, Teq)
# a best estimate of xCO2 - this is an approximation
# one would have to use pCO2 / Peq to get real xCO2
xCO2eq = pCO2sw * dT / Peq
fCO2sw = pCO2sw * virial_coeff(Tsw, Peq, xCO2eq, checks=checks)
fCO2sw_uatm = fCO2sw * 1e6
return fCO2sw_uatm
[docs]def virial_coeff(temp_K, pres_atm, xCO2_mol=None, checks=False):
"""
Calculate the ideal gas correction factor for converting pCO2 to fCO2.
Based on the Lewis and Wallace 1998 Correction.
Args:
temp_K (array): temperature in degrees Kelvin
pres_atm (array): atmospheric pressure in atmospheres
xCO2_mol (array, optional): mole fraction of CO2, can
also be p/fCO2 if xCO2 not available. Can also be None
which makes a small difference. See examples.
Returns:
array: the factor to multiply/divide with pCO2/fCO2. Unitless
.. math::
fCO_2 = pCO_2 \\times \\text{virial expansion}
pCO_2 = fCO_2 \\div \\text{virial expansion}
Examples:
From Dickson et al. (2007)
>>> 350 * virial_coeff(298.15, 1) # CO2 [uatm] * correction factor
348.8836492182758
References:
Weiss, R. (1974). Carbon dioxide in water and seawater: the solubility of a
non-ideal gas. Marine Chemistry, 2(3), 203–215.
https://doi.org/10.1016/0304-4203(74)90015-2
Compared with the Seacarb package in R
"""
from numpy import array, exp, nanmedian
if checks:
if nanmedian(temp_K) < 270:
raise ValueError('Temperature is not in Kelvin')
if nanmedian(pres_atm) > 10:
raise ValueError('Pressure is not in atmospheres')
T = temp_K
P = pres_atm
C = xCO2_mol
R = 82.057 # gas constant for ATM
# B is the virial coefficient for pure CO2
B = -1636.75 + 12.0408 * T - 0.0327957 * T ** 2 + 3.16528e-5 * T ** 3
# d is the virial coefficient for CO2 in air
d = 57.7 - 0.118 * T
# "x2" term often neglected (assumed = 1) in applications of Weiss's
# (1974) equation 9
if xCO2_mol is not None:
x2 = (1 - C) ** 2
else:
x2 = 1
ve = exp(P * (B + 2 * x2 * d) / (R * T))
return ve