Creating Carbon Dioxide Removal (CDR) Forcing#

This notebook demonstrates how to create Carbon Dioxide Removal (CDR) forcing for a ROMS-MARBL simulation. CDR forcing involves specifying one or more releases within a single ROMS run. Releases carry an identifer (tracer_set) that determine which BGC or CDR coupled model (if any) applies to them inside of ROMS.

We will walk through the following steps:

  • Defining a CDR release

  • Creating CDR forcing from one or more releases

  • Visualizing the CDR forcing

  • Saving CDR forcing to NetCDF and YAML

  • Reconstructing CDR forcing from a YAML file

Creating a Release#

CDR forcing in ROMS-Tools begins with defining a CDR release. Two types are supported:

  • VolumeRelease: Injects both water and biogeochemical (BGC) tracers at a specific location (e.g., an underwater pipe).

    • Introduces volume flux and tracer concentrations.

    • Designed for field-scale deployments where mixing still needs to occur.

    • Requires concentration of all 32 MARBL BGC tracers, plus temperature and salinity (defaults available).

  • TracerPerturbation: Perturbs BGC tracer fields without adding water (i.e., no volume flux).

    • Introduces tracer fluxes, but no volume flux.

    • Designed for large-scale applications where mixing has already occurred (e.g., via a pre-run mixing model).

    • Of the 32 MARBL BGC tracers plus temperature and salinity, only the specified tracers are modified; the rest remain unchanged (equivalent to a perturbation of zero).

Specifying Location and Spread#

Both release types require the following parameters to define the release location:

  • lat: Latitude (in degrees North)

  • lon: Longitude (in degrees East)

  • depth: Depth (in meters)

  • hsc: Horizontal scale / standard deviation (in meters)

  • vsc: Vertical scale / standard deviation (in meters)

By default, hsc = vsc = 0, resulting in a point source (single grid cell). If hsc or vsc are greater than zero, the release is spread using a Gaussian.

Notes

  • A VolumeRelease applied over a broad area (hsc >> 0 or vsc >> 0, larger than the grid scale) can destabilize the model due to distributed buoyancy changes. It is best used as a point source (hsc = vsc = 0).

  • TracerPerturbation can safely span larger areas—provided temperature and salinity are not perturbed—since it does not affect buoyancy. A common use case is perturbing alkalinity (ALK) or dissolved inorganic carbon (DIC) with a Gaussian profile (hsc >> 0, vsc >> 0) to represent a well-mixed plume.

  • Note that using hsc much smaller than the grid resolution will result in a point source release.

[2]:
from datetime import datetime

Defining a VolumeRelease#

This section covers how to configure and use the VolumeRelease class to represent localized injections of water and tracers in a ROMS simulation.

In addition to the location parameters, a VolumeRelease has two key fields:

  • volume_fluxes

  • tracer_concentrations

Both can be specified as either constant or time-varying.

[1]:
from roms_tools import VolumeRelease

Call .get_metadata() to view the expected units for each tracer concentration, here which shows the default for tracer_set="marble".

[5]:
VolumeRelease.get_metadata()
[5]:
temp salt PO4 NO3 SiO3 NH4 Fe Lig O2 DIC DIC_ALT_CO2 ALK ALK_ALT_CO2 DOC DON DOP DOPr DONr DOCr zooC spChl spC spP spFe spCaCO3 diatChl diatC diatP diatFe diatSi diazChl diazC diazP diazFe
units degrees Celsius PSU mmol/m^3 mmol/m^3 mmol/m^3 mmol/m^3 mmol/m^3 mmol/m^3 mmol/m^3 mmol/m^3 mmol/m^3 meq/m^3 meq/m^3 mmol/m^3 mmol/m^3 mmol/m^3 mmol/m^3 mmol/m^3 mmol/m^3 mmol/m^3 mg/m^3 mmol/m^3 mmol/m^3 mmol/m^3 mmol/m^3 mg/m^3 mmol/m^3 mmol/m^3 mmol/m^3 mmol/m^3 mg/m^3 mmol/m^3 mmol/m^3 mmol/m^3
long_name potential temperature salinity dissolved inorganic phosphate dissolved inorganic nitrate dissolved inorganic silicate dissolved ammonia dissolved inorganic iron iron binding ligand dissolved oxygen dissolved inorganic carbon dissolved inorganic carbon, alternative CO2 alkalinity alkalinity, alternative CO2 dissolved organic carbon dissolved organic nitrogen dissolved organic phosphorus refractory dissolved organic phosphorus refractory dissolved organic nitrogen refractory dissolved organic carbon zooplankton carbon small phytoplankton chlorophyll small phytoplankton carbon small phytoplankton phosphorous small phytoplankton iron small phytoplankton CaCO3 diatom chloropyll diatom carbon diatom phosphorus diatom iron diatom silicate diazotroph chloropyll diazotroph carbon diazotroph phosphorus diazotroph iron

Constant volume flux and tracer concentrations#

Here we define a point source release close to Iceland with a volume flux and tracer concentrations that remain constant over time:

  • Volume flux: 1000 m³/s

  • Temperature: 20°C

  • Salinity: 1 PSU

  • Alkalinity concentration: 2000 meq/m³

These values will be applied throughout the entire ROMS simulation period.

[6]:
constant_volume_release_iceland = VolumeRelease(
    name="iceland_release",
    tracer_set="marbl",
    lat=65.0,  # degree N
    lon=-10,  # degree E
    depth=100,  # m
    volume_fluxes=1000,  # m3/s
    tracer_concentrations={
        "temp": 20.0,  # degrees C
        "salt": 1.0,  # psu
        "ALK": 2000.0,  # meq/m3
    },
)

Time-varying volume flux and tracer concentrations#

To define a time-varying release, specify the times parameter with explicit time points corresponding to the volume fluxes and tracer concentrations.

In the example below, both volume flux and alkalinity concentration vary over time, provided as lists matching the length of times. Meanwhile, temperature and salinity remain constant throughout the simulation period.

Note

Intermediate volume flux and tracer concentration values between the specified times are linearly interpolated when the CDRForcing object is constructed later.

[7]:
times = [
    datetime(2011, 1, 15, 12, 0),
    datetime(2011, 1, 15, 15, 0),
    datetime(2011, 1, 15, 18, 0),
    datetime(2011, 1, 19, 0, 0),
    datetime(2011, 1, 22, 6, 0),
    datetime(2011, 1, 22, 9, 0),
    datetime(2011, 1, 22, 12, 0),
]  # 7 entries
volume_fluxes = [0, 100, 500, 1000, 500, 100, 0]  # m3/s, 7 entries
tracer_concentrations = {
    "ALK": [
        1900.0,  # meq/m3
        2100.0,  # meq/m3
        2100.0,  # meq/m3
        2100.0,  # meq/m3
        2100.0,  # meq/m3
        2100.0,  # meq/m3
        1900.0,  # meq/m3
    ],  # 7 entries
    "temp": 20.0,  # degrees C
    "salt": 1.0,  # psu
}
[8]:
volume_release_greenland = VolumeRelease(
    name="greenland_release",
    tracer_set="marbl",
    lat=70.0,  # degree N
    lon=-20.0,  # degree E
    depth=100,  # m
    times=times,
    volume_fluxes=volume_fluxes,
    tracer_concentrations=tracer_concentrations,
)

Auto-fill vs. Zero-fill#

As noted earlier, ROMS requires concentrations for all 32 BGC tracers, plus temperature and salinity, when defining a VolumeRelease. If you don’t specify values for all tracers, use the fill_values parameter to control how the missing ones are handled:

  • fill_values = "auto" (default): Fills unspecified tracer concentrations with non-zero default values.

  • fill_values = "zero": Sets all unspecified tracer concentrations to zero.

All previous examples used the default ("auto"), which you can verify by checking the fill values for tracers other than temp, salt, and ALK below.

[9]:
volume_release_greenland
[9]:
VolumeRelease(name='greenland_release', lat=70.0, lon=-20.0, depth=100.0, hsc=0.0, vsc=0.0, times=[datetime.datetime(2011, 1, 15, 12, 0), datetime.datetime(2011, 1, 15, 15, 0), datetime.datetime(2011, 1, 15, 18, 0), datetime.datetime(2011, 1, 19, 0, 0), datetime.datetime(2011, 1, 22, 6, 0), datetime.datetime(2011, 1, 22, 9, 0), datetime.datetime(2011, 1, 22, 12, 0)], time_interpolation=False, tracer_set='marbl', release_type=<ReleaseType.volume: 'volume'>, fill_values='auto', volume_fluxes=Flux(name='volume', values=[0.0, 100.0, 500.0, 1000.0, 500.0, 100.0, 0.0]), tracer_concentrations={'temp': Concentration(name='temp', values=20.0), 'salt': Concentration(name='salt', values=1.0), 'PO4': Concentration(name='PO4', values=4.2453), 'NO3': Concentration(name='NO3', values=57.3565), 'SiO3': Concentration(name='SiO3', values=131.2429), 'NH4': Concentration(name='NH4', values=0.0), 'Fe': Concentration(name='Fe', values=0.07027027027027026), 'Lig': Concentration(name='Lig', values=0.21081081081081077), 'O2': Concentration(name='O2', values=270.8750648003977), 'DIC': Concentration(name='DIC', values=1640.071), 'DIC_ALT_CO2': Concentration(name='DIC_ALT_CO2', values=1640.071), 'ALK': Concentration(name='ALK', values=[1900.0, 2100.0, 2100.0, 2100.0, 2100.0, 2100.0, 1900.0]), 'ALK_ALT_CO2': Concentration(name='ALK_ALT_CO2', values=1173.693), 'DOC': Concentration(name='DOC', values=460.476), 'DON': Concentration(name='DON', values=31.61635), 'DOP': Concentration(name='DOP', values=1.0242011), 'DOPr': Concentration(name='DOPr', values=0.0), 'DONr': Concentration(name='DONr', values=0.0), 'DOCr': Concentration(name='DOCr', values=0.0), 'zooC': Concentration(name='zooC', values=0.0), 'spChl': Concentration(name='spChl', values=0.0), 'spC': Concentration(name='spC', values=0.0), 'spP': Concentration(name='spP', values=0.0), 'spFe': Concentration(name='spFe', values=0.0), 'spCaCO3': Concentration(name='spCaCO3', values=0.0), 'diatChl': Concentration(name='diatChl', values=0.0), 'diatC': Concentration(name='diatC', values=0.0), 'diatP': Concentration(name='diatP', values=0.0), 'diatFe': Concentration(name='diatFe', values=0.0), 'diatSi': Concentration(name='diatSi', values=0.0), 'diazChl': Concentration(name='diazChl', values=0.0), 'diazC': Concentration(name='diazC', values=0.0), 'diazP': Concentration(name='diazP', values=0.0), 'diazFe': Concentration(name='diazFe', values=0.0)})

For example, the default values for DIC and PO4 are 2370.0 mmol/m³ and 2.7 mmol/m³, respectively, as can be seen above.

Next, let’s create another release, but this time with fill_values = "zero" to explicitly set all unspecified tracer concentrations to zero.

[10]:
volume_release_norway_with_zero_fill = VolumeRelease(
    name="norway_release_zero_fill",
    lat=65.0,  # degree N
    lon=10,  # degree E
    depth=50,  # m
    times=times,
    volume_fluxes=volume_fluxes,
    tracer_concentrations=tracer_concentrations,
    fill_values="zero",
)

Below, you can see that missing tracer concentrations were correctly set to zero.

[11]:
volume_release_norway_with_zero_fill
[11]:
VolumeRelease(name='norway_release_zero_fill', lat=65.0, lon=10.0, depth=50.0, hsc=0.0, vsc=0.0, times=[datetime.datetime(2011, 1, 15, 12, 0), datetime.datetime(2011, 1, 15, 15, 0), datetime.datetime(2011, 1, 15, 18, 0), datetime.datetime(2011, 1, 19, 0, 0), datetime.datetime(2011, 1, 22, 6, 0), datetime.datetime(2011, 1, 22, 9, 0), datetime.datetime(2011, 1, 22, 12, 0)], time_interpolation=False, tracer_set='marbl', release_type=<ReleaseType.volume: 'volume'>, fill_values='zero', volume_fluxes=Flux(name='volume', values=[0.0, 100.0, 500.0, 1000.0, 500.0, 100.0, 0.0]), tracer_concentrations={'temp': Concentration(name='temp', values=20.0), 'salt': Concentration(name='salt', values=1.0), 'PO4': Concentration(name='PO4', values=0.0), 'NO3': Concentration(name='NO3', values=0.0), 'SiO3': Concentration(name='SiO3', values=0.0), 'NH4': Concentration(name='NH4', values=0.0), 'Fe': Concentration(name='Fe', values=0.0), 'Lig': Concentration(name='Lig', values=0.0), 'O2': Concentration(name='O2', values=0.0), 'DIC': Concentration(name='DIC', values=0.0), 'DIC_ALT_CO2': Concentration(name='DIC_ALT_CO2', values=0.0), 'ALK': Concentration(name='ALK', values=[1900.0, 2100.0, 2100.0, 2100.0, 2100.0, 2100.0, 1900.0]), 'ALK_ALT_CO2': Concentration(name='ALK_ALT_CO2', values=0.0), 'DOC': Concentration(name='DOC', values=0.0), 'DON': Concentration(name='DON', values=0.0), 'DOP': Concentration(name='DOP', values=0.0), 'DOPr': Concentration(name='DOPr', values=0.0), 'DONr': Concentration(name='DONr', values=0.0), 'DOCr': Concentration(name='DOCr', values=0.0), 'zooC': Concentration(name='zooC', values=0.0), 'spChl': Concentration(name='spChl', values=0.0), 'spC': Concentration(name='spC', values=0.0), 'spP': Concentration(name='spP', values=0.0), 'spFe': Concentration(name='spFe', values=0.0), 'spCaCO3': Concentration(name='spCaCO3', values=0.0), 'diatChl': Concentration(name='diatChl', values=0.0), 'diatC': Concentration(name='diatC', values=0.0), 'diatP': Concentration(name='diatP', values=0.0), 'diatFe': Concentration(name='diatFe', values=0.0), 'diatSi': Concentration(name='diatSi', values=0.0), 'diazChl': Concentration(name='diazChl', values=0.0), 'diazC': Concentration(name='diazC', values=0.0), 'diazP': Concentration(name='diazP', values=0.0), 'diazFe': Concentration(name='diazFe', values=0.0)})

Note: Later, we’ll cover how to visualize release locations, volume fluxes, and tracer concentrations (including default fill values), once a CDRForcing object has been created.

Defining a TracerPerturbation#

This section covers how to configure and use the TracerPerturbation class to represent injections of tracers without water into a ROMS simulation.

In addition to the location parameters, a TracerPerturbation has the key field:

  • tracer_fluxes

Both can be specified as either constant or time-varying.

[3]:
from roms_tools import TracerPerturbation

Call .get_metadata() to view the expected units for each tracer flux, here which shows the default for tracer_set="marble".

[13]:
TracerPerturbation.get_metadata()
[13]:
temp salt PO4 NO3 SiO3 NH4 Fe Lig O2 DIC DIC_ALT_CO2 ALK ALK_ALT_CO2 DOC DON DOP DOPr DONr DOCr zooC spChl spC spP spFe spCaCO3 diatChl diatC diatP diatFe diatSi diazChl diazC diazP diazFe
units degrees Celsius/s PSU/s mmol/s mmol/s mmol/s mmol/s mmol/s mmol/s mmol/s mmol/s meq/s meq/s meq/s mmol/s mmol/s mmol/s mmol/s mmol/s mmol/s mmol/s mg/s mmol/s mmol/s mmol/s mmol/s mg/s mmol/s mmol/s mmol/s mmol/s mg/s mmol/s mmol/s mmol/s
long_name potential temperature salinity dissolved inorganic phosphate dissolved inorganic nitrate dissolved inorganic silicate dissolved ammonia dissolved inorganic iron iron binding ligand dissolved oxygen dissolved inorganic carbon dissolved inorganic carbon, alternative CO2 alkalinity alkalinity, alternative CO2 dissolved organic carbon dissolved organic nitrogen dissolved organic phosphorus refractory dissolved organic phosphorus refractory dissolved organic nitrogen refractory dissolved organic carbon zooplankton carbon small phytoplankton chlorophyll small phytoplankton carbon small phytoplankton phosphorous small phytoplankton iron small phytoplankton CaCO3 diatom chloropyll diatom carbon diatom phosphorus diatom iron diatom silicate diazotroph chloropyll diazotroph carbon diazotroph phosphorus diazotroph iron

Constant tracer flux#

Let’s start by defining a release near Iceland that has a Gaussian distribution (with standard deviations of 100km in the horizontal and 50m in the vertical) and a constant alkalinity flux:

  • Alkalinity flux: 2,000,000 meq/s

This ocean alkalinity enhancement (OAE) experiment is equivalent to the VolumeRelease example above, where 2000 meq/m³ of alkalinity was injected with a 1000 m³/s volume flux. However, in this case, no water is added, only the tracer. As before, this single flux value is applied consistently throughout the entire ROMS simulation.

[14]:
constant_oae_iceland = TracerPerturbation(
    name="iceland_oae",
    tracer_set="marbl",
    lat=65.0,  # degree N
    lon=-10,  # degree E
    depth=100,  # m
    hsc=100000,  # m
    vsc=50,  # m
    tracer_fluxes={"ALK": 2 * 10**6},  # meq/s
)

We can also define a direct ocean removal (DOR) experiment as follows.

[15]:
constant_dor_iceland = TracerPerturbation(
    name="iceland_dor",
    tracer_set="marbl",
    lat=65.0,  # degree N
    lon=-25,  # degree E
    depth=100,  # m
    hsc=100000,  # m
    vsc=50,  # m
    tracer_fluxes={"DIC": - 2 * 10**6},  # mmol/s
)

Time-varying tracer fluxes#

To define a time-varying release, specify the times parameter with explicit time points for tracer fluxes.

In the example below, the alkalinity flux varies over time (as list matching times length).

Note

Intermediate tracer flux values between the specified times are linearly interpolated when the CDRForcing object is constructed later.

[16]:
times_trcr = [
    datetime(2011, 1, 15, 12, 0),
    datetime(2011, 1, 15, 15, 0),
    datetime(2011, 1, 15, 18, 0),
    datetime(2011, 1, 19, 0, 0),
    datetime(2011, 1, 22, 6, 0),
    datetime(2011, 1, 22, 9, 0),
    datetime(2011, 1, 22, 12, 0),
]  # 7 entries
tracer_fluxes = {
    "ALK": [
        0.0,
        2.0 * 10**6,  # meq/s
        3.0 * 10**6,  # meq/s
        4.0 * 10**6,  # meq/s
        3.0 * 10**6,  # meq/s
        2.0 * 10**6,  # meq/s
        0.0,
    ],  # 7 entries
}

Given these tracer fluxes, we now define a tracer perturbation as a Gaussian with a standard deviation of 150km in the horizontal and 100m in the vertical.

[17]:
tracer_perturbation_greenland = TracerPerturbation(
    name="greenland_release",
    lat=70.0,  # degree N
    lon=-20.0,  # degree E
    depth=100,  # m
    hsc=150000,  # m
    vsc=100,  # m
    times=times_trcr,
    tracer_fluxes=tracer_fluxes,
)

Fill values are always zero#

For TracerPerturbation, any tracer flux not explicitly specified is automatically set to zero, as this object represents a perturbation rather than a complete tracer field. For that reason, TracerPerturbation does not have a fill_values parameter.

We confirm this automatic zero-filling behavior in the example below.

[18]:
tracer_perturbation_greenland
[18]:
TracerPerturbation(name='greenland_release', lat=70.0, lon=-20.0, depth=100.0, hsc=150000.0, vsc=100.0, times=[datetime.datetime(2011, 1, 15, 12, 0), datetime.datetime(2011, 1, 15, 15, 0), datetime.datetime(2011, 1, 15, 18, 0), datetime.datetime(2011, 1, 19, 0, 0), datetime.datetime(2011, 1, 22, 6, 0), datetime.datetime(2011, 1, 22, 9, 0), datetime.datetime(2011, 1, 22, 12, 0)], time_interpolation=False, tracer_set='marbl', release_type=<ReleaseType.tracer_perturbation: 'tracer_perturbation'>, tracer_fluxes={'temp': Flux(name='temp', values=0.0), 'salt': Flux(name='salt', values=0.0), 'PO4': Flux(name='PO4', values=0.0), 'NO3': Flux(name='NO3', values=0.0), 'SiO3': Flux(name='SiO3', values=0.0), 'NH4': Flux(name='NH4', values=0.0), 'Fe': Flux(name='Fe', values=0.0), 'Lig': Flux(name='Lig', values=0.0), 'O2': Flux(name='O2', values=0.0), 'DIC': Flux(name='DIC', values=0.0), 'DIC_ALT_CO2': Flux(name='DIC_ALT_CO2', values=0.0), 'ALK': Flux(name='ALK', values=[0.0, 2000000.0, 3000000.0, 4000000.0, 3000000.0, 2000000.0, 0.0]), 'ALK_ALT_CO2': Flux(name='ALK_ALT_CO2', values=0.0), 'DOC': Flux(name='DOC', values=0.0), 'DON': Flux(name='DON', values=0.0), 'DOP': Flux(name='DOP', values=0.0), 'DOPr': Flux(name='DOPr', values=0.0), 'DONr': Flux(name='DONr', values=0.0), 'DOCr': Flux(name='DOCr', values=0.0), 'zooC': Flux(name='zooC', values=0.0), 'spChl': Flux(name='spChl', values=0.0), 'spC': Flux(name='spC', values=0.0), 'spP': Flux(name='spP', values=0.0), 'spFe': Flux(name='spFe', values=0.0), 'spCaCO3': Flux(name='spCaCO3', values=0.0), 'diatChl': Flux(name='diatChl', values=0.0), 'diatC': Flux(name='diatC', values=0.0), 'diatP': Flux(name='diatP', values=0.0), 'diatFe': Flux(name='diatFe', values=0.0), 'diatSi': Flux(name='diatSi', values=0.0), 'diazChl': Flux(name='diazChl', values=0.0), 'diazC': Flux(name='diazC', values=0.0), 'diazP': Flux(name='diazP', values=0.0), 'diazFe': Flux(name='diazFe', values=0.0)})

Using CDR releases with CDR-LiTE (non-MARBL BGC)#

For ROMS setups that calculate CDR using the CDR-LiTE model (currently, the ucla-roms CDR_LITE option), set tracer_set="cdr_lite" on each release. Using the CDR-LiTE model, multiple CDR releases/experiments may be loaded and run in a single ROMS simulation. CDRForcing constructs a forcing file whose tracer axis reflects the necessary ROMS namelist counts (nt_cdr_oae, nt_cdr_dor in &PARAM_SETTINGS).

Each release specifies fluxes for ALK and/or DIC, and the keys determine the intervention type and its tracers:

Release Flux(es)

CDR-LiTE Calculation

Assigned tracers

ALK

OAE

CDR_OAE_ALK{k} / CDR_OAE_DIC{k} tracer pair

ALK & DIC

combined OAE+DOR

CDR_OAE_ALK{k} / CDR_OAE_DIC{k} tracer pair

DIC only (negative = removal)

DOR

single CDR_DOR_DIC{j} tracer

Tracer indices {k} or {j} are assigned in release order, and one release always corresponds to one CDR-LiTE itervention/tracer set. (The release name are saved as attributes in the output netCDF file, for referencing index/tracer number to release name – see below in CDR Forcing section)

CDR-LiTE only supports ``TracerPerturbation``. CDR tracer experiments require the physics to remain untouched by the release, so volume releases (which add water to the continuity equation and force temp/salt) raise a ValidationError. For the same reason, temp/salt are not accepted flux keys on cdr_lite perturbations — passing them also raises a ValidationError. The temp/salt rows still appear in the output file, always zero, purely because ROMS reads the tracer axis positionally and expects every model tracer present.

[4]:
# OAE: add alkalinity
simple_oae = TracerPerturbation(
    name="simple_oae",
    lat=65.0,
    lon=-10,
    depth=100,
    hsc=100000,
    vsc=50,
    tracer_set="cdr_lite",
    tracer_fluxes={"ALK": 2 * 10**6},  # meq/s
)

# DOR: remove DIC (DIC-only fluxes -> a standalone DOR tracer)
simple_dor = TracerPerturbation(
    name="simple_dor",
    lat=65.0,
    lon=-25,
    depth=100,
    hsc=100000,
    vsc=50,
    tracer_set="cdr_lite",
    tracer_fluxes={"DIC": -2 * 10**6},  # mmol/s (negative = removal)
)

# Combined OAE + DOR: one intervention, one OAE pair, with negative DIC
simple_combined = TracerPerturbation(
    name="simple_combined",
    lat=62.0,
    lon=-22,
    depth=100,
    hsc=100000,
    vsc=50,
    tracer_set="cdr_lite",
    tracer_fluxes={"ALK": 1 * 10**6, "DIC": -5 * 10**5},
)

Inspect a release: fluxes are keyed by ALK/DIC rather than by global tracer name; CDRForcing assigns each release its own tracer(s) at build time (e.g. the first OAE release’s ALK becomes CDR_OAE_ALK1):

[5]:
simple_oae
[5]:
TracerPerturbation(name='simple_oae', lat=65.0, lon=-10.0, depth=100.0, hsc=100000.0, vsc=50.0, times=[], time_interpolation=False, tracer_set='cdr_lite', release_type=<ReleaseType.tracer_perturbation: 'tracer_perturbation'>, tracer_fluxes={'ALK': Flux(name='ALK', values=2000000.0)})

The expected role keys and units now come from the CDR-LiTE tracer model:

[6]:
simple_oae.metadata
[6]:
ALK DIC
units meq/s mmol/s
long_name alkalinity of the release's OAE tracer pair DIC of the release's OAE tracer pair (with ALK...

CDR Forcing#

The CDR Forcing object links one or multiple releases to the underlying ROMS simulation. It requires the following inputs:

  1. One or multiple releases, all of the same type (either all VolumeRelease or all TracerPerturbation)

  2. The model grid (optional but strongly recommended)

  3. The simulation start and end dates (required)

Notes

  • Model Grid: Although optional, providing the model grid is highly recommended. Without it, important validation checks, such as verifying whether release locations fall outside the grid domain or on land, cannot be performed. Additionally, plotting release locations requires the model grid to be provided.

  • Simulation Start and End Dates: These are necessary for two reasons:

    • To ensure that the release window does not fall outside the simulation window.

    • ROMS requires forcing data for the entire simulation period. If a release’s specified times do not fully cover the simulation period, ROMS-Tools will extend the release data to the simulation endpoints using a default method (details provided in this section).

Let’s create our CDR forcings for the following grid spanning the Nordic Seas.

[13]:
from roms_tools import Grid
[14]:
grid = Grid(
    nx=250,
    ny=250,
    size_x=2500,
    size_y=2500,
    center_lon=-15,
    center_lat=65,
    rot=-30,
    N=100,
    topography_source={
        "name": "SRTM15",
        "path": "/anvil/projects/x-ees250129/Datasets/SRTM15/SRTM15_V2.6.nc",
    },
)
[23]:
grid.plot()
_images/cdr_forcing_42_0.png

Next, we specify the start and end times of the underlying ROMS simulation.

[10]:
simulation_start_time = datetime(2010, 1, 1)
simulation_end_time = datetime(2012, 12, 31)

Creating the CDRForcing#

[11]:
simulation_end_time-simulation_start_time
[11]:
datetime.timedelta(days=1095)
[12]:
from roms_tools import CDRForcing

Let’s first create a CDRForcing that consists of all the VolumeReleases that we defined above.

[27]:
cdr_forcing_with_volume_releases = CDRForcing(
    grid=grid,
    start_time=simulation_start_time,
    end_time=simulation_end_time,
    model_reference_date=datetime(2000, 1, 1), # this is the default
    releases=[
        constant_volume_release_iceland,
        volume_release_greenland,
        volume_release_norway_with_zero_fill,
    ],
)

Similarly, we can create a CDRForcing that consists of all the TracerPerturbations that we defined above.

[28]:
cdr_forcing_with_tracer_perturbations = CDRForcing(
    grid=grid,
    start_time=simulation_start_time,
    end_time=simulation_end_time,
    model_reference_date=datetime(2000, 1, 1), # this is the default
    releases=[
        constant_oae_iceland,
        constant_dor_iceland,
        tracer_perturbation_greenland,
    ],
)

Creating a CDRForcing with CDR-LiTE releases#

Pass the releases, the grid, and the simulation window — the tracer axis is derived from the releases. Here two OAE-pair releases (simple_oae, simple_combined) and one DOR release give ntracers=7: temp, salt, CDR_OAE_ALK1, CDR_OAE_DIC1, CDR_OAE_ALK2, CDR_OAE_DIC2, CDR_DOR_DIC1. Each release writes only the rows of its own tracer(s); everything else — including the temp/salt physics rows — stays zero.

[17]:
cdr_forcing_simple = CDRForcing(
    grid=grid,
    start_time=simulation_start_time,
    end_time=simulation_end_time,
    model_reference_date=datetime(2000, 1, 1),
    releases=[simple_oae, simple_dor, simple_combined],
)

cdr_forcing_simple.ds.sizes["ntracers"], list(cdr_forcing_simple.ds.tracer_name.values)

[17]:
(7,
 [np.str_('temp'),
  np.str_('salt'),
  np.str_('CDR_OAE_ALK1'),
  np.str_('CDR_OAE_DIC1'),
  np.str_('CDR_OAE_ALK2'),
  np.str_('CDR_OAE_DIC2'),
  np.str_('CDR_DOR_DIC1')])

Because OAE and DOR tracers are grouped into separate blocks, the tracer order in the file differs from the order in which the releases are listed in CDRForcing. Use roms_layout() to see the final layout as ROMS will interpret it — each row’s itrc is the 1-based ROMS Fortran tracer index — together with the ROMS namelist values to set (nt_cdr_oae, nt_cdr_dor, cdr_ncdr_parm); always set the namelist to match the file. The release ↔ tracer linkage is also stored in the file itself (the tracer_release coordinate on the ntracers axis), so a saved forcing file self-documents which release corresponds to which tracer(s). For a ROMS build that also runs MARBL (CDR_LITE + MARBL), pass include_marbl_bgc=True to append the MARBL BGC tracers to the axis (this happens automatically if marbl and cdr_lite releases are mixed in one CDRForcing):

[19]:
layout = cdr_forcing_simple.roms_layout()
itrc    tracer_name     units   release
1       temp    degrees Celsius/s
2       salt    PSU/s
3       CDR_OAE_ALK1    meq/s   simple_oae
4       CDR_OAE_DIC1    mmol/s  simple_oae
5       CDR_OAE_ALK2    meq/s   simple_combined
6       CDR_OAE_DIC2    mmol/s  simple_combined
7       CDR_DOR_DIC1    mmol/s  simple_dor

ROMS namelist correspondence (set the namelist to match the file):
  nt_passive = 0
  nt_cdr_oae = 2
  nt_cdr_dor = 1
  cdr_ncdr_parm = 3
  MARBL BGC tracers included: False

Save to NetCDF for ROMS (same as for MARBL CDR forcing):

cdr_forcing_simple.save("cdr_lite_forcing.nc")

Passive (dye) tracers#

ROMS builds can also carry generic passive tracers; each passive release is auto-assigned the next passive_tracer{i} slot, exactly like the CDR-LiTE assignment.

Unlike CDR-LiTE, passive releases are allowed as ``VolumeRelease`` — a dye in discharged water (with its temp/salt) is not a physics-untouched counterfactual, so the volume restriction doesn’t apply. The usual mixing rules hold: volume and perturbation releases can never be combined in one CDRForcing, so a passive VolumeRelease alongside cdr_lite perturbations is not allowed.

[16]:
dye = TracerPerturbation(
    name="dye_plume",
    lat=62.5,
    lon=-18,
    depth=50,
    hsc=100000,
    vsc=50,
    tracer_set="passive",
    tracer_fluxes={"passive_tracer": 1 * 10**5},  # mmol/s
)

cdr_forcing_dye = CDRForcing(
    grid=grid,
    start_time=simulation_start_time,
    end_time=simulation_end_time,
    releases=[dye, simple_oae, simple_dor],
)

layout = cdr_forcing_dye.roms_layout()

itrc    tracer_name     units   release
1       temp    degrees Celsius/s
2       salt    PSU/s
3       passive_tracer1 mmol/s  dye_plume
4       CDR_OAE_ALK1    meq/s   simple_oae
5       CDR_OAE_DIC1    mmol/s  simple_oae
6       CDR_DOR_DIC1    mmol/s  simple_dor

ROMS namelist correspondence (set the namelist to match the file):
  nt_passive = 1
  nt_cdr_oae = 1
  nt_cdr_dor = 1
  cdr_ncdr_parm = 3
  MARBL BGC tracers included: False

The .ds attribute of each CDRForcing object holds an xarray Dataset with the release information. This dataset can eventually be saved to a NetCDF file, providing the ROMS input file.

[31]:
cdr_forcing_with_volume_releases.ds
[31]:
<xarray.Dataset> Size: 17kB
Dimensions:           (time: 9, ncdr: 3, ntracers: 34)
Coordinates:
  * time              (time) datetime64[ns] 72B 2010-01-01 ... 2012-12-31
    release_name      (ncdr) <U24 288B 'iceland_release' ... 'norway_release_...
    tracer_name       (ntracers) <U11 1kB 'temp' 'salt' ... 'diazP' 'diazFe'
    tracer_unit       (ntracers) <U15 2kB 'degrees Celsius' 'PSU' ... 'mmol/m^3'
    tracer_long_name  (ntracers) <U43 6kB 'potential temperature' ... 'diazot...
Dimensions without coordinates: ncdr, ntracers
Data variables:
    cdr_time          (time) float64 72B 3.653e+03 4.032e+03 ... 4.748e+03
    cdr_lon           (ncdr) float64 24B -10.0 -20.0 10.0
    cdr_lat           (ncdr) float64 24B 65.0 70.0 65.0
    cdr_dep           (ncdr) float64 24B 100.0 100.0 50.0
    cdr_hsc           (ncdr) float64 24B 0.0 0.0 0.0
    cdr_vsc           (ncdr) float64 24B 0.0 0.0 0.0
    cdr_volume        (time, ncdr) float64 216B 1e+03 0.0 0.0 ... 1e+03 0.0 0.0
    cdr_tracer        (time, ntracers, ncdr) float64 7kB 20.0 20.0 ... 0.0 0.0

The dataset above contains 3 releases, as indicated by the ncdr dimension.

In contrast, the next dataset contains only 2 releases, as expected.

[32]:
cdr_forcing_with_tracer_perturbations.ds
[32]:
<xarray.Dataset> Size: 17kB
Dimensions:           (time: 9, ncdr: 3, ntracers: 34)
Coordinates:
  * time              (time) datetime64[ns] 72B 2010-01-01 ... 2012-12-31
    release_name      (ncdr) <U17 204B 'iceland_oae' ... 'greenland_release'
    tracer_name       (ntracers) <U11 1kB 'temp' 'salt' ... 'diazP' 'diazFe'
    tracer_unit       (ntracers) <U17 2kB 'degrees Celsius/s' ... 'mmol/s'
    tracer_long_name  (ntracers) <U43 6kB 'potential temperature' ... 'diazot...
Dimensions without coordinates: ncdr, ntracers
Data variables:
    cdr_time          (time) float64 72B 3.653e+03 4.032e+03 ... 4.748e+03
    cdr_lon           (ncdr) float64 24B -10.0 -25.0 -20.0
    cdr_lat           (ncdr) float64 24B 65.0 65.0 70.0
    cdr_dep           (ncdr) float64 24B 100.0 100.0 100.0
    cdr_hsc           (ncdr) float64 24B 1e+05 1e+05 1.5e+05
    cdr_vsc           (ncdr) float64 24B 50.0 50.0 100.0
    cdr_trcflx        (time, ntracers, ncdr) float64 7kB 0.0 0.0 0.0 ... 0.0 0.0

Plotting the Release Locations and Distributions#

We now continue with visualizing the release locations and distributions. First, we will create a bird’s-eye view of the release location centers.

[33]:
cdr_forcing_with_volume_releases.plot_locations()  # By default, this plots all available releases (but max 20).
_images/cdr_forcing_64_0.png

We can also restrict the plot to display only the releases of interest by specifying them in a list via the release_names parameter.

[34]:
cdr_forcing_with_volume_releases.plot_locations(
    release_names=["iceland_release", "norway_release_zero_fill"]
)
_images/cdr_forcing_66_0.png
[35]:
cdr_forcing_with_tracer_perturbations.plot_locations()
_images/cdr_forcing_67_0.png

Next, we plot the release distributions from a top and side view. Note that we can only plot one release at a time in this view, as the latitude and longitude section to be visualized depends on the specific location of the release itself.

[36]:
cdr_forcing_with_volume_releases.plot_distribution(release_name="greenland_release")
2026-09-03 14:02:03 - INFO - CDR release will be treated as a point-source release: Horizontal scale set to 0..
_images/cdr_forcing_69_1.png

The VolumeRelease we visualized in the plot above is a point source. Next, let’s visualize our tracer perturbations, which are released as Gaussian distributions.

[37]:
cdr_forcing_with_tracer_perturbations.plot_distribution(
    release_name="greenland_release"
)
_images/cdr_forcing_71_0.png

If the black cross at the release center bothers you, you can remove it by setting mark_release_center = False, as shown in the next plot.

[38]:
cdr_forcing_with_tracer_perturbations.plot_distribution(
    release_name="greenland_release", mark_release_center=False
)
_images/cdr_forcing_73_0.png
[39]:
cdr_forcing_with_tracer_perturbations.plot_distribution(release_name="iceland_oae")
_images/cdr_forcing_74_0.png

Plotting Volume Fluxes and Tracer Concentrations#

This section applies only to CDRForcing objects that contain releases of type VolumeRelease. These releases are associated with both volume fluxes and tracer concentrations, which we will visualize next.

[40]:
cdr_forcing_with_volume_releases.plot_volume_flux()
_images/cdr_forcing_76_0.png

You can see that the iceland_release applies a constant volume flux throughout the entire simulation period, while the other two releases have time-varying volume fluxes, just as we designed them earlier.

If desired, you can also limit the plot to specific releases using the release_names parameter.

[41]:
cdr_forcing_with_volume_releases.plot_volume_flux(
    release_names=["iceland_release", "greenland_release"]
)
_images/cdr_forcing_78_0.png

We can also zoom into the specific time window where the greenland_release occurs to better visualize the time-varying behavior of the volume flux.

[42]:
cdr_forcing_with_volume_releases.plot_volume_flux(
    release_names=["iceland_release", "greenland_release"],
    start=datetime(2011, 1, 15),
    end=datetime(2011, 1, 23),
)
_images/cdr_forcing_80_0.png

Next, we plot the alkalinity concentration.

[43]:
cdr_forcing_with_volume_releases.plot_tracer_concentration(
    tracer_name="ALK", release_names=["iceland_release", "greenland_release"]
)
_images/cdr_forcing_82_0.png

Again, the iceland_release uses an alkalinity concentration that is constant in time, while the greenland_release has a time-varying alkalinity concentration.

[44]:
cdr_forcing_with_volume_releases.plot_tracer_concentration(
    tracer_name="ALK",
    release_names=["iceland_release", "greenland_release"],
    start=datetime(2011, 1, 15),
    end=datetime(2011, 1, 23),
)
_images/cdr_forcing_84_0.png

Finally, we plot the DIC concentration to visualize the effect of the default fill versus the zero fill.

[45]:
cdr_forcing_with_volume_releases.plot_tracer_concentration(
    tracer_name="DIC", release_names=["greenland_release", "norway_release_zero_fill"]
)
_images/cdr_forcing_86_0.png

Plotting Tracer Fluxes#

This section applies only to CDRForcing objects that contain releases of type TracerPerturbation. These releases are associated with tracer fluxes, which we will visualize next. The plot_tracer_flux method works similarly as the plot_volume_flux and plot_tracer_concentration methods above.

[46]:
cdr_forcing_with_tracer_perturbations.plot_tracer_flux(tracer_name="ALK")
_images/cdr_forcing_88_0.png
[47]:
cdr_forcing_with_tracer_perturbations.plot_tracer_flux(
    tracer_name="ALK", start=datetime(2011, 1, 15), end=datetime(2011, 1, 23)
)
_images/cdr_forcing_89_0.png
[48]:
cdr_forcing_with_tracer_perturbations.plot_tracer_flux(tracer_name="DIC")
_images/cdr_forcing_90_0.png

Linear Interpolation#

So far, all of our volume flux and tracer concentrations have been shown as step function, where the value stays constant until a new value is prescribed. This is because CDRForcing sets time_interpolation to False by default. If we want to interpolate between points, we can set it to True.

Let’s remake our previous example volume_release_greenland, but setting time_interpolation=True.

[49]:
volume_release_greenland_interpolation = VolumeRelease(
    name="greenland_release",
    lat=70.0,  # degree N
    lon=-20.0,  # degree E
    depth=100,  # m
    times=volume_release_greenland.times,
    volume_fluxes=volume_release_greenland.volume_fluxes.values,
    tracer_concentrations=volume_release_greenland.tracer_concentrations,
    time_interpolation=True,
)

cdr_forcing_with_volume_release_interpolation = CDRForcing(
    grid=grid,
    start_time=simulation_start_time,
    end_time=simulation_end_time,
    model_reference_date=datetime(2000, 1, 1), # this is the default
    releases=[
        volume_release_greenland_interpolation,
    ],
)
[50]:
cdr_forcing_with_volume_release_interpolation.plot_volume_flux(
    release_names=["greenland_release"],
    start=datetime(2011, 1, 15),
    end=datetime(2011, 1, 23),
)
_images/cdr_forcing_94_0.png

Notice how there is a tail between the start time (datetime(2010, 1, 1)) and our first assigned date (datetime(2011, 1, 15, 12, 0)). This is due to the automatic extrapolation that ROMS-Tools performs.

Automatic Extrapolation to Simulation Endpoints#

Important

ROMS requires volume fluxes and tracer concentrations for the entire simulation period. If the user does not specify the simulation’s start and end points in their time series, ROMS-Tools will apply the following defaults:

  • Volume and tracer fluxes are set to 0 at both the start and end times.

  • Tracer concentrations at the start and end times are set to the values from the closest available time stamps.

Let’s examine how these rules play out in practice by revisiting our previous VolumeRelease examples: volume_release_greenland_interpolation. This release was defined with the following times, volume fluxes, and alkalinity concentrations (where the endpoints have been filled in when we built cdr_forcing_with_volume_release_interpolation).

[51]:
volume_release_greenland_interpolation.times
[51]:
[datetime.datetime(2010, 1, 1, 0, 0),
 datetime.datetime(2011, 1, 15, 12, 0),
 datetime.datetime(2011, 1, 15, 15, 0),
 datetime.datetime(2011, 1, 15, 18, 0),
 datetime.datetime(2011, 1, 19, 0, 0),
 datetime.datetime(2011, 1, 22, 6, 0),
 datetime.datetime(2011, 1, 22, 9, 0),
 datetime.datetime(2011, 1, 22, 12, 0),
 datetime.datetime(2012, 12, 31, 0, 0)]
[52]:
volume_release_greenland_interpolation.volume_fluxes.values
[52]:
[0.0, 0.0, 100.0, 500.0, 1000.0, 500.0, 100.0, 0.0, 0.0]
[53]:
volume_release_greenland_interpolation.tracer_concentrations["ALK"].values
[53]:
[1900.0, 1900.0, 2100.0, 2100.0, 2100.0, 2100.0, 2100.0, 1900.0, 1900.0]

In the next example, we define a very similar release as before, but we omit the first two and the last two timestamps from the time series.

[54]:
times = volume_release_greenland_interpolation.times[2:-2]
times
[54]:
[datetime.datetime(2011, 1, 15, 15, 0),
 datetime.datetime(2011, 1, 15, 18, 0),
 datetime.datetime(2011, 1, 19, 0, 0),
 datetime.datetime(2011, 1, 22, 6, 0),
 datetime.datetime(2011, 1, 22, 9, 0)]
[55]:
volume_fluxes = volume_release_greenland_interpolation.volume_fluxes.values[2:-2]
volume_fluxes
[55]:
[100.0, 500.0, 1000.0, 500.0, 100.0]
[56]:
alk_concentration = volume_release_greenland_interpolation.tracer_concentrations["ALK"].values[2:-2]
alk_concentration
[56]:
[2100.0, 2100.0, 2100.0, 2100.0, 2100.0]
[57]:
volume_release_greenland_interpolation_truncated = VolumeRelease(
    name="greenland_release_cut_short",
    lat=volume_release_greenland.lat,
    lon=volume_release_greenland.lon,
    depth=volume_release_greenland.depth,
    times=times,
    volume_fluxes=volume_fluxes,
    tracer_concentrations={"ALK": alk_concentration},
    time_interpolation=True,
)
[58]:
volume_release_greenland_interpolation_truncated.volume_fluxes.values
[58]:
[100.0, 500.0, 1000.0, 500.0, 100.0]
[59]:
cdr_forcing = CDRForcing(
    grid=grid,
    start_time=simulation_start_time,
    end_time=simulation_end_time,
    releases=[volume_release_greenland_interpolation, volume_release_greenland_interpolation_truncated],
)

Let’s now compare the time series of volume flux and alkalinity concentration for the two releases.

[60]:
cdr_forcing.plot_volume_flux(start=datetime(2011, 1, 15), end=datetime(2011, 1, 23))
_images/cdr_forcing_108_0.png

If we zoom out, we can see how the tails are handled.

[61]:
cdr_forcing.plot_volume_flux()
_images/cdr_forcing_110_0.png

We see that when time_interpolation is turned on, not assigning values to the start and end points on the simulation will result in ROMS-Tools extrapolating to 0 for each. Explicitly prescribing times when the volume and tracer fluxes avoids this tail of additional fluxes.

To re-iterate, the above plot shows the effect of choosing:

  • volume_fluxes = [0.0, 100.0, 500.0, 1000.0, 500.0, 100.0, 0.0] (blue line) vs.

  • volume_fluxes = [100.0, 500.0, 1000.0, 500.0, 100.0] (orange line)

The first choice explicitly defines when to shut off the volume flux.
The second choice does not define this, so ROMS-Tools assumes the flux is only shut off at the simulation endpoints and linearly interpolates in between.
[62]:
cdr_forcing.plot_tracer_concentration(tracer_name="ALK")
_images/cdr_forcing_113_0.png

To re-iterate, the above plot shows the effect of choosing:

  • tracer_concentrations["ALK"] = [1900.0, 2100.0, 2100.0, 2100.0, 2100.0, 2100.0, 1900.0] (blue line) vs.

  • tracer_concentrations["ALK"] = [2100.0, 2100.0, 2100.0, 2100.0, 2100.0] (orange line)

In both cases, ROMS-Tools extrapolates the endpoints of the provided time series to the simulation endpoints.

Total Tracer Release Accounting#

In marine CDR experiments, it is essential to ensure that the ROMS simulation matches the intended tracer inputs, e.g., the total amount of alkalinity added or DIC removed. To achieve this, ROMS-Tools computes the cumulative tracer release for each release over the simulation period via the .compute_total_cdr_source() method.

The total amount of tracer released or removed depends on the time discretization used in the ROMS simulation. To reproduce exactly what ROMS will do, ROMS-Tools reconstructs the ROMS time stamps. This requires:

  • The simulation start and end times, already provided when instantiating the CDRForcing object (start_time and end_time). Make sure that these exactly match the actual start and end time of the simulation.

  • The model time step dt, which defines the temporal resolution of the integration.

The output is a pandas DataFrame with releases as rows and tracers as columns, showing the cumulative mass of each tracer released. Different choices of dt may produce slightly different totals due to discretization.

[63]:
dt0 = 150 # ROMS time step in seconds
df0_vr = cdr_forcing_with_volume_releases.compute_total_cdr_source(dt=dt0)
df0_vr
[63]:
PO4 NO3 SiO3 NH4 Fe Lig O2 DIC DIC_ALT_CO2 ALK ALK_ALT_CO2 DOC DON DOP DOPr DONr DOCr zooC spChl spC spP spFe spCaCO3 diatChl diatC diatP diatFe diatSi diazChl diazC diazP diazFe
units mmol mmol mmol mmol mmol mmol mmol mmol meq meq meq mmol mmol mmol mmol mmol mmol mmol mg mmol mmol mmol mmol mg mmol mmol mmol mmol mg mmol mmol mmol
iceland_release 401639342400.0 5426383752000.0 12416628283200.0 0.0 6648129729.72973 19944389189.189201 25626948130636.03125 155163837168000.0 155163837168000.0 189216000000000.0 111040747344000.0 43564713408000.0 2991159640800.0 96897617668.79985 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
greenland_release 1820214828.0 24592172940.0 56271705804.0 0.0 30129081.081081 90387243.243243 116140392783.818481 703196841960.0 703196841960.0 900396000000.0 503232610680.0 197433689760.0 13555826226.0 439136463.636 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
norway_release_zero_fill 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 900396000000.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

We can do a similar analysis for our released tracer perturbations.

[64]:
df0_tp = cdr_forcing_with_tracer_perturbations.compute_total_cdr_source(dt=150)
df0_tp
[64]:
PO4 NO3 SiO3 NH4 Fe Lig O2 DIC DIC_ALT_CO2 ALK ALK_ALT_CO2 DOC DON DOP DOPr DONr DOCr zooC spChl spC spP spFe spCaCO3 diatChl diatC diatP diatFe diatSi diazChl diazC diazP diazFe
units mmol mmol mmol mmol mmol mmol mmol mmol meq meq meq mmol mmol mmol mmol mmol mmol mmol mg mmol mmol mmol mmol mg mmol mmol mmol mmol mg mmol mmol mmol
iceland_oae 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 189216000000000.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
iceland_dor 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -189216000000000.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
greenland_release 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 2041200000000.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

Mixing MARBL, CDR-LiTE, and passive releases in one forcing file#

ROMS can run multiple tracer models at the same time: a build with MARBL and CDR_LITE enabled (plus nt_passive > 0) carries the MARBL tracer suite, the dedicated CDR-LiTE tracers, and generic passive tracers — and a single CDR forcing file can drive releases against each. In roms-tools, simply mix releases with tracer_set="marbl", "cdr_lite", and "passive" in one CDRForcing (tracer perturbations only): marbl releases feed the MARBL tracers by name, cdr_lite and passive releases feed their own auto-assigned tracers, and the MARBL BGC block is appended to the tracer axis automatically.

The file’s tracer order is set by ROMS — the passive slots come right after temp/salt, then the CDR-LiTE OAE pairs and DOR tracers, and the MARBL BGC tracers follow at the end, so the marbl releases’ target tracers (e.g. ALK, DIC) sit at higher itrc indices than the CDR and passive tracers. roms_layout() shows the final ordering as ROMS will interpret it, with the feeding release for each generated tracer:

[15]:
# Two MARBL releases: perturb the MARBL ALK and DIC tracers directly
marbl_oae = TracerPerturbation(
    name="marbl_oae",
    lat=64.0,
    lon=-12,
    depth=100,
    hsc=100000,
    vsc=50,
    tracer_fluxes={"ALK": 1 * 10**6},  # meq/s, into MARBL's ALK tracer
)
marbl_dor = TracerPerturbation(
    name="marbl_dor",
    lat=63.0,
    lon=-24,
    depth=100,
    hsc=100000,
    vsc=50,
    tracer_fluxes={"DIC": -5 * 10**5},  # mmol/s, into MARBL's DIC tracer
)

# Two passive dye releases
dye_a = TracerPerturbation(
    name="dye_a",
    lat=61.5,
    lon=-19,
    depth=50,
    hsc=100000,
    vsc=50,
    tracer_set="passive",
    tracer_fluxes={"passive_tracer": 1 * 10**5},  # mmol/s
)
dye_b = TracerPerturbation(
    name="dye_b",
    lat=62.5,
    lon=-21,
    depth=50,
    hsc=100000,
    vsc=50,
    tracer_set="passive",
    tracer_fluxes={"passive_tracer": 2 * 10**5},  # mmol/s
)

# ...plus the three CDR-LiTE releases from above, in one CDRForcing
cdr_forcing_mixed = CDRForcing(
    grid=grid,
    start_time=simulation_start_time,
    end_time=simulation_end_time,
    releases=[
        marbl_oae,
        dye_a,
        simple_oae,
        simple_dor,
        marbl_dor,
        dye_b,
        simple_combined,
    ],
)

layout = cdr_forcing_mixed.roms_layout()

2026-09-23 16:09:41 - INFO - marbl releases are mixed with generated-tracer releases: appending the MARBL BGC tracers to the file's tracer axis (include_marbl_bgc).
itrc    tracer_name     units   release
1       temp    degrees Celsius/s
2       salt    PSU/s
3       passive_tracer1 mmol/s  dye_a
4       passive_tracer2 mmol/s  dye_b
5       CDR_OAE_ALK1    meq/s   simple_oae
6       CDR_OAE_DIC1    mmol/s  simple_oae
7       CDR_OAE_ALK2    meq/s   simple_combined
8       CDR_OAE_DIC2    mmol/s  simple_combined
9       CDR_DOR_DIC1    mmol/s  simple_dor
10      PO4     mmol/s
11      NO3     mmol/s
12      SiO3    mmol/s
13      NH4     mmol/s
14      Fe      mmol/s
15      Lig     mmol/s
16      O2      mmol/s
17      DIC     mmol/s
18      DIC_ALT_CO2     meq/s
19      ALK     meq/s
20      ALK_ALT_CO2     meq/s
21      DOC     mmol/s
22      DON     mmol/s
23      DOP     mmol/s
24      DOPr    mmol/s
25      DONr    mmol/s
26      DOCr    mmol/s
27      zooC    mmol/s
28      spChl   mg/s
29      spC     mmol/s
30      spP     mmol/s
31      spFe    mmol/s
32      spCaCO3 mmol/s
33      diatChl mg/s
34      diatC   mmol/s
35      diatP   mmol/s
36      diatFe  mmol/s
37      diatSi  mmol/s
38      diazChl mg/s
39      diazC   mmol/s
40      diazP   mmol/s
41      diazFe  mmol/s

ROMS namelist correspondence (set the namelist to match the file):
  nt_passive = 2
  nt_cdr_oae = 2
  nt_cdr_dor = 1
  cdr_ncdr_parm = 7
  MARBL BGC tracers included: True

Saving as NetCDF or YAML File#

We can now save our CDR forcing data as a NetCDF file using the .save method. This file will include the dataset stored in .ds. The resulting NetCDF file can then be used as input for ROMS simulations.

In this example, we focus on the CDR forcing built from VolumeRelease objects, but the same saving procedure applies to CDR forcing created from TracerPerturbation objects.

[65]:
filepath = "my_cdr_forcing.nc"
[66]:
cdr_forcing_with_volume_releases.save(filepath=filepath)
2026-09-03 14:02:16 - INFO - Writing the following NetCDF files:
my_cdr_forcing.nc
[66]:
[PosixPath('my_cdr_forcing.nc')]

We can also export the CDR forcing parameters and their associated release information to a YAML file.

[67]:
yaml_filepath = "my_cdr_forcing.yaml"
[68]:
cdr_forcing_with_volume_releases.to_yaml(yaml_filepath)

This is the YAML file that was created.

[69]:
# Open and read the YAML file
with open(yaml_filepath, "r") as file:
    file_contents = file.read()

# Print the contents
print(file_contents)
---
roms_tools_version: 4.1.2.dev5+g288687ad2.d20260901
---
Grid:
  nx: 250
  ny: 250
  size_x: 2500
  size_y: 2500
  center_lon: -15
  center_lat: 65
  rot: -30
  N: 100
  theta_s: 5.0
  theta_b: 2.0
  hc: 300.0
  topography_source:
    name: SRTM15
    path: /anvil/projects/x-ees250129/Datasets/SRTM15/SRTM15_V2.6.nc
  mask_shapefile: null
  close_narrow_channels: false
  hmin: 5.0
  filename: null
CDRForcing:
  start_time: '2010-01-01T00:00:00'
  end_time: '2012-12-31T00:00:00'
  model_reference_date: '2000-01-01T00:00:00'
  releases:
  - name: iceland_release
    lat: 65.0
    lon: -10.0
    depth: 100.0
    hsc: 0.0
    vsc: 0.0
    times:
    - 2010-01-01 00:00:00
    - 2012-12-31 00:00:00
    time_interpolation: false
    tracer_set: marbl
    release_type: volume
    fill_values: auto
    volume_fluxes:
    - 1000.0
    - 1000.0
    tracer_concentrations:
      temp:
      - 20.0
      - 20.0
      salt:
      - 1.0
      - 1.0
      PO4:
      - 4.2453
      - 4.2453
      NO3:
      - 57.3565
      - 57.3565
      SiO3:
      - 131.2429
      - 131.2429
      NH4:
      - 0.0
      - 0.0
      Fe:
      - 0.07027027027027026
      - 0.07027027027027026
      Lig:
      - 0.21081081081081077
      - 0.21081081081081077
      O2:
      - 270.8750648003977
      - 270.8750648003977
      DIC:
      - 1640.071
      - 1640.071
      DIC_ALT_CO2:
      - 1640.071
      - 1640.071
      ALK:
      - 2000.0
      - 2000.0
      ALK_ALT_CO2:
      - 1173.693
      - 1173.693
      DOC:
      - 460.476
      - 460.476
      DON:
      - 31.61635
      - 31.61635
      DOP:
      - 1.0242011
      - 1.0242011
      DOPr:
      - 0.0
      - 0.0
      DONr:
      - 0.0
      - 0.0
      DOCr:
      - 0.0
      - 0.0
      zooC:
      - 0.0
      - 0.0
      spChl:
      - 0.0
      - 0.0
      spC:
      - 0.0
      - 0.0
      spP:
      - 0.0
      - 0.0
      spFe:
      - 0.0
      - 0.0
      spCaCO3:
      - 0.0
      - 0.0
      diatChl:
      - 0.0
      - 0.0
      diatC:
      - 0.0
      - 0.0
      diatP:
      - 0.0
      - 0.0
      diatFe:
      - 0.0
      - 0.0
      diatSi:
      - 0.0
      - 0.0
      diazChl:
      - 0.0
      - 0.0
      diazC:
      - 0.0
      - 0.0
      diazP:
      - 0.0
      - 0.0
      diazFe:
      - 0.0
      - 0.0
  - name: greenland_release
    lat: 70.0
    lon: -20.0
    depth: 100.0
    hsc: 0.0
    vsc: 0.0
    times:
    - 2010-01-01 00:00:00
    - 2011-01-15 12:00:00
    - 2011-01-15 15:00:00
    - 2011-01-15 18:00:00
    - 2011-01-19 00:00:00
    - 2011-01-22 06:00:00
    - 2011-01-22 09:00:00
    - 2011-01-22 12:00:00
    - 2012-12-31 00:00:00
    time_interpolation: false
    tracer_set: marbl
    release_type: volume
    fill_values: auto
    volume_fluxes:
    - 0.0
    - 0.0
    - 100.0
    - 500.0
    - 1000.0
    - 500.0
    - 100.0
    - 0.0
    - 0.0
    tracer_concentrations:
      temp:
      - 20.0
      - 20.0
      - 20.0
      - 20.0
      - 20.0
      - 20.0
      - 20.0
      - 20.0
      - 20.0
      salt:
      - 1.0
      - 1.0
      - 1.0
      - 1.0
      - 1.0
      - 1.0
      - 1.0
      - 1.0
      - 1.0
      PO4:
      - 4.2453
      - 4.2453
      - 4.2453
      - 4.2453
      - 4.2453
      - 4.2453
      - 4.2453
      - 4.2453
      - 4.2453
      NO3:
      - 57.3565
      - 57.3565
      - 57.3565
      - 57.3565
      - 57.3565
      - 57.3565
      - 57.3565
      - 57.3565
      - 57.3565
      SiO3:
      - 131.2429
      - 131.2429
      - 131.2429
      - 131.2429
      - 131.2429
      - 131.2429
      - 131.2429
      - 131.2429
      - 131.2429
      NH4:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      Fe:
      - 0.07027027027027026
      - 0.07027027027027026
      - 0.07027027027027026
      - 0.07027027027027026
      - 0.07027027027027026
      - 0.07027027027027026
      - 0.07027027027027026
      - 0.07027027027027026
      - 0.07027027027027026
      Lig:
      - 0.21081081081081077
      - 0.21081081081081077
      - 0.21081081081081077
      - 0.21081081081081077
      - 0.21081081081081077
      - 0.21081081081081077
      - 0.21081081081081077
      - 0.21081081081081077
      - 0.21081081081081077
      O2:
      - 270.8750648003977
      - 270.8750648003977
      - 270.8750648003977
      - 270.8750648003977
      - 270.8750648003977
      - 270.8750648003977
      - 270.8750648003977
      - 270.8750648003977
      - 270.8750648003977
      DIC:
      - 1640.071
      - 1640.071
      - 1640.071
      - 1640.071
      - 1640.071
      - 1640.071
      - 1640.071
      - 1640.071
      - 1640.071
      DIC_ALT_CO2:
      - 1640.071
      - 1640.071
      - 1640.071
      - 1640.071
      - 1640.071
      - 1640.071
      - 1640.071
      - 1640.071
      - 1640.071
      ALK:
      - 1900.0
      - 1900.0
      - 2100.0
      - 2100.0
      - 2100.0
      - 2100.0
      - 2100.0
      - 1900.0
      - 1900.0
      ALK_ALT_CO2:
      - 1173.693
      - 1173.693
      - 1173.693
      - 1173.693
      - 1173.693
      - 1173.693
      - 1173.693
      - 1173.693
      - 1173.693
      DOC:
      - 460.476
      - 460.476
      - 460.476
      - 460.476
      - 460.476
      - 460.476
      - 460.476
      - 460.476
      - 460.476
      DON:
      - 31.61635
      - 31.61635
      - 31.61635
      - 31.61635
      - 31.61635
      - 31.61635
      - 31.61635
      - 31.61635
      - 31.61635
      DOP:
      - 1.0242011
      - 1.0242011
      - 1.0242011
      - 1.0242011
      - 1.0242011
      - 1.0242011
      - 1.0242011
      - 1.0242011
      - 1.0242011
      DOPr:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      DONr:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      DOCr:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      zooC:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      spChl:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      spC:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      spP:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      spFe:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      spCaCO3:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diatChl:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diatC:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diatP:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diatFe:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diatSi:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diazChl:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diazC:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diazP:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diazFe:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
  - name: norway_release_zero_fill
    lat: 65.0
    lon: 10.0
    depth: 50.0
    hsc: 0.0
    vsc: 0.0
    times:
    - 2010-01-01 00:00:00
    - 2011-01-15 12:00:00
    - 2011-01-15 15:00:00
    - 2011-01-15 18:00:00
    - 2011-01-19 00:00:00
    - 2011-01-22 06:00:00
    - 2011-01-22 09:00:00
    - 2011-01-22 12:00:00
    - 2012-12-31 00:00:00
    time_interpolation: false
    tracer_set: marbl
    release_type: volume
    fill_values: zero
    volume_fluxes:
    - 0.0
    - 0.0
    - 100.0
    - 500.0
    - 1000.0
    - 500.0
    - 100.0
    - 0.0
    - 0.0
    tracer_concentrations:
      temp:
      - 20.0
      - 20.0
      - 20.0
      - 20.0
      - 20.0
      - 20.0
      - 20.0
      - 20.0
      - 20.0
      salt:
      - 1.0
      - 1.0
      - 1.0
      - 1.0
      - 1.0
      - 1.0
      - 1.0
      - 1.0
      - 1.0
      PO4:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      NO3:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      SiO3:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      NH4:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      Fe:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      Lig:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      O2:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      DIC:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      DIC_ALT_CO2:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      ALK:
      - 1900.0
      - 1900.0
      - 2100.0
      - 2100.0
      - 2100.0
      - 2100.0
      - 2100.0
      - 1900.0
      - 1900.0
      ALK_ALT_CO2:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      DOC:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      DON:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      DOP:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      DOPr:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      DONr:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      DOCr:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      zooC:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      spChl:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      spC:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      spP:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      spFe:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      spCaCO3:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diatChl:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diatC:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diatP:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diatFe:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diatSi:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diazChl:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diazC:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diazP:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      diazFe:
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
      - 0.0
  _tracer_metadata:
    temp:
      units: degrees Celsius
      long_name: potential temperature
    salt:
      units: PSU
      long_name: salinity
    PO4:
      units: mmol/m^3
      long_name: dissolved inorganic phosphate
    NO3:
      units: mmol/m^3
      long_name: dissolved inorganic nitrate
    SiO3:
      units: mmol/m^3
      long_name: dissolved inorganic silicate
    NH4:
      units: mmol/m^3
      long_name: dissolved ammonia
    Fe:
      units: mmol/m^3
      long_name: dissolved inorganic iron
    Lig:
      units: mmol/m^3
      long_name: iron binding ligand
    O2:
      units: mmol/m^3
      long_name: dissolved oxygen
    DIC:
      units: mmol/m^3
      long_name: dissolved inorganic carbon
    DIC_ALT_CO2:
      units: mmol/m^3
      long_name: dissolved inorganic carbon, alternative CO2
    ALK:
      units: meq/m^3
      long_name: alkalinity
    ALK_ALT_CO2:
      units: meq/m^3
      long_name: alkalinity, alternative CO2
    DOC:
      units: mmol/m^3
      long_name: dissolved organic carbon
    DON:
      units: mmol/m^3
      long_name: dissolved organic nitrogen
    DOP:
      units: mmol/m^3
      long_name: dissolved organic phosphorus
    DOPr:
      units: mmol/m^3
      long_name: refractory dissolved organic phosphorus
    DONr:
      units: mmol/m^3
      long_name: refractory dissolved organic nitrogen
    DOCr:
      units: mmol/m^3
      long_name: refractory dissolved organic carbon
    zooC:
      units: mmol/m^3
      long_name: zooplankton carbon
    spChl:
      units: mg/m^3
      long_name: small phytoplankton chlorophyll
    spC:
      units: mmol/m^3
      long_name: small phytoplankton carbon
    spP:
      units: mmol/m^3
      long_name: small phytoplankton phosphorous
    spFe:
      units: mmol/m^3
      long_name: small phytoplankton iron
    spCaCO3:
      units: mmol/m^3
      long_name: small phytoplankton CaCO3
    diatChl:
      units: mg/m^3
      long_name: diatom chloropyll
    diatC:
      units: mmol/m^3
      long_name: diatom carbon
    diatP:
      units: mmol/m^3
      long_name: diatom phosphorus
    diatFe:
      units: mmol/m^3
      long_name: diatom iron
    diatSi:
      units: mmol/m^3
      long_name: diatom silicate
    diazChl:
      units: mg/m^3
      long_name: diazotroph chloropyll
    diazC:
      units: mmol/m^3
      long_name: diazotroph carbon
    diazP:
      units: mmol/m^3
      long_name: diazotroph phosphorus
    diazFe:
      units: mmol/m^3
      long_name: diazotroph iron

Creating CDR forcing from an existing YAML file#

Once we have the YAML file containing the CDR forcing parameters and release information, we can use it to recreate the exact same CDR forcing.

[70]:
the_same_cdr_forcing_with_volume_releases = CDRForcing.from_yaml(yaml_filepath)

The dataset contained in the .ds contains the same three releases as before.

[71]:
the_same_cdr_forcing_with_volume_releases.ds
[71]:
<xarray.Dataset> Size: 17kB
Dimensions:           (time: 9, ncdr: 3, ntracers: 34)
Coordinates:
  * time              (time) datetime64[ns] 72B 2010-01-01 ... 2012-12-31
    release_name      (ncdr) <U24 288B 'iceland_release' ... 'norway_release_...
    tracer_name       (ntracers) <U11 1kB 'temp' 'salt' ... 'diazP' 'diazFe'
    tracer_unit       (ntracers) <U15 2kB 'degrees Celsius' 'PSU' ... 'mmol/m^3'
    tracer_long_name  (ntracers) <U43 6kB 'potential temperature' ... 'diazot...
Dimensions without coordinates: ncdr, ntracers
Data variables:
    cdr_time          (time) float64 72B 3.653e+03 4.032e+03 ... 4.748e+03
    cdr_lon           (ncdr) float64 24B -10.0 -20.0 10.0
    cdr_lat           (ncdr) float64 24B 65.0 70.0 65.0
    cdr_dep           (ncdr) float64 24B 100.0 100.0 50.0
    cdr_hsc           (ncdr) float64 24B 0.0 0.0 0.0
    cdr_vsc           (ncdr) float64 24B 0.0 0.0 0.0
    cdr_volume        (time, ncdr) float64 216B 1e+03 0.0 0.0 ... 1e+03 0.0 0.0
    cdr_tracer        (time, ntracers, ncdr) float64 7kB 20.0 20.0 ... 0.0 0.0
[ ]: