agentsclimarketplace

Run3 glm output extraction

Skill cxcscmu/SkillLearnBench/skills/b3-teacher-feedback-claude-opus-4-6/temperature-simulation/run3_glm-output-extraction

How to extract water temperature profiles from GLM NetCDF output files (output.nc). Use this skill when you need to read GLM simulation results, extract depth and temperature arrays, and construct a dataframe of simulated temperatures at specific depths and times.From its SKILL.md

Install
npx -y skills add cxcscmu/SkillLearnBench --skill run3_glm-output-extraction

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

3.8 KB, ~1.0k tokens by cl100k_base, as published. Nobody here has run it

GLM NetCDF Output Structure

GLM writes output to a NetCDF file (typically output.nc). Key variables:

Variables and Their Shapes

  • time: 1D array of hours since a reference time (e.g., hours since 1904-01-01 00:00:00)
  • z: 4D array with shape (n_times, n_layers, 1, 1) — the height of each layer above the lake bottom (meters)
  • temp: 4D array with shape (n_times, n_layers, 1, 1) — water temperature (°C)
  • NS: 1D array — number of active layers at each timestep

CRITICAL: z and temp are 4D, NOT 2D. You must index them as z[t_idx, layer_idx, 0, 0] and temp[t_idx, layer_idx, 0, 0], or squeeze them first: z_squeezed = z[:, :, 0, 0] and temp_squeezed = temp[:, :, 0, 0].

Converting GLM Time to Datetime

import netCDF4 as nc
import pandas as pd

ds = nc.Dataset('/root/output/output.nc')
time_var = ds.variables['time']
# Use netCDF4's num2date, then convert to pd.Timestamp
raw_dates = nc.num2date(time_var[:], units=time_var.units)
# CRITICAL: Convert to pd.Timestamp for exact merge compatibility
sim_times = [pd.Timestamp(d.strftime('%Y-%m-%d %H:%M:%S')) for d in raw_dates]

Extracting Temperature at Specific Depths

GLM layers are counted from bottom up. The actual depth from surface is computed as:

depth_from_surface = lake_depth - z_height_above_bottom

CRITICAL: Read lake_depth dynamically from the nml file, do NOT hardcode it:

import re
with open('/root/glm3.nml', 'r') as f:
    nml_text = f.read()
match = re.search(r'lake_depth\s*=\s*([\d.]+)', nml_text)
lake_depth = float(match.group(1))

Full Extraction to DataFrame

import numpy as np
import netCDF4 as nc
import pandas as pd

ds = nc.Dataset('/root/output/output.nc')
time_var = ds.variables['time']
raw_dates = nc.num2date(time_var[:], units=time_var.units)
sim_times = [pd.Timestamp(d.strftime('%Y-%m-%d %H:%M:%S')) for d in raw_dates]

z = ds.variables['z'][:, :, 0, 0]       # squeeze to (n_times, n_layers)
temp = ds.variables['temp'][:, :, 0, 0]  # squeeze to (n_times, n_layers)
NS = ds.variables['NS'][:]

# Read lake_depth from nml
with open('/root/glm3.nml', 'r') as f:
    nml_text = f.read()
lake_depth = float(re.search(r'lake_depth\s*=\s*([\d.]+)', nml_text).group(1))

records = []
for t_idx in range(len(sim_times)):
    n_layers = int(NS[t_idx])
    for layer_idx in range(n_layers):
        depth_from_surface = lake_depth - z[t_idx, layer_idx]
        rounded_depth = round(depth_from_surface)
        records.append({
            'datetime': sim_times[t_idx],
            'depth_rounded': rounded_depth,
            'sim_temp': temp[t_idx, layer_idx]
        })

sim_df = pd.DataFrame(records)
# If multiple layers map to the same rounded depth at the same time, average them
sim_df = sim_df.groupby(['datetime', 'depth_rounded'], as_index=False)['sim_temp'].mean()

Datetime Alignment Warning

GLM typically outputs at a specific time of day (often noon 12:00:00). Observations may have different times. For an exact datetime merge, both sides must have identical datetime values. Check both:

print("Sim times sample:", sim_df['datetime'].iloc[:3].tolist())
print("Obs times sample:", obs_df['datetime'].iloc[:3].tolist())

If observations are date-only (00:00:00) and simulation is at noon, you may need to normalize:

# Option: normalize both to date only
sim_df['datetime'] = sim_df['datetime'].dt.normalize()
obs_df['datetime'] = pd.to_datetime(obs_df['datetime']).dt.normalize()

Only do this if the task allows it or if exact merge otherwise yields zero pairs.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.