[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. 
-
SimulationEngineorchestrates the simulation. Loads modules, checks compatibility, order modules and runs them. -
Grid2Dis a simple spatial descriptor. The same grid2D object must be used for a simulation. -
SharedStateis a registry of all current state variable, accessible and editable by any module. -
Moduleis a class structure common to all modules. It containsrequiresandprovidesvariable name lists, so that modules can be ordered and their compatibility checked before running a simulation. Theadvance()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 withdict[<key>] = <value>), but with additional features and checks.
Module
Class for process implementation.
- Abstract class (
ABCinheritance) : 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
pintpackage 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
TimeSeriesInputsclass and unpacking functions were defined, this structure is not very clear.