Adding a New Crop Process

While the WOFOST crop growth model modelled by PCSE contains many crop and soil processes, it may sometimes be the case that the user wants to modify the underlying crop growth model in PCSE to add a new process (e.g. nutrient leeching from the subsoil). Adding a new crop process enables new responses to be studied in WOFOSTGym. This guide outlines how to add a new process to the PCSE model so that its variables can be modified through the WOFOSTGym API.

All crop submodules are contained within the pcse/pcse/crop and pcse/pcse/soil folders. First, choose which submodule the new process should be computed within. Then, add the required states, rates, and parameters to the subclasses defined within the parent module as shown below:

class NPK_Soil_Dynamics(SimulationObject):

    class Parameters(ParamTemplate):
        NEW_PARAM = Float(-99.0)
    class StateVariables(StatesTemplate):
        NEW_STATE = Float(-99.0)
    class RateVariables(RatesTemplate):
        NEW_RATE = Float(-99.0)

Then, add the new states and rates to the StateVariables and RateVariables constructor in the initialize and reset functions as so:

self.states = self.StateVariables(
            kiosk,
            publish=["NEW_STATE"],
            NEW_STATE=NEW_STATE_INIT_VALUE
        )

        self.rates = self.RateVariables(
            kiosk,
            publish=["NEW_RATE"]
        )

This will ensure that your new states and rates can be accessed by other submodules in PCSE and also by the WOFOSTGym API. Then, define the new crop subprocess in the calc_rates() and integrate() functions. Rates can be accessed by r.NEW_RATE and states can be accessed by s.NEW_STATE. Parameters can be accessed by p.NEW_PARAM.

Now the new crop or soil process is completely defined! The next step is ensuring that WOFOSTGym can access it. In the pcse_gym/pcse_gym/utils.py file, add the new states and rates to the OUTPUT_VARS list exactly as it was defined in the StateVariables or RateVariables class.

For a new parameter, it is recommended to add this line of code to the set_params() function in the same utils.py file:

if args.NEW_PARAM is not None:
    env.parameterprovider.set_override("NEW_PARAM", args.NEW_PARAM, check=False)

and the below line of code in the pcse_gym/args.py in the WOFOST_Args class:

class WOFOST_Args:
    """ NEW PARAM FOR XX"""
    NEW_PARAM: Optional[float] = None

These additions will enable the parameter to be set via command line.

Caution

If a new parameter has been added, it must also be added to the crop and site yaml files. Otherwise, a missing parameter error will be thrown by the PCSE module.

Now that the WOFOSTGym API is aware of the new state and rate variables, they can be included as part of the observation space by modifying the npk.output_vars parameter via command line.

Adding a new Crop or Soil Submodules

To add a new Crop or Soil submodule, create a new file in the crop/ or soil/ folders. Every submodule needs to inherit from the SimulationObject class. A template is included below:

from datetime import date

from pcse.util import AfgenTrait
from pcse.utils.traitlets import Float
from pcse.utils.decorators import prepare_rates, prepare_states
from pcse.base import ParamTemplate, StatesTemplate, RatesTemplate, SimulationObject
from pcse.utils import signals
from pcse.base import VariableKiosk
from pcse.nasapower import WeatherDataContainer

class NPK_Soil_Dynamics(SimulationObject):

    class Parameters(ParamTemplate):

    class StateVariables(StatesTemplate):

    class RateVariables(RatesTemplate):

    def initialize(self, day: date, kiosk: VariableKiosk, parvalues: dict) -> None:

        self.params = self.Parameters(parvalues)
        self.kiosk = kiosk
        self.states = self.StateVariables(
            kiosk,
            publish=[],
        )

        self.rates = self.RateVariables(
            kiosk,
            publish=[]
        )

        self._connect_signal(self._on_APPLY_NPK, signals.apply_npk)

    @prepare_rates
    def calc_rates(self, day: date, drv: WeatherDataContainer) -> None:
        """Compute Rates for model"""
        pass

    @prepare_states
    def integrate(self, day: date, delt: float = 1.0) -> None:
        """Integrate states with rates"""
        pass

All Parameters, States, and Rates should be defined in their respective subclasses as so:

class NPK_Soil_Dynamics(SimulationObject):

    class Parameters(ParamTemplate):
        PARAM = Float(-99.0)
    class StateVariables(StatesTemplate):
        STATE = Float(-99.0)
    class RateVariables(RatesTemplate):
        RATE = Float(-99.0)

Any state and rate that should be visible to the kiosk or to the WOFOSTGym wrapper should be published. See the above section on adding states and rates.

After defining all the module processes, its integrate() and calc_rates() methods need to be called. For soil submodules, these are in the soil_wrappers.py file. Add an instance of the new object to the constructor, and add its method calls to the high level integrate() and calc_rates() calls. Ensure that the corresponding reset() function is called in the reset wrapper as well.

For crop submodules, these are defined in the wofost8.py file. Follow the same process for adding the functions listed above.