Process Package Architecture¶
The swimrs.process package provides a high-performance implementation of the SWIM-RS water balance model using typed dataclasses and Numba-compiled kernels. It consumes HDF5 inputs built from a .swim container.
Package Structure¶
src/swimrs/process/
├── __init__.py
├── input.py # SwimInput: HDF5-backed data container
├── state.py # Dataclasses: WaterBalanceState, FieldProperties, CalibrationParameters
├── loop.py # Daily simulation loop orchestration
└── kernels/ # Numba-compiled physics functions
├── cover.py
├── crop_coefficient.py
├── evaporation.py
├── irrigation.py
├── root_growth.py
├── runoff.py
├── snow.py
├── transpiration.py
└── water_balance.py
Core Dataclasses¶
The process package uses five main dataclasses to organize simulation data.
All array attributes have shape (n_fields,) unless otherwise noted.
SwimInput¶
The top-level container that packages everything needed for a simulation run.
It wraps an HDF5 file and provides lazy access to time series data. Build it
from a .swim container via build_swim_input, then distribute the .h5 file
to PEST++ workers. It holds references to the
three dataclasses below plus methods to retrieve daily forcing data by index.
FieldProperties¶
Static soil and crop properties that do not change during simulation. These come from soil surveys (AWC, Ksat), land cover databases (root depth, crop type), and observation-derived values (ke_max from bare-soil ETf, f_sub from ET/precipitation ratios). Examples: available water capacity, curve number, maximum root depth, irrigation status, perennial flag.
CalibrationParameters¶
Parameters that PEST++ adjusts during inverse modeling. These control the
NDVI-to-Kcb relationship (ndvi_k, ndvi_0), snow melt rates (swe_alpha,
swe_beta), stress response damping (ks_damp, kr_damp), and irrigation
behavior (max_irr_rate). The from_base_with_multipliers() method applies
PEST++ multiplier files to base values.
WaterBalanceState¶
Mutable state that evolves each day. The simulation loop reads and writes these arrays in place. Key variables: root zone depletion (depl_root), surface layer depletion (depl_ze), snow water equivalent (swe), current root depth (zr), and the damped stress coefficients (ks, kr). Initialized from spinup values at simulation start.
DailyOutput¶
Accumulator for simulation results. Shape is (n_days, n_fields) for all
arrays. Stores actual ET, crop coefficients, runoff, irrigation, and other
diagnostics. Created by run_daily_loop() and populated incrementally as
the simulation advances through each day.
Data Flow Diagram¶
Shows how data moves from input files through the simulation loop to outputs.
flowchart TB
subgraph Input["Input Layer"]
SWIM["project.swim<br/>(container)"]
HDF5["project.h5"]
SWIM --> |build_swim_input| HDF5
end
subgraph Container["SwimInput Container"]
HDF5 --> Props["FieldProperties<br/>(static soil/crop)"]
HDF5 --> Params["CalibrationParameters<br/>(PEST++ tunable)"]
HDF5 --> Spinup["WaterBalanceState<br/>(initial conditions)"]
HDF5 --> TS["Time Series<br/>(ndvi, prcp, tmin, tmax, srad, etr)"]
end
subgraph Loop["Daily Loop (loop.py)"]
direction TB
State["WaterBalanceState<br/>(mutable)"]
StepDay["step_day()"]
Output["DailyOutput<br/>(eta, etf, kcb, ke, ks, kr, ...)"]
end
subgraph Kernels["Numba Kernels"]
direction LR
Snow["snow.py<br/>partition_precip<br/>albedo_decay<br/>degree_day_melt<br/>snow_water_equivalent"]
Runoff["runoff.py<br/>scs_runoff<br/>infiltration_excess"]
Crop["crop_coefficient.py<br/>kcb_sigmoid"]
Cover["cover.py<br/>fractional_cover<br/>exposed_soil_fraction"]
Root["root_growth.py<br/>root_depth_from_kcb<br/>root_water_redistribution"]
Evap["evaporation.py<br/>kr_reduction<br/>kr_damped<br/>ke_coefficient"]
Trans["transpiration.py<br/>ks_stress<br/>ks_damped"]
Irr["irrigation.py<br/>irrigation_demand<br/>groundwater_subsidy"]
WB["water_balance.py<br/>deep_percolation<br/>layer3_storage<br/>actual_et"]
end
Props --> StepDay
Params --> StepDay
Spinup --> State
TS --> |"day_idx"| StepDay
State <--> StepDay
StepDay --> Output
StepDay --> Kernels
Class/Function Relationships¶
Shows the dataclass structure and how modules interact.
classDiagram
class SwimInput {
+Path h5_path
+datetime start_date, end_date
+int n_days, n_fields
+list fids
+str runoff_process, refet_type
+FieldProperties properties
+CalibrationParameters parameters
+WaterBalanceState spinup_state
+get_time_series(variable, day_idx)
+get_irr_flag(day_idx)
+close()
}
class FieldProperties {
+int n_fields
+NDArray fids, awc, ksat
+NDArray rew, tew, cn2
+NDArray zr_max, zr_min, p_depletion
+NDArray~bool~ irr_status, perennial, gw_status
+NDArray ke_max, f_sub
+compute_taw(zr) NDArray
+compute_raw(taw) NDArray
}
class CalibrationParameters {
+int n_fields
+NDArray kc_max, kc_min
+NDArray ndvi_k, ndvi_0
+NDArray swe_alpha, swe_beta
+NDArray kr_damp, ks_damp
+NDArray max_irr_rate
+from_base_with_multipliers(base, multipliers)$ CalibrationParameters
+copy() CalibrationParameters
}
class WaterBalanceState {
+int n_fields
+NDArray depl_root, depl_ze
+NDArray daw3, taw3
+NDArray swe, albedo
+NDArray zr, kr, ks
+NDArray irr_continue, next_day_irr
+from_spinup(...)$ WaterBalanceState
+copy() WaterBalanceState
}
class DailyOutput {
+int n_days, n_fields
+NDArray eta, etf, kcb, ke
+NDArray ks, kr, runoff
+NDArray rain, melt, swe
+NDArray depl_root, dperc
+NDArray irr_sim, gw_sim
}
SwimInput *-- FieldProperties
SwimInput *-- CalibrationParameters
SwimInput *-- WaterBalanceState
class loop_py {
<<module>>
+run_daily_loop(swim_input, params) tuple
+step_day(state, props, params, ...) dict
}
loop_py ..> SwimInput : reads
loop_py ..> WaterBalanceState : mutates
loop_py ..> DailyOutput : creates
loop_py ..> FieldProperties : uses
loop_py ..> CalibrationParameters : uses
Kernel Call Sequence¶
Shows the order of kernel function calls within a single step_day() execution.
sequenceDiagram
participant L as loop.step_day
participant S as snow
participant R as runoff
participant C as crop_coefficient
participant CV as cover
participant RG as root_growth
participant E as evaporation
participant T as transpiration
participant I as irrigation
participant W as water_balance
L->>S: partition_precip(prcp, temp)
L->>S: albedo_decay(albedo, snow)
L->>S: degree_day_melt(swe, tmax, ...)
L->>S: snow_water_equivalent(swe, snow, melt)
L->>R: scs_runoff(precip_eff, cn2)
L->>C: kcb_sigmoid(ndvi, kc_max, ...)
L->>CV: fractional_cover(kcb, kc_min, kc_max)
L->>CV: exposed_soil_fraction(fc)
L->>RG: root_depth_from_kcb(kcb, ...)
L->>RG: root_water_redistribution(zr_new, zr_prev, ...)
L->>E: kr_reduction(tew, depl_ze, rew)
L->>T: ks_stress(taw, depl_root, raw)
L->>E: kr_damped(kr_base, kr_prev, damp)
L->>T: ks_damped(ks_base, ks_prev, damp)
L->>E: ke_coefficient(kr, kc_max, kcb, few, ke_max)
L->>W: actual_et(ks, kcb, fc, ke, kc_max, etr)
L->>I: irrigation_demand(depl, raw, max_rate, ...)
L->>I: groundwater_subsidy(depl, raw, gw_status, f_sub)
L->>W: deep_percolation(depl_new)
L->>W: layer3_storage(daw3, taw3, gross_dperc)
Key Design Decisions¶
Typed Dataclasses¶
All state and parameter containers use Python dataclasses with explicit NumPy array types. This provides: - Clear documentation of array shapes and meanings - IDE autocompletion and type checking - Easy serialization to/from HDF5
HDF5 Data Container¶
SwimInput wraps an HDF5 file for:
- Portable distribution to PEST++ workers
- Lazy loading of large time series arrays
- Single-file packaging of all simulation inputs
Numba Kernels¶
All physics computations are in separate kernel modules using @njit decorators:
- Parallel execution across fields with parallel=True
- Cache compilation with cache=True
- Pure functions with no side effects (except state mutation in loop.py)
Separation of Concerns¶
- input.py: Data I/O and HDF5 management
- state.py: Data structure definitions only
- loop.py: Orchestration logic
- kernels/: Physics implementations
This separation allows kernels to be tested independently and potentially reused in other contexts.