Architecture#
This page explains how CrocoDash is wired together so you can understand, modify, or extend it. For the day-to-day user workflow see the user guide.
The four-step workflow maps onto four modules#
Workflow step |
CrocoDash module(s) |
Primary external dependency |
|---|---|---|
1. Grids (hgrid / topo / vgrid) |
|
|
2. Case setup |
|
VisualCaseGen + CESM CIME |
3a. Configure forcings |
|
(internal) |
3b. Process forcings |
|
regional-mom6, |
Supporting modules you’ll touch when extending CrocoDash:
raw_data_access— unified registry-based interface to data sources (GLORYS, TPXO, GEBCO, GLOFAS, …).logging— consistent logging setup used across the package.
Key design patterns#
Two registries#
Two pieces of CrocoDash are registry-based so users and contributors can extend them without touching core code:
ForcingConfigRegistry(forcing.base) — each forcing configuration (Tides, BGC, Rivers, …) is aBaseConfiguratorsubclass that auto-registers via the@registerdecorator. The registry knows which configurators are required, valid, or forbidden for a given compset.ProductRegistry(raw_data_access) — each data source is aForcingProductsubclass whose@accessmethodfunctions are discovered and validated at import time.
See the “Add a new …” guides for the mechanics:
Configure vs. process: why they’re separate#
Step 3a (case.configure_forcings) and 3b (case.process_forcings) are
decoupled on purpose. Configuration is lightweight — it validates arguments,
applies xmlchange/user_nl_* edits, and writes a JSON manifest. Processing
downloads large datasets, regrids, and writes netCDF. In practice these happen
on different machines (or at least different wall-clock budgets).
The extraction step is also runnable without Python: after
configure_forcings, the case’s inputdir/extract_forcings/ holds
config.json (plus the raw_data/ and regridded_data/ caches once you’ve run
once). The processing code stays in the installed CrocoDash package — nothing
is copied per-case — and you drive it with the crocodash process CLI entry
point, from a batch script or any shell. This makes HPC submission
straightforward.
Re-exports from mom6_forge#
CrocoDash deliberately re-exports mom6_forge’s Grid, Topo, VGrid,
GridCreator, TopoEditor, VGridCreator with no modification:
# CrocoDash/grid.py
from mom6_forge.grid import *
The only “wrapper” work happens when CrocoDash needs a mom6_forge object in a CESM context (grid file naming, case registration). Everything else routes straight through.
Validation lives in configure_forcings#
Forcing compatibility rules live with the configurators, not scattered through the workflow. Examples enforced there:
Chlorophyll is forbidden when BGC is in the compset — MARBL computes its own (
ChlConfigurator.forbidden_compsets = ["MARBL"])River nutrients require both runoff and BGC in the compset
BGC configurators require
MARBLin the compset longname
When you add a new configurator, declare its compatibility via
required_for_compsets, allowed_compsets, and forbidden_compsets — don’t
write ad-hoc if/else in case.py.
Data flow#
Case(grid, topo, vgrid, compset, ...)
│
▼
_configure_case (VisualCaseGen / CIME)
│
▼
_create_grid_input_files ──► inputdir/ocn grid files
│
▼
_create_newcase ──► caseroot/ CESM case
│
▼
configure_forcings(...)
│
▼
ForcingConfigRegistry ──► xmlchange + user_nl_* edits
──► inputdir/extract_forcings/config.json
│
▼
process_forcings(...)
│
▼
forcing/driver.py::run_workflow
│ (dispatches via each configurator's process_components)
├─► GET obc.py / ic.py (raw_data_access)
├─► REGRID mom6.py (regional-mom6 Segment)
├─► MERGE obc.py
├─► tides.py / bgc.py / runoff.py / chl.py
│
▼
──► inputdir/ocn/... forcing files
Where to add what#
You want to… |
Edit… |
|---|---|
Add a new CESM-compatible forcing (e.g. salt restoring) |
new file in |
Add a new raw data source |
new file in |
Change the regridding of OBCs |
|
Change bathymetry fill behaviour |
|
Add a new |
the relevant configurator’s |
Give a new configurator a |
its |
Change how |
|
What CrocoDash does NOT do itself#
CrocoDash is an orchestrator, not a reimplementation. The heavy lifting lives in three submodules:
Task |
Tool |
|---|---|
Regridding + OBC extraction |
|
Grid / bathymetry / vgrid generation, |
|
CESM case creation + GUI |
For the exact function-by-function call list, see Submodule API Usage.
Testing#
CrocoDash uses pytest. Run from the repo root:
# Full suite (mocked / fast)
pytest --ignore=CrocoDash/visualCaseGen -m "not workflow"
# Workflow tests (slower, may hit real data)
pytest -m "workflow"
# With coverage report
pytest --ignore=CrocoDash/visualCaseGen -m "not workflow" \
--cov=CrocoDash --cov-report=xml --cov-branch
Other useful flags:
Flag |
Purpose |
|---|---|
|
verbose per-test names |
|
stop on first failure |
|
don’t swallow |
|
only run tests whose name matches |
When developing the forcing package:
Use small date ranges and small domains
Set
preview: trueunderconditions.outputsinconfig.jsonto dry-runUse the
--skipflag oncrocodash processto iterate on one component
When developing raw_data_access:
New dataset classes must inherit from the right base (
MOM6ForcingProductfor OBC/IC products,BaseProductorDatedBaseProductotherwise) and declare allrequired_metadata/required_args, including aCalendar.Validation runs at import time — if metadata is missing, your tests will fail on import, which is the intended behaviour.
Common workflows#
Adding a new feature#
git checkout -b feature/my-featureMake your code changes with proper docstrings and type hints
Add tests
Run
pytestlocallycd docs && make htmlto confirm docs buildOpen a PR
Fixing a bug#
Find or file the issue
git checkout -b fix/issue-descriptionWrite a test that reproduces the bug
Fix until the test passes
Open a PR referencing the issue
See also#
Submodule API usage — one-stop reference for every upstream function CrocoDash calls.