Spatial Support UML Diagrams#

Scope#

These diagrams document the architecture around:

  • domain.supports and domain.zone_ids,

  • the different support-definition cases (generated bands, rings, catchment, geology),

  • the bridge from domain-side supports to the generic Field contract,

  • the way FieldParam consumes those supports during solver property mapping.

Code map#

  • hydromodpy/spatial/domain/domain_config.py: high-level domain declaration including support registration.

  • hydromodpy/spatial/domain/spatial_support_config.py: typed support-definition models.

  • hydromodpy/spatial/domain/spatial_support.py: runtime support builders and providers.

  • hydromodpy/spatial/field/core/field_param.py: consumption point through field_spatial_id.

  • hydromodpy/spatial/mesh/cartesian_grid/sgrid_fieldparam_discretization.py: downstream discretization over solver meshes.

Context Diagram#

Use this diagram to position the main modules and runtime responsibilities.

@startuml
title Spatial Support - Context Diagram

component "DomainConfig\nzone_ids, supports" as DomainConfig
component "DomainSupportConfig\nprovider-specific declarations" as SupportConfig
component "Project" as Project
component "DataPlanner" as Planner
component "DataManagersRuntimeLoader" as Loader
component "Structure binders\napply_catchment_zones_to_domain\napply_geology_to_domain" as Binders
component "Domain\nzones registry\nresolve_spatial_support(...)" as Domain
component "SpatialSupportProviderRegistry" as Registry
component "Support providers\nGeneratedBands / Rings /\nCatchmentZones / Geology" as Providers
component "Field\nspatial support contract" as Field
component "FieldParam\nhomogeneous / heterogeneous values" as FieldParam
component "discretize_fieldparam_on_sgrid" as Discretizer
component "resolve_flow_property_arrays" as PropertyMapping
component "Solver property arrays\nhk / sy / ss" as SolverArrays

DomainConfig --> SupportConfig : declares named supports
Project --> Planner : infer required data types
Planner --> Loader : geology when required
Project --> Domain : build runtime domain
Project --> Binders : attach domain-side zone sources
Binders --> Domain : set_zone(...)
Project --> Registry : resolve providers
Registry --> Providers : get(provider_name)
Providers --> Field : build support objects\ncompatible with Field
Providers --> Domain : register supports as zones
PropertyMapping --> FieldParam : select flow parameter
PropertyMapping --> Domain : resolve_spatial_support(field_spatial_id)
PropertyMapping --> Discretizer : project + evaluate values
Discretizer --> Field : on_mesh(...)
Discretizer --> FieldParam : to_mesh_field(...)
PropertyMapping --> SolverArrays : store 2D/3D property arrays

note right of Field
Support objects are consumed through the
generic Field interface, even when they
originate from domain-side concepts
(geology, catchment, generated supports).
end note

@enduml

Class Diagram#

Use this diagram to document the supported zone-definition cases and the relationships between DomainConfig, support configs, provider classes, support-field implementations, Field, and FieldParam.

@startuml
title Spatial Support - Class Diagram

package "hydromodpy.spatial.domain.domain_config" {
  class DomainConfig {
    +zone_ids: list[str]
    +supports: dict[str, DomainSupportConfig]
  }
}

package "hydromodpy.spatial.domain.spatial_support_config" {
  abstract class DomainSupportBaseConfig {
    +provider: str
  }

  class GeneratedBandsSupportConfig {
    +axis: x | y
    +coordinate_mode: relative | absolute
    +breaks: list[float]
    +labels: list[str]
  }

  class GeneratedRingsSupportConfig {
    +coordinate_mode: relative | absolute
    +radii: list[float]
    +labels: list[str]
    +center_x: float | None
    +center_y: float | None
  }

  class CatchmentZonesSupportConfig {
    +source_zone_id: str
  }

  class GeologySupportConfig

  class DomainSupportConfig <<union>>
}

package "hydromodpy.spatial.domain.spatial_support" {
  interface SpatialSupportProvider {
    +provider_name
    +build_phase
    +required_data_types(config)
    +build(support_id, config, context)
  }

  class SpatialSupportProviderRegistry {
    +register(provider)
    +get(provider_name)
  }

  class AliasedSpatialSupportField
  class RasterZonesSupportField
  class GeneratedBandsSupportField
  class GeneratedRingsSupportField

  class GeneratedBandsSupportProvider
  class GeneratedRingsSupportProvider
  class CatchmentZonesSupportProvider
  class GeologySupportProvider
}

package "hydromodpy.spatial.domain.domain" {
  class Domain {
    +zones: dict[str, object]
    +set_zone(zone_id, zone_obj)
    +get_zone(zone_id)
    +resolve_spatial_support(support_id)
  }
}

package "hydromodpy.spatial.domain.catchment_zones_field" {
  class CatchmentZonesField {
    +identifier: str
    +encoded_codes: np.ndarray
    +encoded_to_zone: Mapping[int, str]
    +zone_keys
  }
}

package "hydromodpy.spatial.field.geology.geology_field" {
  class GeologyField {
    +identifier: str
    +encoded_codes: np.ndarray
    +encoded_to_zone: dict[int, str]
    +zone_id(x, y)
    +on_mesh(mesh, cell_samples_per_axis)
  }
}

package "hydromodpy.spatial.field.core.field_spatial" {
  abstract class Field {
    +identifier: str
    +on_mesh(mesh, cell_samples_per_axis)
  }

  abstract class FieldDiscretization {
    +mesh
    +field_id
    +aggregation
    +weighted_components()
  }
}

package "hydromodpy.spatial.field.core.field_spatial_weighted_discretization" {
  class WeightedAverageFieldDiscretization {
    +zone_keys: tuple[str, ...]
    +fractions_by_zone: dict[str, np.ndarray]
  }
}

package "hydromodpy.spatial.field.core.field_param" {
  class FieldParam {
    +identifier: str
    +kind: homogeneous | heterogeneous
    +field_spatial_id: str | None
    +values_by_key: dict[str, float] | None
    +to_array(...)
    +to_mesh_field(...)
    +map_zone_ids(zone_ids)
  }
}

DomainConfig --> DomainSupportConfig : supports[*]
DomainSupportBaseConfig <|-- GeneratedBandsSupportConfig
DomainSupportBaseConfig <|-- GeneratedRingsSupportConfig
DomainSupportBaseConfig <|-- CatchmentZonesSupportConfig
DomainSupportBaseConfig <|-- GeologySupportConfig
DomainSupportConfig ..> DomainSupportBaseConfig

SpatialSupportProviderRegistry o-- SpatialSupportProvider
SpatialSupportProvider <|.. GeneratedBandsSupportProvider
SpatialSupportProvider <|.. GeneratedRingsSupportProvider
SpatialSupportProvider <|.. CatchmentZonesSupportProvider
SpatialSupportProvider <|.. GeologySupportProvider

GeneratedBandsSupportProvider --> GeneratedBandsSupportField : builds
GeneratedRingsSupportProvider --> GeneratedRingsSupportField : builds
CatchmentZonesSupportProvider --> RasterZonesSupportField : builds
CatchmentZonesSupportProvider ..> CatchmentZonesField : adapts source zone
GeologySupportProvider --> AliasedSpatialSupportField : optional alias
GeologySupportProvider ..> GeologyField : returns or delegates

Field <|-- AliasedSpatialSupportField
Field <|-- RasterZonesSupportField
Field <|-- GeneratedBandsSupportField
Field <|-- GeneratedRingsSupportField
Field <|-- GeologyField

FieldDiscretization <|-- WeightedAverageFieldDiscretization
Field --> WeightedAverageFieldDiscretization : on_mesh(...)
FieldParam ..> Field : support selected by field_spatial_id
FieldParam ..> FieldDiscretization : heterogeneous mapping

Domain o-- "0..*" Field : stores Field-compatible supports in zones
Domain o-- "0..*" CatchmentZonesField : may store source zonations

note bottom of CatchmentZonesField
CatchmentZonesField is a compact semantic zone matrix.
It is not itself a Field implementation; the
CatchmentZonesSupportProvider adapts it to RasterZonesSupportField.
end note

@enduml

Support-Resolution Activity Diagram#

Use this diagram to explain how explicit support declarations drive runtime behavior and data dependencies.

@startuml
title Spatial Support - Support Resolution Activity Diagram

start

:Validate DomainConfig;
:Inspect flow parameters and\nrequested field_spatial_id values;

if (Any heterogeneous FieldParam?) then (yes)
  if (Any requested support missing in\ndomain.supports?) then (yes)
    :Reject configuration;\neach heterogeneous field_spatial_id\nmust match a declared domain support;
    stop
  else (no)
    :Attach setup-time zone sources\n(catchment and custom domain zones);

    if (Any requested support uses\nprovider = geology?) then (yes)
      :Infer/load geology data\nthrough DataPlanner\nand runtime loader;
      :Attach GeologyField to Domain;
    endif

    :Build requested explicit supports\nusing generated_bands,\ngenerated_rings, catchment_zones,\nor geology providers;
    :Domain now exposes support ids\nthrough zones + resolve_spatial_support(...);
    :During property mapping,\nresolve field_spatial_id on Domain;

    if (Support found?) then (yes)
      :Project support on mesh\nvia Field.on_mesh(...);
      :Map values with FieldParam.to_mesh_field(...);
    else (no)
      :Fail fast;\nmissing spatial support for\nheterogeneous mapping;
      stop
    endif
  endif
else (no)
  :Use homogeneous FieldParam mapping;\nno spatial support required;
endif

stop
@enduml

Support-Build Sequence Diagram#

Use this diagram to describe how the project facade builds and registers support objects during setup and data phases.

@startuml
title Spatial Support - Build Sequence Diagram

actor User
participant "HydroModPyLauncher" as Launcher
participant "DomainConfig" as DomainCfg
participant "DataPlanner" as Planner
participant "DataManagersRuntimeLoader" as Loader
participant "Domain" as Domain
participant "Structure binders" as Binders
participant "SpatialSupportProviderRegistry" as Registry
participant "Provider" as Provider
participant "loaded_data.geology" as Geology

User -> Launcher: run()
Launcher -> DomainCfg: validate supports / zone_ids
Launcher -> Planner: build(... provider_names,\nrequested_support_ids)
Planner --> Launcher: data load plan

Launcher -> Domain: Domain(config, surface_topo)
Launcher -> Binders: apply_catchment_zones_to_domain(domain, geographic)
alt catchment zone artifacts available
  Binders -> Domain: set_zone("catchment", CatchmentZonesField)
end

loop for each requested support with build_phase = setup
  Launcher -> Registry: get(provider_name)
  Registry --> Launcher: provider
  Launcher -> Provider: build(support_id, config, context)
  alt generated_bands / generated_rings
    Provider --> Launcher: Generated*SupportField
  else catchment_zones
    Provider -> Domain: get_zone(source_zone_id)
    Provider --> Launcher: RasterZonesSupportField
  end
  Launcher -> Domain: set_zone(support_id, support_field)
end

alt geology data required
  Launcher -> Loader: load_all(run_state)
  Loader --> Launcher: loaded_data
  Launcher -> Binders: apply_geology_to_domain(domain, geology)
  Binders -> Domain: set_zone("geology", GeologyField)

  loop for each requested support with build_phase = data
    Launcher -> Registry: get(provider_name)
    Registry --> Launcher: provider
    Launcher -> Provider: build(support_id, config, context)
    Provider -> Geology: use loaded geology field
    Provider --> Launcher: GeologyField or AliasedSpatialSupportField
    Launcher -> Domain: set_zone(support_id, support_field)
  end
end

Launcher --> User: Domain ready with spatial supports
@enduml

FieldParam-Mapping Sequence Diagram#

Use this diagram to describe how a support is consumed when a heterogeneous FieldParam is mapped to solver arrays.

@startuml
title Spatial Support - FieldParam Mapping Sequence Diagram

participant "Solver setup / property mapping" as Solver
participant "resolve_flow_property_arrays" as PropertyMapping
participant "Flow.parameters" as FlowParams
participant "FieldParam" as Param
participant "Domain" as Domain
participant "support Field" as SupportField
participant "discretize_fieldparam_on_sgrid" as Discretizer
participant "WeightedAverageFieldDiscretization" as FieldDisc

Solver -> PropertyMapping: resolve_flow_property_arrays(flow, domain, sgrid)
PropertyMapping -> FlowParams: select canonical parameter\n(K / Sy / Ss)
FlowParams --> PropertyMapping: FieldParam

alt Param.is_homogeneous
  PropertyMapping -> Discretizer: discretize_fieldparam_on_sgrid(\nfield_param=Param,\nsupport_field=None,\nsgrid)
  Discretizer -> Param: to_mesh_field(mesh=mesh, depth=0)
  loop for each SGrid layer
    Discretizer -> Param: to_mesh_field(mesh=mesh,\ndepth=layer_center_depth)
  end
else Param.is_heterogeneous
  PropertyMapping -> Domain: resolve_spatial_support(Param.field_spatial_id)
  Domain --> PropertyMapping: support Field
  PropertyMapping -> Discretizer: discretize_fieldparam_on_sgrid(\nfield_param=Param,\nsupport_field=SupportField,\nsgrid)
  Discretizer -> SupportField: on_mesh(mesh, cell_samples_per_axis)
  SupportField --> Discretizer: WeightedAverageFieldDiscretization
  Discretizer -> Param: to_mesh_field(FieldDisc, depth=0)
  loop for each SGrid layer
    Discretizer -> Param: to_mesh_field(FieldDisc,\ndepth=layer_center_depth)
  end
end

Discretizer --> PropertyMapping: values_2d + values_3d
PropertyMapping --> Solver: hk / sy / ss arrays

note right of Discretizer
Spatial support is discretized on the
planar mesh first. Vertical variation is
then evaluated layer by layer through
FieldParam depth handling.
end note

@enduml