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)

grid, topo, vgrid, topo_editor

mom6_forge

2. Case setup

case

VisualCaseGen + CESM CIME

3a. Configure forcings

forcing (configurator classes)

(internal)

3b. Process forcings

forcing (driver.py + process_* methods)

regional-mom6, mom6_forge

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 a BaseConfigurator subclass that auto-registers via the @register decorator. The registry knows which configurators are required, valid, or forbidden for a given compset.

  • ProductRegistry (raw_data_access) — each data source is a ForcingProduct subclass whose @accessmethod functions 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 MARBL in 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 forcing/, inherits BaseConfigurator, decorated with @register

Add a new raw data source

new file in raw_data_access/datasets/, inherits MOM6ForcingProduct (or BaseProduct), with an @accessmethod function

Change the regridding of OBCs

forcing/mom6.py::_regrid_obc_chunk (it’s where we call Segment.regrid_velocity_tracers)

Change bathymetry fill behaviour

forcing/mom6.py::final_cleanliness_fill (and look at m6b.utils.fill_missing_data)

Add a new xmlchange or user_nl_* edit

the relevant configurator’s output_params + configure method

Give a new configurator a crocodash process flag

its process_components = {"<flag>": "<method>"} — cli.py builds the flag list from ForcingConfigRegistry.all_process_flags()

Change how --all/--skip resolve

forcing/driver.py::resolve_components

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

regional-mom6

Grid / bathymetry / vgrid generation, TopoEditor

mom6_forge

CESM case creation + GUI

VisualCaseGen

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

-v

verbose per-test names

-x

stop on first failure

-s

don’t swallow print()

-k EXPR

only run tests whose name matches EXPR

When developing the forcing package:

  • Use small date ranges and small domains

  • Set preview: true under conditions.outputs in config.json to dry-run

  • Use the --skip flag on crocodash process to iterate on one component

When developing raw_data_access:

  • New dataset classes must inherit from the right base (MOM6ForcingProduct for OBC/IC products, BaseProduct or DatedBaseProduct otherwise) and declare all required_metadata / required_args, including a Calendar.

  • 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#

  1. git checkout -b feature/my-feature

  2. Make your code changes with proper docstrings and type hints

  3. Add tests

  4. Run pytest locally

  5. cd docs && make html to confirm docs build

  6. Open a PR

Fixing a bug#

  1. Find or file the issue

  2. git checkout -b fix/issue-description

  3. Write a test that reproduces the bug

  4. Fix until the test passes

  5. Open a PR referencing the issue

See also#