Skip to contents

⬇ Skip to Parameter Library

Introduction

The General Lake Model (GLM)

The General Lake Model (GLM) is a one-dimensional (1-D), variable-layer hydrodynamic model for lakes, reservoirs, and estuaries. Originally described by Hipsey et al. (2019), GLM simulates the vertical profiles of temperature, salinity, and density driven by surface heat exchange, shortwave radiation, wind mixing, inflows, and outflows. Its key architectural features include:

  • Variable-thickness layer scheme — layers merge and split dynamically, giving high resolution where it is needed (near the thermocline) and coarser resolution elsewhere.
  • Flexible inflow/outflow — supports multiple rivers, surface spillways, and subsurface outlets at arbitrary elevations.
  • Rich surface heat-flux options — sensible heat, latent heat, longwave, and shortwave radiation computed from standard meteorological inputs.
  • Sediment heat exchange — allows depth-varying bed temperatures to drive conductive heat flux into the water column.

There is more detailed GLM documentation available from the GLM website,

Source code: :github: https://github.com/AquaticEcoDynamics/GLM/tree/master

Binaries: :github: https://github.com/AquaticEcoDynamics/Binaries

References

Hipsey, M.R., Bruce, L.C., Boon, C., Busch, B., Carey, C.C., Hamilton, D.P., Hanson, P.C., Read, J.S., de Sousa, E., Weber, M. & Winslow, L.A. (2019). A General Lake Model (GLM 3.0) for linking with high-frequency sensor data from the Global Lake Ecological Observatory Network (GLEON). Geoscientific Model Development, 12, 473–523. https://doi.org/10.5194/gmd-12-473-2019


The Aquatic Ecosystem Dynamics (AED) library

The Aquatic Ecodynamics (AED) library (Hipsey et al., 2013) is a modular biogeochemical modelling framework designed to be coupled with hydrodynamic models such as GLM. Each biogeochemical process is implemented as a self-contained module that can be switched on or off independently, making it easy to build simulations that range from simple oxygen tracking through to full nutrient–phytoplankton–zooplankton food-web dynamics.

There is a detailed AED manual available from the AED website, but here we provide a brief overview of the key modules relevant for GLM-AED.

AED modules available in AEME
AED biogeochemical modules available in AEME.
Module AED name Description
Sediment flux aed_sedflux Sediment–water interface exchange of O2, nutrients, and silica. Supports constant, constant-2D (depth-zone-specific), and dynamic flux models.
Oxygen aed_oxygen Dissolved-oxygen dynamics including reaeration, sediment oxygen demand (SOD), and photosynthesis/respiration coupling.
Silica aed_silica Reactive silica cycling — important for diatom growth.
Nitrogen aed_nitrogen Full dissolved inorganic nitrogen cycle: ammonium (NH4+), nitrite (NO2−), nitrate (NO3−), N2O; nitrification, denitrification, N-fixation.
Phosphorus aed_phosphorus Dissolved reactive phosphorus (DRP / FRP) including redox-sensitive sediment release.
Organic matter aed_organic_matter Particulate and dissolved organic carbon (POC/DOC) and organic nitrogen and phosphorus pools; mineralisation, decomposition.
Phytoplankton aed_phytoplankton Multi-group phytoplankton dynamics: growth, respiration, nutrient uptake, light limitation, settling, mortality. Default groups: cyanobacteria, green algae, diatoms.
Zooplankton aed_zooplankton Zooplankton grazing and dynamics.
Macrophytes aed_macrophyte Submerged and emergent macrophyte dynamics.
Totals aed_totals Diagnostic aggregates: TN, TP, TOC, chlorophyll-a.

GLM-AED Parameter Library

The glm_aed_parameter_library dataset provides a comprehensive list of all parameters used in the GLM-AED configuration, including their default values, units, and typical ranges. This library serves as a reference for users to understand the parameters governing the model behaviour and to guide parameterisation for specific applications. It also includes metadata such as the associated AED module and a brief description of each parameter’s role in the model and a web source link for further information.

AED Phytoplankton Group Parameters


Getting started

We will use the example lake dataset bundled with the AEME package throughout this vignette.

aeme_file <- system.file("extdata/aeme.rds", package = "AEME")
aeme     <- readRDS(aeme_file)
aeme
#>                                                                                 
#> ── AEME v0.4.0 ─────────────────────────────────────────────────────────────────
#>                                                                                 
#> ── Lake ──                                                                      
#>                                                                                 
#> Wainamu (ID: LID45819)                                                          
#> • Lat: -36.89; Lon: 174.47                                                      
#> • Elev: 23.64m; Depth: 13.07m; Area: 152343 m2                                  
#>                                                                                 
#> ── Time ──                                                                      
#>                                                                                 
#> • Start: 2020-08-01 00:00:00; Stop: 2021-06-30 00:00:00; Time step: 3600 s;     
#>   Output step: s                                                                
#> • Timezone: UTC (timestamps stored UTC)                                         
#> • Spin up (days): GLM: 2; GOTM: 1; DYRESM: 1; Simstrat: 2                       
#>                                                                                 
#> ── Configuration ──                                                             
#>                                                                                 
#> • Model: glm_aed                                                                
#> • Path: C:/Users/mooret/Git/AEME                                                
#> • Model controls: Present                                                       
#> • Use biogeochemical model: No                                                  
#> ┌ Model Configuration ─────────────────────────────────────────┐                
#> │       Model              Physical         Biogeochemical     │                
#> │ ---                                                          │                
#> │       DY-CD               Absent              Absent         │                
#> │      GLM-AED              Absent              Absent         │                
#> │      GOTM-WET             Absent              Absent         │                
#> │   SIMSTRAT-AED2           Absent              Absent         │                
#> │    SIMSTRAT-AED           Absent              Absent         │                
#> └──────────────────────────────────────────────────────────────┘                
#>                                                                                 
#> ── Observations ──                                                              
#>                                                                                 
#> • Lake: Present; Level: Present                                                 
#>                                                                                 
#> ── Input ──                                                                     
#>                                                                                 
#> • Initial profile: Absent; Initial depth: 13.07m                                
#> • Hypsograph: Present (n=132)                                                   
#> • Meteo: Present; Use longwave: TRUE; Kw: 1.31                                  
#>                                                                                 
#> ── Inflows ──                                                                   
#>                                                                                 
#> • Number of inflows: 1; Names: FWMT                                             
#> • Scaling factors: DY-CD: 1; GLM-AED: 1; GOTM-WET: 1; Simstrat-AED2: 1          
#>                                                                                 
#> ── Outflows ──                                                                  
#>                                                                                 
#> • Number of outflows: 1; Names: outflow; Elevations: -1                         
#> • Scaling factors: DY-CD: 1; GLM-AED: 1; GOTM-WET: 1; Simstrat-AED2: 1          
#>                                                                                 
#> ── Water Balance ──                                                             
#>                                                                                 
#> • Method: 2; Use: obs                                                           
#> • Modelled: Absent; Water balance: Absent                                       
#>                                                                                 
#> ── Parameters ──                                                                
#>                                                                                 
#> • Number of parameters: 0                                                       
#>                                                                                 
#> ── Output ──                                                                    
#>                                                                                 
#> • DY-CD: 0                                                                      
#> • GLM-AED: 0                                                                    
#> • GOTM-WET: 0                                                                   
#> • SIMSTRAT-AED2: 0                                                              
#> • SIMSTRAT-AED: 0                                                               
#> • Variables: 0                                                                  
#> None                                                                            

Building a GLM-AED simulation

Model controls

get_model_controls() returns a data frame that governs which biogeochemical variables are simulated and how they are initialised. Pass use_bgc = TRUE to enable the full suite of water-quality variables.

model_controls <- get_model_controls(use_bgc = TRUE)
head(model_controls)
#> <model_controls> 6/6 variables simulated
#>   var_aeme simulate inf_default initial_wc initial_sed conversion_aed
#>    CAR_doc      yes           0        0.5   1000000.0          0.012
#>    CAR_poc      yes           0        0.2         0.1          0.012
#>    CHM_oxy      yes          10       10.0        10.0          0.032
#>   CHM_salt      yes           0        0.0         0.0          1.000
#>   HYD_dens      yes           -          -           -          1.000
#>  HYD_strat      yes           -          -           -          1.000

You can narrow the set of simulated variables using set_vars_sim():

vars_sim <- c(
  "HYD_strat",   # stratification flag
  "HYD_temp",    # water temperature
  "HYD_thmcln",  # thermocline depth
  "CHM_oxy",     # dissolved oxygen
  "CHM_oxycln",  # oxycline depth
  "NIT_amm",     # ammonium
  "NIT_nit",     # nitrate
  "NIT_tn",      # total nitrogen
  "PHS_frp",     # filterable reactive phosphorus
  "PHS_tp",      # total phosphorus
  "PHY_tchla"    # total chlorophyll-a
)

model_controls <- set_vars_sim(
  model_controls = model_controls,
  vars_sim       = vars_sim
)

Build the model

We will build the model in a directory called aeme,

path <- "aeme"

build_aeme() translates the AEME object into all configuration files required by GLM-AED. Setting use_bgc = TRUE writes the aed/aed.nml file and supporting CSV parameter files for phytoplankton, zooplankton, and macrophytes.

model <- "glm_aed"

aeme <- build_aeme(
  aeme           = aeme,
  model          = model,
  path           = path,
  model_controls = model_controls,
  ext_elev       = 3,
  use_bgc        = TRUE
)

The configuration files are stored in the configuration slot of the aeme object. For GLM-AED the slot contains the parsed GLM hydrodynamic nml (glm3.nml or glm4.nml, whichever version was built – see find_glm_nml()) and aed/aed.nml:

cfg <- configuration(aeme)
names(cfg[["glm_aed"]])
#> [1] "hydrodynamic"      "bgc"               "hydrodynamic_file"

GLM-AED specific features

Selecting AED biogeochemical modules

By default, all AED modules are enabled. set_glm_aed_models() lets you choose a subset — for example, running hydrodynamics coupled with oxygen and nutrient cycling only, without phytoplankton or zooplankton:

# Rebuild first so we have a clean state
aeme <- build_aeme(
  aeme           = aeme,
  model          = model,
  model_controls = model_controls,
  path           = path,
  ext_elev       = 5,
  use_bgc        = TRUE
)

# Switch on all available modules (the default)
aeme <- set_glm_aed_models(
  aeme       = aeme,
  path       = path,
  aed_models = c(
    "aed_sedflux",
    "aed_oxygen",
    "aed_silica",
    "aed_nitrogen",
    "aed_phosphorus",
    "aed_organic_matter",
    "aed_phytoplankton",
    "aed_zooplankton",
    "aed_macrophyte",
    "aed_totals"
  )
)

To run a minimal simulation with only oxygen and nutrient cycling:

aeme <- set_glm_aed_models(
  aeme       = aeme,
  path       = path,
  aed_models = c(
    "aed_sedflux",
    "aed_oxygen",
    "aed_nitrogen",
    "aed_phosphorus"
  )
)

set_glm_aed_models() works on a model that has already been built. To choose the modules (and which other AED setup steps run) as part of the build itself, pass aed_options() to build_aeme():

aeme <- build_aeme(
  aeme           = aeme,
  model          = model,
  model_controls = model_controls,
  path           = path,
  ext_elev       = 5,
  use_bgc        = TRUE,
  aed            = aed_options(
    # Only oxygen and nutrient cycling (plus whatever they depend on)
    modules   = c("aed_sedflux", "aed_oxygen", "aed_nitrogen", "aed_phosphorus"),
    # Keep the sediment fluxes already in aed.nml rather than estimating them
    sed_zones = FALSE,
    # Don't derive the aed_totals variable lists
    totals    = FALSE
  )
)

By default aed_options() leaves every step on, so a plain build_aeme(use_bgc = TRUE) is unchanged. Each step is also available on its own after the build: set_glm_aed_models(), set_aed_sed_const2d() and set_aed_totals().


AED module dependency hierarchy

AED modules are not independent of one another — most reference state variables that are only computed if another module is switched on. For example, aed_nitrogen reduces ammonium using OXY_oxy as its oxidant, so it requires aed_oxygen to also be active; if it is not, GLM aborts at runtime with an “Undefined variable” error rather than silently ignoring the missing link.

build_aeme() resolves this automatically: when you set simulate = TRUE for a biogeochemical variable via model_controls, initialise_aed() determines which module that variable belongs to, then walks the dependency graph below to a fixed point, adding every module that dependency chain requires — you never need to enable a module’s prerequisites by hand. The one place this isn’t automatic is set_glm_aed_models() (used above), which sets the active-module list exactly as given, so it is up to you to include the full chain if you call it directly. (aed_options(modules = ) adds the prerequisites for you unless you set resolve_deps = FALSE.)

The dependency graph, verified directly against the target-variable links configured in AEME’s bundled aed.nml template:

aed_sedflux
  └─ aed_oxygen           (sediment oxygen demand)
       ├─ aed_silica          (silica reactant = OXY_oxy)
       ├─ aed_nitrogen        (nitrification reactant = OXY_oxy; also needs aed_sedflux directly)
       ├─ aed_phosphorus      (P release reactant = OXY_oxy; also needs aed_sedflux directly)
       └─ aed_organic_matter  (mineralisation reactant = OXY_oxy)
            ├─ needs aed_nitrogen        (DON mineralisation product)
            ├─ needs aed_phosphorus      (DOP mineralisation product = PHS_frp)
            ├─ aed_zooplankton      (excretion/mortality targets)
            └─ aed_phytoplankton    (uptake/excretion/mortality targets;
                                      needs aed_oxygen, aed_nitrogen,
                                      aed_phosphorus, aed_silica, and
                                      aed_organic_matter directly)
                 └─ aed_totals      (TN/TP/TOC totals aggregate state
                                      variables owned by aed_nitrogen,
                                      aed_phosphorus, aed_organic_matter,
                                      and aed_phytoplankton)

aed_macrophyte  (no enforced dependencies — no target-variable links in the
                 bundled template)
AED module dependency requirements enforced by initialise_aed().
Module Required dependencies Why
aed_sedflux — Sediment flux is the base of the chain; nothing else must be active first.
aed_oxygen aed_sedflux Sediment oxygen demand flux comes from aed_sedflux.
aed_silica aed_oxygen Silica reactant variable is OXY_oxy.
aed_nitrogen aed_oxygen, aed_sedflux Nitrification reactant is OXY_oxy; sediment NH4/NO3 fluxes come from aed_sedflux.
aed_phosphorus aed_oxygen, aed_sedflux Redox-sensitive P release reactant is OXY_oxy; sediment FRP flux comes from aed_sedflux.
aed_organic_matter aed_oxygen, aed_nitrogen, aed_phosphorus Mineralisation reactant is OXY_oxy; DON/DOP mineralisation products feed aed_nitrogen/aed_phosphorus.
aed_phytoplankton aed_oxygen, aed_nitrogen, aed_phosphorus, aed_silica, aed_organic_matter Growth/uptake/excretion/mortality target variables span all five modules (e.g. diatom silica uptake, nutrient uptake, DOM excretion).
aed_zooplankton aed_organic_matter Excretion and mortality products are organic-matter state variables.
aed_totals aed_nitrogen, aed_phosphorus, aed_organic_matter, aed_phytoplankton Its TN_vars/TP_vars/TOC_vars lists aggregate state variables owned by these four modules (e.g. NIT_nit, PHS_frp, OGM_doc, PHY_green).
aed_macrophyte — No target-variable links to other modules in the bundled template.

For example, requesting only NIT_tn (total nitrogen) in model_controls looks like a single-variable request, but because it is a diagnostic total computed by aed_totals, and aed_totals in turn depends on nitrogen, phosphorus, organic matter, and phytoplankton, build_aeme() activates the following module set automatically:

mc <- get_model_controls(use_bgc = TRUE) |>
  set_vars_sim(vars_sim = "NIT_tn")

aeme_nit_tn <- build_aeme(
  aeme           = aeme,
  model          = model,
  path           = path,
  model_controls = mc,
  ext_elev       = 5,
  use_bgc        = TRUE
)

config_files <- get_model_config_files(aeme)


nml <- read_nml(config_files$glm_aed["aed"])
get_nml_value(nml, "models")
#> [1] "aed_sedflux"        "aed_noncohesive"    "aed_oxygen"        
#> [4] "aed_silica"         "aed_nitrogen"       "aed_phosphorus"    
#> [7] "aed_organic_matter" "aed_phytoplankton"  "aed_totals"

Sediment zones

One of the most important GLM-AED features for water-quality simulation is depth-varying sediment parameters. GLM divides the lake bed into sediment zones, each with its own temperature regime and (when using aed_sedflux in Constant2d mode) its own sediment–water interface fluxes of oxygen and nutrients.

Deeper zones typically accumulate more organic matter, experience longer periods of anoxia, and therefore have higher sediment oxygen demand (SOD) and nutrient release than shallow littoral zones.

Estimating zone boundaries from the hypsograph

estimate_sed_zones() automatically partitions the hypsograph into an appropriate number of zones by detecting natural breakpoints in the depth–area relationship:

hypsograph   <- get_hypsograph(aeme)
zone_heights <- estimate_sed_zones(hypsograph)
zone_heights
#> [1]  3.07 22.00

The returned vector gives the upper height of each zone measured from the lake bed (metres). The last value equals the maximum lake depth.

Generating GLM sediment parameters

glm_sed_params() builds a parameters data frame (compatible with the AEME parameters slot) for the GLM sediment module:

sed_params <- glm_sed_params(
  n_zones            = length(zone_heights),
  zone_heights       = zone_heights,
  sed_temp_mean      = c(14, 16, 18)[seq_along(zone_heights)],
  sed_temp_amplitude = c(6, 4, 2)[seq_along(zone_heights)],
  sed_temp_peak_doy  = rep(30, length(zone_heights))
)
sed_params
#>      model     file                        name value    min    max group index
#> 1  glm_aed glm4.nml       sediment/benthic_mode  2.00  2.000  2.000  <NA>    NA
#> 2  glm_aed glm4.nml            sediment/n_zones  2.00  2.000  2.000  <NA>    NA
#> 3  glm_aed glm4.nml     sediment/sed_heat_Ksoil  0.01  0.005  0.015  <NA>     1
#> 4  glm_aed glm4.nml     sediment/sed_heat_Ksoil  0.01  0.005  0.015  <NA>     2
#> 5  glm_aed glm4.nml     sediment/sed_temp_depth  0.20  0.100  0.300  <NA>     1
#> 6  glm_aed glm4.nml     sediment/sed_temp_depth  0.20  0.100  0.300  <NA>     2
#> 7  glm_aed glm4.nml      sediment/sed_temp_mean 14.00  7.000 21.000  <NA>     1
#> 8  glm_aed glm4.nml      sediment/sed_temp_mean 16.00  8.000 24.000  <NA>     2
#> 9  glm_aed glm4.nml sediment/sed_temp_amplitude  6.00  3.000  9.000  <NA>     1
#> 10 glm_aed glm4.nml sediment/sed_temp_amplitude  4.00  2.000  6.000  <NA>     2
#> 11 glm_aed glm4.nml  sediment/sed_temp_peak_doy 30.00 15.000 45.000  <NA>     1
#> 12 glm_aed glm4.nml  sediment/sed_temp_peak_doy 30.00 15.000 45.000  <NA>     2
#> 13 glm_aed glm4.nml       sediment/zone_heights  3.07  1.535  4.605  <NA>     1
#> 14 glm_aed glm4.nml       sediment/zone_heights 22.00 11.000 33.000  <NA>     2
#> 15 glm_aed glm4.nml   sediment/sed_reflectivity  0.01  0.005  0.015  <NA>     1
#> 16 glm_aed glm4.nml   sediment/sed_reflectivity  0.01  0.005  0.015  <NA>     2
#> 17 glm_aed glm4.nml      sediment/sed_roughness  0.01  0.005  0.015  <NA>     1
#> 18 glm_aed glm4.nml      sediment/sed_roughness  0.01  0.005  0.015  <NA>     2
#>      module
#> 1  sediment
#> 2  sediment
#> 3  sediment
#> 4  sediment
#> 5  sediment
#> 6  sediment
#> 7  sediment
#> 8  sediment
#> 9  sediment
#> 10 sediment
#> 11 sediment
#> 12 sediment
#> 13 sediment
#> 14 sediment
#> 15 sediment
#> 16 sediment
#> 17 sediment
#> 18 sediment

Add these parameters to the aeme object so that they are applied during the next build_aeme() call:

parameters(aeme) <- sed_params

# Rebuild to apply the new sediment zone parameters
aeme <- build_aeme(
  aeme           = aeme,
  model          = model,
  model_controls = model_controls,
  path           = path,
  ext_elev       = 5,
  use_bgc        = TRUE
)
Inspecting sediment zones in the built model

After building, you can retrieve the number of zones and their parameters:

n_zones   <- get_glm_sed_zones(aeme = aeme, path = path)
sed_pars  <- get_glm_sed_params(aeme = aeme, path = path)
cat("Number of sediment zones:", n_zones, "\n")
#> Number of sediment zones: 2
sed_pars
#>      model     file                        name value   min   max index group
#> 1  glm_aed glm3.nml       sediment/benthic_mode  2.00  2.00  2.00    NA  <NA>
#> 2  glm_aed glm3.nml            sediment/n_zones  2.00  2.00  2.00    NA  <NA>
#> 3  glm_aed glm3.nml       sediment/zone_heights  3.07  3.07  3.07     1  <NA>
#> 4  glm_aed glm3.nml       sediment/zone_heights 22.00 22.00 22.00     2  <NA>
#> 5  glm_aed glm3.nml   sediment/sed_reflectivity  0.01  0.01  0.01     1  <NA>
#> 6  glm_aed glm3.nml   sediment/sed_reflectivity  0.01  0.01  0.01     2  <NA>
#> 7  glm_aed glm3.nml      sediment/sed_roughness  0.01  0.01  0.01     1  <NA>
#> 8  glm_aed glm3.nml      sediment/sed_roughness  0.01  0.01  0.01     2  <NA>
#> 9  glm_aed glm3.nml     sediment/sed_heat_model  2.00  2.00  2.00    NA  <NA>
#> 10 glm_aed glm3.nml       sediment/n_sed_layers  8.00  8.00  8.00    NA  <NA>
#> 11 glm_aed glm3.nml    sediment/sed_layer_depth  0.00  0.00  0.00     1  <NA>
#> 12 glm_aed glm3.nml    sediment/sed_layer_depth  0.02  0.02  0.02     2  <NA>
#> 13 glm_aed glm3.nml    sediment/sed_layer_depth  0.05  0.05  0.05     3  <NA>
#> 14 glm_aed glm3.nml    sediment/sed_layer_depth  0.10  0.10  0.10     4  <NA>
#> 15 glm_aed glm3.nml    sediment/sed_layer_depth  0.20  0.20  0.20     5  <NA>
#> 16 glm_aed glm3.nml    sediment/sed_layer_depth  0.40  0.40  0.40     6  <NA>
#> 17 glm_aed glm3.nml    sediment/sed_layer_depth  0.80  0.80  0.80     7  <NA>
#> 18 glm_aed glm3.nml    sediment/sed_layer_depth  1.50  1.50  1.50     8  <NA>
#> 19 glm_aed glm3.nml            sediment/sed_vwc  0.40  0.40  0.40    NA  <NA>
#> 20 glm_aed glm3.nml    sediment/sed_spinup_days 30.00 30.00 30.00    NA  <NA>
#> 21 glm_aed glm3.nml      sediment/sed_temp_mean 14.00 14.00 14.00     1  <NA>
#> 22 glm_aed glm3.nml      sediment/sed_temp_mean 16.00 16.00 16.00     2  <NA>
#> 23 glm_aed glm3.nml sediment/sed_temp_amplitude  6.00  6.00  6.00     1  <NA>
#> 24 glm_aed glm3.nml sediment/sed_temp_amplitude  4.00  4.00  4.00     2  <NA>
#> 25 glm_aed glm3.nml  sediment/sed_temp_peak_doy 30.00 30.00 30.00     1  <NA>
#> 26 glm_aed glm3.nml  sediment/sed_temp_peak_doy 30.00 30.00 30.00     2  <NA>
#> 27 glm_aed glm3.nml      sediment/sed_temp_deep 10.00 10.00 10.00     1  <NA>
#> 28 glm_aed glm3.nml      sediment/sed_temp_deep 10.00 10.00 10.00     2  <NA>
#> 29 glm_aed glm3.nml     sediment/sed_heat_Ksoil  0.01  0.01  0.01     1  <NA>
#> 30 glm_aed glm3.nml     sediment/sed_heat_Ksoil  0.01  0.01  0.01     2  <NA>
#> 31 glm_aed glm3.nml     sediment/sed_temp_depth  0.20  0.20  0.20     1  <NA>
#> 32 glm_aed glm3.nml     sediment/sed_temp_depth  0.20  0.20  0.20     2  <NA>
Estimating depth-varying sediment fluxes

estimate_zone_fluxes() scales literature-baseline sediment fluxes to each zone according to its mean depth and bed-area fraction (Tier 1). When observed near-bed concentrations are available in the aeme object, an optional Tier 2 adjustment refines the inter-zone ratios using summer near-bottom data:

fluxes <- estimate_zone_fluxes(
  aeme      = aeme,
  path      = path,
  baseline  = c(
    fsed_oxy = -25,   # mmol O2/m2/d  (negative = into sediment)
    fsed_amm =   2,   # mmol N/m2/d
    fsed_nit =   0.2, # mmol N/m2/d
    fsed_frp =   0.05 # mmol P/m2/d
  ),
  verbose   = TRUE
)

The zone summary shows each zone’s depth range, bed area, and estimated fluxes:

fluxes$zone_summary
#>   zone height_lower_m height_upper_m depth_upper_m depth_lower_m mean_depth_m
#> 1    1           0.00           3.07            10         13.07        11.54
#> 2    2           3.07          22.00             0         10.00         5.00
#>   area_m2 area_frac fsed_oxy fsed_amm fsed_nit fsed_frp
#> 1   43957     0.289    -38.8    5.698     -0.4   0.1072
#> 2  108386     0.711    -19.4    0.500      0.1   0.0268
Applying sediment fluxes to the AED configuration

set_aed_sed_const2d() writes the zone-specific fluxes directly into the aed/aed.nml file and updates the aeme object:

aeme <- set_aed_sed_const2d(
  aeme     = aeme,
  path     = path,
  baseline = c(
    fsed_oxy = -25,
    fsed_amm =  2,
    fsed_nit =  0.2,
    fsed_frp =  0.05
  )
)

After calling set_aed_sed_const2d(), you can confirm the written parameters:

get_aed_sed_const2d_param(aeme = aeme, path = path) |>
  dplyr::select(name, value, index) |>
  head(20)
#> # A tibble: 11 × 3
#>    name                          value index
#>    <chr>                         <dbl> <dbl>
#>  1 aed_sed_const2d/active_zones   1        1
#>  2 aed_sed_const2d/active_zones   2        2
#>  3 aed_sed_const2d/fsed_amm       2        1
#>  4 aed_sed_const2d/fsed_amm       2        2
#>  5 aed_sed_const2d/fsed_frp       0.05     1
#>  6 aed_sed_const2d/fsed_frp       0.05     2
#>  7 aed_sed_const2d/fsed_nit      -0.2      1
#>  8 aed_sed_const2d/fsed_nit      -0.2      2
#>  9 aed_sed_const2d/fsed_oxy     -25        1
#> 10 aed_sed_const2d/fsed_oxy     -25        2
#> 11 aed_sed_const2d/n_zones        2       NA

Visualising the model configuration

plot_glm_config() generates an interactive HTML visualisation of the complete GLM-AED setup, including the hypsograph, sediment zones, inflow/outflow positions, active AED modules, and key parameter values. If called inside an RStudio session, the output opens in the Viewer pane; otherwise it is saved to a temporary HTML file.

config_html <- plot_glm_config(aeme = aeme)

This html widget provides a comprehensive overview of the model configuration, making it easy to verify that the setup matches your intentions and to identify any potential issues before running the model.

You can view the HTML widget directly in RStudio’s Viewer pane or open it in your web browser.

Show parameter labels

Hypsograph & sediment zones

AED biogeochemical modules


Running GLM-AED

aeme <- run_aeme(aeme = aeme)

Visualising model output

Temperature and stratification

plot_output() produces a filled contour plot for depth-varying variables (e.g. temperature) and a time-series line plot for scalar variables:

plot_output(aeme = aeme, var_sim = "HYD_temp")
Simulated water temperature (°C) at each model layer over time.

Simulated water temperature (°C) at each model layer over time.

Water quality variables

Any variable that was listed in vars_sim and is present in the output can be plotted the same way:

plot_output(aeme = aeme, var_sim = "CHM_oxy")
Simulated dissolved oxygen (mmol O2 m⁻³) profiles.

Simulated dissolved oxygen (mmol O2 m⁻³) profiles.

plot_output(aeme = aeme, var_sim = "PHY_tchla")
Simulated total chlorophyll-a (µg L⁻¹) time series.

Simulated total chlorophyll-a (µg L⁻¹) time series.


Sediment, flux and diagnostic output

GLM-AED writes much more than the variables listed in model_controls: sediment temperature and heat flux, sediment–water fluxes (lake-wide and for each sediment zone), sediment pools and AED diagnostics. AEME reads all of it from output.nc, so nothing needs to be switched on before the run. It is stored in the model output beside the standard variables:

  • ordinary (time) and (depth, time) variables sit alongside HYD_temp and the rest;
  • variables with other dimensions (sediment zones, sediment layers) are kept as labelled aeme_grouped_var objects in the sediment and diag sub-lists, so a zone is never mistaken for a depth.

Finding the variables

get_output_vars() lists what is available. The group argument picks the sub-list:

get_output_vars(aeme, model = "glm_aed", group = "sediment") |>
  head(12)
#>                                     sediment zone area 
#>                                                "zarea" 
#>                                   sediment temperature 
#>                                             "sed_temp" 
#>                               sediment-water heat flux 
#>                                         "sed_heatflux" 
#>                           zone water temp (sed top BC) 
#>                                           "zone_ztemp" 
#>                                    sediment node depth 
#>                                      "sed_layer_depth" 
#>           flux rate of oxygen across the swi (by zone) 
#>                                       "SDF_Fsed_oxy_Z" 
#>              flux rate of amm across the swi (by zone) 
#>                                       "SDF_Fsed_amm_Z" 
#>              flux rate of nit across the swi (by zone) 
#>                                       "SDF_Fsed_nit_Z" 
#>              flux rate of frp across the swi (by zone) 
#>                                       "SDF_Fsed_frp_Z" 
#> net flux across the sediment-water interface (by zone) 
#>                                            "NCS_swi_Z" 
#>                     cum. swi position change (by zone) 
#>                                         "NCS_swi_dz_Z" 
#>                            resuspension rate (by zone) 
#>                                          "NCS_resus_Z"

These variables are stored under their raw GLM-AED netCDF names. The glm_extra_vars table gives each one an AEME name, a label and units, and either name works anywhere AEME looks up a variable (get_var(), check_var_in_output(), …). SED_* are the sediment-zone variables and SEDL_* the matching lake-wide series:

glm_extra_vars |>
  dplyr::filter(group == "sediment") |>
  dplyr::select(var_aeme, glm_aed, units, name_text) |>
  head(12)
#>           var_aeme         glm_aed        units
#> 1        SED_zarea           zarea           m2
#> 2         SED_temp        sed_temp      celsius
#> 3        SED_hflux    sed_heatflux         W/m2
#> 4        SED_ztemp      zone_ztemp      celsius
#> 5  SED_layer_depth sed_layer_depth       meters
#> 6     SED_Fsed_oxy  SDF_Fsed_oxy_Z mmol O2/m2/d
#> 7     SED_Fsed_amm  SDF_Fsed_amm_Z  mmol N/m2/d
#> 8     SED_Fsed_nit  SDF_Fsed_nit_Z  mmol N/m2/d
#> 9     SED_Fsed_frp  SDF_Fsed_frp_Z  mmol P/m2/d
#> 10     SED_ncs_swi       NCS_swi_Z       g/m2/d
#> 11  SED_ncs_swi_dz    NCS_swi_dz_Z          m/d
#> 12   SED_ncs_resus     NCS_resus_Z       g/m2/s
#>                                                 name_text
#> 1                                      sediment zone area
#> 2                                    sediment temperature
#> 3                                sediment-water heat flux
#> 4                            zone water temp (sed top BC)
#> 5                                     sediment node depth
#> 6            flux rate of oxygen across the swi (by zone)
#> 7               flux rate of amm across the swi (by zone)
#> 8               flux rate of nit across the swi (by zone)
#> 9               flux rate of frp across the swi (by zone)
#> 10 net flux across the sediment-water interface (by zone)
#> 11                     cum. swi position change (by zone)
#> 12                            resuspension rate (by zone)

Sediment temperature

SED_temp is the temperature of the sediment layers beneath each zone. It has three dimensions (sediment layer, zone and time), so get_var() returns it in long format, one row per layer, zone and date. Use zone and sed_layer to subset it. Zones follow the GLM order (1 is the deepest) and layer 1 is the shallowest. It is only written when GLM’s sediment heat model is on (sed_heat_model = 2 in the GLM nml). If your run does not have it, the chunks below that use it are skipped.

sed_temp <- get_var(aeme, model = "glm_aed", var_sim = "SED_temp")
n_layer  <- max(sed_temp$sed_layers)

sed_temp |>
  dplyr::filter(sed_layers %in% c(1, n_layer)) |>
  dplyr::mutate(layer = ifelse(sed_layers == 1, "Top layer", "Bottom layer"),
                zone  = factor(nzones)) |>
  ggplot(aes(Date, value, colour = zone)) +
  geom_line() +
  facet_wrap(~layer, ncol = 1) +
  labs(x = NULL, y = "Sediment temperature (°C)", colour = "Zone") +
  theme_bw()
Sediment temperature (°C) in the top and bottom sediment layer of each zone.

Sediment temperature (°C) in the top and bottom sediment layer of each zone.

A zone that has no area in GLM’s output for the simulated period is returned as NA, so it is missing from the plot above (zone 3 in this lake). The same call with zone and sed_layer returns a single series, for example the top layer of the deepest zone:

get_var(aeme, model = "glm_aed", var_sim = "SED_temp",
        zone = 1, sed_layer = 1) |>
  head(3)
#>   sed_layers nzones       Date    value  var_sim   Model
#> 1          1      1 2020-08-01 9.933457 SED_temp GLM-AED
#> 2          1      1 2020-08-02 9.887445 SED_temp GLM-AED
#> 3          1      1 2020-08-03 9.846911 SED_temp GLM-AED

Sediment–water fluxes by zone

Fluxes across the sediment–water interface are written for each zone as well as for the whole lake. Zone variables (SED_Fsed_oxy, SED_Fsed_amm, …) have a nzones column:

get_var(aeme, model = "glm_aed", var_sim = "SED_Fsed_oxy") |>
  dplyr::mutate(zone = factor(nzones)) |>
  ggplot(aes(Date, value, colour = zone)) +
  geom_line() +
  labs(x = NULL, y = "Sediment oxygen flux (mmol O2 m-2 d-1)",
       colour = "Zone") +
  theme_bw()
Simulated sediment oxygen flux (mmol O2 m⁻² d⁻¹) in each sediment zone.

Simulated sediment oxygen flux (mmol O2 m⁻² d⁻¹) in each sediment zone.

Choosing what to keep

Keeping every sediment and diagnostic variable is convenient for exploring a single run, but it is wasteful when the model is run many times, as in a sensitivity analysis, calibration or ensemble. Each run stores several MB of extra output, and most of it is never looked at. run_aeme() (and run_aeme_ensemble(), load_output()) take an extra_vars argument to choose what is read from output.nc. Variables that are not requested are never read.

extra_vars What is kept
NULL (default) Everything, as above
"none" No extra variables, just the standard set
c("SED_temp", "SED_Fsed_oxy") Only these. AEME or raw netCDF names
"sediment" or "diag" Every variable stored in that sub-list

Two further arguments reduce what is kept for the variables you do select. sed_layers keeps only some sediment layers of SED_temp, and zone_summary = TRUE adds a lake-wide series for each sediment-zone variable. The series is the mean over zones, weighted by the (time-varying) area of each zone, and is stored as <name>_lake:

aeme_sel <- run_aeme(
  aeme         = aeme,
  extra_vars   = c("SED_temp", "SED_Fsed_oxy", "SED_Fsed_amm"),
  sed_layers   = 1:3,
  zone_summary = TRUE
)

size_mb <- function(x) {
  format(object.size(output(x)$ens_001$glm_aed), units = "MB")
}
c(all_extras = size_mb(aeme), selected = size_mb(aeme_sel))
#> all_extras   selected 
#>    "18 Mb"   "3.4 Mb"

The standard output is identical between the two runs. Only the extras differ.

identical(output(aeme)$ens_001$glm_aed$HYD_temp,
          output(aeme_sel)$ens_001$glm_aed$HYD_temp)
#> [1] TRUE
names(output(aeme_sel)$ens_001$glm_aed$sediment)
#> [1] "sed_temp"            "SDF_Fsed_oxy_Z"      "SDF_Fsed_amm_Z"     
#> [4] "SDF_Fsed_oxy_Z_lake" "SDF_Fsed_amm_Z_lake"

The lake-wide series is a single time series, handy as a calibration target or for comparing with whole-lake estimates of sediment oxygen demand:

get_var(aeme_sel, model = "glm_aed", var_sim = "SED_Fsed_oxy_lake") |>
  ggplot(aes(Date, value)) +
  geom_line() +
  labs(x = NULL, y = "Sediment oxygen flux (mmol O2 m-2 d-1)") +
  theme_bw()
Zone-area weighted lake-wide sediment oxygen flux (mmol O2 m⁻² d⁻¹).

Zone-area weighted lake-wide sediment oxygen flux (mmol O2 m⁻² d⁻¹).

A name ending _lake can also be requested on its own, for example extra_vars = "SED_Fsed_oxy_lake". AEME then builds that series and does not keep the per-zone array behind it.

Using these variables in ensembles and calibration

run_aeme_ensemble() passes extra_vars, sed_layers, zone_summary and keep_diag to every member, so a sensitivity analysis or calibration run with aemetools can ask for just the series it needs:

aeme <- run_aeme_ensemble(
  aeme,
  model        = "glm_aed",
  param_sets   = param_sets,     # e.g. sampled with aemetools
  extra_vars   = c("SED_temp", "SED_Fsed_oxy_lake"),
  sed_layers   = 1
)

# Member 3's top-layer sediment temperature, in the deepest zone
get_var(aeme, model = "glm_aed", var_sim = "SED_temp",
        zone = 1, sed_layer = 1, ens_n = 3)

The sediment parameters estimated earlier (sed_temp_mean, the zone fluxes) are the natural ones to vary when these outputs are the target.


Assessing model performance

When observations are stored in the aeme object, assess_model() computes a suite of skill metrics (RMSE, NSE, bias, Pearson r, etc.) for each simulated variable:

skill <- assess_aeme(aeme = aeme, model = model)
skill
#>      Model           name_text   var_aeme      bias      mae     rmse   nmae
#> 1  GLM-AED Dissolved organic C    CAR_doc -2.70e+00 2.70e+00 2.75e+00 0.9940
#> 2  GLM-AED    Dissolved oxygen    CHM_oxy  1.13e+00 1.26e+00 1.93e+00 0.1830
#> 3  GLM-AED      Oxycline depth CHM_oxycln  1.89e+00 1.89e+00 2.26e+00 0.2100
#> 4  GLM-AED            Salinity   CHM_salt -1.17e-01 1.17e-01 1.17e-01 1.0000
#> 5  GLM-AED          Stratified  HYD_strat  0.00e+00 2.00e-01 4.47e-01 0.2860
#> 6  GLM-AED   Water temperature   HYD_temp -2.30e-01 8.20e-01 1.13e+00 0.0455
#> 7  GLM-AED   Thermocline depth HYD_thmcln -2.54e+00 3.21e+00 4.60e+00 0.3340
#> 8  GLM-AED         Water level LKE_lvlwtr -3.60e-02 1.29e-01 1.67e-01 0.0055
#> 9  GLM-AED              Volume    LKE_vol -5.31e+03 1.94e+04 2.49e+04 0.0177
#> 10 GLM-AED Ammoniacal nitrogen    NIT_amm  3.16e-03 1.23e-02 2.66e-02 1.0400
#> 11 GLM-AED             Nitrate    NIT_nit  2.98e-03 3.24e-03 5.07e-03 2.0300
#> 12 GLM-AED      Total nitrogen     NIT_tn -1.43e-01 1.43e-01 1.49e-01 0.7560
#> 13 GLM-AED           Phosphate    PHS_frp -1.23e-03 1.46e-03 1.64e-03 0.8110
#> 14 GLM-AED    Total phosphorus     PHS_tp -8.22e-03 8.30e-03 1.01e-02 0.7350
#> 15 GLM-AED       Cyanobacteria  PHY_cyano -2.69e-02 3.13e-02 6.25e-02 0.8360
#> 16 GLM-AED Total chlorophyll a  PHY_tchla  2.12e-01 6.50e+00 7.79e+00 0.8990
#>          nse     kge     d2       r      rs        B   n obs_na sim_na
#> 1   -33.0000 -1.0400 0.2120 -0.4920 -0.3940 0.006910  10      0      0
#> 2     0.6340  0.6790 0.8810  0.8810  0.9140 0.568000 125      0      0
#> 3    -0.3510  0.5760 0.7090  0.7700  0.8260 0.252000  24      0      0
#> 4  -329.0000      NA 0.0827      NA      NA 0.000000 125      0      0
#> 5     0.0476  0.5240 0.7520  0.5240  0.5240 0.141000  10      0      0
#> 6     0.8690  0.9350 0.9670  0.9390  0.9240 0.779000 125      0      0
#> 7    -1.4300  0.2670 0.6240  0.4910  0.4160 0.070300  10      0      0
#> 8    -7.1000 -0.8960 0.3260  0.0434 -0.0732 0.000206   8      0      0
#> 9    -6.9900 -0.8790 0.3260  0.0394 -0.0732 0.000172   8      0      0
#> 10   -0.9150  0.2090 0.6060  0.4690  0.5790 0.075500  20      0      0
#> 11   -5.6900 -1.2900 0.3050  0.2290  0.4290 0.006830  20      0      0
#> 12  -29.4000 -0.1720 0.2040  0.1680 -0.2290 0.000902  20      0      0
#> 13   -2.5400  0.0355 0.4990  0.3360  0.4070 0.024900  20      0      0
#> 14   -2.6800 -0.4910 0.3760 -0.1290 -0.3020 0.003590  20      0      0
#> 15   -0.3080 -0.6500 0.3600 -0.2010 -0.1310 0.017600  10      0      0
#> 16   -4.6100 -0.5390 0.3220 -0.2240 -0.3210 0.007590  10      0      0

GLM-AED diagnostics

Comprehensive diagnostic report

run_glm_aed_diagnostics() reads the model output and produces a structured diagnostic report — a summary table and a set of grouped plots — that helps you quickly identify unrealistic values or mass-balance issues:

diag <- run_glm_aed_diagnostics(
  aeme        = aeme,
  plot        = TRUE,
  print_table = TRUE
)

The function returns a list with three components:

  • $summary — a data frame with min, median, mean, max, and a flag column ("ok" or a warning string) for each variable.
  • $plots — a named list of patchwork plot objects grouped by biogeochemical element (O, N, P, Phy, Sed).
  • $data — the tidy data frame used to produce the plots.

You can filter the diagnostics to a specific element or type:

# Nitrogen state variables only
diag_N <- run_glm_aed_diagnostics(
  aeme        = aeme,
  groups      = "N",
  plot        = TRUE,
  print_table = FALSE
)

# Process-rate variables only
diag_proc <- run_glm_aed_diagnostics(
  aeme        = aeme,
  groups      = "process",
  plot        = FALSE,
  print_table = TRUE
)

Oxygen diagnostic page

plot_glm_diagnostics() provides a focused four-page diagnostic panel specifically designed to debug anomalous dissolved oxygen behaviour — a common challenge when coupling hydrodynamics with biogeochemistry:

pages <- plot_glm_diagnostics(aeme = aeme)

# Page 1: oxygen state and key physical drivers
print(pages$oxy)

# Page 2: mixing and physical structure
print(pages$physical)

# Page 3: biological oxygen demand
print(pages$bod)

# Page 4: sediment–water interface fluxes
print(pages$sediment)


Working with the GLM configuration directly

The raw GLM hydrodynamic nml (glm3.nml or glm4.nml, resolved via find_glm_nml()) and aed/aed.nml file can be read, modified, and written using the NML helpers bundled with AEME:

# Retrieve the parsed configuration
cfg <- read_model_config(model = "glm_aed",
                         lake_dir = get_lake_dir(aeme))

# Access GLM hydrodynamic section
glm_nml <- cfg$hydrodynamic
glm_nml$morphometry$lake_name

# Access AED biogeochemistry section
aed_nml <- cfg$bgc$aed
aed_nml$aed_nitrogen$rnitrif   # nitrification rate

For quick edits to a single parameter without loading the whole config, get_glm_param()/set_glm_param() work directly against the nml file on disk (see vignette("testing-parameters")):

path_glm <- file.path(get_lake_dir(aeme), "glm_aed")
get_glm_param(path_glm, "Kw")
set_glm_param(path_glm, Kw = 0.5, coef_mix_hyp = 0.3)

Retrieving parameters by module

get_aeme_parameters() provides a convenient way to query the AEME parameter library for all GLM-AED parameters belonging to a particular module:

# All GLM-AED light parameters
light_params <- get_aeme_parameters(model = "glm_aed",
                                    module = "light")
light_params[, c("name", "value", "min", "max")]
#> # A tibble: 6 × 4
#>   name               value   min    max
#>   <chr>              <dbl> <dbl>  <dbl>
#> 1 light/light_mode    0    0      0    
#> 2 light/n_bands       4    2      6    
#> 3 light/light_extc    1    0.5    1.5  
#> 4 light/energy_frac   0.51 0.255  0.765
#> 5 light/Benthic_Imin 10    5     15    
#> 6 light/Kw            0.2  0.1    0.3
# AED nitrogen module parameters
n_params <- get_aeme_parameters(model = "glm_aed",
                                module = "nitrogen")
n_params[, c("name", "value", "min", "max")]
#> # A tibble: 62 × 4
#>    name                          value     min      max
#>    <chr>                         <dbl>   <dbl>    <dbl>
#>  1 aed2_nitrogen/theta_sed_amm   1.08   0.54     1.62  
#>  2 aed2_nitrogen/Fsed_amm       30     15       45     
#>  3 aed2_nitrogen/Ksed_amm       31.2   15.6     46.9   
#>  4 aed2_nitrogen/Fsed_n2o        0      0        0     
#>  5 aed2_nitrogen/Ksed_n2o      100     50      150     
#>  6 aed2_nitrogen/theta_sed_nit   1.08   0.54     1.62  
#>  7 aed2_nitrogen/Fsed_nit        5.2    2.6      7.8   
#>  8 aed2_nitrogen/Ksed_nit      100     50      150     
#>  9 aed2_nitrogen/Kpart_ammox     1      0.5      1.5   
#> 10 aed2_nitrogen/Ranammox        0.001  0.0005   0.0015
#> # ℹ 52 more rows

Summary

This vignette demonstrated the key GLM-AED-specific features available in AEME:

  1. Model description — GLM provides variable-layer 1-D hydrodynamics; AED supplies modular biogeochemistry ranging from simple oxygen dynamics through to full nutrient–phytoplankton food-web simulation.

  2. Module selection — set_glm_aed_models() lets you enable or disable individual AED modules to match the complexity and data requirements of your application.

  3. Sediment zones — The estimate_sed_zones() → glm_sed_params() → set_aed_sed_const2d() workflow automatically derives depth-varying sediment parameters from the lake hypsograph and optional near-bed observations.

  4. Configuration visualisation — plot_glm_config() provides an interactive overview of the model setup.

  5. Diagnostics — run_glm_aed_diagnostics() and plot_glm_diagnostics() offer structured, automated checks for common biogeochemical issues (unrealistic concentrations, oxygen anomalies, excessive or negligible fluxes).

  6. Parameter access — get_aeme_parameters() makes it easy to explore and modify individual model parameters for manual tuning or automated calibration (see the aemetools package).