Skip to content

[feature] Modular refactor of crop models - grassim

Modular crop model : core idea

A Module is defined with the following characteristics :

  • Parameter inputs (and associated units)
  • State variable inputs (and associated units)
  • State variable outputs (and associated units)
  • Implementation (differential equation and associated solving method)

For example a simple plant biomass growth module would have the following characteristics

  • Parameter inputs : growth rate (r)
  • State variable input : biomass (B)
  • State variable outputs : biomass (B)
  • Implementation : dB/dt = r*B, solved with explicit euler : B_(t+delta_t) = B_t + r*B_t*delta_t

Modular crop model : why ?

A modular approach allows flexible, transparent, and reusable model components. Each process (e.g., photosynthesis, transpiration, soil water balance) is defined as an independent, well-documented module. Modules can be combined, replaced, or refined without altering the entire system.

1. Clean and structured developpement framework

  • Encourages structured, readable code and clear interfaces between processes.
  • Facilitates test-driven development (unit tests), as each module can be independently tested.
  • Simplifies code review and maintenance, since modules are self-contained and well-documented.

2. Benchmarking and comparison

  • Different formulation of the same process (identical inputs/outputs) can be easily evaluated.
  • Module upgrades can be easily evaluated.
  • Different models can be compared.

3. Transparency, readability and collaboration

  • Each module’s assumptions and implementation are explicitly documented in a consistent and readble fashion, improving transparency and reducing the occurrence of errors.
  • Enhances collaboration among domain experts (soil, crop, climate) who can focus on their respective modules in a shared framework, "speaking the same language".

4. Automated dependency and execution management

  • A model (combination of modules) can be validated before execution to ensure that all required inputs are either initialized or computed by another module.
  • Enables automatic sorting of module execution order based on dependencies.
  • Prevents runtime errors by identifying missing or circular dependencies.
  • Unit coherence can be validated.

Challenges

1. Spatial description

  • The Python implementation should accept NumPy arrays as inputs and outputs to enable vectorized computation.
  • The framework must handle spatially explicit variables (grids, soil layers). Examples:
    • Horizontal water transfer between grid cells or soil compartments.
    • For crop rotations, a spatial mapping of the state-variable dictionary is needed to apply management masks or operations selectively.

2. Time integration

  • The framework should be able to handle processes at different time steps.

3. Variable naming and standardization

  • Ensure consistent variable names and unit conventions across modules for interoperability.
  • Shared naming convention (e.g., soil_water_content vs SoilWaterContent).

Implementation

The simulation structure bellow is proposed. refactor_crop_models

  • SimulationEngine orchestrates the simulation. Loads modules, checks compatibility, order modules and runs them.
  • Grid2D is a simple spatial descriptor. The same grid2D object must be used for a simulation.
  • SharedState is a registry of all current state variable, accessible and editable by any module.
  • Module is a class structure common to all modules. It contains requires and provides variable name lists, so that modules can be ordered and their compatibility checked before running a simulation. The advance() method contains the actual model process and solver, executed at runtime.

Simulation parameters and modules to load are set in a config file. A config file corresponds to a soil-plant model. Ex: Grassim, Simple, etc.

Current development state (3/11/2025)

A first version of SimulationEngine and SharedState (state variable registry), and Module were developped. Development is pending in !49.

SimulationEngine

Simulation orchestration.

SharedState

Class containing all state variables.

  • Inherits from dict. This allows the use of dict syntax (set item with dict[<key>] = <value>), but with additional features and checks.

Module

Class for process implementation.

  • Abstract class (ABC inheritance) : define abstract methods, that MUST be implemented in the module. This imposes a common structure for each module.
  • exposed variables :
    • requires: what inputs the module needs.
    • provides: what outputs it computes.
    • params: configurable parameters. -> this allows automatic dependency check and module order determination in the engine.

Example modules

  • Test modules were coded to test module orchestration by the engine, avalable here.
  • A ET0 module was added but not yet properly working. Available here.
  • Example simulation config files can be found here.

Unsatisfying for now

  • Simulation time step management.
  • Spatial descriptor : a 2D grid is limiting.
  • Unit management with the pint package makes the code too verbose and rigid. Another system should be designed.
  • Pase input coupling with TimeSeries input. For now, the simulation parameter 'from pase' is used to unpack irradiance and weather data. A custom TimeSeriesInputs class and unpacking functions were defined, this structure is not very clear.
Edited by Lemaire Louis