Catchment-Mesh Architecture#

This page documents the dedicated catchment-mesh workflow built around hydromodpy.spatial.mesh: package layout, runtime entry points, batch loop, simulation embedding, output layout, and the conformal Gmsh meshing core.

Code map#

  • hydromodpy/spatial/mesh/runtime.py: public mono-catchment entry point. Called by Project.build_mesh().

  • hydromodpy/spatial/mesh/hydro_mesh.py: concrete HydroMesh runtime object behind the public facade.

  • hydromodpy/spatial/mesh/batch.py: batch orchestration and manifest handling.

  • hydromodpy/spatial/mesh/batch_io.py and batch_reporting.py: IO and reporting helpers used by batch.py.

  • hydromodpy/spatial/mesh/config.py: Pydantic configuration schema ([mesh_catchment] block).

  • hydromodpy/spatial/mesh/gmsh_grid/zone_meshing/: conformal meshing core and export helpers.

  • hydromodpy/spatial/geographic and related domain helpers: geographic context preparation before meshing.

Recommended reading path:

  1. hydromodpy/spatial/mesh/runtime.py

  2. hydromodpy/spatial/mesh/hydro_mesh.py

  3. hydromodpy/spatial/mesh/gmsh_grid/runtime_support.py

  4. hydromodpy/spatial/mesh/gmsh_grid/zone_meshing/conformal.py

  5. hydromodpy/spatial/mesh/batch.py and config.py

Package components#

The internal package layout: public runtime entry points, the runtime facade and the concrete mono-run execution path, batch-specific IO and reporting helpers, and the configuration objects describing a meshing case.

@startuml
title MeshCatchment Package Components
left to right direction

package "Public entry points" {
  component "hmp.run(mesh.toml)\nmode=mesh launcher" as MainCli
  component "Project.build_mesh()\npython facade" as Facade
}

package "Config and versioned assets" {
  component "config.py\nschema parsing" as Config
  component "templates.py\ntemplate generation" as Templates
}

package "Dedicated mesh runtime" {
  component "runtime.py\npublic facade" as Runtime
  component "hydro_mesh.py\nHydroMesh runtime" as HydroMesh
  component "batch.py\nMeshCatchmentBatchRunner" as Batch
  component "batch_io.py\noutlet tables + raster coverage" as BatchIO
  component "batch_reporting.py\nmanifest + summary" as BatchReporting
}

package "Shared HydroModPy runtime" {
  component "Workspace / Geographic /\nDomain context / Gmsh conformal case" as SharedRuntime
}

MainCli --> Runtime
Facade --> Runtime
Runtime --> Config

Templates --> Config

Runtime --> HydroMesh
Batch --> BatchIO
Batch --> BatchReporting
Batch --> Runtime : callback executes\nmono-run path

HydroMesh --> SharedRuntime
Runtime --> SharedRuntime

note bottom of Runtime
runtime.py stays intentionally short.
It validates the public payload and
delegates the concrete mono-run path
to hydro_mesh.py.
end note

note bottom of Batch
batch.py is orchestration only.
It derives outlet-specific child configs,
then reuses the same mono-catchment
callback for each outlet.
end note
@enduml

Notes:

  • runtime.py is intentionally thin. It validates the public payload and delegates concrete execution to hydro_mesh.py.

  • batch.py does not implement a second meshing engine. It derives one outlet-specific child runtime, then reuses the same mono-catchment callback.

Conformal meshing pipeline#

The chain that turns one domain plus optional geology zones and river constraints into one planar mesh, one QA sidecar, one optional figure, and one optional exchange bundle.

The chosen UML set is intentionally narrow: an activity diagram for the branching driven by constraints_mode and constraint availability, a sequence diagram for the runtime handoff between public mesh facade, shared runtime helpers, geographic context, conformal case, and exports, and a component diagram for the stable boundaries between orchestration, domain preparation, constraint sources, meshing core, and exporters.

Activity:

@startuml
title Catchment Mesh - Conformal Activity Flow

start

:Load mesh TOML;
:Validate [mesh_catchment];
:Resolve constraints_mode;

if (Needs river trace?) then (yes)
  if (Synthetic geographic?) then (yes)
  else (no)
    :Ensure geographic.river_network.enabled = true;
    :Revalidate geographic config;
  endif
endif

:Build Workspace;
:Build DomainGeographicContext;

if (Needs river trace?) then (yes)
  :Extract in-memory river_trace\nfrom DomainGeographicContext;
  if (river_trace available?) then (yes)
  else (no)
    :Raise validation error;
    stop
  endif
endif

:Resolve conformal case config;
:Resolve support domain geometry;

if (Uses geology constraints?) then (yes)
  :Load geology polygons;
  :Clip to domain and optional interface scope;
else (no)
  :Build one synthetic domain zone;
endif

if (Uses river constraints?) then (yes)
  :Resolve river trace for meshing;
  :Optional clip/filter/snap preparation;
endif

:Generate zone-conformal mesh with Gmsh;
:Build QA summary and optional figure/JSON;

if (Mesh file written?) then (yes)
  :Export catchment mesh bundle;
endif

:Return mesh summary payload;
stop
@enduml

Sequence (mesh facade to Gmsh):

@startuml
title Catchment Mesh - Mesh Facade To Gmsh Sequence
autonumber
hide footbox
skinparam sequenceMessageAlign center
skinparam responseMessageBelowArrow true

legend right
|= Arrow |= Meaning |
| ``->`` | call |
| ``-->`` | returned value |
| ``opt`` | optional branch |
| ``alt`` | mutually exclusive branch |
endlegend

actor Caller
participant "run_single_mesh_catchment_workflow(...)\n<<shared runtime>>" as Runtime
participant "Workspace\nhydromodpy.Workspace" as Workspace
participant "build_geographic_derived_features(...)" as GeoPipeline
participant "GeographicDerivedFeatures" as GeoFeatures
participant "DomainGeographicContext" as DomainCtx
participant "run_reference_2d_zone_conformal_case_from_toml(...)\n<<reference case>>" as Case
participant "load_zone_meshing_domain_payload(...)" as DomainLoader
participant "load_vector_geology_dataframe(...)" as GeologyIO
participant "generate_zone_conformal_mesh_from_dataframe(...)\n<<Gmsh core>>" as GmshCore
participant "export_catchment_mesh_bundle(...)" as BundleExporter

Caller -> Runtime: run_single_mesh_catchment_workflow(\nconfig_path, section_data, workspace_cfg,\ngeographic_cfg, constraints_mode, output_overrides?)

opt workspace not injected
  Runtime -> Workspace: Workspace(config=workspace_cfg)
  Workspace --> Runtime: workspace
end

opt geographic_features not injected
  Runtime -> GeoPipeline: build_geographic_derived_features(\nconfig=geographic_cfg, workspace)
  GeoPipeline --> Runtime: GeographicDerivedFeatures
end

opt domain_geographic not injected
  Runtime -> GeoFeatures: to_domain_geographic_context()
  GeoFeatures --> Runtime: DomainGeographicContext
end

Runtime -> GeoFeatures: read rivers.river_mesh_trace
GeoFeatures --> Runtime: in-memory river trace
Runtime -> Runtime: resolve output paths

Runtime -> Case: run_reference_2d_zone_conformal_case_from_toml(\nconfig_path, section, output_mesh,\noutput_summary_json, output_figure,\nriver_trace, geographic_features,\ndomain_geographic)
activate Case

Case -> Case: _resolve_case_config(...)
Case -> DomainLoader: load_zone_meshing_domain_payload(...)
DomainLoader --> Case: support domain payload

alt geology_only or geology_rivers
  Case -> GeologyIO: load_vector_geology_dataframe(...)
  GeologyIO --> Case: geology GeoDataFrame
  Case -> Case: clip geology to domain\nand optional interface scope
else rivers_only
  Case -> Case: build domain-backed zone dataframe
end

opt rivers_only or geology_rivers
  Case -> Case: resolve / clip / filter river trace
end

Case -> GmshCore: generate_zone_conformal_mesh_from_dataframe(...)
GmshCore --> Case: ZoneConformalMeshResult

Case -> Case: build summary / constraints QA /\noptional figure and summary JSON
Case --> Runtime: summary
deactivate Case

opt mesh file exists
  Runtime -> BundleExporter: export_catchment_mesh_bundle(\nmesh_path, domain_geographic,\ngeology_cfg, river_trace, summary)
  BundleExporter --> Runtime: bundle summary
end

Runtime --> Caller: summary + optional exchange bundle

note right of Caller
Caller is usually:
- hydromodpy.spatial.mesh.runtime
- hydromodpy.Project.build_mesh()

Batch mode is not a separate sequence.
It repeats this mono-catchment path
once per outlet.
end note
@enduml

Components:

@startuml
title Catchment Mesh - Conformal Component Diagram
left to right direction

package "Orchestration Layer" {
  component "hmp run mesh.toml\nProject.build_mesh()" as MeshFacade
  component "Embedded mesh phase\n(simulation workflow)" as SimulationPhase
}

package "Shared Mesh Runtime" {
  component "spatial.mesh.runtime\nrequire_mesh_section()\nprepare_geographic_config_for_meshing()\nrun_single_mesh_catchment_workflow()" as Runtime
}

package "Geographic Context Layer" {
  component "Workspace" as Workspace
  component "build_geographic_derived_features(...)" as GeographicPipeline
  component "GeographicDerivedFeatures\nsurface / boundaries / rivers" as GeographicFeatures
  component "DomainGeographicContext\ncatchment / DEM support" as DomainContext
}

package "Constraint Source Layer" {
  component "load_zone_meshing_domain_payload(...)\n(domain support)" as DomainSupport
  component "load_vector_geology_dataframe(...)\n(optional geology zones)" as GeologySource
  component "river_trace\nfrom geographic_features.rivers\nor file-backed source" as RiverTrace
}

package "Meshing Case Layer" {
  component "run_reference_2d_zone_conformal_case_from_toml(...)" as Case
  component "ZoneConformalMeshingInputs\nusage / zone_gdf / scopes /\nrivers_cfg / river_trace_for_meshing" as Inputs
}

package "Gmsh Core Layer" {
  component "generate_zone_conformal_mesh_from_dataframe(...)" as GmshCore
  component "ZoneConformalPartition" as Partition
  component "ZoneConformalMeshResult" as MeshResult
}

package "Output Layer" {
  component "QA sidecar\nsummary JSON + figure" as QaOutputs
  component "export_catchment_mesh_bundle(...)" as BundleExporter
  component "CatchmentMeshBundle" as Bundle
}

MeshFacade --> Runtime
SimulationPhase --> Runtime

Runtime --> Workspace
Runtime --> GeographicPipeline
GeographicPipeline --> GeographicFeatures
GeographicFeatures --> DomainContext

Runtime --> Case
Runtime --> BundleExporter

Case --> DomainSupport
Case --> GeologySource
Case --> GeographicFeatures
Case --> RiverTrace
Case --> Inputs
Inputs --> GmshCore

GmshCore --> Partition
GmshCore --> MeshResult
MeshResult --> QaOutputs

BundleExporter --> DomainContext
BundleExporter --> RiverTrace
BundleExporter --> Bundle

note bottom of Runtime
This runtime is the architectural pivot.
The dedicated mesh facade and the embedded
simulation mesh phase reuse the exact
same mesh execution path.
end note

note bottom of Inputs
constraints_mode drives whether geology
constraints, river constraints, or both
are assembled before the Gmsh call.
end note
@enduml

Notes:

  • mesh_catchment_batch is intentionally not modeled as a separate meshing engine.

  • The diagrams stop at the planar catchment mesh plus exchange bundle. 3D extrusion and field-parameter discretization are documented in Structured Grid Architecture and Mesh Architecture Pivot.

Batch loop#

The [mesh_catchment_batch] loop: pre-loop validation of outputs and raster coverage, derivation of child workspaces and outlet coordinates, incremental manifest updates, and the continue_on_error branch that decides whether the loop stops or keeps running.

@startuml
title MeshCatchment Batch Activity

start

:Load and validate [mesh_catchment_batch];
:validate_output_configuration();
:validate_raster_coverage();
:Load outlet records;
:Resolve manifest CSV path;

while (Remaining outlet record?) is (yes)
  :Build child catch_name,\nworkspace_cfg, geographic_cfg,\noutput_overrides;
  :Run mono-catchment workflow\nthrough injected callback;

  if (Child run raised exception?) then (yes)
    :Format one concise error message;
    :Append error row;
    :Rewrite manifest CSV;
    if (continue_on_error?) then (yes)
      :Keep batch loop alive;
    else (no)
      :Re-raise exception;
      stop
    endif
  else (no)
    if (Summary returned but\nmesh file missing?) then (yes)
      :Append error row;
      :Rewrite manifest CSV;
      if (continue_on_error?) then (yes)
        :Keep batch loop alive;
      else (no)
        :Raise RuntimeError;
        stop
      endif
    else (no)
      :Append success row;
      :Rewrite manifest CSV;
    endif
  endif
endwhile (no)

:Rewrite manifest CSV one last time;
:Return MeshCatchmentBatchSummary;

stop
@enduml

Notes:

  • The batch loop reuses the same mono-catchment callback as the public runtime entry point.

  • The manifest CSV is rewritten after each outlet so progress remains visible even when the batch stops early.

  • A summary that returns without a written mesh file is treated as a failure.

In-process simulation embedding#

How the simulation pipeline accepts, rejects, or reuses runtime meshes: the mutual exclusion between [mesh_catchment] and [mesh_input], the early rejection of [mesh_catchment_batch] inside the simulation workflow, the solver-compatibility guard that rejects runtime Gmsh meshes with modflownwt, and the handoff of loaded or generated mesh artifacts into the Project runtime state before solver execution.

@startuml
title Runtime Mesh Integration In Simulation Workflow

start

:Load validated config and raw TOML;
:Resolve optional [mesh_catchment];
:Resolve optional [mesh_input];

if (Both [mesh_catchment]\nand [mesh_input] present?) then (yes)
  :Raise ValueError;
  stop
endif

if ([mesh_catchment_batch].enabled?) then (yes)
  :Raise ValueError;
  stop
endif

:Build SimulationPlan;

if (Runtime mesh requested?) then (yes)
  if (Flow plan contains\nmodflownwt?) then (yes)
    :Raise ValueError;
    stop
  endif
endif

:Run setup;
:Run shared data loading;

if ([mesh_catchment] present?) then (yes)
  :Prepare geographic config\nfor meshing;
  :Execute embedded mono-catchment\nmesh phase once;
  :Store mesh_summary and optional\nin-memory mesh artifacts in run_state;
else (no)
  if ([mesh_input] present?) then (yes)
    :Load external mesh_path\nand/or bundle_dir;
    :Store mesh_summary and loaded\nmesh artifacts in run_state;
  else (no)
    :Skip mesh injection;
  endif
endif

:Continue standard simulation runner execution;

stop
@enduml

Notes:

  • The simulation workflow can embed one mono-catchment mesh phase or reuse one precomputed mesh, but not both in the same run.

  • Runtime Gmsh meshes are currently intended for boussinesq and modflow6. modflownwt stays on the structured sgrid path.

Output layout#

How the dedicated mesh workflow resolves final artifacts and cleanup behavior: the standard versus flat output layout split, optional figure generation, exchange-bundle export, cleanup of intermediate geographic artifacts, and extra naming and manifest rules in batch mode.

@startuml
title MeshCatchment Output Layout Activity

start

:Resolve output paths from\nsection config + optional overrides;

if (Dedicated mesh output_layout == flat?) then (yes)
  :Create temporary runtime workspace\nunder _mesh_runtime/<catch_name>;
  :Write final mesh artifacts directly under\nworkspace.project_root;
else (standard)
  :Write final mesh artifacts under\nresults_stable/mesh/;
endif

if (figures_enabled?) then (yes)
  :Resolve overview and regional\nfigure paths;
  :Write PNG figures when paths\nare configured;
else (no)
  :Skip figure outputs;
endif

if (Mesh file exists?) then (yes)
  :Export exchange bundle next to\nfinal mesh outputs;
endif

if (Dedicated mesh geographic_outputs_mode == cleanup?) then (yes)
  :Delete results_stable/geographic\nand results_stable/demcorrecflow;
else (keep)
  :Preserve geographic intermediates;
endif

if (Batch mode?) then (yes)
  :Apply outlet-specific filename patterns;
  :Append one manifest CSV row per outlet;
endif

if (Flat layout used?) then (yes)
  :Delete temporary runtime workspace root;
endif

stop

note right
The full simulation workflow keeps the
standard workspace layout and does not
apply the dedicated mesh cleanup branch.
end note
@enduml

Notes:

  • flat layout is a convenience for the dedicated mesh workflow. It writes the final mesh artifacts directly under workspace.project_root while the intermediate runtime workspace lives elsewhere and can be deleted afterwards.

  • The full simulation workflow keeps the standard workspace structure because it still needs its runtime folders.

  • Batch mode adds per-outlet filename patterns and one manifest CSV, but it still reuses the same mono-catchment output-resolution rules.

See also#