| name | glm-lake-simulation |
| description | How to run the General Lake Model (GLM3) for lake temperature simulation, calibrate parameters in glm3.nml, read NetCDF output with Python, and compute RMSE metrics against field observations. Use this skill whenever the user mentions GLM, lake temperature simulation, glm3.nml, or wants to calibrate lake model parameters. |
GLM Lake Simulation Skill
Overview
GLM (General Lake Model) simulates vertical water temperature stratification in lakes. The executable is /usr/local/bin/glm and must be run from the directory containing glm3.nml.
Running GLM
cd /root && glm
GLM reads glm3.nml in the current directory and writes output to the path specified by out_dir/out_fn in the &output namelist block.
Key Configuration File: glm3.nml
Calibration Parameters (allowed to modify)
| Parameter | Namelist Block | Typical Range | Effect |
|---|
Kw | &light | 0.1–0.5 | Light extinction coefficient; higher = more surface heating |
coef_mix_hyp | &mixing | 0.3–0.7 | Hypolimnetic mixing; higher = more deep mixing |
wind_factor | &meteorology | 0.7–1.3 | Wind speed scaling; higher = more mixing |
lw_factor | &meteorology | 0.7–1.3 | Longwave radiation scaling; higher = more surface heat |
ch | &meteorology | 0.0005–0.002 | Sensible heat transfer coefficient |
Do NOT modify
sw_factor, cd, ce
the_depths, the_temps, the_sals (initialization profile)
- All other settings
Reading GLM NetCDF Output
import netCDF4 as nc
import numpy as np
import pandas as pd
ds = nc.Dataset('/root/output/output.nc')
time_var = ds.variables['time']
temp_var = ds.variables['temp']
z_var = ds.variables['z']
NS_var = ds.variables['NS']
from netCDF4 import num2date
times = num2date(time_var[:], time_var.units)
Computing RMSE Against Observations
import pandas as pd
import numpy as np
import netCDF4 as nc
from netCDF4 import num2date
def extract_glm_temps(nc_path):
"""Extract GLM temperatures as DataFrame with datetime, depth_from_surface, temp."""
ds = nc.Dataset(nc_path)
times = num2date(ds.variables['time'][:], ds.variables['time'].units)
temp = ds.variables['temp'][:]
z = ds.variables['z'][:]
NS = ds.variables['NS'][:]
records = []
for i, t in enumerate(times):
n = int(NS[i])
dt = pd.Timestamp(t.year, t.month, t.day, t.hour, t.minute, t.second)
for j in range(n):
elev = float(z[i, j])
tmp = float(temp[i, j])
records.append({'datetime': dt, 'elev': elev, 'temp_sim': tmp})
return pd.DataFrame(records)
def compute_rmse_metrics(obs_path, nc_path, crest_elev=258.0):
"""Compute overall, annual_deep, and summer_deep RMSE."""
obs = pd.read_csv(obs_path, parse_dates=['datetime'])
obs['depth_round'] = obs['depth'].round()
sim_df = extract_glm_temps(nc_path)
sim_df[] = crest_elev - sim_df[]
sim_df[] = sim_df[].()
sim_df[] = sim_df[].dt.date
sim_daily = sim_df.groupby([, ])[].mean().reset_index()
sim_daily[] = pd.to_datetime(sim_daily[])
obs[] = obs[].dt.date
merged = obs.merge(sim_daily, on=[, ], how=)
overall_rmse = np.sqrt(((merged[] - merged[])**).mean())
deep = merged[merged[] >= ]
annual_deep_rmse = np.sqrt(((deep[] - deep[])**).mean())
summer_deep = deep[deep[].dt.month.isin([, , , ])]
summer_deep_rmse = np.sqrt(((summer_deep[] - summer_deep[])**).mean())
{
: (overall_rmse),
: (annual_deep_rmse),
: (summer_deep_rmse),
: ((merged)),
: ((deep)),
: ((summer_deep))
}
Calibration Strategy
- Start with default parameters and run baseline
- Adjust
Kw first — it most strongly affects thermocline depth and deep-water temps
- Lower Kw → deeper light penetration → cooler surface, warmer hypolimnion
- Adjust
coef_mix_hyp to control deep mixing
- Higher → more vertical mixing → warmer deep waters
- Adjust
wind_factor to control surface mixing
- Fine-tune
lw_factor and ch for surface heat balance
Workflow
cd /root && glm
python3 /root/evaluate.py