MATLAB model / codebase for computing hurricane wind and pressure fields using the Generalized Asymmetric Holland Model (GAHM). The model reads tropical cyclone track data, computes GAHM parameters, generates radial wind/pressure profiles, optionally blends with large-scale gridded environmental fields, and writes output to NetCDF files.
When using gridded environmental fields (env_info.type = 3), SeparateEnvHur separates storm-scale features from the large-scale model fields (e.g., ECMWF's ERA5 reanalysis, https://cds.climate.copernicus.eu/datasets/reanalysis-era5-single-levels?tab=overview) to compute the required environmental input. One configuration file specifies the complete pipeline.
Developed by Rick Luettich (UNC/IMS/CNHR/EMES) and Brian Blanton (UNC/RENCI).
V1.5, Aug 2026
Clone this repo into a suitable location on your local computer. E.g., "$HOME/GitHub/GAHM2026". Add the following to your startup.m script:
addpath('/path/to/GAHM2026')
initGAHM % sets up GAHM2026 paths, once per sessionThen, in a working directory (e.g., myGahmStuff, and definitely not the main repo location), create 3 directories called input, output, and config. GAHM2026 looks for these directories in the working directory. Copy a config file from the repo to the newly created config directory. E.g.,
mkdir -p /path/to/myGahmStuff/{input/output,config}
cd /path/to/myGahmStuff/
cp $HOME/GitHub/GAHM2026/config/config_GAHM2026_default.m config/Then, in MATLAB, execute run_GAHM2026:
R = run_GAHM2026; % uses default config/config_GAHM2026_default.m
R = run_GAHM2026('config_Florence'); % uses config/config_Florence.mrun_GAHM2026 returns a Result struct containing all output fields (see Output below). If the SeparateEnvHur .mat file does not exist (e.g., output/FLORENCE_2018_AL06.mat), it will automatically run SeparateEnvHur to generate it before proceeding.
addpath('PlotEvalScripts')
obj = GAHM2026Plotter(R);
fig = obj.contourMap('mvelcon', 1, 20); % wind speed map at timestep 20
obj.radialProfile('velrad', 'envhur', 10, 10); % radial profiles at timestep 10
obj.radialProfile('velrad', {'envhur', 'trackdata'}, 10, 10); % add track data to profiles
obj.animate('mvelcon', 1); % animated GIF/MP4See PlotEvalScripts/README.md for the full GAHM2026Plotter class reference.
You can run SeparateEnvHur standalone using the same config file:
cd GAHM2026
addpath('SeparateEnvHur')
env_vals = SeparateEnvHur('config/config_GAHM2026_default'); % default config
env_vals = SeparateEnvHur('config/config_Florence'); % storm-specific configaddpath('PlotEvalScripts')
obj = GAHM2026Plotter.fromSepEnvHur(env_vals);
% combined env + hurricane wind field
obj.contourMap('mvelcon', 1, 5);
% environmental component only
obj.setOpts('wind', 'clims', [0 16]);
obj.contourMap('velcon', 2, 5, obj.EnvData);
% difference map: env minus hurricane wind speed
obj.differenceMap(obj.EnvData, obj.HurData, 'speed', 3, 5);SeparateEnvHur lives inside the GAHM2026 directory:
GAHM2026/
├── run_GAHM2026.m — top-level driver (returns Result struct)
├── GAHM2026.m — main GAHM2026 orchestrator
├── config/
│ ├── config_GAHM2026_default.m — default config (SeparateEnvHur + GAHM2026)
│ └── config_Florence.m — example storm-specific config
├── util/ — GAHM2026 pipeline functions and shared utilities
├── input/ — track files (IBTrACS, ATCF, fort22)
├── output/ — NetCDF output, warning logs
├── PlotEvalScripts/ — GAHM2026Plotter class and legacy plotting scripts
├── tools/ — regression testing harness
├── documentation/ — derivation, call tree, data structures, config reference
├── docs/ — GitHub Pages site
│
└── SeparateEnvHur/
├── SeparateEnvHur.m — vortex scrubber entry point
├── getERA5Data.m — ERA5 NetCDF reader (supports <year> placeholder)
├── findCutline.m — wind threshold contour detection
├── computeBasicField.m — environmental field separation
├── createOutputStruct.m — package output .mat structure
└── ... — additional processing functions
Every config file has four sections. Storm identity parameters are defined once and shared by both SeparateEnvHur and GAHM2026. The driver validates that storm_year is consistent with storm_start and storm_end at startup.
These values are defined as plain workspace variables and automatically populated into both the sepenvhur and storm_info structs:
| Variable | Description | Example |
|---|---|---|
storm_name |
Storm name (all caps for IBTrACS) | 'FLORENCE' |
storm_year |
4-digit year (numeric) | 2018 |
track_file |
IBTrACS CSV filename | 'ibtracs.NA.list.v04r01.csv' |
storm_designation |
Basin + number | 'AL06' |
storm_start |
Start time for processing (shared) | datetime(2018,9,10,0,0,0) |
storm_end |
End time for processing (shared) | datetime(2018,9,12,0,0,0) |
background_file names the gridded NetCDF input (local path or OPeNDAP URL); use <year> as a
placeholder for storm_year, resolved by getERA5Data at runtime. Everything else is specified
in physical degrees, not grid cells — the grid increment is detected from the input file at
runtime and all cell counts are derived from it.
See the configuration reference for the full parameter list, and the ERA5 page for the available input datasets.
Note:
sepenvhur.storm_name,sepenvhur.storm_year,sepenvhur.storm_designation,sepenvhur.track_file,sepenvhur.storm_start, andsepenvhur.storm_endare automatically populated from the shared variables — do not set them separately. Likewise,storm_info.starttimeandstorm_info.endtimeare derived fromstorm_startandstorm_end.
See the configuration reference for full parameter documentation.
| Struct | Key parameters |
|---|---|
storm_info |
.track_file, .file_type, start/end times (derived from shared variables) |
GAHM_param_info |
GAHM model constants (B limits, BLF, version, etc.) |
GAHM_compute_info |
Radial grid resolution (ntheta, nr, delr) |
WAF_info |
Wind Adjustment Factor flag and raster file |
env_info |
Environmental field type, taper settings |
output_info |
Output format, grid resolution, NetCDF path |
The env_info.file_name is derived from the shared storm identity:
env_info.file_name = sprintf('%s_%d', storm_name, storm_year); % e.g. 'FLORENCE_2018'This matches the output filename that SeparateEnvHur produces (FLORENCE_2018.mat), so the two projects are linked without any manual coordination.
When run_GAHM2026 is called and env_info.type == 3:
- It checks whether
<env_info.file_name>.matexists (e.g.,output/FLORENCE_2018_AL06.mat). This mat file contains the environmental fields needed by GAHM2026. - If the file exists → proceeds directly to GAHM2026 computation.
- If the file is missing → calls
SeparateEnvHur(sepenvhur), saves the.matfile, and continues to GAHM2026.
run_GAHM2026
│
├── Load config → storm_info, sepenvhur, env_info, ...
├── Download IBTrACS if missing
├── Check for FLORENCE_2018.mat
│ │
│ └── Missing? ──► SeparateEnvHur(sepenvhur)
│ ├── Load ERA5 NetCDF
│ ├── Extract & filter vortex
│ └── Save FLORENCE_2018.mat
│
├── GAHM2026 computation
└── Write NetCDF output
- Copy
config/config_GAHM2026_default.m(or any existing storm config) toconfig/config_<StormName>.m. - Update the shared storm identity section:
storm_name = 'MICHAEL'; storm_year = 2018; track_file = 'ibtracs.NA.list.v04r01.csv'; storm_designation = 'AL14';
- Update
sepenvhurwith the path to the ERA5 data and the desired extraction time window. - Update
storm_startandstorm_endto set the processing time window (used by both SeparateEnvHur and GAHM2026). - Adjust any model parameters as needed.
- Run:
>> run_GAHM2026('config_Michael')
Download from: https://www.ncei.noaa.gov/data/international-best-track-archive-for-climate-stewardship-ibtracs/v04r01/access/csv/
Place in GAHM2026/input/. If not found, run_GAHM2026 will attempt to download it automatically.
The configurations in the config directory point to ERA5 output downloaded and hosted on a THREDDS data server at RENCI. These ERA5 NetCDF files contain variables msl, u10, v10, and time (or valid_time) with spatial extends that cover a typical ADCIRC grid for the northwest Atlantic Ocean, including the Gulf of Mexico. The URL to this data is
'https://tdsres.apps.renci.org/thredds/dodsC/Datalayers/ERA5/regional/wna/uvp/<year>/<year>.wna.nc';
The file path is specified in sepenvhur.background_file. Use the <year> placeholder in the path to have it automatically replaced with storm_year at runtime. Any model dataset can be used as long as the files contain at least the above-specified variables.
The DDS for these files looks like this:
Dataset {
Grid {
ARRAY:
Int16 msl[time = 10248][latitude = 201][longitude = 201];
MAPS:
Int32 time[time = 10248];
Float32 latitude[latitude = 201];
Float32 longitude[longitude = 201];
} msl;
Grid {
ARRAY:
Int16 u10[time = 10248][latitude = 201][longitude = 201];
MAPS:
Int32 time[time = 10248];
Float32 latitude[latitude = 201];
Float32 longitude[longitude = 201];
} u10;
Float32 latitude[latitude = 201];
Float32 longitude[longitude = 201];
Int32 time[time = 10248];
Grid {
ARRAY:
Int16 v10[time = 10248][latitude = 201][longitude = 201];
MAPS:
Int32 time[time = 10248];
Float32 latitude[latitude = 201];
Float32 longitude[longitude = 201];
} v10;
} Datalayers/ERA5/regional/wna/uvp/1979/1979.wna.nc;
Always present:
| Field | Contents |
|---|---|
Result.Trackdata |
Storm track with .Rmax_t1, .Vmax_t1, .RQuad_t1, quadrant info |
Result.GAHM_out |
Per-timestep GAHM solver output |
Result.VPrad |
Radial grid data: .r, .theta, .VVor_bt(i), .VVor_at(i), .Env(i), .EnvVor_bt(i) |
Result.storm_info |
Storm identity (name, year, designation) |
Result.env_info |
Environmental field configuration |
When output_info.type = "grid":
| Field | Contents |
|---|---|
Result.Reggrid_out |
Grid coordinates (.Lon, .Lat), .datetime, .Mask1, .Mask2 |
Result.Reggrid_TC_out |
Final blended TC fields: .VelU, .VelV (m/s), .Press (mb) |
Result.Reggrid_Env_out |
Environmental fields: .VelU, .VelV (m/s), .Press (mb) |
Result.Reggrid_VVor_invtapHur_out |
GAHM vortex + inverse-tapered hurricane (env_info.type = 3 only) |
When output_info.type = "points":
| Field | Contents |
|---|---|
Result.Points_TC_out |
Point TC output: .datetime, .Lon, .Lat, .U10, .V10, .Press |
Result.Points_Env_out |
Point environmental output, same fields |
Result.Points_VVor_invtapHur_out |
GAHM vortex + inverse-tapered hurricane at points (env_info.type = 3 only) |
A NetCDF file is written to output/<storm>_<year>_<designation>.nc (e.g., FLORENCE_AL06_2018.nc) containing:
- Combined TC wind and pressure fields (
Reggrid_TC_out) - Environmental fields (
Reggrid_Env_out) - Grid coordinates and timestamps (
Reggrid_out)
MATLAB structs are returned in the Result struct with wind velocity (U10, V10) and pressure at specified lon/lat locations. Set output_info.lon and output_info.lat to equal-length vectors; output is computed at the corresponding pairs. No NetCDF file is written.
WAF_info.flag = true applies a land-roughness wind adjustment to the vortex velocity before the environmental wind is added. Pressure is not adjusted. The file format depends on the output type:
output_info.type |
WAF_info.file_name |
Read by |
|---|---|---|
"grid" |
GeoTIFF raster, one layer per wind direction | util/applyWAFfromRaster.m |
"points" |
MAT-file containing WAF_points |
util/applyWAFfromPoints.m |
WAF_points is a struct array with scalar .lon, scalar .lat, and a .WAF vector of factors for evenly spaced wind directions, ordered clockwise from north starting at 0°. Wind direction is the direction the wind blows from. The point coordinates must match output_info.lon/.lat exactly; unmatched points raise an error.
The .mat file (e.g., FLORENCE_2018_AL06.mat) contains the env_vals struct with:
- Environmental fields:
env_msl,env_u10,env_v10 - Hurricane fields:
hur_msl,hur_u10,hur_v10 - Vortex masks:
Vortex_mask,Vortex_mask_inner - Grid coordinates:
Lo,La - Track positions:
BestTrack_lon/lat,min_pressure_center_lon/lat
All diagnostic output uses the logMsg utility (util/logMsg.m):
logMsg(fid, 'INFO', 'step %d complete', i);
logMsg(fid, 'WARNING', 'Missing data at time %s', t);
logMsg(fid, 'ERROR', 'File not found: %s', fname); % terminates via error()
logMsg(-1, 'DEBUG', 'grid=%dx%d', nx, ny); % stdout only (fid=-1)Messages are printed to stdout and optionally to a log file. The ERROR level logs the message and then calls error() to terminate execution. The caller name is determined automatically via dbstack. Both GAHM2026 and SeparateEnvHur use logMsg for all logging.
GAHM2026 and SeparateEnvHur share the following utilities in util/:
| File | Purpose |
|---|---|
readIBTrACS.m |
IBTrACS CSV parser (used by both projects) |
logMsg.m |
Standardized logging (DEBUG/INFO/WARNING/ERROR) |
computeRmaxTot.m |
Total Rmax from quadrant values |
quadrantUnitVectors.m |
Vortex/environmental unit vectors per quadrant |
thetaToQuadrantPair.m |
Map azimuth to bounding quadrant pair |
turnAngleDeg.m |
Boundary layer turning angle |
gahmPhysicalConstants.m |
Centralized physical constants |
Full documentation is published at https://renci.github.io/GAHM2026/:
| Page | Contents |
|---|---|
| Getting Started | Requirements, first run, regression tests, gotchas |
| Derivation of GAHM2026 | Derivation, implementation, default conditions, blending, WAF |
| Configuration | Every parameter and output data structure |
| ERA5 Data | The reanalysis data used, and how to substitute your own |
| Examples | The shipped configurations |
| Input Track Files | fort.22 formats |
| SeparateEnvHur | The vortex separation algorithm |
| Plotting | The GAHM2026Plotter class |
| NWS13 / NWS30 Output | Combining storm and background NetCDF into ADCIRC NWS13/NWS30 forcing |
The site is built from two sources by .github/workflows/pages.yml:
documentation/*.docx— the authoritative narrative documents. Edit them in Word, commit, and push; the workflow renders them to markdown with pandoc and publishes them. The set that is published is listed indocs/_data/docx_pages.yml. Superseded documents live indocumentation/archive/and are not published.docs/*.md— hand-written pages, edited directly.
To preview locally:
python tools/docs/build_docs.py --clean # needs pandoc and pyyaml
cd docs && bundle install
bundle exec jekyll serve --source ../_docs_buildAlso in the repository:
documentation/GAHM2026_workflow.md— Mermaid workflow diagram.PlotEvalScripts/README.md— exhaustiveGAHM2026Plotterfile listing.SeparateEnvHur/README.md— vortex scrubbing algorithm notes.tools/README.md— regression testing harness.DECISIONS.md— recorded design decisions and known defects carried forward.
