The EarthSystemModel
The EarthSystemModel is the central abstraction in NumericalEarth. It wraps an ocean, a sea ice component, and an atmosphere into a single object that can be handed to Simulation and advanced with run!, just like any Oceananigans model! Internally, it takes care of computing turbulent fluxes between components and communicating those fluxes as boundary conditions.
Let's start with the simplest possible coupled model: an ocean column with no atmosphere and no sea ice, we have an alias for that: an OceanOnlyModel.
grid = RectilinearGrid(size=10, z=(-100, 0), topology=(Flat, Flat, Bounded))
ocean = ocean_simulation(grid, timestepper = :QuasiAdamsBashforth2)
model = OceanOnlyModel(ocean)
# output
EarthSystemModel{CPU}(time = 0 seconds, iteration = 0)
├── ocean: HydrostaticFreeSurfaceModel{CPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── atmosphere: Nothing
├── sea_ice: FreezingLimitedOceanTemperature{ClimaSeaIce.SeaIceThermodynamics.LinearLiquidus{Float64}}
└── interfaces: ComponentInterfacesEven though we didn't specify atmosphere or sea ice, we got both. The atmosphere is Nothing – no atmospheric forcing at all. The sea ice is FreezingLimitedOceanTemperature, which is not really a sea ice model: it simply clamps the ocean temperature to the local freezing point, preventing supercooling. No ice thickness or concentration is tracked.
Since EarthSystemModel conforms to the Oceananigans AbstractModel interface, we can build a Simulation, attach callbacks and output writers, and run it:
using Oceananigans.Units
using Logging
simulation = Simulation(model, Δt=20minutes, stop_time=1hour)
with_logger(NullLogger()) do
run!(simulation)
end
model
# output
EarthSystemModel{CPU}(time = 1 hour, iteration = 3)
├── ocean: HydrostaticFreeSurfaceModel{CPU, RectilinearGrid}(time = 1 hour, iteration = 3)
├── atmosphere: Nothing
├── sea_ice: FreezingLimitedOceanTemperature{ClimaSeaIce.SeaIceThermodynamics.LinearLiquidus{Float64}}
└── interfaces: ComponentInterfacesThree time steps of 20 minutes each, and we've reached 1 hour.
Three constructors for three levels of complexity
NumericalEarth provides three constructors. They all return the same type – EarthSystemModel – but differ in what they ask you to provide.
OceanOnlyModel
NumericalEarth.EarthSystemModels.OceanOnlyModel — Function
OceanOnlyModel(ocean; atmosphere=nothing, radiation=Radiation(), kw...)Construct an ocean-only model without a sea ice component. This is a convenience constructor for EarthSystemModel that sets sea_ice to FreezingLimitedOceanTemperature (a simple freezing limiter that does not evolve sea ice variables).
The atmosphere keyword can be used to specify a prescribed atmospheric forcing (e.g., JRA55PrescribedAtmosphere). All other keyword arguments are forwarded to EarthSystemModel.
using NumericalEarth
using Oceananigans
grid = RectilinearGrid(size=10, z=(-100, 0), topology=(Flat, Flat, Bounded))
ocean = ocean_simulation(grid, timestepper = :QuasiAdamsBashforth2)
model = OceanOnlyModel(ocean)
# output
EarthSystemModel{CPU}(time = 0 seconds, iteration = 0)
├── ocean: HydrostaticFreeSurfaceModel{CPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── atmosphere: Nothing
├── sea_ice: FreezingLimitedOceanTemperature{ClimaSeaIce.SeaIceThermodynamics.LinearLiquidus{Float64}}
└── interfaces: ComponentInterfacesWe already used this one above. It takes an ocean simulation and optionally a prescribed atmosphere. Sea ice is set to FreezingLimitedOceanTemperature automatically. This is the right starting point when you don't need sea ice evolution.
OceanSeaIceModel
NumericalEarth.EarthSystemModels.OceanSeaIceModel — Function
OceanSeaIceModel(ocean, sea_ice; atmosphere=nothing, radiation=Radiation(), kw...)Construct a coupled ocean–sea ice model. This is a convenience constructor for EarthSystemModel with an explicit sea ice component and an optional prescribed atmosphere.
The atmosphere keyword can be used to specify a prescribed atmospheric forcing (e.g., JRA55PrescribedAtmosphere). All other keyword arguments are forwarded to EarthSystemModel.
using NumericalEarth
using Oceananigans
grid = RectilinearGrid(size=10, z=(-100, 0), topology=(Flat, Flat, Bounded))
ocean = ocean_simulation(grid, timestepper = :QuasiAdamsBashforth2)
sea_ice = FreezingLimitedOceanTemperature()
model = OceanSeaIceModel(ocean, sea_ice)
# output
EarthSystemModel{CPU}(time = 0 seconds, iteration = 0)
├── ocean: HydrostaticFreeSurfaceModel{CPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── atmosphere: Nothing
├── sea_ice: FreezingLimitedOceanTemperature{ClimaSeaIce.SeaIceThermodynamics.LinearLiquidus{Float64}}
└── interfaces: ComponentInterfacesWhen you want prognostic sea ice, use OceanSeaIceModel. It takes an ocean simulation and a sea ice component as positional arguments:
ocean = ocean_simulation(grid, timestepper = :QuasiAdamsBashforth2)
sea_ice = FreezingLimitedOceanTemperature()
model = OceanSeaIceModel(ocean, sea_ice)
# output
EarthSystemModel{CPU}(time = 0 seconds, iteration = 0)
├── ocean: HydrostaticFreeSurfaceModel{CPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── atmosphere: Nothing
├── sea_ice: FreezingLimitedOceanTemperature{ClimaSeaIce.SeaIceThermodynamics.LinearLiquidus{Float64}}
└── interfaces: ComponentInterfacesHere we passed FreezingLimitedOceanTemperature again, but a "real" sea ice simulation, built with sea_ice_simulation which wraps ClimaSeaIce.SeaIceModel in an Oceananigans Simulation, would also work.
EarthSystemModel
NumericalEarth.EarthSystemModels.EarthSystemModel — Type
EarthSystemModel(atmosphere, ocean, sea_ice;
radiation = Radiation(),
clock = Clock{Float64}(time=0),
ocean_reference_density = reference_density(ocean),
ocean_heat_capacity = heat_capacity(ocean),
sea_ice_reference_density = reference_density(sea_ice),
sea_ice_heat_capacity = heat_capacity(sea_ice),
interfaces = nothing)Construct a coupled earth system model with an atmosphere, ocean, and sea ice component. For simpler configurations, see OceanOnlyModel and OceanSeaIceModel.
Arguments
atmosphere: A representation of a possibly time-dependent atmospheric state. For a prognostic atmosphere, useatmosphere_simulation. For prescribed atmospheric forcing, useJRA55PrescribedAtmosphereorPrescribedAtmosphere.ocean: A representation of a possibly time-dependent ocean state. Currently, onlyOceananigans.Simulations ofOceananigans.HydrostaticFreeSurfaceModelare tested.sea_ice: A representation of a possibly time-dependent sea ice state. For example, the minimalistFreezingLimitedOceanTemperaturerepresents oceanic latent heating during freezing only, but does not evolve sea ice variables. For prognostic sea ice use anOceananigans.SimulationofClimaSeaIce.SeaIceModel.
Keyword Arguments
radiation: Radiation component used to compute surface fluxes at the bottom of the atmosphere.clock: Keeps track of time.ocean_reference_density: Reference density for the ocean. Defaults to value from ocean model.ocean_heat_capacity: Heat capacity for the ocean. Defaults to value from ocean model.sea_ice_reference_density: Reference density for sea ice. Defaults to value from sea ice model.sea_ice_heat_capacity: Heat capacity for sea ice. Defaults to value from sea ice model.interfaces: Component interfaces for coupling. Defaults tonothingand will be constructed automatically. To customize the sea ice-ocean heat flux formulation, create interfaces manually usingComponentInterfaces.
Stability Functions
The model uses similarity theory for turbulent fluxes between components. You can customize the stability functions by creating a new SimilarityTheoryFluxes object with your desired stability functions. For example:
using NumericalEarth
using Oceananigans
grid = RectilinearGrid(size=10, z=(-100, 0), topology=(Flat, Flat, Bounded))
ocean = ocean_simulation(grid, timestepper = :QuasiAdamsBashforth2)
# Three choices for stability function:
# "No stability function", which also apply to neutral boundary layers
stability_functions = nothing
# Atmosphere-sea ice specific stability functions
stability_functions = NumericalEarth.EarthSystemModels.atmosphere_sea_ice_stability_functions(Float64)
# Edson et al. (2013) stability functions
stability_functions = NumericalEarth.EarthSystemModels.atmosphere_ocean_stability_functions(Float64)
atmosphere_ocean_fluxes = SimilarityTheoryFluxes(; stability_functions)
interfaces = NumericalEarth.EarthSystemModels.ComponentInterfaces(nothing, ocean; atmosphere_ocean_fluxes)
model = OceanOnlyModel(ocean; interfaces)
# output
EarthSystemModel{CPU}(time = 0 seconds, iteration = 0)
├── ocean: HydrostaticFreeSurfaceModel{CPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── atmosphere: Nothing
├── sea_ice: FreezingLimitedOceanTemperature{ClimaSeaIce.SeaIceThermodynamics.LinearLiquidus{Float64}}
└── interfaces: ComponentInterfacesThe available stability function options include:
atmosphere_ocean_stability_functions: Based on Edson et al. (2013)atmosphere_sea_ice_stability_functions: Specifically designed for atmosphere-sea ice interactionsnothing: No stability functions will be used- Custom stability functions can be created by defining functions of the "stability parameter" (the flux Richardson number),
ζ.
The full constructor takes positional arguments (atmosphere, ocean, sea_ice) and gives access to every knob: radiation parameters, reference densities, heat capacities, and – most importantly – the interfaces keyword, which controls how fluxes are computed.
Customizing flux formulations
The interfaces field of an EarthSystemModel is a ComponentInterfaces object. If you don't pass one, it gets built automatically with default settings: similarity-theory fluxes for the atmosphere–surface interface and a three-equation thermodynamic model for the sea ice–ocean interface.
To change the defaults, construct ComponentInterfaces yourself and pass it in. For example, to use constant transfer coefficients instead of similarity theory:
atmosphere_ocean_fluxes = CoefficientBasedFluxes(drag_coefficient=2e-3)
interfaces = NumericalEarth.EarthSystemModels.ComponentInterfaces(nothing, ocean;
atmosphere_ocean_fluxes)
model = OceanOnlyModel(ocean; interfaces)
# output
EarthSystemModel{CPU}(time = 0 seconds, iteration = 0)
├── ocean: HydrostaticFreeSurfaceModel{CPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── atmosphere: Nothing
├── sea_ice: FreezingLimitedOceanTemperature{ClimaSeaIce.SeaIceThermodynamics.LinearLiquidus{Float64}}
└── interfaces: ComponentInterfacesOr to use similarity theory with specific stability functions:
stability_functions = NumericalEarth.EarthSystemModels.atmosphere_ocean_stability_functions(Float64)
atmosphere_ocean_fluxes = SimilarityTheoryFluxes(; stability_functions)
interfaces = NumericalEarth.EarthSystemModels.ComponentInterfaces(nothing, ocean;
atmosphere_ocean_fluxes)
model = OceanOnlyModel(ocean; interfaces)
# output
EarthSystemModel{CPU}(time = 0 seconds, iteration = 0)
├── ocean: HydrostaticFreeSurfaceModel{CPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── atmosphere: Nothing
├── sea_ice: FreezingLimitedOceanTemperature{ClimaSeaIce.SeaIceThermodynamics.LinearLiquidus{Float64}}
└── interfaces: ComponentInterfacesThe choices for stability functions are:
atmosphere_ocean_stability_functions: based on Edson et al. (2013), the default for ocean surfaces.atmosphere_sea_ice_stability_functions: tuned for atmosphere–sea ice interactions.nothing: neutral boundary layer (no stability correction).
For the sea ice–ocean interface, the two available heat flux formulations are ThreeEquationHeatFlux (default) and IceBathHeatFlux. See the Interface fluxes page for a full treatment.
What happens at construction
When you call any of the three constructors, a few things happen behind the scenes:
- Certain callbacks are stripped from the ocean and sea ice simulations (
stop_time_exceeded,nan_checker, etc.), because the coupled model manages stopping conditions itself. - If no
interfaceswas provided, aComponentInterfacesobject is built with defaults. - The ocean temperature field is clamped above the local freezing point.
- Interface fluxes are computed for the initial state.
How time-stepping works
Each call to time_step! advances the coupled system by Δt:
- The sea ice is stepped forward (if present).
- The ocean is stepped forward.
- The atmosphere is stepped forward (if prognostic).
- The clock is ticked.
- Component states are interpolated onto the shared exchange grid, interface fluxes are recomputed, and the resulting boundary conditions are communicated back to each component.
Checkpointing
EarthSystemModel supports Oceananigans' checkpointing infrastructure. The functions prognostic_state and restore_prognostic_state! capture and restore the full state of all components – ocean, atmosphere, sea ice, clock, and interfaces – so simulations can be restarted from any saved checkpoint.