Models
There are 3 main types of models in ODINN.jl, iceflow models, mass balance models, and machine learning models. These three families are determined by abstract types, with specific types being declared as subtypes of these abstract types to ensure compatibility through the ODINN ecosystem.
The three main types of models are gathered in a parent type Model in the following way:
Sleipnir.Model — Type
Model{IFM <: AbstractEmptyModel, MBM <: Union{<:AbstractEmptyModel, Vector{<:AbstractEmptyModel}}, TC <: AbstractEmptyModel}A mutable struct that represents a model with three components: iceflow, mass balance, and machine learning.
Model(
iceflow::IFM,
mass_balance::MBM,
trainable_components::TC,
) where {IFM <: AbstractEmptyModel, MBM <: Union{<:AbstractEmptyModel, Vector{<:AbstractEmptyModel}}, TC <: AbstractEmptyModel}
Model(;iceflow, mass_balance) = Model(iceflow, mass_balance, nothing)Initialize Model (no machine learning model).
Keyword arguments
iceflow::IFM: Represents the iceflow component, which is an instance ofIFM.mass_balance::MBM: Represents the mass balance component. Either a singleMBMinstance or aVector{MBM}of per-glacier models.trainable_components::TC: Represents the trainable components, which is an instance ofTC.
Type Parameters
IFM: A subtype ofAbstractEmptyModelrepresenting the type of the iceflow model.MBM: Either a subtype ofAbstractEmptyModel(single shared model) or aVectorof such subtypes (per-glacier models). The field stores the exact runtime type — noUnionin the field — which is required for AD compatibility.TC: A subtype ofAbstractEmptyModelrepresenting the type of the trainable components.
Sleipnir.Model — Method
Model(;
iceflow::Union{<: AbstractModel, Nothing} = nothing,
mass_balance::Union{<: AbstractModel, Nothing} = nothing,
regressors::Union{NamedTuple, Nothing} = nothing
)Creates a new model instance using the provided iceflow, mass balance, and machine learning components.
Arguments
iceflow::Union{IFM, Nothing}: The iceflow model to be used. Can be a single model ornothing.mass_balance::Union{MBM, Nothing}: The mass balance model to be used. Can be a single model ornothing.regressors::Union{NamedTuple, Nothing}: The regressors to be used in the laws.
Returns
model: A new instance ofSleipnir.Modelinitialized with the provided components.
Note
Since Model is a keyword argument function, it is defined in Sleipnir. Otherwise we would have to define it multiple times, making the ODINN precompilation impossible because of method overwriting.
Ice flow models
Ice flow models are used to solve the PDEs describing the gravitational flow of glaciers. All ice flow models must be a subtype of abstract type IceflowModel. Ice flow models are managed by the Huginn.jl package.
The main type of ice flow model used in ODINN.jl right now is a 2D Shallow Ice Approximation (SIA) model (Hutter, 1983). This is declared in the following way:
Huginn.SIA2Dmodel — Type
SIA2Dmodel(A, C, n, Y, U, n_H, n_∇S)
SIA2Dmodel(params; A, C, n, Y, U, n_H, n_∇S)Create a SIA2Dmodel, representing a two-dimensional Shallow Ice Approximation (SIA) model.
The SIA model describes glacier flow under the assumption that deformation and basal sliding dominate the ice dynamics. It relies on:
Glen's flow law for internal deformation, with flow rate factor
Aand exponentn,A sliding law governed by coefficient
C,Optionally the user can provide either:
- A specific diffusive velocity
Usuch thatD = U * H - A modified creep coefficient
Ythat takes into account the ice thickness such thatD = (C + Y * 2/(n+2)) * (ρ*g)^n * H^(n_H+1) * |∇S|^(n_∇S-1)wheren_Handn_∇Sare optional parameters that control if the SIA should use thenlaw or not. This formulation is denoted as the hybrid diffusivity in the code.
- A specific diffusive velocity
This struct stores the laws used to compute these three parameters during a simulation. If not provided, default constant laws are used based on glacier-specific values.
Arguments
A: Law for the flow rate factor. Defaults to a constant value from the glacier.C: Law for the sliding coefficient. Defaults similarly.n: Law for the flow law exponent. Defaults similarly.p: Law for the sliding law exponent (basal drag). Defaults similarly.q: Law for the sliding law exponent (normal stress). Defaults similarly.Y: Law for the hybrid diffusivity. Providing a law forYdiscards the laws ofA,Candn.U: Law for the diffusive velocity. Defaults behavior is to disable it and in such a case it is computed fromA,Candn. Providing a law forUdiscards the laws ofA,C,nandY.n_H::F: The exponent to use forHin the SIA equation when using the Y law (hybrid diffusivity). It should benothingwhen this law is not used.n_∇S::F: The exponent to use for∇Sin the SIA equation when using the Y law (hybrid diffusivity). It should benothingwhen this law is not used.Y_is_provided::Bool: Whether the diffusivity is provided by the user through the hybrid diffusivityYor it has to be computed from the SIA formula fromA,Candn.U_is_provided::Bool: Whether the diffusivity is provided by the user through the diffusive velocityUor it has to be computed from the SIA formula fromA,Candn.n_H_is_provided::Bool: Whether theHexponent is prescribed by the user, or the one of thenlaw has to be used. This flag is used only when a law forYis used.n_∇S_is_provided::Bool: Whether the∇Sexponent is prescribed by the user, or the one of thenlaw has to be used. This flag is used only when a law forYis used.apply_A_in_SIA::Bool: Whether the value of theAlaw should be computed each time the SIA is evaluated.apply_C_in_SIA::Bool: Whether the value of theClaw should be computed each time the SIA is evaluated.apply_n_in_SIA::Bool: Whether the value of thenlaw should be computed each time the SIA is evaluated.apply_p_in_SIA::Bool: Whether the value of theplaw should be computed each time the SIA is evaluated.apply_q_in_SIA::Bool: Whether the value of theqlaw should be computed each time the SIA is evaluated.apply_Y_in_SIA::Bool: Whether the value of theYlaw should be computed each time the SIA is evaluated.apply_U_in_SIA::Bool: Whether the value of theUlaw should be computed each time the SIA is evaluated.
When a simulation will be run in ODINN.jl using an ice flow model, its related equation will be solved using OrdinaryDiffEq.jl. The related equation to a SIA2Dmodel is declared in its related util functions. These equations need to be defined in-place (to reduce memory allocations and ensure maximum performance, see example below). This is both compatible with the forward runs and with the reverse pass differentiated using Enzyme.jl.
Huginn.SIA2D! — Function
SIA2D!(
dH::Matrix{R},
H::Matrix{R},
simulation::SIM,
t::R,
θ,
) where {R <:Real, SIM <: Simulation}Simulates the evolution of ice thickness in a 2D shallow ice approximation (SIA) model. Works in-place.
Arguments
dH::Matrix{R}: Matrix to store the rate of change of ice thickness.H::Matrix{R}: Matrix representing the ice thickness.simulation::SIM: Simulation object containing model parameters and state.t::R: Current simulation time.θ: Parameters of the laws to be used in the SIA. Can benothingwhen no learnable laws are used.
Details
This function updates the ice thickness H and computes the rate of change dH using the shallow ice approximation in 2D. It retrieves necessary parameters from the simulation object, enforces positive ice thickness values, updates glacier surface altimetry and computes surface gradients. It then applies the necessary laws that are not updated via callbacks (A, C, n or U depending on the use-case) and computes the flux components, and flux divergence.
Notes
- The function operates on a staggered grid for computing gradients and fluxes.
- Surface elevation differences are capped using upstream ice thickness to impose boundary conditions.
- The function modifies the input matrices
dHandHin-place.
See also SIA2D
Mass balance models
(Surface) Mass balance models are used to simulate the simplified thermodynamics of the forcing of the atmosphere on glaciers. As per ice flow models, all specific mass balance models needs to be a subtype of the abstract type MBmodel. Mass balance models are managed by Muninn.jl. For now, we have simple temperature-index models, with either one or two degree-day factors (DDFs), Hock (2003) [2]:
Muninn.TImodel1 — Type
TImodel1{F <: AbstractFloat}A structure representing a temperature index model with degree-day factor and precipitation correction factor.
Keyword arguments
DDF::F: Degree-day factor (m w.e. °C⁻¹ d⁻¹), which converts positive degree days to melt.prcp_fac::F: Dimensionless precipitation correction factor applied as a multiplier to the snowfall field before computing accumulation. A value of1.0(default) leaves precipitation unchanged; values > 1 increase accumulation (useful when the climate input underestimates solid precipitation), values < 1 reduce it.
Type Parameters
temp_bias::F: Uniform temperature bias (°C) added to the glacier's climate before computing melt and snow/rain partitioning.0.0(default) leaves the climate unchanged. Used as the third calibration lever whenDDFandprcp_facalone cannot bracket the geodetic mass-balance target.F: A subtype ofAbstractFloatrepresenting the type of the factors.
Note: The unit conversion from mm to m w.e. is handled internally via the constant PRECIP_UNIT_CONVERSION (1/1000) and is not a tunable parameter.
Muninn.TImodel1 — Method
TImodel1(params::Sleipnir.Parameters; DDF::F = 7.0/1000.0, prcp_fac::F = 1.0, temp_bias::F = 0.0) where {F <: AbstractFloat}Create a temperature index model with one degree-day factor (DDF) with the given parameters.
Arguments
params::Sleipnir.Parameters: The simulation parameters.DDF::F: Degree-day factor in m w.e. °C⁻¹ d⁻¹ (default is 7.0/1000.0 = 0.007).prcp_fac::F: Dimensionless precipitation correction factor (default is 1.0).temp_bias::F: Uniform temperature bias in °C (default is 0.0).
Returns
TI1_model: An instance of TImodel1 with the specified parameters.
Note: Precipitation unit conversion (mm → m w.e.) is handled internally via PRECIP_UNIT_CONVERSION.
Surface mass balance models are run in DiscreteCallbacks from OrdinaryDiffEq.jl, which enable the safe execution during the solving of a PDE in specifically prescribed time steps determined in the steps field in Sleipnir.SimulationParameters.
Calibrating a temperature-index model
Temperature-index models can be calibrated per glacier against geodetic mass balance observations, by default the 2000–2020 estimates of Hugonnet et al. (2021) [4], which initialize_glaciers stores in glacier.dhdtData. The high-level entry point is:
Muninn.calibrate_MB_model — Function
calibrate_MB_model(
model::Sleipnir.Model,
glaciers::Vector{<:AbstractGlacier},
params::Parameters,
) -> Sleipnir.ModelHigh-level entry point that calibrates the mass balance model of model per glacier against geodetic observations, returning a new model.
Calibration cannot happen in place: for TImodel1 it replaces a single model by one model per glacier, which changes the type of the mass_balance field. Callers must use the return value; discarding it silently keeps the uncalibrated model.
The behaviour dispatches on the mass balance model type:
TImodel1: returns a newModelwhosemass_balanceis a per-glacier vector ofTImodel1s, each fitted against its glacier's geodetic mass balance withcalibrate_ti_model. Glaciers withoutdhdtDatakeep the original (uncalibrated) model and a warning is emitted. If no glacier carries geodetic data, the model is returned unchanged.- any other
MBmodel: no-op — no calibration routine is defined, so the model is returned untouched. Add a_calibrate_MB_model(model, ::YourType, …)method to support a new model type.
An already-vectorized (per-glacier) mass balance model is left unchanged. Any keyword arguments are forwarded to the type-specific calibrator (e.g. calibrate_ti_model for TImodel1). This is the function the Prediction and Inversion constructors call when params.simulation.calibrate_MB is true.
Calibration cannot happen in place: for TImodel1 it replaces a single model by one model per glacier, which changes the type of the mass_balance field. Discarding the return value silently keeps the uncalibrated model.
For each glacier the calibration follows a three-step cascade analogous to OGGM v1.6 [3], using Brent's method at each step. It first fits DDF; if DDF alone cannot bracket the observed mass balance it falls back to prcp_fac, and finally to a uniform temp_bias. By default prcp_fac is derived per glacier from mean winter precipitation rather than fixed globally.
Muninn.calibrate_ti_model — Function
calibrate_ti_model(
glacier::AbstractGlacier,
params::Parameters;
DDF_bounds::Tuple{Sleipnir.Float, Sleipnir.Float} = (
Sleipnir.Float(params.physical.DDF_min),
Sleipnir.Float(params.physical.DDF_max)),
prcp_fac_bounds::Tuple{Sleipnir.Float, Sleipnir.Float} = (
Sleipnir.Float(params.physical.prcp_fac_min),
Sleipnir.Float(params.physical.prcp_fac_max)),
temp_bias_bounds::Tuple{Sleipnir.Float, Sleipnir.Float} = (
Sleipnir.Float(params.physical.temp_bias_min),
Sleipnir.Float(params.physical.temp_bias_max)),
density_ratio::Sleipnir.Float = Sleipnir.Float(1.0),
calibration_period::Union{Nothing, Tuple{Sleipnir.Float, Sleipnir.Float}} = nothing,
prcp_fac::Union{Symbol, Real} = :from_winter_prcp,
step::Sleipnir.Float = Sleipnir.Float(1.0 / 12.0),
) -> TImodel1Calibrate TImodel1 for a single glacier against the geodetic mass balance stored in glacier.dhdtData (e.g. the 2000-2020 observations from Hugonnet et al. 2021).
The calibration follows a 3-step cascade analogous to the OGGM v1.6 approach, using Brent's method for root-finding at each step:
DDF step: With
prcp_facfixed (glacier-specific from winter precipitation by default, see theprcp_fackeyword) andtemp_bias = 0.0, find the degree-day factor that matches the geodetic MB target. If the target can be bracketed withinDDF_bounds, this step alone produces the calibrated model.prcp_facstep (fallback): If the DDF search cannot bracket the target, DDF is fixed at its boundary value andprcp_facis varied withinprcp_fac_bounds. A warning is emitted when this fallback is used.temp_biasstep (fallback): If both previous steps fail, DDF andprcp_facare fixed at their best boundary values and a uniform temperature bias (°C) is varied withintemp_bias_bounds. This handles glaciers where the climate forcing is systematically biased for the glacier's hypsometry (e.g. high-elevation accumulation overestimation in coarse reanalysis data).
A static glacier geometry is assumed throughout (no ice-flow dynamics).
Arguments
glacier::AbstractGlacier: Glacier whosedhdtDatafield contains the observed geodetic mass balance (seeDhdtData).params::Parameters: Simulation parameters (provides physical bounds).
Keyword arguments
DDF_bounds: Search interval(DDF_min, DDF_max)in m w.e. °C⁻¹ d⁻¹. Default: fromparams.physical.prcp_fac_bounds: Search interval for the precipitation correction factor (dimensionless). Default: fromparams.physical.temp_bias_bounds: Search interval for the temperature bias (°C). Default: fromparams.physical.density_ratio: Conversion factor applied toglacier.dhdtData.dhdt. Useparams.physical.ρ / params.physical.ρ_w ≈ 0.9when the data are in m ice yr⁻¹, or1.0(default) for m w.e. yr⁻¹ (Hugonnet et al. 2021).calibration_period: Time window(t_start, t_end)in fractional years. Defaults toglacier.dhdtData.t.prcp_fac: Precipitation factor used in the DDF step.:from_winter_prcp(default) derives a glacier-specific factor from winter precipitation viaSleipnir.get_winter_prcp_factor; aRealvalue fixes it (e.g.2.5for OGGM's global W5E5 default). Only used in step 1; the fallback steps still searchprcp_fac_bounds.step: Integration timestep in fractional years. Default:1/12(monthly).
Returns
- A
TImodel1{Sleipnir.Float}with calibratedDDF,prcp_fac, andtemp_bias.
This is the per-glacier building block used by calibrate_MB_model to build a per-glacier vector of calibrated models.
References
Hugonnet, R. et al. (2021). Accelerated global glacier mass loss in the early twenty-first century. Nature, 592, 726–731. https://doi.org/10.1038/s41586-021-03436-z
Maussion, F., Butenko, A., Eis, J., Fourteau, K., Jarosch, A. H., Landmann, J., Oesterle, F., Recinos, B., USias, S., Valsecchi, L., Marzeion, B., and Cogley, J. G. (2019). The Open Global Glacier Model (OGGM) v1.1. Geoscientific Model Development, 12, 909–941. https://doi.org/10.5194/gmd-12-909-2019
Sleipnir.get_winter_prcp_factor — Function
get_winter_prcp_factor(glacier::AbstractGlacier, params::Parameters; prcp_fac_bounds) -> FloatCompute a glacier-specific precipitation correction factor from mean winter precipitation, following OGGM's decide_winter_precip_factor.
Arguments
glacier::AbstractGlacier: Glacier providingrgi_id,cenlat(hemisphere) andclimate.climate_data_source(:W5E5or:ERA5).params::Parameters: Simulation parameters, used to locate the climate file.prcp_fac_bounds: Lower/upper clip bounds (default(0.1, 10.0), matching OGGM).
Description
Reads the full daily climate record (1979–2019, the window OGGM's coefficients were fit on), averages winter daily precipitation (NH: Oct–Apr, SH: Apr–Oct) in kg/m²/day, and maps it to a precipitation factor: log fit for W5E5, linear fit for ERA5. If the daily file is missing, falls back to 2.5. Note: we average daily values directly (OGGM averages monthly daily-rates; equivalent within noise).
Because calibration produces one mass balance model per glacier, use get_mb_model to retrieve the one belonging to a given glacier index:
Muninn.get_mb_model — Function
get_mb_model(mass_balance, glacier_idx::Integer = 1)Return the mass balance model for a given glacier from a Model's mass_balance field.
When mass_balance is a per-glacier vector of models (e.g. one calibrated TImodel1 per glacier), the glacier_idx-th entry is returned. When it is a single shared model, that model is returned regardless of glacier_idx. This is the single accessor used throughout the ecosystem so that callers never need to branch on whether the mass balance model is vectorized.
To evaluate a calibrated model, compute_mean_annual_MB returns the glacier-wide scalar that the calibration targets, and compute_cumulative_MB the underlying gridded field:
Muninn.compute_mean_annual_MB — Function
compute_mean_annual_MB(
mb_model::TImodel1,
glacier::AbstractGlacier,
t_start::F,
t_end::F;
step::F = F(1.0 / 12.0),
) where {F <: AbstractFloat} -> Sleipnir.FloatGlacier-wide mean annual mass balance (m w.e. yr⁻¹) over [t_start, t_end] with static geometry. Spatially averages the output of compute_cumulative_MB over the ice-covered area and normalises to an annual rate.
Muninn.compute_cumulative_MB — Function
compute_cumulative_MB(
mb_model::TImodel1,
glacier::AbstractGlacier,
t_start::F,
t_end::F;
step::F = F(1.0 / 12.0),
) where {F <: AbstractFloat} -> Matrix{Sleipnir.Float}Accumulate the gridded mass balance (m w.e.) over [t_start, t_end] using a static glacier geometry. Returns the full 2D cumulative MB field, suitable for spatial visualisation via plot_cumulative_mb.
See compute_mean_annual_MB for the glacier-wide scalar summary.
See the SMB calibration tutorial for a full worked example.
Neural network-based surface mass balance models trained with MassBalanceMachine are also supported. They can be loaded via the MassBalanceMachine.jl package and used directly as drop-in MBmodels. Pre-trained models are exported from Python as a pair of JSON files (params.json and model.json) and loaded as follows:
using MassBalanceMachine
mlp = CustomMLP("path/to/params.json", "path/to/model.json")
model = Model(
iceflow = SIA2Dmodel(params),
mass_balance = mlp
)CustomMLP is a subtype of MBmodel and wraps a Lux.jl feedforward network whose architecture, input feature normalisation bounds, and pre-trained weights are all read directly from the JSON export. The network takes monthly ERA5 climate features as inputs (e.g. t2m, tp, ssrd, …) and outputs a surface mass balance rate in m w.e. per time step. For now, only monthly time steps are supported. It is the de facto data-driven surface mass balance model in the ODINN ecosystem.
Once loaded, models can be saved to a local registry to avoid re-parsing JSON on subsequent runs:
save_model(mlp, "norway_nongeo") # saves to ~/.MassBalanceMachine/models/
mlp = load_model("norway_nongeo") # fast retrieval by nameInternally, MassBalanceMachine.jl converts those exported Pytorch MLPs into Lux.jl, to be compatible with the whole Julia and ODINN.jl ecosystem. We also provide an API to download pre-trained models which are stored in our HuggingFace MLP repository. This API simply retrieves the params.json and model.json files associated to a pre-trained model. The list of available pre-trained models and their characteristics are given in this repository. For example you can load one very simple model trained with the WGMS on region 11 (European Alps) and register it in your local registry by running:
download_MLP("mlp_noSvf_wgms11_small_0.1")See the MassBalanceMachine.jl repository for more details on model training, and the full registry API.
Regressors
Regressors (e.g. machine learning models) are used in the context of Universal Differential Equations [1] to parametrize or learn specific parts of differential equations. Machine Learning models are managed by ODINN.jl. Virtually all available regressors in Julia can be used inside ODINN, but they need to be correctly interfaced. Here is an example of a simple neural network (multilayer perceptron) using Lux.jl:
ODINN.NeuralNetwork — Type
NeuralNetwork{
ChainType <: Lux.Chain,
ComponentVectorType <: ComponentVector,
NamedTupleType <: NamedTuple,
} <: FunctionalModelFeed-forward neural network.
Fields
architecture::ChainType:Flux.Chainneural network architectureθ::ComponentVectorType: Neural network parametersst::NamedTupleType: Neural network status
In order to parametrize a given variable inside an (ice flow) model, one can do it via the regressors keyword in Model:
nn_model = NeuralNetwork(params)
A_law = LawA(nn_model, params)
model = Model(
iceflow = SIA2Dmodel(params; A = A_law),
mass_balance = TImodel1(params; DDF = 6.0/1000.0, prcp_fac = 1.2),
regressors = (; A = nn_model)
)