Adding isotopes
ESBMTK support the use of isotopes for many processes and species out of the box. This is best demonstrated with an example. In the following we use tow reservoirs of equal size, and apply an exchange flux. The reservoirs have similar concentrations but their isotopic ratios are different (-20 mUr versus +20 mUr [VSMOV]. After 3 ky both reservoirs are isotopically homogeneous and have approached a \(\delta\)18O value of 0 mUr [VSMOV]. Note that this only required code changes on lines 17 and 23 where declare the initial delta values.
# This example is available as iso_1.py in ESBMTK Examples Repository
# https://github.com/uliw/ESBMTK-Examples/tree/main/Examples_from_the_manual
from esbmtk import (
Model, # the model class
Reservoir, # the reservoir class
ConnectionProperties, # the connection class
SourceProperties, # the source class
SinkProperties, # sink class
)
M = Model(
stop="3 kyr", # end time of model
max_timestep="100 yr", # upper limit of time step
element=["Oxygen"], # list of element definitions
)
Reservoir(
name="S_b", # box name
volume="50E16 m**3", # surface box volume
concentration={M.O2: "200 umol/l"}, # initial concentration
delta={M.O2: -20},
)
Reservoir(
name="D_b", # box name
volume="50E16 m**3", # deeb box volume
concentration={M.O2: "200 umol/l"}, # initial concentration
delta={M.O2: 20},
)
ConnectionProperties( # thermohaline downwelling
source=M.S_b, # source of flux
sink=M.D_b, # target of flux
ctype="scale_with_concentration",
scale="20 Sv",
id="downwelling"
)
ConnectionProperties( # thermohaline upwelling
source=M.D_b, # source of flux
sink=M.S_b, # target of flux
ctype="scale_with_concentration",
scale="20 Sv",
id="upwelling",
)
M.run(method="BDF")
ESBMTK 0.14.1.0.post1.dev11+g530dbbd.d20250125
Copyright (C) 2020 - 2025 Ulrich G.Wortmann
This program comes with ABSOLUTELY NO WARRANTY
For details see the LICENSE file
This is free software, and you are welcome to redistribute it
under certain conditions; See the LICENSE file for details.
nfev=101, njev=1, nlu=22
status=0
message=The solver successfully reached the end of the integration interval.
Execution took 0.00 cpu seconds, wt = 0.00
This run used 0.29 Gbytes of memory
Plotting with
M.plot([M.S_b.O2, M.D_b.O2], fn="iso_1.png")
results in the following output:
It takes about 3 kys to equilibrate both ocean boxes.
In the next example, we use two reservoirs of equal size, and equal isotope ratios. This time, we declare that the transport from the D_b to S_b involves a fractionation of 5 mUr (line 38). Note that the fractionation has to be provided as the enrichment factor \(\epsilon\) and not as the fractionation factor \(\alpha\), where
Similarly to the first example, the system reaches equilibrium after about 3 kyrs. The above is then easily adapted to model, e.g., isotope fractionation during the evaporation of water.
# This example is available as iso_3.py in ESBMTK Examples Repository
# https://github.com/uliw/ESBMTK-Examples/tree/main/Examples_from_the_manual
from esbmtk import (
ConnectionProperties, # the connection class
Model, # the model class
Reservoir, # the reservoir class
)
M = Model(
stop="3 kyr", # end time of model
max_timestep="100 yr", # upper limit of time step
element=["Oxygen"], # list of element definitions
)
Reservoir(
name="S_b", # box name
volume="50E16 m**3", # surface box volume
concentration={M.O2: "200 umol/l"}, # initial concentration
delta={M.O2: 0},
)
Reservoir(
name="D_b", # box name
volume="50E16 m**3", # deeb box volume
concentration={M.O2: "200 umol/l"}, # initial concentration
delta={M.O2: 0},
)
ConnectionProperties( # thermohaline downwelling
source=M.S_b, # source of flux
sink=M.D_b, # target of flux
ctype="scale_with_concentration",
scale="20 Sv",
id="downwelling",
)
ConnectionProperties( # thermohaline upwelling
source=M.D_b, # source of flux
sink=M.S_b, # target of flux
ctype="scale_with_concentration",
scale="20 Sv",
id="upwelling",
epsilon=5, # mUr
)
M.run()
Plotting with
M.plot([M.S_b.O2, M.D_b.O2], fn="iso_3.png")
results in the following output:
It takes about 3 kys to fully fractionate both ocean boxes.
Isotope and Signals
While signals can be used to multiply or add, e.g., a given flux, care must be taken when signals also modify isotope values, since there is currently no universal way how to interpret the delta value. As such, the signal delta value is ignored for multiplication type signals. For addition type signals, ESBMTK will not perform isotope mixing calculations. It is thus best, to add signals not o an existing flux, but to create a new flux with a value of zero, and assign a signal to this zero-value flux.
Isotopes and Gas Exchange
In the last example, we model the oxygen isotope fractionation during the air-sea gas exchange. This involves kinetic fractionation during degassing, and equilibrium fractionation during gas solution. At present, ESBMTK
uses the equilibrium fractionation factor parametrization of Benson and Krause, 1984 (http://dx.doi.org/10.4319/lo.1984.29.3.0620)
and the kinetic fraction factor of Knox and McElroy 1984 (http://dx.doi.org/10.1029/JD089iD03p04629)
The fractionation factors for CO2follow the parametrization of Zeebe 2012 (http://dx.doi.org/10.1029/JD089iD03p04629)
Note however, that in order to calculate gas-exchange fluxes, we need to declare the surface area, of S_b (line 22), as well as the pressure, temperature and salinity conditions (line 25). The base model code looks like this:
# This example is available as iso_4.py in ESBMTK Examples Repository
# https://github.com/uliw/ESBMTK-Examples/tree/main/Examples_from_the_manual
from esbmtk import (
Model, # the model class
Reservoir, # the reservoir class
ConnectionProperties, # the connection class
GasReservoir, # sink class
Species2Species,
)
M = Model(
stop="1 yr", # end time of model
max_timestep="1 month", # upper limit of time step
element=["Oxygen"], # list of element definitions
)
GasReservoir(
name="O2_At",
species=M.O2,
species_ppm="21 percent",
delta=0,
)
Reservoir(
name="S_b", # box name
geometry={"area": "2.85e14m**2", "volume": "3E16 m**3"},
concentration={M.O2: "200 umol/l"}, # initial concentration
delta={M.O2: 0},
seawater_parameters={"T": 21.5, "P": 1, "S": 35},
)
Presently ESBMTK does not support the automatic matching of liquid species to gas reservoirs. Therefore we have to declare each gas-exchange flux individually using the Species2Species connection type:
Species2Species( # Ocean to atmosphere F8
source=M.O2_At, # Reservoir Species
sink=M.S_b.O2, # Reservoir Species
species=M.O2,
piston_velocity="4.8 m/d",
ctype="gasexchange",
id="ex_O2",
)
M.run(method="BDF")
In the following example the atmosphere equilibrates withe the surface ocean within a couple of months. plotting with
M.plot([M.S_b.O2, M.O2_At], fn="iso_4.png")
results in the following output:
It takes about 3 kys to fully fractionate both ocean boxes.
Using user supplied parametrizations and custom species for gas exchange reactions.
Currently ESBMTK provides useful defaults for CO2and O2, but other gas species (or parametrizations) can be easily added. Users can provide their own fractionation factor parametrizations by providing their own values to the connection object. To make simple example that considers fixed values:
Species2Species( # Ocean to atmosphere F8
source=M.O2_At, # Reservoir Species
sink=M.S_b.O2, # Reservoir Species
species=M.O2,
solubility="1098 mmol/(m^3 * atm)", # solubility
a_u=0.9972, # kinetic fractionation factor
a_dg=1.00073, # equilibrium fractionation factor
piston_velocity="4.8 m/d",
ctype="gasexchange",
id="ex_O2",
)
Note that in the above case, the ESBMTK will automatically correct the
solubility value for water vapor pressure. For carbon there is another variable
that defines the fractionation between CO2aq and HCO3-(a_db the
fractionation between HCO3-and CO32-) is currently not considered by ESBMTK.