Skip to content

API reference

Simulation settings

Main simulation settings for the SWAP model.

Modules:

Name Description
general

General simulation settings for the SWAP model.

metadata

Metadata for the SWAP model.

richards

Richards simulation settings for the SWAP model.

UNITRANGE = {'ge': 0.0, 'le': 1.0} module-attribute

Range of values between 0.0 and 1.0.

YEARRANGE = {'ge': 0, 'le': 366} module-attribute

Range of values for year (0 <= x <= 366).

GeneralSettings

Bases: PySWAPBaseModel

General settings of the simulation.

Todo

  • remove the individual file extension switches and replace with a list of extensions. Make the class automatically generate the switches based on the list of extensions.

Attributes:

Name Type Description
pathwork str

Path to the working directory

pathatm str

Path to folder with weather files

pathcrop str

Path to folder with crop files

pathdrain str

Path to folder with drainage files

swscre Literal[0, 1, 3]

Switch, display progression of simulation run to screen

swerror Literal[0, 1]

Switch for printing errors to screen

tstart date

Start date of simulation run, give day-month-year

tend date

End date of simulation run, give day-month-year

nprintday int

Number of output times during a day

swmonth Literal[0, 1]

Switch, output each month

swyrvar Literal[0, 1]

Output times for overall water and solute balances in .BAL and .BLC file: choose output at a fixed date each year or at different dates

period Optional[int]

Fixed output interval

swres Optional[Literal[0, 1]]

Switch, reset output interval counter each year

swodat Optional[Literal[0, 1]]

Switch, extra output dates are given in table below

outdatin Optional[DateList]

list of specific dates

datefix Optional[DayMonth]

fixed date for output

outdat Optional[DateList]

specify all output dates

outfil str

Generic file name of output files

swheader Literal[0, 1]

Print header at the start of each balance period

swwba Literal[0, 1]

Switch, output daily water balance

swend Literal[0, 1]

Switch, output end-conditions

swvap Literal[0, 1]

Switch, output soil profiles of moisture, solute and temperature

swbal Literal[0, 1]

Switch, output file with yearly water balance

swblc Literal[0, 1]

Switch, output file with detailed yearly water balance

swsba Literal[0, 1]

Switch, output file of daily solute balance

swate Literal[0, 1]

Switch, output file with soil temperature profiles

swbma Literal[0, 1]

Switch, output file with water fluxes, only for macropore flow

swdrf Literal[0, 1]

Switch, output of drainage fluxes, only for extended drainage

swswb Literal[0, 1]

Switch, output surface water reservoir, only for extended drainage

swini Literal[0, 1]

Switch, output of initial SoilPhysParam and HeatParam

swinc Literal[0, 1]

Switch, output of water balance increments

swcrp Literal[0, 1]

Switch, output of simple or detailed crop growth model

swstr Literal[0, 1]

Switch, output of stress values for wetness, drought, salinity and frost

swirg Literal[0, 1]

Switch, output of irrigation gifts

swcsv Literal[0, 1]

Switch, csv output

inlist_csv Optional[StringList]

list of variables for the csv output

swcsv_tz Literal[0, 1]

Switch, csv output with depth

inlist_csv_tz Optional[StringList]

list of variables for the csv tz output

swafo Literal[0, 1, 2]

Switch, output file with formatted hydrological data

swaun Literal[0, 1, 2]

Switch, output file with unformatted hydrological data

critdevmasbal Optional[float]

Critical Deviation in water balance during PERIOD

swdiscrvert Literal[0, 1]

Switch to convert vertical discretization

numnodnew Optional[int]

New number of nodes

dznew Optional[FloatList]

Thickness of compartments

Source code in pyswap/simsettings/general.py
class GeneralSettings(PySWAPBaseModel):
    """General settings of the simulation.

    !!! todo
        * remove the individual file extension switches and replace with a list of extensions. Make the class
            automatically generate the switches based on the list of extensions.

    Attributes:
        pathwork (str): Path to the working directory
        pathatm (str): Path to folder with weather files
        pathcrop (str): Path to folder with crop files
        pathdrain (str): Path to folder with drainage files
        swscre (Literal[0, 1, 3]): Switch, display progression of simulation run to screen
        swerror (Literal[0, 1]): Switch for printing errors to screen
        tstart (d): Start date of simulation run, give day-month-year
        tend (d): End date of simulation run, give day-month-year
        nprintday (int): Number of output times during a day
        swmonth (Literal[0, 1]): Switch, output each month
        swyrvar (Literal[0, 1]): Output times for overall water and solute balances in *.BAL and *.BLC file: choose output 
            at a fixed date each year or at different dates
        period (Optional[int]): Fixed output interval
        swres (Optional[Literal[0, 1]]): Switch, reset output interval counter each year
        swodat (Optional[Literal[0, 1]]): Switch, extra output dates are given in table below
        outdatin (Optional[DateList]): list of specific dates
        datefix (Optional[DayMonth]): fixed date for output
        outdat (Optional[DateList]): specify all output dates
        outfil (str): Generic file name of output files
        swheader (Literal[0, 1]): Print header at the start of each balance period
        swwba (Literal[0, 1]): Switch, output daily water balance
        swend (Literal[0, 1]): Switch, output end-conditions
        swvap (Literal[0, 1]): Switch, output soil profiles of moisture, solute and temperature
        swbal (Literal[0, 1]): Switch, output file with yearly water balance
        swblc (Literal[0, 1]): Switch, output file with detailed yearly water balance
        swsba (Literal[0, 1]): Switch, output file of daily solute balance
        swate (Literal[0, 1]): Switch, output file with soil temperature profiles
        swbma (Literal[0, 1]): Switch, output file with water fluxes, only for macropore flow
        swdrf (Literal[0, 1]): Switch, output of drainage fluxes, only for extended drainage
        swswb (Literal[0, 1]): Switch, output surface water reservoir, only for extended drainage
        swini (Literal[0, 1]): Switch, output of initial SoilPhysParam and HeatParam
        swinc (Literal[0, 1]): Switch, output of water balance increments
        swcrp (Literal[0, 1]): Switch, output of simple or detailed crop growth model
        swstr (Literal[0, 1]): Switch, output of stress values for wetness, drought, salinity and frost
        swirg (Literal[0, 1]): Switch, output of irrigation gifts
        swcsv (Literal[0, 1]): Switch, csv output
        inlist_csv (Optional[StringList]): list of variables for the csv output
        swcsv_tz (Literal[0, 1]): Switch, csv output with depth
        inlist_csv_tz (Optional[StringList]): list of variables for the csv tz output
        swafo (Literal[0, 1, 2]): Switch, output file with formatted hydrological data
        swaun (Literal[0, 1, 2]): Switch, output file with unformatted hydrological data
        critdevmasbal (Optional[float]): Critical Deviation in water balance during PERIOD
        swdiscrvert (Literal[0, 1]): Switch to convert vertical discretization
        numnodnew (Optional[int]): New number of nodes
        dznew (Optional[FloatList]): Thickness of compartments
    """

    pathwork: str = BASE_PATH
    pathatm: str = BASE_PATH
    pathcrop: str = BASE_PATH
    pathdrain: str = BASE_PATH
    swscre: Literal[0, 1, 3] = 0
    swerror: Literal[0, 1] = 0

    tstart: d  # convert this to DD-MM-YYYY
    tend: d  # convert this to DD-MM-YYYY

    nprintday: int = Field(default=1, ge=1, le=1440)
    swmonth: Literal[0, 1] = 1
    swyrvar: Literal[0, 1] = 0
    # if swmonth is 0
    period: Optional[int] = Field(default=None, **YEARRANGE)
    swres: Optional[Literal[0, 1]] = None
    swodat: Optional[Literal[0, 1]] = None
    # if swyrvar is 1
    outdatin: Optional[DateList] = None
    datefix: Optional[DayMonth] = None
    outdat: Optional[DateList] = None

    outfil: str = "result"
    swheader: Literal[0, 1] = 0
    swwba: Literal[0, 1] = 0
    swend: Literal[0, 1] = 0
    swvap: Literal[0, 1] = 0
    swbal: Literal[0, 1] = 0
    swblc: Literal[0, 1] = 1
    swsba: Literal[0, 1] = 0
    swate: Literal[0, 1] = 0
    swbma: Literal[0, 1] = 0
    swdrf: Literal[0, 1] = 0
    swswb: Literal[0, 1] = 0
    swini: Literal[0, 1] = 0
    swinc: Literal[0, 1] = 0
    swcrp: Literal[0, 1] = 0
    swstr: Literal[0, 1] = 0
    swirg: Literal[0, 1] = 0
    swcsv: Literal[0, 1] = 1
    inlist_csv: Optional[StringList] = None
    swcsv_tz: Literal[0, 1] = 0
    inlist_csv_tz: Optional[StringList] = None
    swafo: Literal[0, 1, 2] = 0
    swaun: Literal[0, 1, 2] = 0
    critdevmasbal: Optional[float] = Field(default=None, **UNITRANGE)
    swdiscrvert: Literal[0, 1] = 0
    numnodnew: Optional[int] = None
    dznew: Optional[FloatList] = None

    @model_validator(mode='after')
    def _validate_model(self) -> Self:

        if not self.swmonth:
            assert self.period is not None, "period is required when swmonth is 0"
            assert self.swres is not None, "swres is required when swmonth is 0"
            assert self.swodat is not None, "swodat is required when swmonth is 0"
            if self.swodat:
                assert self.outdatin is not None, "outdatin is required when swodat is 1"

        if self.swyrvar:
            assert self.outdat is not None, "outdat is required when svyrvar is 1"
        else:
            assert self.datefix is not None, "datefix is required when swyrvar is 0"

        if self.swafo in [1, 2] or self.swaun in [1, 2]:
            assert self.critdevmasbal is not None, "critdevmasbal is required when SWAFO = 1 or 2 or SWAUN = 1 or 2"
            assert self.swdiscrvert, "SWDISCRVERT is required when SWAFO = 1 or 2 or SWAUN = 1 or 2"
        if self.swdiscrvert:
            assert self.numnodnew is not None, "NUMNODNEW is required when SWDISCRVERT = 1"
            assert self.dznew is not None, "DZNEW is required when SWDISCRVERT = 1"

        return self

Metadata

Bases: PySWAPBaseModel

Metadata of a SWAP model.

Attributes:

Name Type Description
author str

Author of the model.

institution str

Institution of the author.

email str

Email of the author.

project str

Name of the project.

swap_ver str

Version of SWAP used.

comment Optional[str]

Comment about the model.

Source code in pyswap/simsettings/metadata.py
class Metadata(PySWAPBaseModel):
    """Metadata of a SWAP model.

    Attributes:
        author (str): Author of the model.
        institution (str): Institution of the author.
        email (str): Email of the author.
        project (str): Name of the project.
        swap_ver (str): Version of SWAP used.
        comment (Optional[str]): Comment about the model.
    """

    author: str = Field(exclude=True)
    institution: str = Field(exclude=True)
    email: str = Field(exclude=True)
    project: str
    swap_ver: str = Field(exclude=True)
    comment: Optional[str] = Field(default=None, exclude=True)

PySWAPBaseModel

Bases: BaseModel

Base class for PySWAP models.

Attributes:

Name Type Description
model_config ConfigDict

Overriding Pydantic model configuration.

Methods:

Name Description
save_element

Saves model element to a file.

model_string

Returns a custom model string representation that matches the requirements of .swp file.

_concat_sections

Concatenate a string from individual sections.

model_string

Returns a custom model string representation that matches the requirements of .swp file.

Source code in pyswap/core/basemodel.py
class PySWAPBaseModel(BaseModel):
    """Base class for PySWAP models.

    Attributes:
        model_config (ConfigDict): Overriding Pydantic model configuration.

    Methods:
        save_element: Saves model element to a file.
        model_string: Returns a custom model string representation that matches the requirements of .swp file.
        _concat_sections: Concatenate a string from individual sections.
        model_string: Returns a custom model string representation that matches the requirements of .swp file.
    """

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
        validate_assignment=True,
        extra='forbid'
    )

    @staticmethod
    def save_element(string: str, path: str, filename: str, extension: str | None = None) -> str:
        """Saves model element to a file.

        Args:
            string (str): String to be saved.
            path (str): Path to the file.
            filename (str): File name.

        Returns:
            str: Success message.
        """
        save_file(
            string=string,
            fname=filename,
            extension=extension,
            path=path
        )
        return f'{filename}.{extension} saved successfully.'

    def model_string(self) -> str:
        """Returns a custom model string representation that matches the requirements of .swp file.

        Note:
            If values are simple types, they are formatted as 'ATTR = VALUE'. If the valies are
            tables (in pySWAP pd.DataFrame are used), they are formatted simply as 'TABLE_VALUE'. Additionally,
            a custom serializer (pyswap.core.utils.serializers.quote_string) is used to quote strings.

        Returns:
            str: Custom model string representation.
        """
        string = ''

        def formatter(attr, value, string):
            if attr.startswith('table_') or attr.startswith('list_'):
                return string + value
            else:
                return string + f'{attr.upper()} = {quote_string(value)}\n'

        for attr, value in self.model_dump(
                mode='json', exclude_none=True).items():
            if isinstance(value, dict):
                for k, v in value.items():
                    string = formatter(k, v, string)
            else:
                string = formatter(attr, value, string)

        return string

    def _concat_sections(self) -> str:
        """Concatenate a string from individual sections.

        This method is meant to be used on models that collect other
        models, like DraFile, or Model.
        """

        string = ''
        for k, v in dict(self).items():
            if v is None or isinstance(v, str):
                continue
            string += v.model_string()
        return string

model_string()

Returns a custom model string representation that matches the requirements of .swp file.

Note

If values are simple types, they are formatted as 'ATTR = VALUE'. If the valies are tables (in pySWAP pd.DataFrame are used), they are formatted simply as 'TABLE_VALUE'. Additionally, a custom serializer (pyswap.core.utils.serializers.quote_string) is used to quote strings.

Returns:

Name Type Description
str str

Custom model string representation.

Source code in pyswap/core/basemodel.py
def model_string(self) -> str:
    """Returns a custom model string representation that matches the requirements of .swp file.

    Note:
        If values are simple types, they are formatted as 'ATTR = VALUE'. If the valies are
        tables (in pySWAP pd.DataFrame are used), they are formatted simply as 'TABLE_VALUE'. Additionally,
        a custom serializer (pyswap.core.utils.serializers.quote_string) is used to quote strings.

    Returns:
        str: Custom model string representation.
    """
    string = ''

    def formatter(attr, value, string):
        if attr.startswith('table_') or attr.startswith('list_'):
            return string + value
        else:
            return string + f'{attr.upper()} = {quote_string(value)}\n'

    for attr, value in self.model_dump(
            mode='json', exclude_none=True).items():
        if isinstance(value, dict):
            for k, v in value.items():
                string = formatter(k, v, string)
        else:
            string = formatter(attr, value, string)

    return string

save_element(string, path, filename, extension=None) staticmethod

Saves model element to a file.

Parameters:

Name Type Description Default
string str

String to be saved.

required
path str

Path to the file.

required
filename str

File name.

required

Returns:

Name Type Description
str str

Success message.

Source code in pyswap/core/basemodel.py
@staticmethod
def save_element(string: str, path: str, filename: str, extension: str | None = None) -> str:
    """Saves model element to a file.

    Args:
        string (str): String to be saved.
        path (str): Path to the file.
        filename (str): File name.

    Returns:
        str: Success message.
    """
    save_file(
        string=string,
        fname=filename,
        extension=extension,
        path=path
    )
    return f'{filename}.{extension} saved successfully.'

RichardsSettings

Bases: PySWAPBaseModel

Settings for the Richards' equation.

Attributes:

Name Type Description
swkmean int

Switch for averaging method of hydraulic conductivity

swkimpl Literal[0, 1]

Switch for updating hydraulic conductivity during iteration

dtmin float

Minimum timestep

dtmax float

Maximum timestep

gwlconv float

Maximum difference of groundwater level between time steps

critdevh1cp float

Maximum relative difference in pressure heads per compartment

critdevh2cp float

Maximum absolute difference in pressure heads per compartment

critdevponddt float

Maximum water balance error of ponding layer

maxit int

Maximum number of iteration cycles

maxbacktr int

Maximum number of back track cycles within an iteration cycle

Source code in pyswap/simsettings/richards.py
class RichardsSettings(PySWAPBaseModel):
    """Settings for the Richards' equation.

    Attributes:
        swkmean (int): Switch for averaging method of hydraulic conductivity
        swkimpl (Literal[0, 1]): Switch for updating hydraulic conductivity during iteration
        dtmin (float): Minimum timestep
        dtmax (float): Maximum timestep
        gwlconv (float): Maximum difference of groundwater level between time steps
        critdevh1cp (float): Maximum relative difference in pressure heads per compartment
        critdevh2cp (float): Maximum absolute difference in pressure heads per compartment
        critdevponddt (float): Maximum water balance error of ponding layer
        maxit (int): Maximum number of iteration cycles
        maxbacktr (int): Maximum number of back track cycles within an iteration cycle
    """

    swkmean: int
    swkimpl: Literal[0, 1]
    dtmin: float = 0.000001
    dtmax: float = 0.04
    gwlconv: float = 100.0
    critdevh1cp: float = 0.01
    critdevh2cp: float = 0.1
    critdevponddt: float = 0.0001
    maxit: int = 30
    maxbacktr: int = 3

general

GeneralSettings

Bases: PySWAPBaseModel

General settings of the simulation.

Todo

  • remove the individual file extension switches and replace with a list of extensions. Make the class automatically generate the switches based on the list of extensions.

Attributes:

Name Type Description
pathwork str

Path to the working directory

pathatm str

Path to folder with weather files

pathcrop str

Path to folder with crop files

pathdrain str

Path to folder with drainage files

swscre Literal[0, 1, 3]

Switch, display progression of simulation run to screen

swerror Literal[0, 1]

Switch for printing errors to screen

tstart date

Start date of simulation run, give day-month-year

tend date

End date of simulation run, give day-month-year

nprintday int

Number of output times during a day

swmonth Literal[0, 1]

Switch, output each month

swyrvar Literal[0, 1]

Output times for overall water and solute balances in .BAL and .BLC file: choose output at a fixed date each year or at different dates

period Optional[int]

Fixed output interval

swres Optional[Literal[0, 1]]

Switch, reset output interval counter each year

swodat Optional[Literal[0, 1]]

Switch, extra output dates are given in table below

outdatin Optional[DateList]

list of specific dates

datefix Optional[DayMonth]

fixed date for output

outdat Optional[DateList]

specify all output dates

outfil str

Generic file name of output files

swheader Literal[0, 1]

Print header at the start of each balance period

swwba Literal[0, 1]

Switch, output daily water balance

swend Literal[0, 1]

Switch, output end-conditions

swvap Literal[0, 1]

Switch, output soil profiles of moisture, solute and temperature

swbal Literal[0, 1]

Switch, output file with yearly water balance

swblc Literal[0, 1]

Switch, output file with detailed yearly water balance

swsba Literal[0, 1]

Switch, output file of daily solute balance

swate Literal[0, 1]

Switch, output file with soil temperature profiles

swbma Literal[0, 1]

Switch, output file with water fluxes, only for macropore flow

swdrf Literal[0, 1]

Switch, output of drainage fluxes, only for extended drainage

swswb Literal[0, 1]

Switch, output surface water reservoir, only for extended drainage

swini Literal[0, 1]

Switch, output of initial SoilPhysParam and HeatParam

swinc Literal[0, 1]

Switch, output of water balance increments

swcrp Literal[0, 1]

Switch, output of simple or detailed crop growth model

swstr Literal[0, 1]

Switch, output of stress values for wetness, drought, salinity and frost

swirg Literal[0, 1]

Switch, output of irrigation gifts

swcsv Literal[0, 1]

Switch, csv output

inlist_csv Optional[StringList]

list of variables for the csv output

swcsv_tz Literal[0, 1]

Switch, csv output with depth

inlist_csv_tz Optional[StringList]

list of variables for the csv tz output

swafo Literal[0, 1, 2]

Switch, output file with formatted hydrological data

swaun Literal[0, 1, 2]

Switch, output file with unformatted hydrological data

critdevmasbal Optional[float]

Critical Deviation in water balance during PERIOD

swdiscrvert Literal[0, 1]

Switch to convert vertical discretization

numnodnew Optional[int]

New number of nodes

dznew Optional[FloatList]

Thickness of compartments

Source code in pyswap/simsettings/general.py
class GeneralSettings(PySWAPBaseModel):
    """General settings of the simulation.

    !!! todo
        * remove the individual file extension switches and replace with a list of extensions. Make the class
            automatically generate the switches based on the list of extensions.

    Attributes:
        pathwork (str): Path to the working directory
        pathatm (str): Path to folder with weather files
        pathcrop (str): Path to folder with crop files
        pathdrain (str): Path to folder with drainage files
        swscre (Literal[0, 1, 3]): Switch, display progression of simulation run to screen
        swerror (Literal[0, 1]): Switch for printing errors to screen
        tstart (d): Start date of simulation run, give day-month-year
        tend (d): End date of simulation run, give day-month-year
        nprintday (int): Number of output times during a day
        swmonth (Literal[0, 1]): Switch, output each month
        swyrvar (Literal[0, 1]): Output times for overall water and solute balances in *.BAL and *.BLC file: choose output 
            at a fixed date each year or at different dates
        period (Optional[int]): Fixed output interval
        swres (Optional[Literal[0, 1]]): Switch, reset output interval counter each year
        swodat (Optional[Literal[0, 1]]): Switch, extra output dates are given in table below
        outdatin (Optional[DateList]): list of specific dates
        datefix (Optional[DayMonth]): fixed date for output
        outdat (Optional[DateList]): specify all output dates
        outfil (str): Generic file name of output files
        swheader (Literal[0, 1]): Print header at the start of each balance period
        swwba (Literal[0, 1]): Switch, output daily water balance
        swend (Literal[0, 1]): Switch, output end-conditions
        swvap (Literal[0, 1]): Switch, output soil profiles of moisture, solute and temperature
        swbal (Literal[0, 1]): Switch, output file with yearly water balance
        swblc (Literal[0, 1]): Switch, output file with detailed yearly water balance
        swsba (Literal[0, 1]): Switch, output file of daily solute balance
        swate (Literal[0, 1]): Switch, output file with soil temperature profiles
        swbma (Literal[0, 1]): Switch, output file with water fluxes, only for macropore flow
        swdrf (Literal[0, 1]): Switch, output of drainage fluxes, only for extended drainage
        swswb (Literal[0, 1]): Switch, output surface water reservoir, only for extended drainage
        swini (Literal[0, 1]): Switch, output of initial SoilPhysParam and HeatParam
        swinc (Literal[0, 1]): Switch, output of water balance increments
        swcrp (Literal[0, 1]): Switch, output of simple or detailed crop growth model
        swstr (Literal[0, 1]): Switch, output of stress values for wetness, drought, salinity and frost
        swirg (Literal[0, 1]): Switch, output of irrigation gifts
        swcsv (Literal[0, 1]): Switch, csv output
        inlist_csv (Optional[StringList]): list of variables for the csv output
        swcsv_tz (Literal[0, 1]): Switch, csv output with depth
        inlist_csv_tz (Optional[StringList]): list of variables for the csv tz output
        swafo (Literal[0, 1, 2]): Switch, output file with formatted hydrological data
        swaun (Literal[0, 1, 2]): Switch, output file with unformatted hydrological data
        critdevmasbal (Optional[float]): Critical Deviation in water balance during PERIOD
        swdiscrvert (Literal[0, 1]): Switch to convert vertical discretization
        numnodnew (Optional[int]): New number of nodes
        dznew (Optional[FloatList]): Thickness of compartments
    """

    pathwork: str = BASE_PATH
    pathatm: str = BASE_PATH
    pathcrop: str = BASE_PATH
    pathdrain: str = BASE_PATH
    swscre: Literal[0, 1, 3] = 0
    swerror: Literal[0, 1] = 0

    tstart: d  # convert this to DD-MM-YYYY
    tend: d  # convert this to DD-MM-YYYY

    nprintday: int = Field(default=1, ge=1, le=1440)
    swmonth: Literal[0, 1] = 1
    swyrvar: Literal[0, 1] = 0
    # if swmonth is 0
    period: Optional[int] = Field(default=None, **YEARRANGE)
    swres: Optional[Literal[0, 1]] = None
    swodat: Optional[Literal[0, 1]] = None
    # if swyrvar is 1
    outdatin: Optional[DateList] = None
    datefix: Optional[DayMonth] = None
    outdat: Optional[DateList] = None

    outfil: str = "result"
    swheader: Literal[0, 1] = 0
    swwba: Literal[0, 1] = 0
    swend: Literal[0, 1] = 0
    swvap: Literal[0, 1] = 0
    swbal: Literal[0, 1] = 0
    swblc: Literal[0, 1] = 1
    swsba: Literal[0, 1] = 0
    swate: Literal[0, 1] = 0
    swbma: Literal[0, 1] = 0
    swdrf: Literal[0, 1] = 0
    swswb: Literal[0, 1] = 0
    swini: Literal[0, 1] = 0
    swinc: Literal[0, 1] = 0
    swcrp: Literal[0, 1] = 0
    swstr: Literal[0, 1] = 0
    swirg: Literal[0, 1] = 0
    swcsv: Literal[0, 1] = 1
    inlist_csv: Optional[StringList] = None
    swcsv_tz: Literal[0, 1] = 0
    inlist_csv_tz: Optional[StringList] = None
    swafo: Literal[0, 1, 2] = 0
    swaun: Literal[0, 1, 2] = 0
    critdevmasbal: Optional[float] = Field(default=None, **UNITRANGE)
    swdiscrvert: Literal[0, 1] = 0
    numnodnew: Optional[int] = None
    dznew: Optional[FloatList] = None

    @model_validator(mode='after')
    def _validate_model(self) -> Self:

        if not self.swmonth:
            assert self.period is not None, "period is required when swmonth is 0"
            assert self.swres is not None, "swres is required when swmonth is 0"
            assert self.swodat is not None, "swodat is required when swmonth is 0"
            if self.swodat:
                assert self.outdatin is not None, "outdatin is required when swodat is 1"

        if self.swyrvar:
            assert self.outdat is not None, "outdat is required when svyrvar is 1"
        else:
            assert self.datefix is not None, "datefix is required when swyrvar is 0"

        if self.swafo in [1, 2] or self.swaun in [1, 2]:
            assert self.critdevmasbal is not None, "critdevmasbal is required when SWAFO = 1 or 2 or SWAUN = 1 or 2"
            assert self.swdiscrvert, "SWDISCRVERT is required when SWAFO = 1 or 2 or SWAUN = 1 or 2"
        if self.swdiscrvert:
            assert self.numnodnew is not None, "NUMNODNEW is required when SWDISCRVERT = 1"
            assert self.dznew is not None, "DZNEW is required when SWDISCRVERT = 1"

        return self

metadata

Metadata class collecting basic information about the model.

Classes:

Name Description
Metadata

Metadata of a SWAP model.

Metadata

Bases: PySWAPBaseModel

Metadata of a SWAP model.

Attributes:

Name Type Description
author str

Author of the model.

institution str

Institution of the author.

email str

Email of the author.

project str

Name of the project.

swap_ver str

Version of SWAP used.

comment Optional[str]

Comment about the model.

Source code in pyswap/simsettings/metadata.py
class Metadata(PySWAPBaseModel):
    """Metadata of a SWAP model.

    Attributes:
        author (str): Author of the model.
        institution (str): Institution of the author.
        email (str): Email of the author.
        project (str): Name of the project.
        swap_ver (str): Version of SWAP used.
        comment (Optional[str]): Comment about the model.
    """

    author: str = Field(exclude=True)
    institution: str = Field(exclude=True)
    email: str = Field(exclude=True)
    project: str
    swap_ver: str = Field(exclude=True)
    comment: Optional[str] = Field(default=None, exclude=True)

richards

Settings for the Richards' equation with some reasonable defaults included.

Classes:

Name Description
RichardsSettings

Holds the settings for the Richards' equation.

RichardsSettings

Bases: PySWAPBaseModel

Settings for the Richards' equation.

Attributes:

Name Type Description
swkmean int

Switch for averaging method of hydraulic conductivity

swkimpl Literal[0, 1]

Switch for updating hydraulic conductivity during iteration

dtmin float

Minimum timestep

dtmax float

Maximum timestep

gwlconv float

Maximum difference of groundwater level between time steps

critdevh1cp float

Maximum relative difference in pressure heads per compartment

critdevh2cp float

Maximum absolute difference in pressure heads per compartment

critdevponddt float

Maximum water balance error of ponding layer

maxit int

Maximum number of iteration cycles

maxbacktr int

Maximum number of back track cycles within an iteration cycle

Source code in pyswap/simsettings/richards.py
class RichardsSettings(PySWAPBaseModel):
    """Settings for the Richards' equation.

    Attributes:
        swkmean (int): Switch for averaging method of hydraulic conductivity
        swkimpl (Literal[0, 1]): Switch for updating hydraulic conductivity during iteration
        dtmin (float): Minimum timestep
        dtmax (float): Maximum timestep
        gwlconv (float): Maximum difference of groundwater level between time steps
        critdevh1cp (float): Maximum relative difference in pressure heads per compartment
        critdevh2cp (float): Maximum absolute difference in pressure heads per compartment
        critdevponddt (float): Maximum water balance error of ponding layer
        maxit (int): Maximum number of iteration cycles
        maxbacktr (int): Maximum number of back track cycles within an iteration cycle
    """

    swkmean: int
    swkimpl: Literal[0, 1]
    dtmin: float = 0.000001
    dtmax: float = 0.04
    gwlconv: float = 100.0
    critdevh1cp: float = 0.01
    critdevh2cp: float = 0.1
    critdevponddt: float = 0.0001
    maxit: int = 30
    maxbacktr: int = 3

Atmosphere subpackage

Meteorological settings and data for SWAP simulations.

Modules:

Name Description
metfile

meteorological data for SWAP simulations

meteorology

meteorological settings for SWAP simulations


UNITRANGE = {'ge': 0.0, 'le': 1.0} module-attribute

Range of values between 0.0 and 1.0.

MetFile

Bases: PySWAPBaseModel

Meteorological data for the .met file.

This object is created by functions fetching or loading meteorological data from various sources. The data is stored as a pandas.DataFrame, but is formatted with a custom field serializer of the CSVTable field type.

Attributes:

Name Type Description
metfil str

name of the .met file

content CSVTable

meteorological data file

Source code in pyswap/atmosphere/metfile.py
class MetFile(PySWAPBaseModel):
    """Meteorological data for the .met file.

    This object is created by functions fetching or loading meteorological data
    from various sources. The data is stored as a pandas.DataFrame, but is formatted 
    with a custom field serializer of the CSVTable field type.

    Attributes:
        metfil (str): name of the .met file
        content (CSVTable): meteorological data file
    """

    metfil: str
    content: Optional[CSVTable] = Field(default=None, exclude=True)

Meteorology

Bases: PySWAPBaseModel

Meteorological settings of the simulation.

Attributes:

Name Type Description
lat float

latitude of the meteo station [degrees].

swetr int

Switch type of weather data for potential evapotranspiration:

  • 0 - Use basic weather data and apply Penman-Monteith equation.
  • 1 - Use reference evapotranspiration data in combination with crop factors.
swdivide int

Switch for distribution of E and T. Defaults to 0:

  • 0 - Based on crop and soil factors.
  • 1 - Based on direct application of Penman-Monteith.
swmetdetail int

Switch for time interval of evapotranspiration and rainfall weather data:

  • 0 - Daily data.
  • 1 - Subdaily data.
swrain int

Switch for use of actual rainfall intensity, defaults to 0:

  • 0 - Use daily rainfall amounts.
  • 1 - Use daily rainfall amounts + mean intensity.
  • 2 - Use daily rainfall amounts + duration.
  • 3 - Use detailed rainfall records (dt < 1 day), as supplied in separate file.
swetsine int

Switch, distribute daily Tp and Ep according to sinus wave, default to 0:

  • 0 - No distribution.
  • 1 - Distribute Tp and Ep according to sinus wave.
metfile MetFile

MetFile model containing meteorological data to be saved to .met file.

alt float

Altitude of the meteo station [m].

altw float

Altitude of the wind [m].

angstroma float

Fraction of extraterrestrial radiation reaching the earth on overcast days.

angstromb float

Additional fraction of extraterrestrial radiation reaching the earth on clear days.

table_rainflux Table

rainfall intensity RAINFLUX as function of time TIME.

rainfil str

file name of file with detailed rainfall data.

nmetdetail int

Number of weather data records each day.

Methods:

Name Description
write_met

Write the .met file.

Source code in pyswap/atmosphere/meteorology.py
class Meteorology(PySWAPBaseModel):
    """Meteorological settings of the simulation.

    Attributes:
        lat (float): latitude of the meteo station [degrees].
        swetr (int): Switch type of weather data for potential evapotranspiration:

            * 0 - Use basic weather data and apply Penman-Monteith equation.
            * 1 - Use reference evapotranspiration data in combination with crop factors.

        swdivide (int): Switch for distribution of E and T. Defaults to 0:

            * 0 - Based on crop and soil factors.
            * 1 - Based on direct application of Penman-Monteith.

        swmetdetail (int): Switch for time interval of evapotranspiration and rainfall weather data:

            * 0 - Daily data.
            * 1 - Subdaily data.

        swrain (int): Switch for use of actual rainfall intensity, defaults to 0:

            * 0 - Use daily rainfall amounts.
            * 1 - Use daily rainfall amounts + mean intensity.
            * 2 - Use daily rainfall amounts + duration.
            * 3 - Use detailed rainfall records (dt < 1 day), as supplied in separate file.

        swetsine (int): Switch, distribute daily Tp and Ep according to sinus wave, default to 0:

            * 0 - No distribution.
            * 1 - Distribute Tp and Ep according to sinus wave.

        metfile (MetFile): MetFile model containing meteorological data to be saved to .met file.
        alt (float): Altitude of the meteo station [m].
        altw (float): Altitude of the wind [m].
        angstroma (float): Fraction of extraterrestrial radiation reaching the earth on overcast days.
        angstromb (float): Additional fraction of extraterrestrial radiation reaching the earth on clear days.
        table_rainflux (Table): rainfall intensity RAINFLUX as function of time TIME.
        rainfil (str): file name of file with detailed rainfall data.
        nmetdetail (int): Number of weather data records each day.

    Methods:
        write_met: Write the .met file.
    """

    # metfil: str
    lat: float = Field(ge=-90, le=90)
    swetr: Literal[0, 1]
    swdivide: Literal[0, 1]
    # TODO: SWRAIN should be optional, but Fortran code evaluates its presence anyway
    swrain: Optional[Literal[0, 1, 2, 3]] = 0
    # TODO: SWETSINE should be optional, but Fortran code evaluates its presence anyway
    swetsine: Literal[0, 1] = 0
    metfile: Optional[MetFile] = Field(
        default=None, repr=False)
    alt: float = Field(ge=-400.0, le=3000.0)
    altw: float = Field(default=None, ge=0.0, le=99.0)
    angstroma: float = Field(default=None, **UNITRANGE)
    angstromb: float = Field(default=None, **UNITRANGE)
    swmetdetail: Optional[Literal[0, 1]] = None
    table_rainflux: Optional[Table] = None
    rainfil: Optional[str] = None
    nmetdetail: Optional[int] = Field(default=None, ge=1, le=96)

    @model_validator(mode='after')
    def _validate_meteo_section(self) -> Self:

        if self.swetr == 1:  # if PM method is NOT used
            assert self.swetsine is not None, "SWETSINE is required when SWETR is 1"
            assert self.swrain is not None, "SWRAIN is required when SWETR is 1"
            if self.swrain == 1:
                assert self.table_rainflux is not None, "RAINFLUX is required when SWRAIN is 1"
            elif self.swrain == 3:
                assert self.rainfil, "RAINFIL is required when SWRAIN is 3"

        else:
            assert self.alt is not None, "alt settings are required when SWETR is 0"
            assert self.altw is not None, "altw settings are required when SWETR is 0"
            assert self.angstroma is not None, "angstroma settings are required when SWETR is 0"
            assert self.angstromb is not None, "angstromb settings are required when SWETR is 0"
            assert self.swmetdetail is not None, "SWMETDETAIL is required when SWETR is 0"
            if self.swmetdetail == 1:
                assert self.nmetdetail is not None, "NMETDETAIL is required when SWMETDETAIL is 1"

        return self

    def write_met(self, path: str):
        """Write the .met file.

        !!! note

            in this function the extension is not passed because
            swp file requires the metfile parameter to be passed already with 
            the extension.

        Parameters:
            path (str): Path to the file.
        """
        save_file(
            string=self.metfile.content.to_csv(
                index=False, lineterminator='\n'),
            fname=self.metfile.metfil,
            path=path
        )

        print(f'{self.metfile.metfil} saved.')

write_met(path)

Write the .met file.

Note

in this function the extension is not passed because swp file requires the metfile parameter to be passed already with the extension.

Parameters:

Name Type Description Default
path str

Path to the file.

required
Source code in pyswap/atmosphere/meteorology.py
def write_met(self, path: str):
    """Write the .met file.

    !!! note

        in this function the extension is not passed because
        swp file requires the metfile parameter to be passed already with 
        the extension.

    Parameters:
        path (str): Path to the file.
    """
    save_file(
        string=self.metfile.content.to_csv(
            index=False, lineterminator='\n'),
        fname=self.metfile.metfil,
        path=path
    )

    print(f'{self.metfile.metfil} saved.')

PySWAPBaseModel

Bases: BaseModel

Base class for PySWAP models.

Attributes:

Name Type Description
model_config ConfigDict

Overriding Pydantic model configuration.

Methods:

Name Description
save_element

Saves model element to a file.

model_string

Returns a custom model string representation that matches the requirements of .swp file.

_concat_sections

Concatenate a string from individual sections.

model_string

Returns a custom model string representation that matches the requirements of .swp file.

Source code in pyswap/core/basemodel.py
class PySWAPBaseModel(BaseModel):
    """Base class for PySWAP models.

    Attributes:
        model_config (ConfigDict): Overriding Pydantic model configuration.

    Methods:
        save_element: Saves model element to a file.
        model_string: Returns a custom model string representation that matches the requirements of .swp file.
        _concat_sections: Concatenate a string from individual sections.
        model_string: Returns a custom model string representation that matches the requirements of .swp file.
    """

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
        validate_assignment=True,
        extra='forbid'
    )

    @staticmethod
    def save_element(string: str, path: str, filename: str, extension: str | None = None) -> str:
        """Saves model element to a file.

        Args:
            string (str): String to be saved.
            path (str): Path to the file.
            filename (str): File name.

        Returns:
            str: Success message.
        """
        save_file(
            string=string,
            fname=filename,
            extension=extension,
            path=path
        )
        return f'{filename}.{extension} saved successfully.'

    def model_string(self) -> str:
        """Returns a custom model string representation that matches the requirements of .swp file.

        Note:
            If values are simple types, they are formatted as 'ATTR = VALUE'. If the valies are
            tables (in pySWAP pd.DataFrame are used), they are formatted simply as 'TABLE_VALUE'. Additionally,
            a custom serializer (pyswap.core.utils.serializers.quote_string) is used to quote strings.

        Returns:
            str: Custom model string representation.
        """
        string = ''

        def formatter(attr, value, string):
            if attr.startswith('table_') or attr.startswith('list_'):
                return string + value
            else:
                return string + f'{attr.upper()} = {quote_string(value)}\n'

        for attr, value in self.model_dump(
                mode='json', exclude_none=True).items():
            if isinstance(value, dict):
                for k, v in value.items():
                    string = formatter(k, v, string)
            else:
                string = formatter(attr, value, string)

        return string

    def _concat_sections(self) -> str:
        """Concatenate a string from individual sections.

        This method is meant to be used on models that collect other
        models, like DraFile, or Model.
        """

        string = ''
        for k, v in dict(self).items():
            if v is None or isinstance(v, str):
                continue
            string += v.model_string()
        return string

model_string()

Returns a custom model string representation that matches the requirements of .swp file.

Note

If values are simple types, they are formatted as 'ATTR = VALUE'. If the valies are tables (in pySWAP pd.DataFrame are used), they are formatted simply as 'TABLE_VALUE'. Additionally, a custom serializer (pyswap.core.utils.serializers.quote_string) is used to quote strings.

Returns:

Name Type Description
str str

Custom model string representation.

Source code in pyswap/core/basemodel.py
def model_string(self) -> str:
    """Returns a custom model string representation that matches the requirements of .swp file.

    Note:
        If values are simple types, they are formatted as 'ATTR = VALUE'. If the valies are
        tables (in pySWAP pd.DataFrame are used), they are formatted simply as 'TABLE_VALUE'. Additionally,
        a custom serializer (pyswap.core.utils.serializers.quote_string) is used to quote strings.

    Returns:
        str: Custom model string representation.
    """
    string = ''

    def formatter(attr, value, string):
        if attr.startswith('table_') or attr.startswith('list_'):
            return string + value
        else:
            return string + f'{attr.upper()} = {quote_string(value)}\n'

    for attr, value in self.model_dump(
            mode='json', exclude_none=True).items():
        if isinstance(value, dict):
            for k, v in value.items():
                string = formatter(k, v, string)
        else:
            string = formatter(attr, value, string)

    return string

save_element(string, path, filename, extension=None) staticmethod

Saves model element to a file.

Parameters:

Name Type Description Default
string str

String to be saved.

required
path str

Path to the file.

required
filename str

File name.

required

Returns:

Name Type Description
str str

Success message.

Source code in pyswap/core/basemodel.py
@staticmethod
def save_element(string: str, path: str, filename: str, extension: str | None = None) -> str:
    """Saves model element to a file.

    Args:
        string (str): String to be saved.
        path (str): Path to the file.
        filename (str): File name.

    Returns:
        str: Success message.
    """
    save_file(
        string=string,
        fname=filename,
        extension=extension,
        path=path
    )
    return f'{filename}.{extension} saved successfully.'

load_from_csv(metfil, csv_path, **kwargs)

Method for loading meteorological data from a CSV file.

Parameters:

Name Type Description Default
metfil str

name of the .met file

required
csv_path str

path to the CSV file

required
**kwargs dict

keyword arguments for pandas.read_csv

{}

Returns:

Type Description
MetFile

MetFile object.

Source code in pyswap/atmosphere/metfile.py
def load_from_csv(metfil: str, csv_path: str, **kwargs) -> MetFile:
    """Method for loading meteorological data from a CSV file.

    Parameters:
        metfil (str): name of the .met file
        csv_path (str): path to the CSV file
        **kwargs (dict): keyword arguments for pandas.read_csv

    Returns:
        MetFile object.
    """

    return MetFile(metfil=metfil, content=read_csv(csv_path, **kwargs))

load_from_knmi(metfil, stations, variables=['TEMP', 'PRCP', 'Q', 'UG', 'FG', 'UX', 'UN'], start='20000101', end='20200101', frequency='day', inseason=False)

Retrieves the meteorological data from KNMI API using knmi-py.

Parameters:

Name Type Description Default
metfil str

name of the .met file

required
stations str | list

station number(s) to retrieve data from

required
variables str | list

variables to retrieve

['TEMP', 'PRCP', 'Q', 'UG', 'FG', 'UX', 'UN']
start str | datetime

start date of the data

'20000101'
end str | datetime

end date of the data

'20200101'
frequency Literal['day', 'hour']

frequency of the data (day or hour)

'day'
inseason bool

whether to retrieve in-season data

False

Returns:

Type Description
MetFile

MetFile object.

Source code in pyswap/atmosphere/metfile.py
def load_from_knmi(metfil: str,
                   stations: str | list,
                   variables: str | list = [
                       'TEMP', 'PRCP', 'Q', 'UG',  'FG', 'UX', 'UN'],
                   start: str | dt = '20000101',
                   end: str | dt = '20200101',
                   frequency: Literal['day', 'hour'] = 'day',
                   inseason: bool = False) -> MetFile:
    """Retrieves the meteorological data from KNMI API using knmi-py.

    Parameters:
        metfil (str): name of the .met file
        stations (str | list): station number(s) to retrieve data from
        variables (str | list): variables to retrieve
        start (str | dt): start date of the data
        end (str | dt): end date of the data
        frequency (Literal['day', 'hour']): frequency of the data (day or hour)
        inseason (bool): whether to retrieve in-season data

    Returns:
        MetFile object.
    """

    if isinstance(stations, str):
        stations = [stations]
    if isinstance(variables, str):
        variables = [variables]

    get_func = get_day_data_dataframe if frequency == 'day' else get_hour_data_dataframe

    df = get_func(stations=stations,
                  start=start,
                  end=end,
                  variables=variables,
                  inseason=inseason)

    # rename some columns
    required_column_names = {'STN': 'Station',
                             'TN': 'Tmin',
                             'TX': 'Tmax',
                             'UG': 'HUM',
                             'DR': 'WET',
                             'FG': 'WIND',
                             'RH': 'RAIN',
                             'EV24': 'ETref',
                             'Q': 'RAD'}

    df = df.rename(columns=required_column_names)

    # recalculation of the parameters
    df[['Tmin', 'Tmax', 'ETref', 'RAIN', 'WIND']] = df[['Tmin', 'Tmax', 'ETref', 'RAIN',
                                                                        'WIND']] * 0.1  # the original unit is 0.1 Unit
    df['WET'] = df['WET'] * \
        0.1 * 24  # the required unit is days

    return MetFile(metfil=metfil, content=df)

save_file(string, fname, path, mode='w', extension=None, encoding='ascii')

Saves a string to a file.

Parameters:

Name Type Description Default
string str

The string to be saved to a file.

required
extension str | None

The extension that the file should have (e.g. 'txt', 'csv', etc.).

None
fname str

The name of the file.

required
path str

The path where the file should be saved.

required
mode str

The mode in which the file should be opened (e.g. 'w' for write, 'a' for append, etc.).

'w'
encoding str

The encoding to use for the file (default is 'ascii').

'ascii'

Returns:

Type Description
None

None

Source code in pyswap/core/files.py
def save_file(string: str,
              fname: str,
              path: str,
              mode: str = 'w',
              extension: str | None = None,
              encoding: str = 'ascii') -> None:
    """
    Saves a string to a file.

    Arguments:
        string: The string to be saved to a file.
        extension: The extension that the file should have (e.g. 'txt', 'csv', etc.).
        fname: The name of the file.
        path: The path where the file should be saved.
        mode: The mode in which the file should be opened (e.g. 'w' for write, 'a' for append, etc.).
        encoding: The encoding to use for the file (default is 'ascii').

    Returns:
        None
    """

    if extension is not None:
        fname = f'{fname}.{extension}'

    with open(f'{path}/{fname}', f'{mode}', encoding=f'{encoding}') as f:
        f.write(string)

meteorology

Meteorological settings for SWAP simulations.

Note

Meteorology object requires the MetFile object to be passed upon initialization. When the model is run, the MetFile object is saved to a .met file.

Classes:

Name Description
Meteorology

Holds the settings of the meteo section of the .swp file.

Meteorology

Bases: PySWAPBaseModel

Meteorological settings of the simulation.

Attributes:

Name Type Description
lat float

latitude of the meteo station [degrees].

swetr int

Switch type of weather data for potential evapotranspiration:

  • 0 - Use basic weather data and apply Penman-Monteith equation.
  • 1 - Use reference evapotranspiration data in combination with crop factors.
swdivide int

Switch for distribution of E and T. Defaults to 0:

  • 0 - Based on crop and soil factors.
  • 1 - Based on direct application of Penman-Monteith.
swmetdetail int

Switch for time interval of evapotranspiration and rainfall weather data:

  • 0 - Daily data.
  • 1 - Subdaily data.
swrain int

Switch for use of actual rainfall intensity, defaults to 0:

  • 0 - Use daily rainfall amounts.
  • 1 - Use daily rainfall amounts + mean intensity.
  • 2 - Use daily rainfall amounts + duration.
  • 3 - Use detailed rainfall records (dt < 1 day), as supplied in separate file.
swetsine int

Switch, distribute daily Tp and Ep according to sinus wave, default to 0:

  • 0 - No distribution.
  • 1 - Distribute Tp and Ep according to sinus wave.
metfile MetFile

MetFile model containing meteorological data to be saved to .met file.

alt float

Altitude of the meteo station [m].

altw float

Altitude of the wind [m].

angstroma float

Fraction of extraterrestrial radiation reaching the earth on overcast days.

angstromb float

Additional fraction of extraterrestrial radiation reaching the earth on clear days.

table_rainflux Table

rainfall intensity RAINFLUX as function of time TIME.

rainfil str

file name of file with detailed rainfall data.

nmetdetail int

Number of weather data records each day.

Methods:

Name Description
write_met

Write the .met file.

Source code in pyswap/atmosphere/meteorology.py
class Meteorology(PySWAPBaseModel):
    """Meteorological settings of the simulation.

    Attributes:
        lat (float): latitude of the meteo station [degrees].
        swetr (int): Switch type of weather data for potential evapotranspiration:

            * 0 - Use basic weather data and apply Penman-Monteith equation.
            * 1 - Use reference evapotranspiration data in combination with crop factors.

        swdivide (int): Switch for distribution of E and T. Defaults to 0:

            * 0 - Based on crop and soil factors.
            * 1 - Based on direct application of Penman-Monteith.

        swmetdetail (int): Switch for time interval of evapotranspiration and rainfall weather data:

            * 0 - Daily data.
            * 1 - Subdaily data.

        swrain (int): Switch for use of actual rainfall intensity, defaults to 0:

            * 0 - Use daily rainfall amounts.
            * 1 - Use daily rainfall amounts + mean intensity.
            * 2 - Use daily rainfall amounts + duration.
            * 3 - Use detailed rainfall records (dt < 1 day), as supplied in separate file.

        swetsine (int): Switch, distribute daily Tp and Ep according to sinus wave, default to 0:

            * 0 - No distribution.
            * 1 - Distribute Tp and Ep according to sinus wave.

        metfile (MetFile): MetFile model containing meteorological data to be saved to .met file.
        alt (float): Altitude of the meteo station [m].
        altw (float): Altitude of the wind [m].
        angstroma (float): Fraction of extraterrestrial radiation reaching the earth on overcast days.
        angstromb (float): Additional fraction of extraterrestrial radiation reaching the earth on clear days.
        table_rainflux (Table): rainfall intensity RAINFLUX as function of time TIME.
        rainfil (str): file name of file with detailed rainfall data.
        nmetdetail (int): Number of weather data records each day.

    Methods:
        write_met: Write the .met file.
    """

    # metfil: str
    lat: float = Field(ge=-90, le=90)
    swetr: Literal[0, 1]
    swdivide: Literal[0, 1]
    # TODO: SWRAIN should be optional, but Fortran code evaluates its presence anyway
    swrain: Optional[Literal[0, 1, 2, 3]] = 0
    # TODO: SWETSINE should be optional, but Fortran code evaluates its presence anyway
    swetsine: Literal[0, 1] = 0
    metfile: Optional[MetFile] = Field(
        default=None, repr=False)
    alt: float = Field(ge=-400.0, le=3000.0)
    altw: float = Field(default=None, ge=0.0, le=99.0)
    angstroma: float = Field(default=None, **UNITRANGE)
    angstromb: float = Field(default=None, **UNITRANGE)
    swmetdetail: Optional[Literal[0, 1]] = None
    table_rainflux: Optional[Table] = None
    rainfil: Optional[str] = None
    nmetdetail: Optional[int] = Field(default=None, ge=1, le=96)

    @model_validator(mode='after')
    def _validate_meteo_section(self) -> Self:

        if self.swetr == 1:  # if PM method is NOT used
            assert self.swetsine is not None, "SWETSINE is required when SWETR is 1"
            assert self.swrain is not None, "SWRAIN is required when SWETR is 1"
            if self.swrain == 1:
                assert self.table_rainflux is not None, "RAINFLUX is required when SWRAIN is 1"
            elif self.swrain == 3:
                assert self.rainfil, "RAINFIL is required when SWRAIN is 3"

        else:
            assert self.alt is not None, "alt settings are required when SWETR is 0"
            assert self.altw is not None, "altw settings are required when SWETR is 0"
            assert self.angstroma is not None, "angstroma settings are required when SWETR is 0"
            assert self.angstromb is not None, "angstromb settings are required when SWETR is 0"
            assert self.swmetdetail is not None, "SWMETDETAIL is required when SWETR is 0"
            if self.swmetdetail == 1:
                assert self.nmetdetail is not None, "NMETDETAIL is required when SWMETDETAIL is 1"

        return self

    def write_met(self, path: str):
        """Write the .met file.

        !!! note

            in this function the extension is not passed because
            swp file requires the metfile parameter to be passed already with 
            the extension.

        Parameters:
            path (str): Path to the file.
        """
        save_file(
            string=self.metfile.content.to_csv(
                index=False, lineterminator='\n'),
            fname=self.metfile.metfil,
            path=path
        )

        print(f'{self.metfile.metfil} saved.')

write_met(path)

Write the .met file.

Note

in this function the extension is not passed because swp file requires the metfile parameter to be passed already with the extension.

Parameters:

Name Type Description Default
path str

Path to the file.

required
Source code in pyswap/atmosphere/meteorology.py
def write_met(self, path: str):
    """Write the .met file.

    !!! note

        in this function the extension is not passed because
        swp file requires the metfile parameter to be passed already with 
        the extension.

    Parameters:
        path (str): Path to the file.
    """
    save_file(
        string=self.metfile.content.to_csv(
            index=False, lineterminator='\n'),
        fname=self.metfile.metfil,
        path=path
    )

    print(f'{self.metfile.metfil} saved.')

metfile

Getting and formatting meteorological data for SWAP similation.

Classes:

Name Description
MetFile

meteorological data for the .met file

Functions:

Name Description
load_from_csv

loading meteorological data from a CSV file

load_from_knmi

retrieving meteorological data from KNMI API

MetFile

Bases: PySWAPBaseModel

Meteorological data for the .met file.

This object is created by functions fetching or loading meteorological data from various sources. The data is stored as a pandas.DataFrame, but is formatted with a custom field serializer of the CSVTable field type.

Attributes:

Name Type Description
metfil str

name of the .met file

content CSVTable

meteorological data file

Source code in pyswap/atmosphere/metfile.py
class MetFile(PySWAPBaseModel):
    """Meteorological data for the .met file.

    This object is created by functions fetching or loading meteorological data
    from various sources. The data is stored as a pandas.DataFrame, but is formatted 
    with a custom field serializer of the CSVTable field type.

    Attributes:
        metfil (str): name of the .met file
        content (CSVTable): meteorological data file
    """

    metfil: str
    content: Optional[CSVTable] = Field(default=None, exclude=True)

load_from_csv(metfil, csv_path, **kwargs)

Method for loading meteorological data from a CSV file.

Parameters:

Name Type Description Default
metfil str

name of the .met file

required
csv_path str

path to the CSV file

required
**kwargs dict

keyword arguments for pandas.read_csv

{}

Returns:

Type Description
MetFile

MetFile object.

Source code in pyswap/atmosphere/metfile.py
def load_from_csv(metfil: str, csv_path: str, **kwargs) -> MetFile:
    """Method for loading meteorological data from a CSV file.

    Parameters:
        metfil (str): name of the .met file
        csv_path (str): path to the CSV file
        **kwargs (dict): keyword arguments for pandas.read_csv

    Returns:
        MetFile object.
    """

    return MetFile(metfil=metfil, content=read_csv(csv_path, **kwargs))

load_from_knmi(metfil, stations, variables=['TEMP', 'PRCP', 'Q', 'UG', 'FG', 'UX', 'UN'], start='20000101', end='20200101', frequency='day', inseason=False)

Retrieves the meteorological data from KNMI API using knmi-py.

Parameters:

Name Type Description Default
metfil str

name of the .met file

required
stations str | list

station number(s) to retrieve data from

required
variables str | list

variables to retrieve

['TEMP', 'PRCP', 'Q', 'UG', 'FG', 'UX', 'UN']
start str | datetime

start date of the data

'20000101'
end str | datetime

end date of the data

'20200101'
frequency Literal['day', 'hour']

frequency of the data (day or hour)

'day'
inseason bool

whether to retrieve in-season data

False

Returns:

Type Description
MetFile

MetFile object.

Source code in pyswap/atmosphere/metfile.py
def load_from_knmi(metfil: str,
                   stations: str | list,
                   variables: str | list = [
                       'TEMP', 'PRCP', 'Q', 'UG',  'FG', 'UX', 'UN'],
                   start: str | dt = '20000101',
                   end: str | dt = '20200101',
                   frequency: Literal['day', 'hour'] = 'day',
                   inseason: bool = False) -> MetFile:
    """Retrieves the meteorological data from KNMI API using knmi-py.

    Parameters:
        metfil (str): name of the .met file
        stations (str | list): station number(s) to retrieve data from
        variables (str | list): variables to retrieve
        start (str | dt): start date of the data
        end (str | dt): end date of the data
        frequency (Literal['day', 'hour']): frequency of the data (day or hour)
        inseason (bool): whether to retrieve in-season data

    Returns:
        MetFile object.
    """

    if isinstance(stations, str):
        stations = [stations]
    if isinstance(variables, str):
        variables = [variables]

    get_func = get_day_data_dataframe if frequency == 'day' else get_hour_data_dataframe

    df = get_func(stations=stations,
                  start=start,
                  end=end,
                  variables=variables,
                  inseason=inseason)

    # rename some columns
    required_column_names = {'STN': 'Station',
                             'TN': 'Tmin',
                             'TX': 'Tmax',
                             'UG': 'HUM',
                             'DR': 'WET',
                             'FG': 'WIND',
                             'RH': 'RAIN',
                             'EV24': 'ETref',
                             'Q': 'RAD'}

    df = df.rename(columns=required_column_names)

    # recalculation of the parameters
    df[['Tmin', 'Tmax', 'ETref', 'RAIN', 'WIND']] = df[['Tmin', 'Tmax', 'ETref', 'RAIN',
                                                                        'WIND']] * 0.1  # the original unit is 0.1 Unit
    df['WET'] = df['WET'] * \
        0.1 * 24  # the required unit is days

    return MetFile(metfil=metfil, content=df)

tables

Plant subpackage

Crop settings and files for the SWAP simulation.

Modules:

Name Description
crop

The crop settings.

crpfile

The crop file.

create_crp_tables

Experimental module with functions to create crop tables.

crop

UNITRANGE = {'ge': 0.0, 'le': 1.0} module-attribute

Range of values between 0.0 and 1.0.

YEARRANGE = {'ge': 0, 'le': 366} module-attribute

Range of values for year (0 <= x <= 366).

CO2Correction

Bases: PySWAPBaseModel

CO2 correction settings for WOFOST-type .crp file.

Attributes:

Name Type Description
swco2 Literal[0, 1]

Switch for assimilation correction due to CO2 impact

  • 0 - No CO2 assimilation correction
  • 1 - CO2 assimilation correction
atmofil Optional[str]

alternative filename for atmosphere.co2

co2amaxtb Optional[Arrays]

Correction of photosynthesis as a function of atmospheric CO2 concentration

co2efftb Optional[Arrays]

orrection of radiation use efficiency as a function of atmospheric CO2 concentration

co2tratb Optional[Arrays]

Correction of transpiration as a function of atmospheric CO2 concentration

Source code in pyswap/plant/crpfile.py
class CO2Correction(PySWAPBaseModel):
    """CO2 correction settings for WOFOST-type .crp file.

    Attributes:
        swco2 (Literal[0, 1]): Switch for assimilation correction due to CO2 impact

            * 0 - No CO2 assimilation correction
            * 1 - CO2 assimilation correction

        atmofil (Optional[str]): alternative filename for atmosphere.co2
        co2amaxtb (Optional[Arrays]): Correction of photosynthesis as a function of atmospheric CO2 concentration
        co2efftb (Optional[Arrays]): orrection of radiation use efficiency as a function of atmospheric CO2 concentration
        co2tratb (Optional[Arrays]): Correction of transpiration as a function of atmospheric CO2 concentration
    """

    swco2: Literal[0, 1]
    atmofil: Optional[str] = None
    co2amaxtb: Optional[Arrays] = None
    co2efftb: Optional[Arrays] = None
    co2tratb: Optional[Arrays] = None

    @model_validator(mode='after')
    def _validate_co2correction(self) -> Self:
        if self.swco2 == 1:
            assert self.atmofil is not None, 'amofil is required when swco2 is 1'
            assert self.co2amaxtb is not None, 'co2amaxtb is required when swco2 is 1'
            assert self.co2efftb is not None, 'co2efftb is required when swco2 is 1'
            assert self.co2tratb is not None, 'co2tratb is required when swco2 is 1'

        return self

CompensateRWUStress

Bases: PySWAPBaseModel

Compensate root water uptake stress settings for .crp file.

Attributes:

Name Type Description
swcompensate Literal[0, 1, 2]

Switch for compensate root water uptake stress

  • 0 - No compensation
  • 1 - Compensation according to Jarvis (1989)
  • 2 - Compensation according to Walsum (2019)
swstressor Optional[Literal[1, 2, 3, 4, 5]]

Switch for stressor

  • 1 - Compensation of all stressors
  • 2 - Compensation of drought stress
  • 3 - Compensation of oxygen stress
  • 4 - Compensation of salinity stress
  • 5 - Compensation of frost stress
alphacrit Optional[float]

Critical stress index for compensation of root water uptake

dcritrtz Optional[float]

Threshold of rootzone thickness after which compensation occurs

Source code in pyswap/plant/crpfile.py
class CompensateRWUStress(PySWAPBaseModel):
    """Compensate root water uptake stress settings for .crp file.

    Attributes:
        swcompensate (Literal[0, 1, 2]): Switch for compensate root water uptake stress

            * 0 - No compensation
            * 1 - Compensation according to Jarvis (1989)
            * 2 - Compensation according to Walsum (2019)

        swstressor (Optional[Literal[1, 2, 3, 4, 5]]): Switch for stressor

            * 1 - Compensation of all stressors
            * 2 - Compensation of drought stress
            * 3 - Compensation of oxygen stress
            * 4 - Compensation of salinity stress
            * 5 - Compensation of frost stress

        alphacrit (Optional[float]): Critical stress index for compensation of root water uptake
        dcritrtz (Optional[float]): Threshold of rootzone thickness after which compensation occurs
    """
    swcompensate: Literal[0, 1, 2]
    swstressor: Optional[Literal[1, 2, 3, 4, 5]] = None
    alphacrit: Optional[float] = Field(default=None, ge=0.2, le=1.0)
    dcritrtz: Optional[float] = Field(default=None, ge=0.02, le=100.0)

    @model_validator(mode='after')
    def _validate_prepartion(self) -> Self:
        if self.swcompensate in [1, 2]:
            assert self.swstressor is not None, "swstressor is required when swcompensate is 1 or 2."
        if self.swcompensate == 1:
            assert self.alphacrit is not None, "alphacrit is required when swcompensate is 1."
        if self.swcompensate == 2:
            assert self.dcritrtz is not None, "dcritrtz is required when swcompensate is 2."

        return self

Crop

Bases: PySWAPBaseModel

Holds the crop settings of the simulation.

Attributes:

Name Type Description
swcrop int

Switch for crop:

  • 0 - Bare soil.
  • 1 - Simulate crop.
rds Optional[float]

Rooting depth of the crop [cm].

table_croprotation Optional[Table]

Table with crop rotation data.

cropfiles Optional[List[CropFile]]

List of crop files.

Methods:

Name Description
write_crop

Write the crop files.

Source code in pyswap/plant/crop.py
class Crop(PySWAPBaseModel):
    """Holds the crop settings of the simulation.

    Attributes:
        swcrop (int): Switch for crop:

            * 0 - Bare soil.
            * 1 - Simulate crop.

        rds (Optional[float]): Rooting depth of the crop [cm].
        table_croprotation (Optional[Table]): Table with crop rotation data.
        cropfiles (Optional[List[CropFile]]): List of crop files.

    Methods:
        write_crop: Write the crop files.
    """

    swcrop: Literal[0, 1]
    rds: Optional[float] = Field(default=None, ge=1, le=5000)
    table_croprotation: Optional[Table] = None
    cropfiles: Optional[List[CropFile]] = Field(default=None, exclude=True)

    def _validate_crop_section(self) -> Self:
        if self.swcrop == 1:
            assert self.rds is not None, "rds must be specified if swcrop is True"
            assert self.table_croprotation is not None, "croprotation must be specified if swcrop is True"

        return self

    def write_crop(self, path: str):
        count = 0
        for cropfile in self.cropfiles:
            count += 1
            save_file(
                string=cropfile.content,
                extension='crp',
                fname=cropfile.name,
                path=path
            )

        print(f'{count} crop file(s) saved.')

CropDevelopmentSettings

Bases: PySWAPBaseModel

Crop development settings (parts 1-xx form the template)

Note

The validation of this class should be optimized. The current implementation repeats the validation of the base class in each subclass. The observed issue is that when the validator is inherited from the base class and there is another validator in the subclass (even if they have different names), at the validation step of the child class the validator throws an error that the attribute is NoneType. To be fixed later.

Attributes:

Name Type Description
swcf Literal[1, 2]

Choose between crop factor and crop height

  • 1 - Crop factor
  • 2 - Crop height
table_dvs_cf Optional[Table]

Table with crop factors as a function of development stage

table_dvs_ch Optional[Table]

Table with crop height as a function of development stage

albedo Optional[float]

Crop reflection coefficient

rsc Optional[float]

Minimum canopy resistance

rsw Optional[float]

Canopy resistance of intercepted water

tsumea float

Temperature sum from emergence to anthesis

tsumam float

Temperature sum from anthesis to maturity

tbase Optional[float]

Start value of temperature sum

kdif float

Extinction coefficient for diffuse visible light

kdir float

Extinction coefficient for direct visible light

swrd Optional[Literal[1, 2, 3]]

Switch development of root growth

  • 1 - Root growth depends on development stage
  • 2 - Root growth depends on maximum daily increase
  • 3 - Root growth depends on available root biomass
rdtb Optional[Arrays]

Rooting Depth as a function of development stage

rdi float

Initial rooting depth

rri float

Maximum daily increase in rooting depth

rdc float

Maximum rooting depth of particular crop

swdmi2rd Optional[Literal[0, 1]]

Switch for calculation rooting depth

  • 0 - Rooting depth increase is related to availability assimilates for roots
  • 1 - Rooting depth increase is related to relative dry matter increase
rlwtb Optional[Arrays]

rooting depth as function of root weight

wrtmax float

Maximum root weight

swrdc Literal[0, 1]

Switch for calculation of relative root density

rdctb Arrays

root density as function of relative rooting depth

Source code in pyswap/plant/crpfile.py
class CropDevelopmentSettings(PySWAPBaseModel):
    """Crop development settings (parts 1-xx form the template)

    Note:
        The validation of this class should be optimized. The current implementation
        repeats the validation of the base class in each subclass. The observed issue is that
        when the validator is inherited from the base class and there is another validator in the
        subclass (even if they have different names), at the validation step of the child class the
        validator throws an error that the attribute is NoneType. To be fixed later.

    Attributes:
        swcf (Literal[1, 2]): Choose between crop factor and crop height

            * 1 - Crop factor
            * 2 - Crop height

        table_dvs_cf (Optional[Table]): Table with crop factors as a function of development stage
        table_dvs_ch (Optional[Table]): Table with crop height as a function of development stage
        albedo (Optional[float]): Crop reflection coefficient
        rsc (Optional[float]): Minimum canopy resistance
        rsw (Optional[float]): Canopy resistance of intercepted water
        tsumea (float): Temperature sum from emergence to anthesis
        tsumam (float): Temperature sum from anthesis to maturity
        tbase (Optional[float]): Start value of temperature sum
        kdif (float): Extinction coefficient for diffuse visible light
        kdir (float): Extinction coefficient for direct visible light
        swrd (Optional[Literal[1, 2, 3]]): Switch development of root growth

            * 1 - Root growth depends on development stage
            * 2 - Root growth depends on maximum daily increase
            * 3 - Root growth depends on available root biomass

        rdtb (Optional[Arrays]): Rooting Depth as a function of development stage
        rdi (float): Initial rooting depth
        rri (float): Maximum daily increase in rooting depth
        rdc (float): Maximum rooting depth of particular crop
        swdmi2rd (Optional[Literal[0, 1]]): Switch for calculation rooting depth

            * 0 - Rooting depth increase is related to availability assimilates for roots
            * 1 - Rooting depth increase is related to relative dry matter increase

        rlwtb (Optional[Arrays]): rooting depth as function of root weight
        wrtmax (float): Maximum root weight
        swrdc (Literal[0, 1]): Switch for calculation of relative root density
        rdctb (Arrays): root density as function of relative rooting depth
    """
    swcf: Literal[1, 2]
    table_dvs_cf: Optional[Table] = None
    table_dvs_ch: Optional[Table] = None
    albedo: Optional[float] = Field(default=None, **UNITRANGE)
    rsc: Optional[float] = Field(default=None, ge=0.0, le=1.0e6)
    rsw: Optional[float] = Field(default=None, ge=0.0, le=1.0e6)
    # In WOFOST reference yaml files this is called TSUM1
    tsumea: float = Field(default=None, ge=0.0, le=1.0e4)
    # In WOFOST reference yaml files this is called TSUM2
    tsumam: float = Field(default=None, ge=0.0, le=1.0e4)
    # In SWAP this parameter seems to meen something different than in the
    # WOFOST template. The range of value is the same though.
    tbase: Optional[float] = Field(default=None, ge=-10.0, le=30.0)
    kdif: float = Field(ge=0.0, le=2.0)
    kdir: float = Field(ge=0.0, le=2.0)
    swrd: Optional[Literal[1, 2, 3]] = None
    rdtb: Optional[Arrays] = None
    rdi: float = Field(default=None, ge=0.0, le=1000.0)
    rri: float = Field(default=None, ge=0.0, le=100.0)
    rdc: float = Field(default=None, ge=0.0, le=1000.0)
    swdmi2rd: Optional[Literal[0, 1]] = None
    rlwtb: Optional[Arrays] = None
    wrtmax: float = Field(default=None, ge=0.0, le=1.0e5)
    swrdc: Literal[0, 1] = 0
    rdctb: Arrays

CropDevelopmentSettingsFixed

Bases: CropDevelopmentSettings

Fixed crop development settings (parts 1-xx form the template)

Warning

This class is not complete. It is missing the validation.

Note

I noticed an issue with the tables here. They are actually arrays (each array is a column) that are preceeded by the variable name and "=". That variable name is the same for all options of tables which have different column names (e.g., DVS/LAI or DVS/SCF) but the variable name is the same (e.g., GCTB). TODO: implement a check of the column before the df is converted to string.

Attributes:

Name Type Description
idev Literal[1, 2]

Duration of crop growing period

  • 1 - Duration is fixed
  • 2 - Duration is variable
lcc Optional[int]

Duration of the crop growing period

swgc Literal[1, 2]

Choose between Leaf Area Index or Soil Cover Fraction

  • 1 - LAI
  • 2 - SCF
gctb Arrays

Soil Cover Fraction as a function of development stage

Source code in pyswap/plant/crpfile.py
class CropDevelopmentSettingsFixed(CropDevelopmentSettings):
    """Fixed crop development settings (parts 1-xx form the template)

    Warning:
        This class is not complete. It is missing the validation.

    Note:
        I noticed an issue with the tables here. They are actually arrays (each
        array is a column) that are preceeded by the variable name and "=". That variable
        name is the same for all options of tables which have different column names (e.g., DVS/LAI or
        DVS/SCF) but the variable name is the same (e.g., GCTB).
        TODO: implement a check of the column before the df is converted to string.

    Attributes:
        idev (Literal[1, 2]): Duration of crop growing period

            * 1 - Duration is fixed
            * 2 - Duration is variable

        lcc (Optional[int]): Duration of the crop growing period
        swgc (Literal[1, 2]): Choose between Leaf Area Index or Soil Cover Fraction

            * 1 - LAI
            * 2 - SCF

        gctb (Arrays): Soil Cover Fraction as a function of development stage
    """

    idev: Literal[1, 2]
    lcc: Optional[int] = Field(default=None, **YEARRANGE)
    swgc: Literal[1, 2]
    gctb: Arrays
    kytb: Optional[Arrays] = None

    @model_validator(mode='after')
    def _validate_crop_fixed(self) -> Self:
        # validation of the base class
        if self.swcf == 1:
            assert self.table_dvs_cf is not None, "table_dvs_cf is required when swcf is 1."
        elif self.swcf == 2:
            assert self.table_dvs_ch is not None, "table_dvs_ch is required when swcf is 2."
            assert self.albedo is not None, "albedo is required when swcf is 2."
            assert self.rsc is not None, "rsc is required when swcf is 2."
            assert self.rsw is not None, "rsw is required when swcf is 2."
        if self.swrd == 1:
            assert self.rdtb is not None, "rdtb is required when swrd is 1."
        elif self.swrd == 2:
            assert self.rdi is not None, "rdi is required when swrd is 2."
            assert self.rri is not None, "rri is required when swrd is 2."
            assert self.rdc is not None, "rdc is required when swrd is 2."
            assert self.swdmi2rd is not None, "swdmi2rd is required when swrd is 2."
        elif self.swrd == 3:
            assert self.rlwtb is not None, "rlwtb is required when swrd is 3."
            assert self.wrtmax is not None, "wrtmax is required when swrd is 3."
        # validation specific to the fixed crop development settings
        if self.idev == 1:
            assert self.lcc is not None, "lcc is required when idev is 1."
        elif self.idev == 2:
            assert self.tsumea is not None, "tsumea is required when idev is 2."
            assert self.tsumam is not None, "tsumam is required when idev is 2."
            assert self.tbase is not None, "tbase is required when idev is 2."

        return self

CropDevelopmentSettingsGrass

Bases: CropDevelopmentSettingsWOFOST

Crop development settings specific to grass growth.

Attributes:

Name Type Description
swtsum Literal[0, 1, 2]

Select either sum air temperatures or soil temperature at particular depth

  • 0 - no delay of start grass growth
  • 1 - start of grass growth based on sum air temperatures > 200 degree C
  • 2 - start of grass growth based on soil temperature at particular depth
tsumtemp Optional[float]

Specific stem area [0..1 ha/kg, R]

tsumdepth Optional[float]

Life span under leaves under optimum conditions [0..366 d, R]

tsumtime Optional[float]

Lower threshold temperature for ageing of leaves [-10..30 degree C, R]

Source code in pyswap/plant/crpfile.py
class CropDevelopmentSettingsGrass(CropDevelopmentSettingsWOFOST):
    """Crop development settings specific to grass growth.

    Attributes:
        swtsum (Literal[0, 1, 2]): Select either sum air temperatures or soil temperature at particular depth

            * 0 - no delay of start grass growth
            * 1 - start of grass growth based on sum air temperatures > 200 degree C
            * 2 - start of grass growth based on soil temperature at particular depth

        tsumtemp (Optional[float]): Specific stem area [0..1 ha/kg, R]
        tsumdepth (Optional[float]): Life span under leaves under optimum conditions [0..366 d, R]
        tsumtime (Optional[float]): Lower threshold temperature for ageing of leaves [-10..30 degree C, R]
    """
    swtsum: Literal[0, 1, 2]
    tsumtemp: Optional[float] = None
    tsumdepth: Optional[float] = None
    tsumtime: Optional[float] = None

CropDevelopmentSettingsWOFOST

Bases: CropDevelopmentSettings

Additional settings for the

Warning

The validation for this class is not complete. Also check the Optional attributes!

Note

Use serialization_alias to change the parameter names who are different between WOFOST and SWAP.

Attributes:

Name Type Description
idsl Literal[0, 1, 2]
dtsmtb Arrays
dlo Optional[float]
dlc Optional[float]
vernsat Optional[float]
vernbase Optional[float]
verndvs Optional[float]
verntb Optional[Arrays]
tdwi float
laiem float
rgrlai float
spa float
ssa float
span float
slatb Arrays
eff float
amaxtb Arrays
tmpftb Arrays
tmnftb Arrays
cvo float
cvl float
cvr float
cvs float
q10 float
rml float
rmo float
rmr float
rms float
rfsetb Arrays
frtb Arrays
fltb Arrays
fstb Arrays
fotb Arrays
perdl float
rdrrtb Arrays
rdrstb Arrays
Source code in pyswap/plant/crpfile.py
class CropDevelopmentSettingsWOFOST(CropDevelopmentSettings):
    """Additional settings for the 

    Warning:
        The validation for this class is not complete. Also check the Optional attributes!

    Note:
        Use serialization_alias to change the parameter names who are different between WOFOST and SWAP.

    Attributes:
        idsl (Literal[0, 1, 2]):
        dtsmtb (Arrays):
        dlo (Optional[float]):
        dlc (Optional[float]):
        vernsat (Optional[float]):
        vernbase (Optional[float]):
        verndvs (Optional[float]):
        verntb (Optional[Arrays]):
        tdwi (float):
        laiem (float):
        rgrlai (float):
        spa (float):
        ssa (float):
        span (float):
        slatb (Arrays):
        eff (float):
        amaxtb (Arrays):
        tmpftb (Arrays):
        tmnftb (Arrays):
        cvo (float):
        cvl (float):
        cvr (float):
        cvs (float):
        q10 (float):
        rml (float):
        rmo (float):
        rmr (float):
        rms (float):
        rfsetb (Arrays):
        frtb (Arrays):
        fltb (Arrays):
        fstb (Arrays):
        fotb (Arrays):
        perdl (float):
        rdrrtb (Arrays):
        rdrstb (Arrays):
    """
    idsl: Optional[Literal[0, 1, 2]] = None  # for grass at least
    dtsmtb: Optional[Arrays] = None   # for grass at least
    dlo: Optional[float] = Field(default=None, ge=0.0, le=24.0)
    dlc: Optional[float] = Field(default=None, ge=0.0, le=24.0)
    vernsat: Optional[float] = Field(default=None, ge=0.0, le=100.0)
    vernbase: Optional[float] = Field(default=None, ge=0.0, le=100.0)
    verndvs: Optional[float] = Field(default=None, ge=0.0, le=0.3)
    verntb: Optional[Arrays] = None
    tdwi: float = Field(ge=0.0, le=10_000)
    laiem: float = Field(ge=0.0, le=10)
    rgrlai: float = Field(**UNITRANGE)
    spa: Optional[float] = Field(**UNITRANGE, default=None)
    ssa: float = Field(**UNITRANGE)
    span: float = Field(**YEARRANGE)
    slatb: Arrays
    eff:  float = Field(ge=0.0, le=10.0)
    amaxtb: Arrays
    tmpftb: Arrays
    tmnftb: Arrays
    cvo: Optional[float] = Field(
        **UNITRANGE, default=None)  # for grass at least
    cvl: float = Field(**UNITRANGE)
    cvr: float = Field(**UNITRANGE)
    cvs: float = Field(**UNITRANGE)
    q10: float = Field(ge=0.0, le=5.0)
    rml: float = Field(**UNITRANGE)
    rmo: Optional[float] = Field(
        **UNITRANGE, default=None)  # for grass at least
    rmr: float = Field(**UNITRANGE)
    rms: float = Field(**UNITRANGE)
    rfsetb: Arrays
    frtb: Arrays
    fltb: Arrays
    fstb: Arrays
    fotb: Optional[Arrays] = None  # for grass at least
    perdl: float = Field(ge=0.0, le=3.0)
    rdrrtb: Arrays
    rdrstb: Arrays

    @model_validator(mode='after')
    def _validate_crop_wofost(self) -> Self:
        # validation of the base class
        if self.swcf == 1:
            assert self.table_dvs_cf is not None, "table_dvs_cf is required when swcf is 1."
        elif self.swcf == 2:
            assert self.table_dvs_ch is not None, "table_dvs_ch is required when swcf is 2."
            assert self.albedo is not None, "albedo is required when swcf is 2."
            assert self.rsc is not None, "rsc is required when swcf is 2."
            assert self.rsw is not None, "rsw is required when swcf is 2."
        if self.swrd == 1:
            assert self.rdtb is not None, "rdtb is required when swrd is 1."
        elif self.swrd == 2:
            assert self.rdi is not None, "rdi is required when swrd is 2."
            assert self.rri is not None, "rri is required when swrd is 2."
            assert self.rdc is not None, "rdc is required when swrd is 2."
            assert self.swdmi2rd is not None, "swdmi2rd is required when swrd is 2."
        elif self.swrd == 3:
            assert self.rlwtb is not None, "rlwtb is required when swrd is 3."
            assert self.wrtmax is not None, "wrtmax is required when swrd is 3."
        # validation specific to the WOFOST crop development settings
        if self.idsl in [1, 2]:
            assert self.dlc is not None, "dlc is required when idsl is either 1 or 2."
            assert self.dlo is not None, "dlo is required when idsl is either 1 or 2."
        elif self.idsl == 2:
            assert self.vernsat is not None, "vernsat is required when idsl is 2."
            assert self.vernbase is not None, "vernbase is required when idsl is 2."
            assert self.verndvs is not None, "verndvs is required when idsl is 2."
            assert self.verntb is not None, "verntb is required when idsl is 2."

        return self

CropFile

Bases: PySWAPBaseModel

Main class for the .crp file.

This class collects all the settings for the crop file. Currently the types of the attributes are set to Any because the validation is not yet implemented.

Attributes:

Name Type Description
name str

Name of the crop

path Optional[str]

Path to the .crp file

prep Optional[Preparation]

Preparation settings

oxygenstress Optional[OxygenStress]

Oxygen stress settings

droughtstress Optional[DroughtStress]

Drought stress settings

saltstress Optional[SaltStress]

Salt stress settings

compensaterwu Optional[CompensateRWUStress]

Compensate root water uptake stress settings

interception Optional[Interception]

Interception settings

scheduledirrigation Optional[ScheduledIrrigation]

Scheduled irrigation settings

grassland_management Optional[GrasslandManagement]

Grassland management settings

Source code in pyswap/plant/crpfile.py
class CropFile(PySWAPBaseModel):
    """Main class for the .crp file.

    This class collects all the settings for the crop file. Currently the types of the 
    attributes are set to Any because the validation is not yet implemented.

    Attributes:
        name (str): Name of the crop
        path (Optional[str]): Path to the .crp file
        prep (Optional[Preparation]): Preparation settings
        cropdev_settings (Optional[CropDevelopmentSettings | 
            CropDevelopmentSettingsFixed | 
            CropDevelopmentSettingsWOFOST]): Crop development settings
        oxygenstress (Optional[OxygenStress]): Oxygen stress settings
        droughtstress (Optional[DroughtStress]): Drought stress settings
        saltstress (Optional[SaltStress]): Salt stress settings
        compensaterwu (Optional[CompensateRWUStress]): Compensate root water uptake stress settings
        interception (Optional[Interception]): Interception settings
        scheduledirrigation (Optional[ScheduledIrrigation]): Scheduled irrigation settings
        grassland_management (Optional[GrasslandManagement]): Grassland management settings
    """

    name: str = Field(exclude=True)
    path: Optional[str] = None
    prep: Optional[Preparation] = None
    cropdev_settings: Optional[CropDevelopmentSettings |
                               CropDevelopmentSettingsFixed |
                               CropDevelopmentSettingsWOFOST |
                               CropDevelopmentSettingsGrass] = None
    oxygenstress: Optional[OxygenStress] = None
    droughtstress: Optional[DroughtStress] = None
    saltstress: Optional[SaltStress] = SaltStress(swsalinity=0)
    compensaterwu: Optional[CompensateRWUStress] = CompensateRWUStress(
        swcompensate=0)
    interception: Optional[Interception] = None
    scheduledirrigation: Optional[ScheduledIrrigation] = ScheduledIrrigation(
        schedule=0)
    grasslandmanagement: Optional[GrasslandManagement] = None
    co2correction: Optional[CO2Correction] = None

    @computed_field(return_type=str)
    def content(self):
        if self.path:
            return open_file(self.path)
        else:
            return self._concat_sections()

DroughtStress

Bases: PySWAPBaseModel

Drought stress settings for .crp file.

Attributes:

Name Type Description
swdrought Literal[1, 2]

Switch for drought stress

  • 1 - Drought stress according to Feddes et al. (1978)
  • 2 - rought stress according to De Jong van Lier et al. (2008)
swjarvis Optional[Literal[0, 1, 2, 3, 4]]

DEPRECATED Switch for Jarvis model for water uptake reduction

alphcrit Optional[float]

Optional[float] = DEPRECATED Critical stress index (Jarvis, 1989) for compensation of root water uptake [0.2..1 -, R]

hlim3h Optional[float]

Pressure head below which water uptake reduction starts at high Tpot

hlim3l Optional[float]

Pressure head below which water uptake reduction starts at low Tpot

hlim4 Optional[float]

No water extraction at lower soil water pressure heads

adcrh Optional[float]

Level of high atmospheric demand, corresponding to HLIM3H

adcrl Optional[float]

Level of low atmospheric demand, corresponding to HLIM3L

wiltpoint Optional[float]

Minimum pressure head in leaves

kstem Optional[float]

Hydraulic conductance between leaf and root xylem

rxylem Optional[float]

Xylem radius

rootradius Optional[float]

Root radius

kroot Optional[float]

Radial hydraulic conductivity of root tissue

rootcoefa Optional[float]

Defines relative distance between roots at which mean soil water content occurs

swhydrlift Optional[Literal[0, 1]]

Switch for possibility hydraulic lift in root system

rooteff Optional[float]

Root system efficiency factor

stephr Optional[float]

Step between values of hroot and hxylem in iteration cycle

criterhr Optional[float]

Maximum difference of Hroot between iterations; convergence criterium

taccur Optional[float]

Maximum absolute difference between simulated and calculated potential transpiration rate

Source code in pyswap/plant/crpfile.py
class DroughtStress(PySWAPBaseModel):
    """Drought stress settings for .crp file.

    Attributes:
        swdrought (Literal[1, 2]): Switch for drought stress

            * 1 - Drought stress according to Feddes et al. (1978)
            * 2 - rought stress according to De Jong van Lier et al. (2008)

        swjarvis (Optional[Literal[0, 1, 2, 3, 4]]): _DEPRECATED_ Switch for Jarvis model for water uptake reduction
        alphcrit: Optional[float] = _DEPRECATED_ Critical stress index (Jarvis, 1989) for compensation of root water uptake [0.2..1 -, R]
        hlim3h (Optional[float]): Pressure head below which water uptake reduction starts at high Tpot
        hlim3l (Optional[float]): Pressure head below which water uptake reduction starts at low Tpot
        hlim4 (Optional[float]): No water extraction at lower soil water pressure heads
        adcrh (Optional[float]): Level of high atmospheric demand, corresponding to HLIM3H
        adcrl (Optional[float]): Level of low atmospheric demand, corresponding to HLIM3L
        wiltpoint (Optional[float]): Minimum pressure head in leaves
        kstem (Optional[float]): Hydraulic conductance between leaf and root xylem
        rxylem (Optional[float]): Xylem radius
        rootradius (Optional[float]): Root radius
        kroot (Optional[float]): Radial hydraulic conductivity of root tissue
        rootcoefa (Optional[float]): Defines relative distance between roots at which mean soil water content occurs
        swhydrlift (Optional[Literal[0, 1]]): Switch for possibility hydraulic lift in root system
        rooteff (Optional[float]): Root system efficiency factor
        stephr (Optional[float]): Step between values of hroot and hxylem in iteration cycle
        criterhr (Optional[float]): Maximum difference of Hroot between iterations; convergence criterium
        taccur (Optional[float]): Maximum absolute difference between simulated and calculated potential transpiration rate
    """
    swdrought: Literal[1, 2]
    swjarvis: Optional[Literal[0, 1, 2, 3, 4]] = None
    alphcrit: Optional[float] = Field(default=None, ge=0.2, le=1.0)
    hlim3h: Optional[float] = Field(default=None, ge=-1.0e4, le=100.0)
    hlim3l: Optional[float] = Field(default=None, ge=-1.0e4, le=100.0)
    hlim4: Optional[float] = Field(default=None, ge=-1.6e4, le=100.0)
    adcrh: Optional[float] = Field(default=None, ge=0.0, le=5.0)
    adcrl: Optional[float] = Field(default=None, ge=0.0, le=5.0)
    wiltpoint: Optional[float] = Field(default=None, ge=-1.0e8, le=-1.0e2)
    kstem: Optional[float] = Field(default=None, ge=1.0e-10, le=10.0)
    rxylem: Optional[float] = Field(default=None, ge=1.0e-4, le=1.0)
    rootradius: Optional[float] = Field(default=None, ge=1.0e-4, le=1.0)
    kroot: Optional[float] = Field(default=None, ge=1.0e-10, le=1.0e10)
    rootcoefa: Optional[float] = Field(default=None, **UNITRANGE)
    swhydrlift: Optional[Literal[0, 1]] = None
    rooteff: Optional[float] = Field(default=None, **UNITRANGE)
    stephr: Optional[float] = Field(default=None, ge=0.0, le=10.0)
    criterhr: Optional[float] = Field(default=None, ge=0.0, le=10.0)
    taccur: Optional[float] = Field(default=None, ge=1.0e-5, le=1.0e-2)

    @model_validator(mode='after')
    def _validate_prepartion(self) -> Self:
        if self.swdrought == 1:
            assert self.hlim3h is not None, "hlim3h is required when swdrought is 1."
            assert self.hlim3l is not None, "hlim3l is required when swdrought is 1."
            assert self.hlim4 is not None, "hlim4 is required when swdrought is 1."
            assert self.adcrh is not None, "adcrh is required when swdrought is 1."
            assert self.adcrl is not None, "adcrl is required when swdrought is 1."
        if self.swdrought == 2:
            assert self.wiltpoint is not None, "wiltpoint is required when swdrought is 2."
            assert self.kstem is not None, "kstem is required when swdrought is 2."
            assert self.rxylem is not None, "rxylem is required when swdrought is 2."
            assert self.rootradius is not None, "rootradius is required when swdrought is 2."
            assert self.kroot is not None, "kroot is required when swdrought is 2."
            assert self.rootcoefa is not None, "rootcoefa is required when swdrought is 2."
            assert self.swhydrlift is not None, "swhydrlift is required when swdrought is 2."
            assert self.rooteff is not None, "rooteff is required when swdrought is 2."
            assert self.stephr is not None, "stephr is required when swdrought is 2."
            assert self.criterhr is not None, "criterhr is required when swdrought is 2."
            assert self.taccur is not None, "taccur is required when swdrought is 2."

        return self

GrasslandManagement

Bases: PySWAPBaseModel

Settings specific to the dynamic grass growth module.

Warning

Validation still required.

Attributes:

Name Type Description
seqgrazmow IntList

sequence of periods with different practices within calender year. Available options:

  • 1 - Grazing
  • 2 - Mowing
  • 3 - Grazing with dewooling
swharvest Literal[1, 2]

Switch for timing harvest, either for mowing or grazing

  • 1 - Use dry matter threshold
  • 2 - Use fixed dates
dateharvest Optional[DateList]

harvest dates (maximum 999)

swdmgrz Optional[Literal[1, 2]]

Switch for dry matter threshold to trigger harvest by grazing

  • 1 - Use fixed threshold
  • 2 - Use flexible threshold
dmgrazing Optional[Arrays]

Minimum dry matter amount for cattle to enter the field [0..1d6 kg DM/ha, R]

dmgrztb Optional[int]

List threshold of above ground dry matter [0..1d6 kg DM/ha, R] to trigger grazing as function of daynumber [1..366 d, R]

maxdaygrz Optional[int]

Maximum growing period after harvest [1..366 -, I]

swlossgrz Optional[Literal[0, 1]]

Switch for losses due to insufficient pressure head during grazing

  • 0 - No loss
  • 1 - Losses due to treading
tagprest Optional[float]

Minimum amount of above ground DM after grazing [0..1d6 kg DM/ha, R]

dewrest Optional[float]

Remaining yield above ground after dewooling event [0..1d6 kg DM/ha, R]

table_lsda Optional[Table]

Actual livestock density of each grazing period

table_lsdb Optional[Table]

Relation between livestock density, number of grazing days and dry matter uptake

swdmmow Optional[int]

Switch for dry matter threshold to trigger harvest by mowing

  • 1 - Use fixed threshold
  • 2 - Use flexible threshold
dmharvest Optional[float]

Threshold of above ground dry matter to trigger mowing [0..1d6 kg DM/ha, R]

daylastharvest Optional[int]

Last calendar day on which mowing may occur [1..366 -, I]

dmlastharvest Optional[float]

Minimum above ground dry matter for mowing on last date [0..1d6 kg DM/ha, R]

dmmowtb Optional[int]

Dry matter mowing threshold

maxdaymow Optional[int]

Maximum growing period after harvest [1..366 -, I]

swlossmow Optional[int]

Switch for losses due to insufficient pressure head during mowing

  • 0 - No loss
  • 1 - Losses due to treading
mowrest Optional[float]

Remaining yield above ground after mowing event [0..1d6 kg DM/ha, R]

table_dmmowdelay Optional[Optional[Table]]

Relation between dry matter harvest [0..1d6 kg/ha, R] and days of delay in regrowth [0..366 d, I] after mowing

swpotrelmf int

Switch for calculation of potential yield

  • 1 - theoretical potential yield
  • 2 - attainable yield
relmf float

Relative management factor to reduce theoretical potential yield to attainable yield [0..1 -, R]

Source code in pyswap/plant/crpfile.py
class GrasslandManagement(PySWAPBaseModel):
    """Settings specific to the dynamic grass growth module.

    !!! warning

        Validation still required.

    Attributes:
        seqgrazmow (IntList): sequence of periods with different practices within calender year. Available options:

            * 1 - Grazing
            * 2 - Mowing
            * 3 - Grazing with dewooling

        swharvest (Literal[1, 2]): Switch for timing harvest, either for mowing or grazing

            * 1 - Use dry matter threshold
            * 2 - Use fixed dates

        dateharvest Optional[(DateList)]: harvest dates (maximum 999)
        swdmgrz Optional[(Literal[1, 2])]: Switch for dry matter threshold to trigger harvest by grazing

            * 1 - Use fixed threshold
            * 2 - Use flexible threshold

        dmgrazing Optional[(Arrays)]: Minimum dry matter amount for cattle to enter the field [0..1d6 kg DM/ha, R]
        dmgrztb Optional[(int)]: List threshold of above ground dry matter [0..1d6 kg DM/ha, R] to trigger grazing as function of daynumber [1..366 d, R]
        maxdaygrz Optional[(int)]: Maximum growing period after harvest [1..366 -, I]
        swlossgrz Optional[(Literal[0, 1])]: Switch for losses due to insufficient pressure head during grazing

            * 0 - No loss
            * 1 - Losses due to treading

        tagprest Optional[(float)]: Minimum amount of above ground DM after grazing [0..1d6 kg DM/ha, R]
        dewrest Optional[(float)]: Remaining yield above ground after dewooling event [0..1d6 kg DM/ha, R]
        table_lsda (Optional[Table]): Actual livestock density of each grazing period
        table_lsdb (Optional[Table]): Relation between livestock density, number of grazing days and dry matter uptake
        swdmmow Optional[(int)]: Switch for dry matter threshold to trigger harvest by mowing

            * 1 - Use fixed threshold
            * 2 - Use flexible threshold

        dmharvest Optional[(float)]: Threshold of above ground dry matter to trigger mowing [0..1d6 kg DM/ha, R]
        daylastharvest Optional[(int)]: Last calendar day on which mowing may occur [1..366 -, I]
        dmlastharvest Optional[(float)]: Minimum above ground dry matter for mowing on last date [0..1d6 kg DM/ha, R]
        dmmowtb Optional[(int)]: Dry matter mowing threshold
        maxdaymow Optional[(int)]:Maximum growing period after harvest [1..366 -, I]
        swlossmow Optional[(int)]: Switch for losses due to insufficient pressure head during mowing

            * 0 - No loss
            * 1 - Losses due to treading

        mowrest Optional[(float)]: Remaining yield above ground after mowing event [0..1d6 kg DM/ha, R]
        table_dmmowdelay Optional[(Optional[Table])]: Relation between dry matter harvest [0..1d6 kg/ha, R] and days of delay in regrowth [0..366 d, I] after mowing
        swpotrelmf (int): Switch for calculation of potential yield

            * 1 - theoretical potential yield
            * 2 - attainable yield

        relmf (float): Relative management factor to reduce theoretical potential yield to attainable yield [0..1 -, R]
    """

    seqgrazmow: IntList
    swharvest: Literal[1, 2]
    dateharvest: Optional[DateList] = None
    swdmgrz: Optional[Literal[1, 2]] = None
    dmgrazing: Optional[Arrays] = None
    dmgrztb: Optional[Arrays] = None
    maxdaygrz: Optional[int] = None
    swlossgrz: Optional[Literal[0, 1]] = None
    tagprest: Optional[float] = None
    dewrest: Optional[float] = None
    table_lsda: Optional[Table] = None
    table_lsdb: Optional[Table] = None
    swdmmow: Optional[int] = None
    dmharvest: Optional[float] = None
    daylastharvest: Optional[int] = None
    dmlastharvest: Optional[float] = None
    dmmowtb: Optional[Arrays] = None
    maxdaymow: Optional[int] = None
    swlossmow: Optional[int] = None
    mowrest: Optional[float] = None
    table_dmmowdelay: Optional[Table] = None
    swpotrelmf: int
    relmf: float

Interception

Bases: PySWAPBaseModel

Interception settings for .crp file.

Attributes:

Name Type Description
swinter Literal[0, 1, 2]

Switch for rainfall interception method

  • 0 - No interception
  • 1 - Agricultural crops (Von Hoyningen-Hune and Braden)
  • 2 - Trees and forests (Gash)
cofab Optional[float]

Interception coefficient, corresponding to maximum interception amount

table_intertb Optional[Table]

table with the following columns as a function of time T:

  • PFREE - Free throughfall coefficient
  • PSTEM - Stemflow coefficient
  • SCANOPY - Canopy storage coefficient
  • AVPREC = Average rainfall intensity
  • AVEVAP = Average evaporation intensity during rainfall from a wet canopy
Source code in pyswap/plant/crpfile.py
class Interception(PySWAPBaseModel):
    """Interception settings for .crp file.

    Attributes:
        swinter (Literal[0, 1, 2]): Switch for rainfall interception method

            * 0 - No interception
            * 1 - Agricultural crops (Von Hoyningen-Hune and Braden)
            * 2 - Trees and forests (Gash)

        cofab (Optional[float]): Interception coefficient, corresponding to maximum interception amount
        table_intertb (Optional[Table]): table with the following columns as a function of time T:

            * PFREE - Free throughfall coefficient
            * PSTEM - Stemflow coefficient
            * SCANOPY - Canopy storage coefficient
            * AVPREC = Average rainfall intensity
            * AVEVAP = Average evaporation intensity during rainfall from a wet canopy
    """
    swinter: Literal[0, 1, 2]
    cofab: Optional[float] = Field(default=None, **UNITRANGE)
    table_intertb: Optional[Table] = None

    @model_validator(mode='after')
    def _validate_prepartion(self) -> Self:
        if self.swinter == 1:
            assert self.cofab is not None, "cofab is required when swinter is 1."
        elif self.swinter == 1:
            assert self.table_intertb is not None, "table_intertb is required when swinter is 2."

        return self

OxygenStress

Bases: PySWAPBaseModel

Oxygen stress settings for .crp file.

Attributes:

Name Type Description
swoxygen Literal[0, 1, 2]

Switch for oxygen stress

  • 0 - No oxygen stress
  • 1 - Oxygen stress according to Feddes et al. (1978)
  • 2 - Oxygen stress according to Bartholomeus et al. (2008)
swoxygentype Optional[Literal[1, 2]]

switch for physical processes or repro. functions to calculate oxygen stress

  • 1 - physical processes
  • 2 - reproduction functions
swwrtnonox Literal[0, 1]

Switch for checking aerobic conditions in root zone to stop root(zone) development

aeratecrit Optional[float]

Threshold to stop root extension in case of oxygenstress; 0.0 maximum oxygen stress

hlim1 Optional[float]

No water extraction at higher pressure heads

hlim2u Optional[float]

H below which optimum water extr. starts for top layer

hlim2l Optional[float]

H below which optimum water extr. starts for sub layer

q10_microbial Optional[float]

Relative increase in microbial respiration at temperature increase of 10 C

specific_resp_humus Optional[float]

Respiration rate of humus at 25 C

srl Optional[float]

Specific root length

swrootradius Optional[Literal[1, 2]]

Switch for calculation of root radius

  • 1 - Calculate root radius
  • 2 - Root radius given in an input file
dry_mat_cont_roots Optional[float]

Dry matter content of roots

air_filled_root_por Optional[float]

Air filled root porosity

spec_weight_root_tissue Optional[float]

Specific weight of non-airfilled root tissue

var_a Optional[float]

Variance of root radius

root_radiuso2 Optional[float]

Root radius for oxygen stress module

q10_root Optional[float]

Relative increase in root respiration at temperature increase of 10 oC

f_senes Optional[float]

Reduction factor for senescence, used for maintenance respiration

c_mroot Optional[float]

Maintenance coefficient of root

table_max_resp_factor Optional[Table]

Ratio root total respiration / maintenance respiration as a function of development stage

table_dvs_w_root_ss Optional[Table]

List dry weight of roots at soil surface as a function of development stage

TODO: Find a way to validate the parameters that are required when the croptype=1 and swoxygen=2 (currently I cannot access the croptype parameter)

Source code in pyswap/plant/crpfile.py
class OxygenStress(PySWAPBaseModel):
    """Oxygen stress settings for .crp file.

    Attributes:
        swoxygen (Literal[0, 1, 2]): Switch for oxygen stress

            * 0 - No oxygen stress
            * 1 - Oxygen stress according to Feddes et al. (1978)
            * 2 - Oxygen stress according to Bartholomeus et al. (2008)

        swoxygentype (Optional[Literal[1, 2]]): switch for physical processes or repro. functions to calculate oxygen stress

            * 1 - physical processes
            * 2 - reproduction functions

        swwrtnonox (Literal[0, 1]): Switch for checking aerobic conditions in root zone to stop root(zone) development
        aeratecrit (Optional[float]): Threshold to stop root extension in case of oxygenstress; 0.0 maximum oxygen stress
        hlim1 (Optional[float]): No water extraction at higher pressure heads
        hlim2u (Optional[float]): H below which optimum water extr. starts for top layer
        hlim2l (Optional[float]): H below which optimum water extr. starts for sub layer
        q10_microbial (Optional[float]): Relative increase in microbial respiration at temperature increase of 10 C
        specific_resp_humus (Optional[float]): Respiration rate of humus at 25 C
        srl (Optional[float]): Specific root length
        swrootradius (Optional[Literal[1, 2]]): Switch for calculation of root radius

            * 1 - Calculate root radius
            * 2 - Root radius given in an input file

        dry_mat_cont_roots (Optional[float]): Dry matter content of roots
        air_filled_root_por (Optional[float]): Air filled root porosity
        spec_weight_root_tissue (Optional[float]): Specific weight of non-airfilled root tissue
        var_a (Optional[float]): Variance of root radius
        root_radiuso2 (Optional[float]): Root radius for oxygen stress module
        q10_root (Optional[float]): Relative increase in root respiration at temperature increase of 10 oC
        f_senes (Optional[float]): Reduction factor for senescence, used for maintenance respiration
        c_mroot (Optional[float]): Maintenance coefficient of root
        table_max_resp_factor (Optional[Table]): Ratio root total respiration / maintenance respiration as a function of development stage
        table_dvs_w_root_ss (Optional[Table]): List dry weight of roots at soil surface as a function of development stage

    TODO: Find a way to validate the parameters that are required when the
    croptype=1 and swoxygen=2 (currently I cannot access the croptype parameter)
    """

    swoxygen: Literal[0, 1, 2]
    swwrtnonox: Literal[0, 1]
    swoxygentype: Optional[Literal[1, 2]] = None
    aeratecrit: Optional[float] = Field(default=None, ge=0.0001, le=1.0)
    hlim1: Optional[float] = Field(default=None, ge=-100.0, le=100.0)
    hlim2u: Optional[float] = Field(default=None, ge=-1000.0, le=100.0)
    hlim2l: Optional[float] = Field(default=None, ge=-1000.0, le=100.0)
    q10_microbial: Optional[float] = Field(default=None, ge=1.0, le=4.0)
    specific_resp_humus: Optional[float] = Field(default=None, **UNITRANGE)
    srl: Optional[float] = Field(default=None, ge=0.0, le=1.0e10)
    swrootradius: Optional[Literal[1, 2]] = None
    dry_mat_cont_roots: Optional[float] = Field(default=None, **UNITRANGE)
    air_filled_root_por: Optional[float] = Field(default=None, **UNITRANGE)
    spec_weight_root_tissue: Optional[float] = Field(
        default=None, ge=0.0, le=1.0e5)
    var_a: Optional[float] = Field(default=None, **UNITRANGE)
    root_radiuso2: Optional[float] = Field(default=None, ge=1.0e-6, le=0.1)
    q10_root: Optional[float] = Field(default=None, ge=1.0, le=4.0)
    f_senes: Optional[float] = Field(default=None, **UNITRANGE)
    c_mroot: Optional[float] = Field(default=None, **UNITRANGE)
    mrftb: Optional[Arrays] = None
    wrtb: Optional[Arrays] = None

    @model_validator(mode='after')
    def _validate_oxygen(self) -> Self:
        if self.swoxygen == 1:
            assert self.hlim1 is not None, "hlim1 is required when swoxygen is 1."
            assert self.hlim2u is not None, "hlim2u is required when swoxygen is 1."
            assert self.hlim2l is not None, "hlim2l is required when swoxygen is 1."
        elif self.swoxygen == 2:
            assert self.q10_microbial is not None, "q10_microbial is required when swoxygen is 2."
            assert self.specific_resp_humus is not None, "specific_resp_humus is required when swoxygen is 2."
            assert self.srl is not None, "srl is required when swoxygen is 2."
            assert self.swrootradius is not None, "swrootradius is required when swoxygen is 2."
            if self.swrootradius == 1:
                assert self.dry_mat_cont_roots is not None, "dry_mat_cont_roots is required when swrootradius is 1."
                assert self.air_filled_root_por is not None, "air_filled_root_por is required when swrootradius is 1."
                assert self.spec_weight_root_tissue is not None, "spec_weight_root_tissue is required when swrootradius is 1."
                assert self.var_a is not None, "var_a is required when swrootradius is 1."
            elif self.swrootradius == 2:
                assert self.root_radiuso2 is not None, "root_radiuso2 is required when swrootradius is 2."
        if self.swwrtnonox == 1:
            assert self.aeratecrit is not None, "aeratecrit is required when swwrtnonox is 1."

        return self

Preparation

Bases: PySWAPBaseModel

Preparation, sowing and germination settings for .crp file.

Attributes:

Name Type Description
swprep Literal[0, 1]

Switch for preparation

swsow Literal[0, 1]

Switch for sowing

swgerm Literal[0, 1, 2]

Switch for germination

  • 0 - No germination
  • 1 - Germination with temperature sum
  • 2 - Germination with temperature sum and water potential
swharv Literal[0, 1]

Switch for harvest

  • 0 - Timing of harvest depends on end of growing period (CROPEND)
  • 1 - Timing of harvest depends on development stage (DVSEND)
dvsend Optional[float]

Development stage at harvest

zprep Optional[float]

Z-level for monitoring work-ability for the crop

hprep Optional[float]

Maximum pressure head during preparation

maxprepdelay Optional[int]

Maximum delay of preparation from start of growing season

zsow Optional[float]

Z-level for monitoring work-ability for the crop

hsow Optional[float]

Maximum pressure head during sowing

ztempsow Optional[float]

Z-level for monitoring temperature for sowing

tempsow Optional[float]

Soil temperature needed for sowing

maxsowdelay Optional[int]

Maximum delay of sowing from start of growing season

tsumemeopt Optional[float]

Temperature sum needed for crop emergence

tbasem Optional[float]

Minimum temperature, used for germination trajectory

teffmx Optional[float]

Maximum temperature, used for germination trajectory

hdrygerm Optional[float]

Pressure head rootzone for dry germination trajectory

hwetgerm Optional[float]

Pressure head rootzone for wet germination trajectory

zgerm Optional[float]

Z-level for monitoring average pressure head

agerm Optional[float]

A-coefficient Eq. 24/25 Feddes & Van Wijk

Source code in pyswap/plant/crpfile.py
class Preparation(PySWAPBaseModel):
    """Preparation, sowing and germination settings for .crp file.

    Attributes:
        swprep (Literal[0, 1]): Switch for preparation
        swsow (Literal[0, 1]): Switch for sowing
        swgerm (Literal[0, 1, 2]): Switch for germination

            * 0 - No germination
            * 1 - Germination with temperature sum
            * 2 - Germination with temperature sum and water potential

        swharv (Literal[0, 1]): Switch for harvest

            * 0 - Timing of harvest depends on end of growing period (CROPEND)
            * 1 - Timing of harvest depends on development stage (DVSEND)

        dvsend (Optional[float]): Development stage at harvest
        zprep (Optional[float]): Z-level for monitoring work-ability for the crop
        hprep (Optional[float]): Maximum pressure head during preparation
        maxprepdelay (Optional[int]): Maximum delay of preparation from start of growing season
        zsow (Optional[float]): Z-level for monitoring work-ability for the crop
        hsow (Optional[float]): Maximum pressure head during sowing
        ztempsow (Optional[float]): Z-level for monitoring temperature for sowing
        tempsow (Optional[float]): Soil temperature needed for sowing
        maxsowdelay (Optional[int]): Maximum delay of sowing from start of growing season
        tsumemeopt (Optional[float]): Temperature sum needed for crop emergence
        tbasem (Optional[float]): Minimum temperature, used for germination trajectory
        teffmx (Optional[float]): Maximum temperature, used for germination trajectory
        hdrygerm (Optional[float]): Pressure head rootzone for dry germination trajectory
        hwetgerm (Optional[float]): Pressure head rootzone for wet germination trajectory
        zgerm (Optional[float]): Z-level for monitoring average pressure head
        agerm (Optional[float]): A-coefficient Eq. 24/25 Feddes & Van Wijk
    """

    swprep: Literal[0, 1]
    swsow: Literal[0, 1]
    swgerm: Literal[0, 1, 2]
    swharv: Literal[0, 1]
    dvsend: Optional[float] = Field(default=None, ge=0.0, le=3.0)
    zprep: Optional[float] = Field(default=None, ge=-100.0, le=0.0)
    hprep: Optional[float] = Field(default=None, ge=-200.0, le=0.0)
    maxprepdelay: Optional[int] = Field(default=None, ge=1, le=366)
    zsow: Optional[float] = Field(default=None, ge=-100.0, le=0.0)
    hsow: Optional[float] = Field(default=None, ge=-200.0, le=0.0)
    ztempsow: Optional[float] = Field(default=None,  ge=-100.0, le=0.0)
    tempsow: Optional[float] = Field(default=None, ge=0.0, le=30.0)
    maxsowdelay: Optional[int] = Field(default=None, ge=1, le=366)
    tsumemeopt: Optional[float] = Field(default=None, ge=0.0, le=1000.0)
    tbasem: Optional[float] = Field(default=None, ge=0.0, le=1000.0)
    teffmx: Optional[float] = Field(default=None, ge=0.0, le=1000.0)
    hdrygerm: Optional[float] = Field(default=None, ge=-1000.0, le=1000.0)
    hwetgerm: Optional[float] = Field(default=None, ge=-100.0, le=1000.0)
    zgerm: Optional[float] = Field(default=None, ge=-100.0, le=1000.0)
    agerm: Optional[float] = Field(default=None, ge=0.0, le=1000.0)

    @model_validator(mode='after')
    def _validate_prepartion(self) -> Self:
        if self.swprep == 1:
            assert self.zprep is not None, "zprep is required when swprep is 1."
            assert self.hprep is not None, "hprep is required when swprep is 1."
            assert self.maxprepdelay is not None, "maxprepdelay is required when swprep is 1."

        if self.swsow == 1:
            assert self.zsow is not None, "zsow is required when swsow is 1."
            assert self.hsow is not None, "hsow is required when swsow is 1."
            assert self.ztempsow is not None, "ztempsow is required when swsow is 1."
            assert self.tempsow is not None, "tempsow is required when swsow is 1."
            assert self.maxsowdelay is not None, "maxsowdelay is required when swsow is 1."

        if self.swgerm in (1, 2):
            assert self.tsumemeopt is not None, "tsumemeopt is required when swgerm is 1 or 2."
            assert self.tbasem is not None, "tbasem is required when swgerm is 1 or 2."
            assert self.teffmx is not None, "teffmx is required when swgerm is 1 or 2."
        elif self.swgerm == 2:
            assert self.hdrygerm is not None, "hdrygerm is required when swgerm is 2."
            assert self.hwetgerm is not None, "hwetgerm is required when swgerm is 2."
            assert self.zgerm is not None, "zgerm is required when swgerm is 2."
            assert self.agerm is not None, "agerm is required when swgerm is 2."

        return self

SaltStress

Bases: PySWAPBaseModel

Salt stress settings for .crp file.

Attributes:

Name Type Description
swsalinity Literal[0, 1, 2]

Switch for salt stress

  • 0 - No salt stress
  • 1 - Maas and Hoffman reduction function
  • 2 - Use osmotic head
saltmax Optional[float]

Threshold salt concentration in soil water

saltslope Optional[float]

Decline of root water uptake above threshold

salthead Optional[float]

Conversion factor salt concentration (mg/cm3) into osmotic head (cm)

Source code in pyswap/plant/crpfile.py
class SaltStress(PySWAPBaseModel):
    """Salt stress settings for .crp file.

    Attributes:
        swsalinity (Literal[0, 1, 2]): Switch for salt stress

            * 0 - No salt stress
            * 1 - Maas and Hoffman reduction function
            * 2 - Use osmotic head

        saltmax (Optional[float]): Threshold salt concentration in soil water
        saltslope (Optional[float]): Decline of root water uptake above threshold
        salthead (Optional[float]): Conversion factor salt concentration (mg/cm3) into osmotic head (cm)
    """
    swsalinity: Literal[0, 1, 2]
    saltmax: Optional[float] = Field(default=None, ge=0.0, le=100.0)
    saltslope: Optional[float] = Field(default=None, **UNITRANGE)
    salthead: Optional[float] = Field(default=None, ge=0.0, le=1000.0)

    @model_validator(mode='after')
    def _validate_prepartion(self) -> Self:
        if self.swsalinity == 1:
            assert self.saltmax is not None, "saltmax is required when swsalinity is 1."
            assert self.saltslope is not None, "saltslope is required when swsalinity is 1."
        elif self.swsalinity == 2:
            assert self.salthead is not None, "salthead is required when swsalinity is 2."

        return self

ScheduledIrrigation

Bases: PySWAPBaseModel

Irrigation scheduling settings.

Warning

The docstring needs to be updated.

Note

This class is only used in the .crp file.

Attributes:

Name Type Description
schedule Literal[0, 1]

Switch for application irrigation scheduling

startirr str

Specify day and month at which irrigation scheduling starts

endirr str

Specify day and month at which irrigation scheduling stops

cirrs float

Solute concentration of irrigation water

isuas int

Switch for type of irrigation method

  • 0 - Sprinkler irrigation
  • 1 - Surface irrigation
tcs int

Choose one of the following timing criteria options

  • 1 - Ratio actual/potential transpiration
  • 2 - Depletion of Readily Available Water
  • 3 - Depletion of Totally Available Water
  • 4 - Depletion of absolute Water Amount
  • 6 - Fixed weekly irrigation
  • 7 - Pressure head
  • 8 - Moisture content
phFieldCapacity float

Soil water pressure head at field capacity

irgthreshold Optional[float]

Threshold value for weekly irrigation

dcrit Optional[float]

Depth of the sensor

swcirrthres Optional[bool]

Switch for over-irrigation

cirrthres Optional[float]

Threshold salinity concentration above which over-irrigation occur

perirrsurp Optional[float]

Over-irrigation of the usually scheduled irrigation depth

tcsfix Optional[int]

Switch for minimum time interval between irrigation applications

irgdayfix Optional[int]

Minimum number of days between irrigation applications

phormc Optional[int]

Switch for the use of pressure head or water content

  • 0 - Pressure head
  • 1 - Water content
dvs_tc1 Optional[Table]
dvs_tc2 Optional[Table]
dvs_tc3 Optional[Table]
dvs_tc4 Optional[Table]
dvs_tc5 Optional[Table]
Source code in pyswap/irrigation/irrigation.py
class ScheduledIrrigation(PySWAPBaseModel):
    """Irrigation scheduling settings.

    !!! warning
        The docstring needs to be updated.

    !!! note
        This class is only used in the .crp file.

    Attributes:
        schedule (Literal[0, 1]): Switch for application irrigation scheduling
        startirr (str): Specify day and month at which irrigation scheduling starts
        endirr (str): Specify day and month at which irrigation scheduling stops
        cirrs (float): Solute concentration of irrigation water
        isuas (int): Switch for type of irrigation method

            * 0 - Sprinkler irrigation
            * 1 - Surface irrigation

        tcs (int): Choose one of the following timing criteria options

            * 1 - Ratio actual/potential transpiration
            * 2 - Depletion of Readily Available Water
            * 3 - Depletion of Totally Available Water
            * 4 - Depletion of absolute Water Amount
            * 6 - Fixed weekly irrigation
            * 7 - Pressure head
            * 8 - Moisture content

        phFieldCapacity (float): Soil water pressure head at field capacity
        irgthreshold (Optional[float]): Threshold value for weekly irrigation
        dcrit (Optional[float]): Depth of the sensor
        swcirrthres (Optional[bool]): Switch for over-irrigation
        cirrthres (Optional[float]): Threshold salinity concentration above which over-irrigation occur
        perirrsurp (Optional[float]): Over-irrigation of the usually scheduled irrigation depth
        tcsfix (Optional[int]): Switch for minimum time interval between irrigation applications
        irgdayfix (Optional[int]): Minimum number of days between irrigation applications
        phormc (Optional[int]): Switch for the use of pressure head or water content

            * 0 - Pressure head
            * 1 - Water content

        dvs_tc1 (Optional[Table]):
        dvs_tc2 (Optional[Table]):
        dvs_tc3 (Optional[Table]):
        dvs_tc4 (Optional[Table]):
        dvs_tc5 (Optional[Table]):
    """
    schedule: Literal[0, 1]
    startirr: Optional[DayMonth] = None
    endirr: Optional[DayMonth] = None
    cirrs: Optional[float] = Field(default=None, ge=0.0, le=100.0)
    isuas: Optional[Literal[0, 1]] = None
    tcs: Optional[Literal[1, 2, 3, 4, 6, 7, 8]] = None
    phfieldcapacity: Optional[float] = Field(default=None, ge=-1000.0, le=0.0)
    irgthreshold: Optional[float] = Field(default=None, ge=0.0, le=20.0)
    dcrit: Optional[float] = Field(default=None, ge=-100.0, le=0.0)
    swcirrthres: Optional[Literal[0, 1]] = None
    cirrthres: Optional[float] = Field(default=None, ge=0.0, le=100.0)
    perirrsurp: Optional[float] = Field(default=None, ge=0.0, le=100.0)
    tcsfix: Optional[Literal[0, 1]] = None
    irgdayfix: Optional[int] = Field(default=None, **YEARRANGE)
    dcs: Optional[Literal[0, 1]] = None
    dcslim: Optional[Literal[0, 1]] = None
    irgdepmin: Optional[float] = Field(default=None, ge=0.0, le=100.0)
    irgdepmax: Optional[float] = Field(default=None, ge=0.0, le=1.0e7)
    table_tc1tb: Optional[Table] = None
    table_tc2tb: Optional[Table] = None
    table_tc3tb: Optional[Table] = None
    table_tc4tb: Optional[Table] = None
    table_tc7tb: Optional[Table] = None
    table_tc8tb: Optional[Table] = None

    @model_validator(mode='after')
    def _validate_scheduled_irrigation(self) -> Self:

        if self.tcs == 1:
            self.dvs_tc1 = {'dvs_tc1': [0.0, 2.0],
                            'Trel': [0.95, 0.95]}
        elif self.tcs == 2:
            self.dvs_tc2 = {'dvs_tc2': [0.0, 2.0],
                            'RAW': [0.95, 0.95]}
        elif self.tcs == 3:
            self.dvs_tc3 = {'dvs_tc3': [0.0, 2.0],
                            'TAW': [0.50, 0.50]}
        elif self.tcs == 4:
            self.dvs_tc4 = {'dvs_tc4': [0.0, 2.0],
                            'DWA': [0.40, 0.40]}
        elif self.tcs == 5:
            self.dvs_tc5 = {'dvs_tc5': [0.0, 2.0],
                            'Value_tc5': [-1000.0, -1000.0]}
        elif self.tcs == 6:
            assert self.irgthreshold is not None, "irgthreshold is required when tcs is 6"
            assert self.tcsfix is not None, "tcsfix is required when tcs is 6"
            if self.tcsfix:
                assert self.irgdayfix is not None, "irgdayfix is required when tcsfix is True"

        return self

open_file(file_path)

Open file and detect encoding.

Parameters:

Name Type Description Default
file_path str

Path to the file to be opened.

required
Source code in pyswap/core/files.py
def open_file(file_path: str) -> str:
    """Open file and detect encoding.

    Arguments:
        file_path (str): Path to the file to be opened.
    """
    with open(file_path, 'rb') as f:
        raw_data = f.read()
    encoding = chardet.detect(raw_data)['encoding']

    return raw_data.decode(encoding)

crpfile

Create .crp file for SWAP model.

Similar to the .dra or .swp files, the .crp file is a configuration file for the SWAP model. The classes in this module represent distincs sections of the .crp file. The main class is the CropFile class which holds the settings for the crop simulation.

Classes:

Name Description
CropFile

Class for the .crp file.

CropDevelopmentSettings

Class for the crop development settings.

CropDevelopmentSettingsWOFOST

Class for the crop development settings in WOFOST.

CropDevelopmentSettingsFixed

Class for the fixed crop development settings.

OxygenStress

Class for the oxygen stress settings.

DroughtStress

Class for the drought stress settings.

SaltStress

Class for the salt stress settings.

CompensateRWUStress

Class for the compensate root water uptake stress settings.

Interception

Class for the interception settings.

CO2Correction

Class for the CO2 correction settings.

ScheduledIrrigation

Class for the scheduled irrigation settings.

Preparation

Class for the preparation settings.

Warning

This script will undergo major changes in the future. Some things to improve include smoother integration with WOFOST configuration files (yaml) and code readability.

CO2Correction

Bases: PySWAPBaseModel

CO2 correction settings for WOFOST-type .crp file.

Attributes:

Name Type Description
swco2 Literal[0, 1]

Switch for assimilation correction due to CO2 impact

  • 0 - No CO2 assimilation correction
  • 1 - CO2 assimilation correction
atmofil Optional[str]

alternative filename for atmosphere.co2

co2amaxtb Optional[Arrays]

Correction of photosynthesis as a function of atmospheric CO2 concentration

co2efftb Optional[Arrays]

orrection of radiation use efficiency as a function of atmospheric CO2 concentration

co2tratb Optional[Arrays]

Correction of transpiration as a function of atmospheric CO2 concentration

Source code in pyswap/plant/crpfile.py
class CO2Correction(PySWAPBaseModel):
    """CO2 correction settings for WOFOST-type .crp file.

    Attributes:
        swco2 (Literal[0, 1]): Switch for assimilation correction due to CO2 impact

            * 0 - No CO2 assimilation correction
            * 1 - CO2 assimilation correction

        atmofil (Optional[str]): alternative filename for atmosphere.co2
        co2amaxtb (Optional[Arrays]): Correction of photosynthesis as a function of atmospheric CO2 concentration
        co2efftb (Optional[Arrays]): orrection of radiation use efficiency as a function of atmospheric CO2 concentration
        co2tratb (Optional[Arrays]): Correction of transpiration as a function of atmospheric CO2 concentration
    """

    swco2: Literal[0, 1]
    atmofil: Optional[str] = None
    co2amaxtb: Optional[Arrays] = None
    co2efftb: Optional[Arrays] = None
    co2tratb: Optional[Arrays] = None

    @model_validator(mode='after')
    def _validate_co2correction(self) -> Self:
        if self.swco2 == 1:
            assert self.atmofil is not None, 'amofil is required when swco2 is 1'
            assert self.co2amaxtb is not None, 'co2amaxtb is required when swco2 is 1'
            assert self.co2efftb is not None, 'co2efftb is required when swco2 is 1'
            assert self.co2tratb is not None, 'co2tratb is required when swco2 is 1'

        return self

CompensateRWUStress

Bases: PySWAPBaseModel

Compensate root water uptake stress settings for .crp file.

Attributes:

Name Type Description
swcompensate Literal[0, 1, 2]

Switch for compensate root water uptake stress

  • 0 - No compensation
  • 1 - Compensation according to Jarvis (1989)
  • 2 - Compensation according to Walsum (2019)
swstressor Optional[Literal[1, 2, 3, 4, 5]]

Switch for stressor

  • 1 - Compensation of all stressors
  • 2 - Compensation of drought stress
  • 3 - Compensation of oxygen stress
  • 4 - Compensation of salinity stress
  • 5 - Compensation of frost stress
alphacrit Optional[float]

Critical stress index for compensation of root water uptake

dcritrtz Optional[float]

Threshold of rootzone thickness after which compensation occurs

Source code in pyswap/plant/crpfile.py
class CompensateRWUStress(PySWAPBaseModel):
    """Compensate root water uptake stress settings for .crp file.

    Attributes:
        swcompensate (Literal[0, 1, 2]): Switch for compensate root water uptake stress

            * 0 - No compensation
            * 1 - Compensation according to Jarvis (1989)
            * 2 - Compensation according to Walsum (2019)

        swstressor (Optional[Literal[1, 2, 3, 4, 5]]): Switch for stressor

            * 1 - Compensation of all stressors
            * 2 - Compensation of drought stress
            * 3 - Compensation of oxygen stress
            * 4 - Compensation of salinity stress
            * 5 - Compensation of frost stress

        alphacrit (Optional[float]): Critical stress index for compensation of root water uptake
        dcritrtz (Optional[float]): Threshold of rootzone thickness after which compensation occurs
    """
    swcompensate: Literal[0, 1, 2]
    swstressor: Optional[Literal[1, 2, 3, 4, 5]] = None
    alphacrit: Optional[float] = Field(default=None, ge=0.2, le=1.0)
    dcritrtz: Optional[float] = Field(default=None, ge=0.02, le=100.0)

    @model_validator(mode='after')
    def _validate_prepartion(self) -> Self:
        if self.swcompensate in [1, 2]:
            assert self.swstressor is not None, "swstressor is required when swcompensate is 1 or 2."
        if self.swcompensate == 1:
            assert self.alphacrit is not None, "alphacrit is required when swcompensate is 1."
        if self.swcompensate == 2:
            assert self.dcritrtz is not None, "dcritrtz is required when swcompensate is 2."

        return self

CropDevelopmentSettings

Bases: PySWAPBaseModel

Crop development settings (parts 1-xx form the template)

Note

The validation of this class should be optimized. The current implementation repeats the validation of the base class in each subclass. The observed issue is that when the validator is inherited from the base class and there is another validator in the subclass (even if they have different names), at the validation step of the child class the validator throws an error that the attribute is NoneType. To be fixed later.

Attributes:

Name Type Description
swcf Literal[1, 2]

Choose between crop factor and crop height

  • 1 - Crop factor
  • 2 - Crop height
table_dvs_cf Optional[Table]

Table with crop factors as a function of development stage

table_dvs_ch Optional[Table]

Table with crop height as a function of development stage

albedo Optional[float]

Crop reflection coefficient

rsc Optional[float]

Minimum canopy resistance

rsw Optional[float]

Canopy resistance of intercepted water

tsumea float

Temperature sum from emergence to anthesis

tsumam float

Temperature sum from anthesis to maturity

tbase Optional[float]

Start value of temperature sum

kdif float

Extinction coefficient for diffuse visible light

kdir float

Extinction coefficient for direct visible light

swrd Optional[Literal[1, 2, 3]]

Switch development of root growth

  • 1 - Root growth depends on development stage
  • 2 - Root growth depends on maximum daily increase
  • 3 - Root growth depends on available root biomass
rdtb Optional[Arrays]

Rooting Depth as a function of development stage

rdi float

Initial rooting depth

rri float

Maximum daily increase in rooting depth

rdc float

Maximum rooting depth of particular crop

swdmi2rd Optional[Literal[0, 1]]

Switch for calculation rooting depth

  • 0 - Rooting depth increase is related to availability assimilates for roots
  • 1 - Rooting depth increase is related to relative dry matter increase
rlwtb Optional[Arrays]

rooting depth as function of root weight

wrtmax float

Maximum root weight

swrdc Literal[0, 1]

Switch for calculation of relative root density

rdctb Arrays

root density as function of relative rooting depth

Source code in pyswap/plant/crpfile.py
class CropDevelopmentSettings(PySWAPBaseModel):
    """Crop development settings (parts 1-xx form the template)

    Note:
        The validation of this class should be optimized. The current implementation
        repeats the validation of the base class in each subclass. The observed issue is that
        when the validator is inherited from the base class and there is another validator in the
        subclass (even if they have different names), at the validation step of the child class the
        validator throws an error that the attribute is NoneType. To be fixed later.

    Attributes:
        swcf (Literal[1, 2]): Choose between crop factor and crop height

            * 1 - Crop factor
            * 2 - Crop height

        table_dvs_cf (Optional[Table]): Table with crop factors as a function of development stage
        table_dvs_ch (Optional[Table]): Table with crop height as a function of development stage
        albedo (Optional[float]): Crop reflection coefficient
        rsc (Optional[float]): Minimum canopy resistance
        rsw (Optional[float]): Canopy resistance of intercepted water
        tsumea (float): Temperature sum from emergence to anthesis
        tsumam (float): Temperature sum from anthesis to maturity
        tbase (Optional[float]): Start value of temperature sum
        kdif (float): Extinction coefficient for diffuse visible light
        kdir (float): Extinction coefficient for direct visible light
        swrd (Optional[Literal[1, 2, 3]]): Switch development of root growth

            * 1 - Root growth depends on development stage
            * 2 - Root growth depends on maximum daily increase
            * 3 - Root growth depends on available root biomass

        rdtb (Optional[Arrays]): Rooting Depth as a function of development stage
        rdi (float): Initial rooting depth
        rri (float): Maximum daily increase in rooting depth
        rdc (float): Maximum rooting depth of particular crop
        swdmi2rd (Optional[Literal[0, 1]]): Switch for calculation rooting depth

            * 0 - Rooting depth increase is related to availability assimilates for roots
            * 1 - Rooting depth increase is related to relative dry matter increase

        rlwtb (Optional[Arrays]): rooting depth as function of root weight
        wrtmax (float): Maximum root weight
        swrdc (Literal[0, 1]): Switch for calculation of relative root density
        rdctb (Arrays): root density as function of relative rooting depth
    """
    swcf: Literal[1, 2]
    table_dvs_cf: Optional[Table] = None
    table_dvs_ch: Optional[Table] = None
    albedo: Optional[float] = Field(default=None, **UNITRANGE)
    rsc: Optional[float] = Field(default=None, ge=0.0, le=1.0e6)
    rsw: Optional[float] = Field(default=None, ge=0.0, le=1.0e6)
    # In WOFOST reference yaml files this is called TSUM1
    tsumea: float = Field(default=None, ge=0.0, le=1.0e4)
    # In WOFOST reference yaml files this is called TSUM2
    tsumam: float = Field(default=None, ge=0.0, le=1.0e4)
    # In SWAP this parameter seems to meen something different than in the
    # WOFOST template. The range of value is the same though.
    tbase: Optional[float] = Field(default=None, ge=-10.0, le=30.0)
    kdif: float = Field(ge=0.0, le=2.0)
    kdir: float = Field(ge=0.0, le=2.0)
    swrd: Optional[Literal[1, 2, 3]] = None
    rdtb: Optional[Arrays] = None
    rdi: float = Field(default=None, ge=0.0, le=1000.0)
    rri: float = Field(default=None, ge=0.0, le=100.0)
    rdc: float = Field(default=None, ge=0.0, le=1000.0)
    swdmi2rd: Optional[Literal[0, 1]] = None
    rlwtb: Optional[Arrays] = None
    wrtmax: float = Field(default=None, ge=0.0, le=1.0e5)
    swrdc: Literal[0, 1] = 0
    rdctb: Arrays

CropDevelopmentSettingsFixed

Bases: CropDevelopmentSettings

Fixed crop development settings (parts 1-xx form the template)

Warning

This class is not complete. It is missing the validation.

Note

I noticed an issue with the tables here. They are actually arrays (each array is a column) that are preceeded by the variable name and "=". That variable name is the same for all options of tables which have different column names (e.g., DVS/LAI or DVS/SCF) but the variable name is the same (e.g., GCTB). TODO: implement a check of the column before the df is converted to string.

Attributes:

Name Type Description
idev Literal[1, 2]

Duration of crop growing period

  • 1 - Duration is fixed
  • 2 - Duration is variable
lcc Optional[int]

Duration of the crop growing period

swgc Literal[1, 2]

Choose between Leaf Area Index or Soil Cover Fraction

  • 1 - LAI
  • 2 - SCF
gctb Arrays

Soil Cover Fraction as a function of development stage

Source code in pyswap/plant/crpfile.py
class CropDevelopmentSettingsFixed(CropDevelopmentSettings):
    """Fixed crop development settings (parts 1-xx form the template)

    Warning:
        This class is not complete. It is missing the validation.

    Note:
        I noticed an issue with the tables here. They are actually arrays (each
        array is a column) that are preceeded by the variable name and "=". That variable
        name is the same for all options of tables which have different column names (e.g., DVS/LAI or
        DVS/SCF) but the variable name is the same (e.g., GCTB).
        TODO: implement a check of the column before the df is converted to string.

    Attributes:
        idev (Literal[1, 2]): Duration of crop growing period

            * 1 - Duration is fixed
            * 2 - Duration is variable

        lcc (Optional[int]): Duration of the crop growing period
        swgc (Literal[1, 2]): Choose between Leaf Area Index or Soil Cover Fraction

            * 1 - LAI
            * 2 - SCF

        gctb (Arrays): Soil Cover Fraction as a function of development stage
    """

    idev: Literal[1, 2]
    lcc: Optional[int] = Field(default=None, **YEARRANGE)
    swgc: Literal[1, 2]
    gctb: Arrays
    kytb: Optional[Arrays] = None

    @model_validator(mode='after')
    def _validate_crop_fixed(self) -> Self:
        # validation of the base class
        if self.swcf == 1:
            assert self.table_dvs_cf is not None, "table_dvs_cf is required when swcf is 1."
        elif self.swcf == 2:
            assert self.table_dvs_ch is not None, "table_dvs_ch is required when swcf is 2."
            assert self.albedo is not None, "albedo is required when swcf is 2."
            assert self.rsc is not None, "rsc is required when swcf is 2."
            assert self.rsw is not None, "rsw is required when swcf is 2."
        if self.swrd == 1:
            assert self.rdtb is not None, "rdtb is required when swrd is 1."
        elif self.swrd == 2:
            assert self.rdi is not None, "rdi is required when swrd is 2."
            assert self.rri is not None, "rri is required when swrd is 2."
            assert self.rdc is not None, "rdc is required when swrd is 2."
            assert self.swdmi2rd is not None, "swdmi2rd is required when swrd is 2."
        elif self.swrd == 3:
            assert self.rlwtb is not None, "rlwtb is required when swrd is 3."
            assert self.wrtmax is not None, "wrtmax is required when swrd is 3."
        # validation specific to the fixed crop development settings
        if self.idev == 1:
            assert self.lcc is not None, "lcc is required when idev is 1."
        elif self.idev == 2:
            assert self.tsumea is not None, "tsumea is required when idev is 2."
            assert self.tsumam is not None, "tsumam is required when idev is 2."
            assert self.tbase is not None, "tbase is required when idev is 2."

        return self

CropDevelopmentSettingsGrass

Bases: CropDevelopmentSettingsWOFOST

Crop development settings specific to grass growth.

Attributes:

Name Type Description
swtsum Literal[0, 1, 2]

Select either sum air temperatures or soil temperature at particular depth

  • 0 - no delay of start grass growth
  • 1 - start of grass growth based on sum air temperatures > 200 degree C
  • 2 - start of grass growth based on soil temperature at particular depth
tsumtemp Optional[float]

Specific stem area [0..1 ha/kg, R]

tsumdepth Optional[float]

Life span under leaves under optimum conditions [0..366 d, R]

tsumtime Optional[float]

Lower threshold temperature for ageing of leaves [-10..30 degree C, R]

Source code in pyswap/plant/crpfile.py
class CropDevelopmentSettingsGrass(CropDevelopmentSettingsWOFOST):
    """Crop development settings specific to grass growth.

    Attributes:
        swtsum (Literal[0, 1, 2]): Select either sum air temperatures or soil temperature at particular depth

            * 0 - no delay of start grass growth
            * 1 - start of grass growth based on sum air temperatures > 200 degree C
            * 2 - start of grass growth based on soil temperature at particular depth

        tsumtemp (Optional[float]): Specific stem area [0..1 ha/kg, R]
        tsumdepth (Optional[float]): Life span under leaves under optimum conditions [0..366 d, R]
        tsumtime (Optional[float]): Lower threshold temperature for ageing of leaves [-10..30 degree C, R]
    """
    swtsum: Literal[0, 1, 2]
    tsumtemp: Optional[float] = None
    tsumdepth: Optional[float] = None
    tsumtime: Optional[float] = None

CropDevelopmentSettingsWOFOST

Bases: CropDevelopmentSettings

Additional settings for the

Warning

The validation for this class is not complete. Also check the Optional attributes!

Note

Use serialization_alias to change the parameter names who are different between WOFOST and SWAP.

Attributes:

Name Type Description
idsl Literal[0, 1, 2]
dtsmtb Arrays
dlo Optional[float]
dlc Optional[float]
vernsat Optional[float]
vernbase Optional[float]
verndvs Optional[float]
verntb Optional[Arrays]
tdwi float
laiem float
rgrlai float
spa float
ssa float
span float
slatb Arrays
eff float
amaxtb Arrays
tmpftb Arrays
tmnftb Arrays
cvo float
cvl float
cvr float
cvs float
q10 float
rml float
rmo float
rmr float
rms float
rfsetb Arrays
frtb Arrays
fltb Arrays
fstb Arrays
fotb Arrays
perdl float
rdrrtb Arrays
rdrstb Arrays
Source code in pyswap/plant/crpfile.py
class CropDevelopmentSettingsWOFOST(CropDevelopmentSettings):
    """Additional settings for the 

    Warning:
        The validation for this class is not complete. Also check the Optional attributes!

    Note:
        Use serialization_alias to change the parameter names who are different between WOFOST and SWAP.

    Attributes:
        idsl (Literal[0, 1, 2]):
        dtsmtb (Arrays):
        dlo (Optional[float]):
        dlc (Optional[float]):
        vernsat (Optional[float]):
        vernbase (Optional[float]):
        verndvs (Optional[float]):
        verntb (Optional[Arrays]):
        tdwi (float):
        laiem (float):
        rgrlai (float):
        spa (float):
        ssa (float):
        span (float):
        slatb (Arrays):
        eff (float):
        amaxtb (Arrays):
        tmpftb (Arrays):
        tmnftb (Arrays):
        cvo (float):
        cvl (float):
        cvr (float):
        cvs (float):
        q10 (float):
        rml (float):
        rmo (float):
        rmr (float):
        rms (float):
        rfsetb (Arrays):
        frtb (Arrays):
        fltb (Arrays):
        fstb (Arrays):
        fotb (Arrays):
        perdl (float):
        rdrrtb (Arrays):
        rdrstb (Arrays):
    """
    idsl: Optional[Literal[0, 1, 2]] = None  # for grass at least
    dtsmtb: Optional[Arrays] = None   # for grass at least
    dlo: Optional[float] = Field(default=None, ge=0.0, le=24.0)
    dlc: Optional[float] = Field(default=None, ge=0.0, le=24.0)
    vernsat: Optional[float] = Field(default=None, ge=0.0, le=100.0)
    vernbase: Optional[float] = Field(default=None, ge=0.0, le=100.0)
    verndvs: Optional[float] = Field(default=None, ge=0.0, le=0.3)
    verntb: Optional[Arrays] = None
    tdwi: float = Field(ge=0.0, le=10_000)
    laiem: float = Field(ge=0.0, le=10)
    rgrlai: float = Field(**UNITRANGE)
    spa: Optional[float] = Field(**UNITRANGE, default=None)
    ssa: float = Field(**UNITRANGE)
    span: float = Field(**YEARRANGE)
    slatb: Arrays
    eff:  float = Field(ge=0.0, le=10.0)
    amaxtb: Arrays
    tmpftb: Arrays
    tmnftb: Arrays
    cvo: Optional[float] = Field(
        **UNITRANGE, default=None)  # for grass at least
    cvl: float = Field(**UNITRANGE)
    cvr: float = Field(**UNITRANGE)
    cvs: float = Field(**UNITRANGE)
    q10: float = Field(ge=0.0, le=5.0)
    rml: float = Field(**UNITRANGE)
    rmo: Optional[float] = Field(
        **UNITRANGE, default=None)  # for grass at least
    rmr: float = Field(**UNITRANGE)
    rms: float = Field(**UNITRANGE)
    rfsetb: Arrays
    frtb: Arrays
    fltb: Arrays
    fstb: Arrays
    fotb: Optional[Arrays] = None  # for grass at least
    perdl: float = Field(ge=0.0, le=3.0)
    rdrrtb: Arrays
    rdrstb: Arrays

    @model_validator(mode='after')
    def _validate_crop_wofost(self) -> Self:
        # validation of the base class
        if self.swcf == 1:
            assert self.table_dvs_cf is not None, "table_dvs_cf is required when swcf is 1."
        elif self.swcf == 2:
            assert self.table_dvs_ch is not None, "table_dvs_ch is required when swcf is 2."
            assert self.albedo is not None, "albedo is required when swcf is 2."
            assert self.rsc is not None, "rsc is required when swcf is 2."
            assert self.rsw is not None, "rsw is required when swcf is 2."
        if self.swrd == 1:
            assert self.rdtb is not None, "rdtb is required when swrd is 1."
        elif self.swrd == 2:
            assert self.rdi is not None, "rdi is required when swrd is 2."
            assert self.rri is not None, "rri is required when swrd is 2."
            assert self.rdc is not None, "rdc is required when swrd is 2."
            assert self.swdmi2rd is not None, "swdmi2rd is required when swrd is 2."
        elif self.swrd == 3:
            assert self.rlwtb is not None, "rlwtb is required when swrd is 3."
            assert self.wrtmax is not None, "wrtmax is required when swrd is 3."
        # validation specific to the WOFOST crop development settings
        if self.idsl in [1, 2]:
            assert self.dlc is not None, "dlc is required when idsl is either 1 or 2."
            assert self.dlo is not None, "dlo is required when idsl is either 1 or 2."
        elif self.idsl == 2:
            assert self.vernsat is not None, "vernsat is required when idsl is 2."
            assert self.vernbase is not None, "vernbase is required when idsl is 2."
            assert self.verndvs is not None, "verndvs is required when idsl is 2."
            assert self.verntb is not None, "verntb is required when idsl is 2."

        return self

CropFile

Bases: PySWAPBaseModel

Main class for the .crp file.

This class collects all the settings for the crop file. Currently the types of the attributes are set to Any because the validation is not yet implemented.

Attributes:

Name Type Description
name str

Name of the crop

path Optional[str]

Path to the .crp file

prep Optional[Preparation]

Preparation settings

oxygenstress Optional[OxygenStress]

Oxygen stress settings

droughtstress Optional[DroughtStress]

Drought stress settings

saltstress Optional[SaltStress]

Salt stress settings

compensaterwu Optional[CompensateRWUStress]

Compensate root water uptake stress settings

interception Optional[Interception]

Interception settings

scheduledirrigation Optional[ScheduledIrrigation]

Scheduled irrigation settings

grassland_management Optional[GrasslandManagement]

Grassland management settings

Source code in pyswap/plant/crpfile.py
class CropFile(PySWAPBaseModel):
    """Main class for the .crp file.

    This class collects all the settings for the crop file. Currently the types of the 
    attributes are set to Any because the validation is not yet implemented.

    Attributes:
        name (str): Name of the crop
        path (Optional[str]): Path to the .crp file
        prep (Optional[Preparation]): Preparation settings
        cropdev_settings (Optional[CropDevelopmentSettings | 
            CropDevelopmentSettingsFixed | 
            CropDevelopmentSettingsWOFOST]): Crop development settings
        oxygenstress (Optional[OxygenStress]): Oxygen stress settings
        droughtstress (Optional[DroughtStress]): Drought stress settings
        saltstress (Optional[SaltStress]): Salt stress settings
        compensaterwu (Optional[CompensateRWUStress]): Compensate root water uptake stress settings
        interception (Optional[Interception]): Interception settings
        scheduledirrigation (Optional[ScheduledIrrigation]): Scheduled irrigation settings
        grassland_management (Optional[GrasslandManagement]): Grassland management settings
    """

    name: str = Field(exclude=True)
    path: Optional[str] = None
    prep: Optional[Preparation] = None
    cropdev_settings: Optional[CropDevelopmentSettings |
                               CropDevelopmentSettingsFixed |
                               CropDevelopmentSettingsWOFOST |
                               CropDevelopmentSettingsGrass] = None
    oxygenstress: Optional[OxygenStress] = None
    droughtstress: Optional[DroughtStress] = None
    saltstress: Optional[SaltStress] = SaltStress(swsalinity=0)
    compensaterwu: Optional[CompensateRWUStress] = CompensateRWUStress(
        swcompensate=0)
    interception: Optional[Interception] = None
    scheduledirrigation: Optional[ScheduledIrrigation] = ScheduledIrrigation(
        schedule=0)
    grasslandmanagement: Optional[GrasslandManagement] = None
    co2correction: Optional[CO2Correction] = None

    @computed_field(return_type=str)
    def content(self):
        if self.path:
            return open_file(self.path)
        else:
            return self._concat_sections()

DroughtStress

Bases: PySWAPBaseModel

Drought stress settings for .crp file.

Attributes:

Name Type Description
swdrought Literal[1, 2]

Switch for drought stress

  • 1 - Drought stress according to Feddes et al. (1978)
  • 2 - rought stress according to De Jong van Lier et al. (2008)
swjarvis Optional[Literal[0, 1, 2, 3, 4]]

DEPRECATED Switch for Jarvis model for water uptake reduction

alphcrit Optional[float]

Optional[float] = DEPRECATED Critical stress index (Jarvis, 1989) for compensation of root water uptake [0.2..1 -, R]

hlim3h Optional[float]

Pressure head below which water uptake reduction starts at high Tpot

hlim3l Optional[float]

Pressure head below which water uptake reduction starts at low Tpot

hlim4 Optional[float]

No water extraction at lower soil water pressure heads

adcrh Optional[float]

Level of high atmospheric demand, corresponding to HLIM3H

adcrl Optional[float]

Level of low atmospheric demand, corresponding to HLIM3L

wiltpoint Optional[float]

Minimum pressure head in leaves

kstem Optional[float]

Hydraulic conductance between leaf and root xylem

rxylem Optional[float]

Xylem radius

rootradius Optional[float]

Root radius

kroot Optional[float]

Radial hydraulic conductivity of root tissue

rootcoefa Optional[float]

Defines relative distance between roots at which mean soil water content occurs

swhydrlift Optional[Literal[0, 1]]

Switch for possibility hydraulic lift in root system

rooteff Optional[float]

Root system efficiency factor

stephr Optional[float]

Step between values of hroot and hxylem in iteration cycle

criterhr Optional[float]

Maximum difference of Hroot between iterations; convergence criterium

taccur Optional[float]

Maximum absolute difference between simulated and calculated potential transpiration rate

Source code in pyswap/plant/crpfile.py
class DroughtStress(PySWAPBaseModel):
    """Drought stress settings for .crp file.

    Attributes:
        swdrought (Literal[1, 2]): Switch for drought stress

            * 1 - Drought stress according to Feddes et al. (1978)
            * 2 - rought stress according to De Jong van Lier et al. (2008)

        swjarvis (Optional[Literal[0, 1, 2, 3, 4]]): _DEPRECATED_ Switch for Jarvis model for water uptake reduction
        alphcrit: Optional[float] = _DEPRECATED_ Critical stress index (Jarvis, 1989) for compensation of root water uptake [0.2..1 -, R]
        hlim3h (Optional[float]): Pressure head below which water uptake reduction starts at high Tpot
        hlim3l (Optional[float]): Pressure head below which water uptake reduction starts at low Tpot
        hlim4 (Optional[float]): No water extraction at lower soil water pressure heads
        adcrh (Optional[float]): Level of high atmospheric demand, corresponding to HLIM3H
        adcrl (Optional[float]): Level of low atmospheric demand, corresponding to HLIM3L
        wiltpoint (Optional[float]): Minimum pressure head in leaves
        kstem (Optional[float]): Hydraulic conductance between leaf and root xylem
        rxylem (Optional[float]): Xylem radius
        rootradius (Optional[float]): Root radius
        kroot (Optional[float]): Radial hydraulic conductivity of root tissue
        rootcoefa (Optional[float]): Defines relative distance between roots at which mean soil water content occurs
        swhydrlift (Optional[Literal[0, 1]]): Switch for possibility hydraulic lift in root system
        rooteff (Optional[float]): Root system efficiency factor
        stephr (Optional[float]): Step between values of hroot and hxylem in iteration cycle
        criterhr (Optional[float]): Maximum difference of Hroot between iterations; convergence criterium
        taccur (Optional[float]): Maximum absolute difference between simulated and calculated potential transpiration rate
    """
    swdrought: Literal[1, 2]
    swjarvis: Optional[Literal[0, 1, 2, 3, 4]] = None
    alphcrit: Optional[float] = Field(default=None, ge=0.2, le=1.0)
    hlim3h: Optional[float] = Field(default=None, ge=-1.0e4, le=100.0)
    hlim3l: Optional[float] = Field(default=None, ge=-1.0e4, le=100.0)
    hlim4: Optional[float] = Field(default=None, ge=-1.6e4, le=100.0)
    adcrh: Optional[float] = Field(default=None, ge=0.0, le=5.0)
    adcrl: Optional[float] = Field(default=None, ge=0.0, le=5.0)
    wiltpoint: Optional[float] = Field(default=None, ge=-1.0e8, le=-1.0e2)
    kstem: Optional[float] = Field(default=None, ge=1.0e-10, le=10.0)
    rxylem: Optional[float] = Field(default=None, ge=1.0e-4, le=1.0)
    rootradius: Optional[float] = Field(default=None, ge=1.0e-4, le=1.0)
    kroot: Optional[float] = Field(default=None, ge=1.0e-10, le=1.0e10)
    rootcoefa: Optional[float] = Field(default=None, **UNITRANGE)
    swhydrlift: Optional[Literal[0, 1]] = None
    rooteff: Optional[float] = Field(default=None, **UNITRANGE)
    stephr: Optional[float] = Field(default=None, ge=0.0, le=10.0)
    criterhr: Optional[float] = Field(default=None, ge=0.0, le=10.0)
    taccur: Optional[float] = Field(default=None, ge=1.0e-5, le=1.0e-2)

    @model_validator(mode='after')
    def _validate_prepartion(self) -> Self:
        if self.swdrought == 1:
            assert self.hlim3h is not None, "hlim3h is required when swdrought is 1."
            assert self.hlim3l is not None, "hlim3l is required when swdrought is 1."
            assert self.hlim4 is not None, "hlim4 is required when swdrought is 1."
            assert self.adcrh is not None, "adcrh is required when swdrought is 1."
            assert self.adcrl is not None, "adcrl is required when swdrought is 1."
        if self.swdrought == 2:
            assert self.wiltpoint is not None, "wiltpoint is required when swdrought is 2."
            assert self.kstem is not None, "kstem is required when swdrought is 2."
            assert self.rxylem is not None, "rxylem is required when swdrought is 2."
            assert self.rootradius is not None, "rootradius is required when swdrought is 2."
            assert self.kroot is not None, "kroot is required when swdrought is 2."
            assert self.rootcoefa is not None, "rootcoefa is required when swdrought is 2."
            assert self.swhydrlift is not None, "swhydrlift is required when swdrought is 2."
            assert self.rooteff is not None, "rooteff is required when swdrought is 2."
            assert self.stephr is not None, "stephr is required when swdrought is 2."
            assert self.criterhr is not None, "criterhr is required when swdrought is 2."
            assert self.taccur is not None, "taccur is required when swdrought is 2."

        return self

GrasslandManagement

Bases: PySWAPBaseModel

Settings specific to the dynamic grass growth module.

Warning

Validation still required.

Attributes:

Name Type Description
seqgrazmow IntList

sequence of periods with different practices within calender year. Available options:

  • 1 - Grazing
  • 2 - Mowing
  • 3 - Grazing with dewooling
swharvest Literal[1, 2]

Switch for timing harvest, either for mowing or grazing

  • 1 - Use dry matter threshold
  • 2 - Use fixed dates
dateharvest Optional[DateList]

harvest dates (maximum 999)

swdmgrz Optional[Literal[1, 2]]

Switch for dry matter threshold to trigger harvest by grazing

  • 1 - Use fixed threshold
  • 2 - Use flexible threshold
dmgrazing Optional[Arrays]

Minimum dry matter amount for cattle to enter the field [0..1d6 kg DM/ha, R]

dmgrztb Optional[int]

List threshold of above ground dry matter [0..1d6 kg DM/ha, R] to trigger grazing as function of daynumber [1..366 d, R]

maxdaygrz Optional[int]

Maximum growing period after harvest [1..366 -, I]

swlossgrz Optional[Literal[0, 1]]

Switch for losses due to insufficient pressure head during grazing

  • 0 - No loss
  • 1 - Losses due to treading
tagprest Optional[float]

Minimum amount of above ground DM after grazing [0..1d6 kg DM/ha, R]

dewrest Optional[float]

Remaining yield above ground after dewooling event [0..1d6 kg DM/ha, R]

table_lsda Optional[Table]

Actual livestock density of each grazing period

table_lsdb Optional[Table]

Relation between livestock density, number of grazing days and dry matter uptake

swdmmow Optional[int]

Switch for dry matter threshold to trigger harvest by mowing

  • 1 - Use fixed threshold
  • 2 - Use flexible threshold
dmharvest Optional[float]

Threshold of above ground dry matter to trigger mowing [0..1d6 kg DM/ha, R]

daylastharvest Optional[int]

Last calendar day on which mowing may occur [1..366 -, I]

dmlastharvest Optional[float]

Minimum above ground dry matter for mowing on last date [0..1d6 kg DM/ha, R]

dmmowtb Optional[int]

Dry matter mowing threshold

maxdaymow Optional[int]

Maximum growing period after harvest [1..366 -, I]

swlossmow Optional[int]

Switch for losses due to insufficient pressure head during mowing

  • 0 - No loss
  • 1 - Losses due to treading
mowrest Optional[float]

Remaining yield above ground after mowing event [0..1d6 kg DM/ha, R]

table_dmmowdelay Optional[Optional[Table]]

Relation between dry matter harvest [0..1d6 kg/ha, R] and days of delay in regrowth [0..366 d, I] after mowing

swpotrelmf int

Switch for calculation of potential yield

  • 1 - theoretical potential yield
  • 2 - attainable yield
relmf float

Relative management factor to reduce theoretical potential yield to attainable yield [0..1 -, R]

Source code in pyswap/plant/crpfile.py
class GrasslandManagement(PySWAPBaseModel):
    """Settings specific to the dynamic grass growth module.

    !!! warning

        Validation still required.

    Attributes:
        seqgrazmow (IntList): sequence of periods with different practices within calender year. Available options:

            * 1 - Grazing
            * 2 - Mowing
            * 3 - Grazing with dewooling

        swharvest (Literal[1, 2]): Switch for timing harvest, either for mowing or grazing

            * 1 - Use dry matter threshold
            * 2 - Use fixed dates

        dateharvest Optional[(DateList)]: harvest dates (maximum 999)
        swdmgrz Optional[(Literal[1, 2])]: Switch for dry matter threshold to trigger harvest by grazing

            * 1 - Use fixed threshold
            * 2 - Use flexible threshold

        dmgrazing Optional[(Arrays)]: Minimum dry matter amount for cattle to enter the field [0..1d6 kg DM/ha, R]
        dmgrztb Optional[(int)]: List threshold of above ground dry matter [0..1d6 kg DM/ha, R] to trigger grazing as function of daynumber [1..366 d, R]
        maxdaygrz Optional[(int)]: Maximum growing period after harvest [1..366 -, I]
        swlossgrz Optional[(Literal[0, 1])]: Switch for losses due to insufficient pressure head during grazing

            * 0 - No loss
            * 1 - Losses due to treading

        tagprest Optional[(float)]: Minimum amount of above ground DM after grazing [0..1d6 kg DM/ha, R]
        dewrest Optional[(float)]: Remaining yield above ground after dewooling event [0..1d6 kg DM/ha, R]
        table_lsda (Optional[Table]): Actual livestock density of each grazing period
        table_lsdb (Optional[Table]): Relation between livestock density, number of grazing days and dry matter uptake
        swdmmow Optional[(int)]: Switch for dry matter threshold to trigger harvest by mowing

            * 1 - Use fixed threshold
            * 2 - Use flexible threshold

        dmharvest Optional[(float)]: Threshold of above ground dry matter to trigger mowing [0..1d6 kg DM/ha, R]
        daylastharvest Optional[(int)]: Last calendar day on which mowing may occur [1..366 -, I]
        dmlastharvest Optional[(float)]: Minimum above ground dry matter for mowing on last date [0..1d6 kg DM/ha, R]
        dmmowtb Optional[(int)]: Dry matter mowing threshold
        maxdaymow Optional[(int)]:Maximum growing period after harvest [1..366 -, I]
        swlossmow Optional[(int)]: Switch for losses due to insufficient pressure head during mowing

            * 0 - No loss
            * 1 - Losses due to treading

        mowrest Optional[(float)]: Remaining yield above ground after mowing event [0..1d6 kg DM/ha, R]
        table_dmmowdelay Optional[(Optional[Table])]: Relation between dry matter harvest [0..1d6 kg/ha, R] and days of delay in regrowth [0..366 d, I] after mowing
        swpotrelmf (int): Switch for calculation of potential yield

            * 1 - theoretical potential yield
            * 2 - attainable yield

        relmf (float): Relative management factor to reduce theoretical potential yield to attainable yield [0..1 -, R]
    """

    seqgrazmow: IntList
    swharvest: Literal[1, 2]
    dateharvest: Optional[DateList] = None
    swdmgrz: Optional[Literal[1, 2]] = None
    dmgrazing: Optional[Arrays] = None
    dmgrztb: Optional[Arrays] = None
    maxdaygrz: Optional[int] = None
    swlossgrz: Optional[Literal[0, 1]] = None
    tagprest: Optional[float] = None
    dewrest: Optional[float] = None
    table_lsda: Optional[Table] = None
    table_lsdb: Optional[Table] = None
    swdmmow: Optional[int] = None
    dmharvest: Optional[float] = None
    daylastharvest: Optional[int] = None
    dmlastharvest: Optional[float] = None
    dmmowtb: Optional[Arrays] = None
    maxdaymow: Optional[int] = None
    swlossmow: Optional[int] = None
    mowrest: Optional[float] = None
    table_dmmowdelay: Optional[Table] = None
    swpotrelmf: int
    relmf: float

Interception

Bases: PySWAPBaseModel

Interception settings for .crp file.

Attributes:

Name Type Description
swinter Literal[0, 1, 2]

Switch for rainfall interception method

  • 0 - No interception
  • 1 - Agricultural crops (Von Hoyningen-Hune and Braden)
  • 2 - Trees and forests (Gash)
cofab Optional[float]

Interception coefficient, corresponding to maximum interception amount

table_intertb Optional[Table]

table with the following columns as a function of time T:

  • PFREE - Free throughfall coefficient
  • PSTEM - Stemflow coefficient
  • SCANOPY - Canopy storage coefficient
  • AVPREC = Average rainfall intensity
  • AVEVAP = Average evaporation intensity during rainfall from a wet canopy
Source code in pyswap/plant/crpfile.py
class Interception(PySWAPBaseModel):
    """Interception settings for .crp file.

    Attributes:
        swinter (Literal[0, 1, 2]): Switch for rainfall interception method

            * 0 - No interception
            * 1 - Agricultural crops (Von Hoyningen-Hune and Braden)
            * 2 - Trees and forests (Gash)

        cofab (Optional[float]): Interception coefficient, corresponding to maximum interception amount
        table_intertb (Optional[Table]): table with the following columns as a function of time T:

            * PFREE - Free throughfall coefficient
            * PSTEM - Stemflow coefficient
            * SCANOPY - Canopy storage coefficient
            * AVPREC = Average rainfall intensity
            * AVEVAP = Average evaporation intensity during rainfall from a wet canopy
    """
    swinter: Literal[0, 1, 2]
    cofab: Optional[float] = Field(default=None, **UNITRANGE)
    table_intertb: Optional[Table] = None

    @model_validator(mode='after')
    def _validate_prepartion(self) -> Self:
        if self.swinter == 1:
            assert self.cofab is not None, "cofab is required when swinter is 1."
        elif self.swinter == 1:
            assert self.table_intertb is not None, "table_intertb is required when swinter is 2."

        return self

OxygenStress

Bases: PySWAPBaseModel

Oxygen stress settings for .crp file.

Attributes:

Name Type Description
swoxygen Literal[0, 1, 2]

Switch for oxygen stress

  • 0 - No oxygen stress
  • 1 - Oxygen stress according to Feddes et al. (1978)
  • 2 - Oxygen stress according to Bartholomeus et al. (2008)
swoxygentype Optional[Literal[1, 2]]

switch for physical processes or repro. functions to calculate oxygen stress

  • 1 - physical processes
  • 2 - reproduction functions
swwrtnonox Literal[0, 1]

Switch for checking aerobic conditions in root zone to stop root(zone) development

aeratecrit Optional[float]

Threshold to stop root extension in case of oxygenstress; 0.0 maximum oxygen stress

hlim1 Optional[float]

No water extraction at higher pressure heads

hlim2u Optional[float]

H below which optimum water extr. starts for top layer

hlim2l Optional[float]

H below which optimum water extr. starts for sub layer

q10_microbial Optional[float]

Relative increase in microbial respiration at temperature increase of 10 C

specific_resp_humus Optional[float]

Respiration rate of humus at 25 C

srl Optional[float]

Specific root length

swrootradius Optional[Literal[1, 2]]

Switch for calculation of root radius

  • 1 - Calculate root radius
  • 2 - Root radius given in an input file
dry_mat_cont_roots Optional[float]

Dry matter content of roots

air_filled_root_por Optional[float]

Air filled root porosity

spec_weight_root_tissue Optional[float]

Specific weight of non-airfilled root tissue

var_a Optional[float]

Variance of root radius

root_radiuso2 Optional[float]

Root radius for oxygen stress module

q10_root Optional[float]

Relative increase in root respiration at temperature increase of 10 oC

f_senes Optional[float]

Reduction factor for senescence, used for maintenance respiration

c_mroot Optional[float]

Maintenance coefficient of root

table_max_resp_factor Optional[Table]

Ratio root total respiration / maintenance respiration as a function of development stage

table_dvs_w_root_ss Optional[Table]

List dry weight of roots at soil surface as a function of development stage

TODO: Find a way to validate the parameters that are required when the croptype=1 and swoxygen=2 (currently I cannot access the croptype parameter)

Source code in pyswap/plant/crpfile.py
class OxygenStress(PySWAPBaseModel):
    """Oxygen stress settings for .crp file.

    Attributes:
        swoxygen (Literal[0, 1, 2]): Switch for oxygen stress

            * 0 - No oxygen stress
            * 1 - Oxygen stress according to Feddes et al. (1978)
            * 2 - Oxygen stress according to Bartholomeus et al. (2008)

        swoxygentype (Optional[Literal[1, 2]]): switch for physical processes or repro. functions to calculate oxygen stress

            * 1 - physical processes
            * 2 - reproduction functions

        swwrtnonox (Literal[0, 1]): Switch for checking aerobic conditions in root zone to stop root(zone) development
        aeratecrit (Optional[float]): Threshold to stop root extension in case of oxygenstress; 0.0 maximum oxygen stress
        hlim1 (Optional[float]): No water extraction at higher pressure heads
        hlim2u (Optional[float]): H below which optimum water extr. starts for top layer
        hlim2l (Optional[float]): H below which optimum water extr. starts for sub layer
        q10_microbial (Optional[float]): Relative increase in microbial respiration at temperature increase of 10 C
        specific_resp_humus (Optional[float]): Respiration rate of humus at 25 C
        srl (Optional[float]): Specific root length
        swrootradius (Optional[Literal[1, 2]]): Switch for calculation of root radius

            * 1 - Calculate root radius
            * 2 - Root radius given in an input file

        dry_mat_cont_roots (Optional[float]): Dry matter content of roots
        air_filled_root_por (Optional[float]): Air filled root porosity
        spec_weight_root_tissue (Optional[float]): Specific weight of non-airfilled root tissue
        var_a (Optional[float]): Variance of root radius
        root_radiuso2 (Optional[float]): Root radius for oxygen stress module
        q10_root (Optional[float]): Relative increase in root respiration at temperature increase of 10 oC
        f_senes (Optional[float]): Reduction factor for senescence, used for maintenance respiration
        c_mroot (Optional[float]): Maintenance coefficient of root
        table_max_resp_factor (Optional[Table]): Ratio root total respiration / maintenance respiration as a function of development stage
        table_dvs_w_root_ss (Optional[Table]): List dry weight of roots at soil surface as a function of development stage

    TODO: Find a way to validate the parameters that are required when the
    croptype=1 and swoxygen=2 (currently I cannot access the croptype parameter)
    """

    swoxygen: Literal[0, 1, 2]
    swwrtnonox: Literal[0, 1]
    swoxygentype: Optional[Literal[1, 2]] = None
    aeratecrit: Optional[float] = Field(default=None, ge=0.0001, le=1.0)
    hlim1: Optional[float] = Field(default=None, ge=-100.0, le=100.0)
    hlim2u: Optional[float] = Field(default=None, ge=-1000.0, le=100.0)
    hlim2l: Optional[float] = Field(default=None, ge=-1000.0, le=100.0)
    q10_microbial: Optional[float] = Field(default=None, ge=1.0, le=4.0)
    specific_resp_humus: Optional[float] = Field(default=None, **UNITRANGE)
    srl: Optional[float] = Field(default=None, ge=0.0, le=1.0e10)
    swrootradius: Optional[Literal[1, 2]] = None
    dry_mat_cont_roots: Optional[float] = Field(default=None, **UNITRANGE)
    air_filled_root_por: Optional[float] = Field(default=None, **UNITRANGE)
    spec_weight_root_tissue: Optional[float] = Field(
        default=None, ge=0.0, le=1.0e5)
    var_a: Optional[float] = Field(default=None, **UNITRANGE)
    root_radiuso2: Optional[float] = Field(default=None, ge=1.0e-6, le=0.1)
    q10_root: Optional[float] = Field(default=None, ge=1.0, le=4.0)
    f_senes: Optional[float] = Field(default=None, **UNITRANGE)
    c_mroot: Optional[float] = Field(default=None, **UNITRANGE)
    mrftb: Optional[Arrays] = None
    wrtb: Optional[Arrays] = None

    @model_validator(mode='after')
    def _validate_oxygen(self) -> Self:
        if self.swoxygen == 1:
            assert self.hlim1 is not None, "hlim1 is required when swoxygen is 1."
            assert self.hlim2u is not None, "hlim2u is required when swoxygen is 1."
            assert self.hlim2l is not None, "hlim2l is required when swoxygen is 1."
        elif self.swoxygen == 2:
            assert self.q10_microbial is not None, "q10_microbial is required when swoxygen is 2."
            assert self.specific_resp_humus is not None, "specific_resp_humus is required when swoxygen is 2."
            assert self.srl is not None, "srl is required when swoxygen is 2."
            assert self.swrootradius is not None, "swrootradius is required when swoxygen is 2."
            if self.swrootradius == 1:
                assert self.dry_mat_cont_roots is not None, "dry_mat_cont_roots is required when swrootradius is 1."
                assert self.air_filled_root_por is not None, "air_filled_root_por is required when swrootradius is 1."
                assert self.spec_weight_root_tissue is not None, "spec_weight_root_tissue is required when swrootradius is 1."
                assert self.var_a is not None, "var_a is required when swrootradius is 1."
            elif self.swrootradius == 2:
                assert self.root_radiuso2 is not None, "root_radiuso2 is required when swrootradius is 2."
        if self.swwrtnonox == 1:
            assert self.aeratecrit is not None, "aeratecrit is required when swwrtnonox is 1."

        return self

Preparation

Bases: PySWAPBaseModel

Preparation, sowing and germination settings for .crp file.

Attributes:

Name Type Description
swprep Literal[0, 1]

Switch for preparation

swsow Literal[0, 1]

Switch for sowing

swgerm Literal[0, 1, 2]

Switch for germination

  • 0 - No germination
  • 1 - Germination with temperature sum
  • 2 - Germination with temperature sum and water potential
swharv Literal[0, 1]

Switch for harvest

  • 0 - Timing of harvest depends on end of growing period (CROPEND)
  • 1 - Timing of harvest depends on development stage (DVSEND)
dvsend Optional[float]

Development stage at harvest

zprep Optional[float]

Z-level for monitoring work-ability for the crop

hprep Optional[float]

Maximum pressure head during preparation

maxprepdelay Optional[int]

Maximum delay of preparation from start of growing season

zsow Optional[float]

Z-level for monitoring work-ability for the crop

hsow Optional[float]

Maximum pressure head during sowing

ztempsow Optional[float]

Z-level for monitoring temperature for sowing

tempsow Optional[float]

Soil temperature needed for sowing

maxsowdelay Optional[int]

Maximum delay of sowing from start of growing season

tsumemeopt Optional[float]

Temperature sum needed for crop emergence

tbasem Optional[float]

Minimum temperature, used for germination trajectory

teffmx Optional[float]

Maximum temperature, used for germination trajectory

hdrygerm Optional[float]

Pressure head rootzone for dry germination trajectory

hwetgerm Optional[float]

Pressure head rootzone for wet germination trajectory

zgerm Optional[float]

Z-level for monitoring average pressure head

agerm Optional[float]

A-coefficient Eq. 24/25 Feddes & Van Wijk

Source code in pyswap/plant/crpfile.py
class Preparation(PySWAPBaseModel):
    """Preparation, sowing and germination settings for .crp file.

    Attributes:
        swprep (Literal[0, 1]): Switch for preparation
        swsow (Literal[0, 1]): Switch for sowing
        swgerm (Literal[0, 1, 2]): Switch for germination

            * 0 - No germination
            * 1 - Germination with temperature sum
            * 2 - Germination with temperature sum and water potential

        swharv (Literal[0, 1]): Switch for harvest

            * 0 - Timing of harvest depends on end of growing period (CROPEND)
            * 1 - Timing of harvest depends on development stage (DVSEND)

        dvsend (Optional[float]): Development stage at harvest
        zprep (Optional[float]): Z-level for monitoring work-ability for the crop
        hprep (Optional[float]): Maximum pressure head during preparation
        maxprepdelay (Optional[int]): Maximum delay of preparation from start of growing season
        zsow (Optional[float]): Z-level for monitoring work-ability for the crop
        hsow (Optional[float]): Maximum pressure head during sowing
        ztempsow (Optional[float]): Z-level for monitoring temperature for sowing
        tempsow (Optional[float]): Soil temperature needed for sowing
        maxsowdelay (Optional[int]): Maximum delay of sowing from start of growing season
        tsumemeopt (Optional[float]): Temperature sum needed for crop emergence
        tbasem (Optional[float]): Minimum temperature, used for germination trajectory
        teffmx (Optional[float]): Maximum temperature, used for germination trajectory
        hdrygerm (Optional[float]): Pressure head rootzone for dry germination trajectory
        hwetgerm (Optional[float]): Pressure head rootzone for wet germination trajectory
        zgerm (Optional[float]): Z-level for monitoring average pressure head
        agerm (Optional[float]): A-coefficient Eq. 24/25 Feddes & Van Wijk
    """

    swprep: Literal[0, 1]
    swsow: Literal[0, 1]
    swgerm: Literal[0, 1, 2]
    swharv: Literal[0, 1]
    dvsend: Optional[float] = Field(default=None, ge=0.0, le=3.0)
    zprep: Optional[float] = Field(default=None, ge=-100.0, le=0.0)
    hprep: Optional[float] = Field(default=None, ge=-200.0, le=0.0)
    maxprepdelay: Optional[int] = Field(default=None, ge=1, le=366)
    zsow: Optional[float] = Field(default=None, ge=-100.0, le=0.0)
    hsow: Optional[float] = Field(default=None, ge=-200.0, le=0.0)
    ztempsow: Optional[float] = Field(default=None,  ge=-100.0, le=0.0)
    tempsow: Optional[float] = Field(default=None, ge=0.0, le=30.0)
    maxsowdelay: Optional[int] = Field(default=None, ge=1, le=366)
    tsumemeopt: Optional[float] = Field(default=None, ge=0.0, le=1000.0)
    tbasem: Optional[float] = Field(default=None, ge=0.0, le=1000.0)
    teffmx: Optional[float] = Field(default=None, ge=0.0, le=1000.0)
    hdrygerm: Optional[float] = Field(default=None, ge=-1000.0, le=1000.0)
    hwetgerm: Optional[float] = Field(default=None, ge=-100.0, le=1000.0)
    zgerm: Optional[float] = Field(default=None, ge=-100.0, le=1000.0)
    agerm: Optional[float] = Field(default=None, ge=0.0, le=1000.0)

    @model_validator(mode='after')
    def _validate_prepartion(self) -> Self:
        if self.swprep == 1:
            assert self.zprep is not None, "zprep is required when swprep is 1."
            assert self.hprep is not None, "hprep is required when swprep is 1."
            assert self.maxprepdelay is not None, "maxprepdelay is required when swprep is 1."

        if self.swsow == 1:
            assert self.zsow is not None, "zsow is required when swsow is 1."
            assert self.hsow is not None, "hsow is required when swsow is 1."
            assert self.ztempsow is not None, "ztempsow is required when swsow is 1."
            assert self.tempsow is not None, "tempsow is required when swsow is 1."
            assert self.maxsowdelay is not None, "maxsowdelay is required when swsow is 1."

        if self.swgerm in (1, 2):
            assert self.tsumemeopt is not None, "tsumemeopt is required when swgerm is 1 or 2."
            assert self.tbasem is not None, "tbasem is required when swgerm is 1 or 2."
            assert self.teffmx is not None, "teffmx is required when swgerm is 1 or 2."
        elif self.swgerm == 2:
            assert self.hdrygerm is not None, "hdrygerm is required when swgerm is 2."
            assert self.hwetgerm is not None, "hwetgerm is required when swgerm is 2."
            assert self.zgerm is not None, "zgerm is required when swgerm is 2."
            assert self.agerm is not None, "agerm is required when swgerm is 2."

        return self

SaltStress

Bases: PySWAPBaseModel

Salt stress settings for .crp file.

Attributes:

Name Type Description
swsalinity Literal[0, 1, 2]

Switch for salt stress

  • 0 - No salt stress
  • 1 - Maas and Hoffman reduction function
  • 2 - Use osmotic head
saltmax Optional[float]

Threshold salt concentration in soil water

saltslope Optional[float]

Decline of root water uptake above threshold

salthead Optional[float]

Conversion factor salt concentration (mg/cm3) into osmotic head (cm)

Source code in pyswap/plant/crpfile.py
class SaltStress(PySWAPBaseModel):
    """Salt stress settings for .crp file.

    Attributes:
        swsalinity (Literal[0, 1, 2]): Switch for salt stress

            * 0 - No salt stress
            * 1 - Maas and Hoffman reduction function
            * 2 - Use osmotic head

        saltmax (Optional[float]): Threshold salt concentration in soil water
        saltslope (Optional[float]): Decline of root water uptake above threshold
        salthead (Optional[float]): Conversion factor salt concentration (mg/cm3) into osmotic head (cm)
    """
    swsalinity: Literal[0, 1, 2]
    saltmax: Optional[float] = Field(default=None, ge=0.0, le=100.0)
    saltslope: Optional[float] = Field(default=None, **UNITRANGE)
    salthead: Optional[float] = Field(default=None, ge=0.0, le=1000.0)

    @model_validator(mode='after')
    def _validate_prepartion(self) -> Self:
        if self.swsalinity == 1:
            assert self.saltmax is not None, "saltmax is required when swsalinity is 1."
            assert self.saltslope is not None, "saltslope is required when swsalinity is 1."
        elif self.swsalinity == 2:
            assert self.salthead is not None, "salthead is required when swsalinity is 2."

        return self

read_yaml

Script reading YAML crop settings files.

WOFOSTCrop

Bases: BaseModel

Class for managing the library of WOFOST crop parameters from https://github.com/ajwdewit.

The library is included as a submodule in pySWAP and is located at pyswap/libs/WOFOST_crop_parameters. All files in that library have .yaml extension and fairly uniform format so it made sense to set up a pySWAP specific data structure for reading them.

Source code in pyswap/plant/read_yaml.py
class WOFOSTCrop(BaseModel):
    """Class for managing the library of WOFOST crop parameters from https://github.com/ajwdewit.

    The library is included as a submodule in pySWAP and is located at pyswap/libs/WOFOST_crop_parameters.
    All files in that library have .yaml extension and fairly uniform format so it made sense to set up a 
    pySWAP specific data structure for reading them.
    """
    libdir: Path = LIBDIR
    yaml_content: dict

    @computed_field(return_type=dict)
    def metadata(self):
        return self.yaml_content['Metadata']

    @computed_field(return_type=dict)
    def ecotypes(self):
        return list(self.yaml_content['CropParameters']['EcoTypes'])

    @computed_field(return_type=dict)
    def genericc3(self):
        """Get generic settings for C3 crop types - plants that bind CO2 into 
        3-phosphoglycerate having three carbon atoms. E.g., wheat, rice"""
        return self.yaml_content['CropParameters']['GenericC3']

    @computed_field(return_type=dict)
    def genericc4(self):
        """Get generic settings for C4 crop types - plants that bind CO2 into 
        oxaloacetate having four carbon atoms. E.g., maize, sugarcane"""
        return self.yaml_content['CropParameters']['GenericC4']

    @computed_field(return_type=dict)
    def varieties(self):
        return list(self.yaml_content['CropParameters']['Varieties'])

    @staticmethod
    def _serialize_variety(variety_dict: dict, what: str):
        variety_dict.pop("Metadata")
        if what == "parameters":
            return {k: v[0] for k, v in variety_dict.items()}
        if what == "explanation":
            return {k: f'{v[1]} {v[2]}' for k, v in variety_dict.items()}

    def get_variety(self, variety: str, what: str = 'parameters'):
        content = self.yaml_content['CropParameters']['Varieties'][variety]
        return self._serialize_variety(content, what=what)

    def get_variety_meta(self, variety: str):
        content = self.yaml_content['CropParameters']['Varieties'][variety]
        return content['Metadata']

genericc3()

Get generic settings for C3 crop types - plants that bind CO2 into 3-phosphoglycerate having three carbon atoms. E.g., wheat, rice

Source code in pyswap/plant/read_yaml.py
@computed_field(return_type=dict)
def genericc3(self):
    """Get generic settings for C3 crop types - plants that bind CO2 into 
    3-phosphoglycerate having three carbon atoms. E.g., wheat, rice"""
    return self.yaml_content['CropParameters']['GenericC3']

genericc4()

Get generic settings for C4 crop types - plants that bind CO2 into oxaloacetate having four carbon atoms. E.g., maize, sugarcane

Source code in pyswap/plant/read_yaml.py
@computed_field(return_type=dict)
def genericc4(self):
    """Get generic settings for C4 crop types - plants that bind CO2 into 
    oxaloacetate having four carbon atoms. E.g., maize, sugarcane"""
    return self.yaml_content['CropParameters']['GenericC4']

croptypes()

Print the list of available files

Source code in pyswap/plant/read_yaml.py
def croptypes():
    """Print the list of available files"""
    pprint([file for file in os.listdir(LIBDIR)
            if file.endswith('.yaml')])

tables

Tables for the crop settings

Classes:

Name Description
RDTB

Root depth table

AMAXTB

Bases: BaseModel

maximum CO2 assimilation rate [0..100 kg/ha/hr, R] as function of development stage [0..2 -, R]

Attributes:

Name Type Description
DVS Series[float]

Development stage of the crop.

AMAX Series[float]

Maximum CO2 assimilation rate.

Source code in pyswap/plant/tables.py
class AMAXTB(BaseModel):
    """maximum CO2 assimilation rate [0..100 kg/ha/hr, R] as function of development stage [0..2 -, R]

    Attributes:
        DVS (Series[float]): Development stage of the crop.
        AMAX (Series[float]): Maximum CO2 assimilation rate.
    """

    DVS: Series[float] = pa.Field(**DVSRANGE)
    AMAX: Series[float] = pa.Field(ge=0.0, le=100.0)

AMAXTB_GRASS

Bases: BaseModel

maximum CO2 assimilation rate [0..100 kg/ha/hr, R] as function of development stage [0..2 -, R]

Attributes:

Name Type Description
DNR Series[float]

Day number.

AMAX Series[float]

Maximum CO2 assimilation rate.

Source code in pyswap/plant/tables.py
class AMAXTB_GRASS(BaseModel):
    """maximum CO2 assimilation rate [0..100 kg/ha/hr, R] as function of development stage [0..2 -, R]

    Attributes:
        DNR (Series[float]): Day number.
        AMAX (Series[float]): Maximum CO2 assimilation rate.
    """

    DNR: Series[float] = pa.Field(**YEARRANGE)
    AMAX: Series[float] = pa.Field(ge=0.0, le=100.0)

CHTB

Bases: BaseModel

Crop Height [0..1.d4 cm, R], as function of dev. stage [0..2 -, R]

Attributes:

Name Type Description
DVS Series[float]

Development stage of the crop.

CH Series[float]

Crop height of the crop.

Source code in pyswap/plant/tables.py
class CHTB(BaseModel):
    """Crop Height [0..1.d4 cm, R], as function of dev. stage [0..2 -, R]

    Attributes:
        DVS (Series[float]): Development stage of the crop.
        CH (Series[float]): Crop height of the crop.
    """

    DVS: Series[float] = pa.Field(**DVSRANGE)
    CH: Series[float] = pa.Field(ge=0.0, le=1.0e4)

CHTB_GRASS

Bases: BaseModel

Crop Height [0..1.d4 cm, R], as function of dev. stage [0..2 -, R]

Attributes:

Name Type Description
DNR Series[float]

day number.

CH Series[float]

Crop height of the crop.

Source code in pyswap/plant/tables.py
class CHTB_GRASS(BaseModel):
    """Crop Height [0..1.d4 cm, R], as function of dev. stage [0..2 -, R]

    Attributes:
        DNR (Series[float]): day number.
        CH (Series[float]): Crop height of the crop.
    """

    DNR: Series[float] = pa.Field(**YEARRANGE)
    CH: Series[float] = pa.Field(ge=0.0, le=1.0e4)

CROPROTATION

Bases: BaseModel

Crop rotation settings

Attributes:

Name Type Description
CROPSTART Series[DateTime]

Start date of the crop.

CROPEND Series[DateTime]

End date of the crop.

CROPFIL Series[str]

Crop file name.

CROPTYPE Series[int]

Crop module type

  • 1 - simple
  • 2 - detailed, WOFOST general
  • 3 - detailed, WOFOST grass
Source code in pyswap/plant/tables.py
class CROPROTATION(BaseModel):
    """Crop rotation settings

    Attributes:
        CROPSTART (Series[pa.DateTime]): Start date of the crop.
        CROPEND (Series[pa.DateTime]): End date of the crop.
        CROPFIL (Series[str]): Crop file name.
        CROPTYPE (Series[int]): Crop module type

            * 1 - simple
            * 2 - detailed, WOFOST general
            * 3 - detailed, WOFOST grass
    """
    CROPSTART: Series[pa.DateTime]
    CROPEND: Series[pa.DateTime]
    CROPFIL: Series[str]
    CROPTYPE: Series[int] = pa.Field(ge=1, le=3)

DMGRZTB

Bases: BaseModel

threshold of above ground dry matter [0..1d6 kg DM/ha, R] to trigger grazing as function of daynumber [1..366 d, R]

Attributes:

Name Type Description
DNR Series[float]

Day number.

DMGRZ Series[float]

Dry matter growth rate of roots.

Source code in pyswap/plant/tables.py
class DMGRZTB(BaseModel):
    """threshold of above ground dry matter [0..1d6 kg DM/ha, R] to trigger grazing as function of daynumber [1..366 d, R]

    Attributes:
        DNR (Series[float]): Day number.
        DMGRZ (Series[float]): Dry matter growth rate of roots.
    """

    DNR: Series[float] = pa.Field(**YEARRANGE)
    DMGRZ: Series[float] = pa.Field(ge=0.0, le=1.0e6)

DMMOWDELAY

Bases: BaseModel

Relation between dry matter harvest [0..1d6 kg/ha, R] and days of delay in regrowth [0..366 d, I] after mowing

Attributes:

Name Type Description
DMMOWDELAY Series[float]

Dry matter harvest [0..1d6 kg/ha, R]

DAYDELAY Series[int]

days of delay in regrowth [0..366 d, I]

Source code in pyswap/plant/tables.py
class DMMOWDELAY(BaseModel):
    """Relation between dry matter harvest [0..1d6 kg/ha, R] and days of delay in regrowth [0..366 d, I] after mowing

    Attributes:
        DMMOWDELAY (Series[float]): Dry matter harvest [0..1d6 kg/ha, R]
        DAYDELAY (Series[int]): days of delay in regrowth [0..366 d, I]
    """

    DMMOWDELAY: Series[float] = pa.Field(ge=0.0, le=1.0e6)
    DAYDELAY: Series[int] = pa.Field(**YEARRANGE)

DMMOWTB

Bases: BaseModel

List threshold of above ground dry matter [0..1d6 kg DM/ha, R] to trigger mowing as function of daynumber [1..366 d, R]

Note

maximum 20 records

Attributes:

Name Type Description
DNR Series[float]

Day number.

DMMOW Series[float]

threshold of above ground dry matter [0..1d6 kg DM/ha, R]

Source code in pyswap/plant/tables.py
class DMMOWTB(BaseModel):
    """List threshold of above ground dry matter [0..1d6 kg DM/ha, R] to trigger mowing as function of daynumber [1..366 d, R]

    !!! note

        maximum 20 records


    Attributes:
        DNR (Series[float]): Day number.
        DMMOW (Series[float]): threshold of above ground dry matter [0..1d6 kg DM/ha, R]
    """

    DNR: Series[float] = pa.Field(**YEARRANGE)
    DMMOW: Series[float] = pa.Field(ge=0.0, le=1.0e6)

DTSMTB

Bases: BaseModel

increase in temperature sum [0..60 oC, R] as function of daily average temperature [0..100 oC, R]

Attributes:

Name Type Description
TAV Series[float]

Daily average temperature.

DTSM Series[float]

Increase in temperature sum.

Source code in pyswap/plant/tables.py
class DTSMTB(BaseModel):
    """increase in temperature sum [0..60 oC, R] as function of daily average temperature [0..100 oC, R]

    Attributes:
        TAV (Series[float]): Daily average temperature.
        DTSM (Series[float]): Increase in temperature sum.
    """

    TAV: Series[float] = pa.Field(ge=0.0, le=100.0)
    DTSM: Series[float] = pa.Field(ge=0.0, le=60.0)

FLTB

Bases: BaseModel

fraction of total above ground dry matter increase partitioned to the leaves [kg/kg, R]

Attributes:

Name Type Description
DVS Series[float]

Development stage of the crop.

FL Series[float]

Fraction of total above ground dry matter increase partitioned to the leaves.

Source code in pyswap/plant/tables.py
class FLTB(BaseModel):
    """fraction of total above ground dry matter increase partitioned to the leaves [kg/kg, R]

    Attributes:
        DVS (Series[float]): Development stage of the crop.
        FL (Series[float]): Fraction of total above ground dry matter increase partitioned to the leaves.
    """

    DVS: Series[float] = pa.Field(**DVSRANGE)
    FL: Series[float] = pa.Field(ge=0.0, le=1.0)

FLTB_GRASS

Bases: BaseModel

fraction of total above ground dry matter increase partitioned to the leaves [kg/kg, R]

Attributes:

Name Type Description
DNR Series[float]

Day number.

FL Series[float]

Fraction of total above ground dry matter increase partitioned to the leaves.

Source code in pyswap/plant/tables.py
class FLTB_GRASS(BaseModel):
    """fraction of total above ground dry matter increase partitioned to the leaves [kg/kg, R]

    Attributes:
        DNR (Series[float]): Day number.
        FL (Series[float]): Fraction of total above ground dry matter increase partitioned to the leaves.
    """

    DNR: Series[float] = pa.Field(**YEARRANGE)
    FL: Series[float] = pa.Field(**UNITRANGE)

FOTB

Bases: BaseModel

fraction of total above ground dry matter increase partitioned to the storage organs [kg/kg, R]

Attributes:

Name Type Description
DVS Series[float]

Development stage of the crop.

FO Series[float]

Fraction of total above ground dry matter increase partitioned to the storage organs.

Source code in pyswap/plant/tables.py
class FOTB(BaseModel):
    """fraction of total above ground dry matter increase partitioned to the storage organs [kg/kg, R]

    Attributes:
        DVS (Series[float]): Development stage of the crop.
        FO (Series[float]): Fraction of total above ground dry matter increase partitioned to the storage organs.
    """

    DVS: Series[float] = pa.Field(**DVSRANGE)
    FO: Series[float] = pa.Field(ge=0.0, le=1.0)

FRTB

Bases: BaseModel

fraction of total dry matter increase partitioned to the roots [kg/kg, R]

Attributes:

Name Type Description
DVS Series[float]

Development stage of the crop.

FR Series[float]

Fraction of total dry matter increase partitioned to the roots.

Source code in pyswap/plant/tables.py
class FRTB(BaseModel):
    """fraction of total dry matter increase partitioned to the roots [kg/kg, R]

    Attributes:
        DVS (Series[float]): Development stage of the crop.
        FR (Series[float]): Fraction of total dry matter increase partitioned to the roots.
    """

    DVS: Series[float] = pa.Field(**DVSRANGE)
    FR: Series[float] = pa.Field(ge=0.0, le=1.0)

FRTB_GRASS

Bases: BaseModel

fraction of total dry matter increase partitioned to the roots [kg/kg, R]

Attributes:

Name Type Description
DNR Series[float]

Day number.

FR Series[float]

Fraction of total dry matter increase partitioned to the roots.

Source code in pyswap/plant/tables.py
class FRTB_GRASS(BaseModel):
    """fraction of total dry matter increase partitioned to the roots [kg/kg, R]

    Attributes:
        DNR (Series[float]): Day number.
        FR (Series[float]): Fraction of total dry matter increase partitioned to the roots.
    """

    DNR: Series[float] = pa.Field(**YEARRANGE)
    FR: Series[float] = pa.Field(**UNITRANGE)

FSTB

Bases: BaseModel

fraction of total above ground dry matter increase partitioned to the stems [kg/kg, R]

Attributes:

Name Type Description
DVS Series[float]

Development stage of the crop.

FS Series[float]

Fraction of total above ground dry matter increase partitioned to the stems.

Source code in pyswap/plant/tables.py
class FSTB(BaseModel):
    """fraction of total above ground dry matter increase partitioned to the stems [kg/kg, R]

    Attributes:
        DVS (Series[float]): Development stage of the crop.
        FS (Series[float]): Fraction of total above ground dry matter increase partitioned to the stems.
    """

    DVS: Series[float] = pa.Field(**DVSRANGE)
    FS: Series[float] = pa.Field(ge=0.0, le=1.0)

FSTB_GRASS

Bases: BaseModel

fraction of total above ground dry matter increase partitioned to the stems [kg/kg, R]

Attributes:

Name Type Description
DNR Series[float]

Day number.

FS Series[float]

Fraction of total above ground dry matter increase partitioned to the stems.

Source code in pyswap/plant/tables.py
class FSTB_GRASS(BaseModel):
    """fraction of total above ground dry matter increase partitioned to the stems [kg/kg, R]

    Attributes:
        DNR (Series[float]): Day number.
        FS (Series[float]): Fraction of total above ground dry matter increase partitioned to the stems.
    """

    DNR: Series[float] = pa.Field(**YEARRANGE)
    FS: Series[float] = pa.Field(**UNITRANGE)

GCTB

Bases: BaseModel

Leaf Area Index [0..12 (m2 leaf)/(m2 soil), R], as function of dev. stage [0..2 -, R]

Attributes:

Name Type Description
DVS Series[float]

Development stage of the crop.

LAI Series[float]

Leaf Area Index of the crop.

Source code in pyswap/plant/tables.py
class GCTB(BaseModel):
    """Leaf Area Index [0..12 (m2 leaf)/(m2 soil), R], as function of dev. stage [0..2 -, R]

    Attributes:
        DVS (Series[float]): Development stage of the crop.
        LAI (Series[float]): Leaf Area Index of the crop.
    """

    DVS: Series[float] = pa.Field(**DVSRANGE)
    LAI: Series[float] = pa.Field(ge=0.0, le=12.0)

KYTB

Bases: BaseModel

Yield response factor [0..5 -, R], as function of dev. stage [0..2 -, R]

Attributes:

Name Type Description
DVS Series[float]

Development stage of the crop.

KY Series[float]

Yield response factor of the crop.

Source code in pyswap/plant/tables.py
class KYTB(BaseModel):
    """Yield response factor [0..5 -, R], as function of dev. stage [0..2 -, R]

    Attributes:
        DVS (Series[float]): Development stage of the crop.
        KY (Series[float]): Yield response factor of the crop.
    """
    DVS: Series[float] = pa.Field(**DVSRANGE)
    KY: Series[float] = pa.Field(ge=0.0, le=5.0)

LSDATB

Bases: BaseModel

Actual livestock density of each grazing period

Note

total number of periods should be equal to number of periods in SEQGRAZMOW

Attributes:

Name Type Description
SEQNR Series[int]

number of the sequence period with mowing/grazing [0..366 d, I]

LSDA Series[float]

Actual Live Stock Density of the grazing period [0.0..1000.0 LS/ha, R]

Source code in pyswap/plant/tables.py
class LSDATB(BaseModel):
    """Actual livestock density of each grazing period

    !!! note

        total number of periods should be equal to number of periods in SEQGRAZMOW

    Attributes:
        SEQNR (Series[int]): number of the sequence period with mowing/grazing [0..366 d, I]
        LSDA (Series[float]): Actual Live Stock Density of the grazing period [0.0..1000.0 LS/ha, R]
    """

    SEQNR: Series[int] = pa.Field(**YEARRANGE)
    LSDA: Series[float] = pa.Field(ge=0.0, le=1000.0)

LSDBTB

Bases: BaseModel

Relation between livestock density, number of grazing days and dry matter uptake

Attributes:

Name Type Description
LSDB Series[float]

Basic Live Stock Density [0.0..1000.0 LS/ha, R]

DAYSGRAZING Series[float]

Maximum days of grazing [0.0..366.0 d, R]

UPTGRAZING Series[float]

Dry matter uptake by grazing [0.0..1000.0 kg/ha, R] (kg/ha DM)

LOSSGRAZING Series[float]

Dry matter loss during grazing due to droppings and treading [0.0..1000.0 kg/ha, R] (kg/ha DM)

Source code in pyswap/plant/tables.py
class LSDBTB(BaseModel):
    """Relation between livestock density, number of grazing days and dry matter uptake

    Attributes:
        LSDB (Series[float]): Basic Live Stock Density [0.0..1000.0 LS/ha, R]
        DAYSGRAZING (Series[float]): Maximum days of grazing [0.0..366.0 d, R]
        UPTGRAZING (Series[float]): Dry matter uptake by grazing [0.0..1000.0 kg/ha, R] (kg/ha DM)
        LOSSGRAZING (Series[float]): Dry matter loss during grazing due to droppings and treading [0.0..1000.0 kg/ha, R] (kg/ha DM)
    """

    LSDB: Series[float] = pa.Field(ge=0.0, le=1000.0)
    DAYSGRAZING: Series[float] = pa.Field(**YEARRANGE)
    UPTGRAZING: Series[float] = pa.Field(ge=0.0, le=1000.0)
    LOSSGRAZING: Series[float] = pa.Field(ge=0.0, le=1000.0)

MRFTB

Bases: BaseModel

Ratio root total respiration / maintenance respiration [1..5.0 -, R]

Attributes:

Name Type Description
DVS Series[float]

Development stage of the crop.

MAX_RESP_FACTOR Series[float]

Ratio root total respiration / maintenance respiration.

Source code in pyswap/plant/tables.py
class MRFTB(BaseModel):
    """Ratio root total respiration / maintenance respiration [1..5.0 -, R]

    Attributes:
        DVS (Series[float]): Development stage of the crop.
        MAX_RESP_FACTOR (Series[float]): Ratio root total respiration / maintenance respiration.
    """

    DVS: Series[float] = pa.Field(**DVSRANGE)
    MAX_RESP_FACTOR: Series[float] = pa.Field(ge=1.0, le=5.0)

RDCTB

Bases: BaseModel

List root density [0..100 cm/cm3, R] as function of relative rooting depth [0..1 -, R]

Attributes:

Name Type Description
RRD Series[float]

Relative rooting depth of the crop.

RDENS Series[float]

Root density of the crop.

Source code in pyswap/plant/tables.py
class RDCTB(BaseModel):
    """List root density [0..100 cm/cm3, R] as function of relative rooting depth [0..1 -, R]

    Attributes:
        RRD (Series[float]): Relative rooting depth of the crop.
        RDENS (Series[float]): Root density of the crop.

    """
    RRD: Series[float] = pa.Field(ge=0.0, le=100.0)
    RDENS: Series[float] = pa.Field(**UNITRANGE)

RDRRTB

Bases: BaseModel

relative death rates of roots [kg/kg/d] as function of development stage [0..2 -, R]

Attributes:

Name Type Description
DVS Series[float]

Development stage of the crop.

RDRR Series[float]

Relative death rates of roots.

Source code in pyswap/plant/tables.py
class RDRRTB(BaseModel):
    """relative death rates of roots [kg/kg/d] as function of development stage [0..2 -, R]

    Attributes:
        DVS (Series[float]): Development stage of the crop.
        RDRR (Series[float]): Relative death rates of roots.
    """

    DVS: Series[float] = pa.Field(**DVSRANGE)
    RDRR: Series[float] = pa.Field(ge=0.0)

RDRRTB_GRASS

Bases: BaseModel

relative death rates of roots [kg/kg/d] as function of development stage [0..2 -, R]

Attributes:

Name Type Description
DNR Series[float]

Day number.

RDRR Series[float]

Relative death rates of roots.

Source code in pyswap/plant/tables.py
class RDRRTB_GRASS(BaseModel):
    """relative death rates of roots [kg/kg/d] as function of development stage [0..2 -, R]

    Attributes:
        DNR (Series[float]): Day number.
        RDRR (Series[float]): Relative death rates of roots.
    """

    DNR: Series[float] = pa.Field(**YEARRANGE)
    RDRR: Series[float] = pa.Field(ge=0.0)

RDRSTB

Bases: BaseModel

relative death rates of stems [kg/kg/d] as function of development stage [0..2 -, R]

Attributes:

Name Type Description
DVS Series[float]

Development stage of the crop.

RDRS Series[float]

Relative death rates of stems.

Source code in pyswap/plant/tables.py
class RDRSTB(BaseModel):
    """relative death rates of stems [kg/kg/d] as function of development stage [0..2 -, R]

    Attributes:
        DVS (Series[float]): Development stage of the crop.
        RDRS (Series[float]): Relative death rates of stems.
    """

    DVS: Series[float] = pa.Field(**DVSRANGE)
    RDRS: Series[float] = pa.Field(ge=0.0)

RDRSTB_GRASS

Bases: BaseModel

relative death rates of stems [kg/kg/d] as function of development stage [0..2 -, R]

Attributes:

Name Type Description
DNR Series[float]

Day number.

RDRS Series[float]

Relative death rates of stems.

Source code in pyswap/plant/tables.py
class RDRSTB_GRASS(BaseModel):
    """relative death rates of stems [kg/kg/d] as function of development stage [0..2 -, R]

    Attributes:
        DNR (Series[float]): Day number.
        RDRS (Series[float]): Relative death rates of stems.
    """

    DNR: Series[float] = pa.Field(**YEARRANGE)
    RDRS: Series[float] = pa.Field(ge=0.0)

RDTB

Bases: BaseModel

Rooting Depth [0..1000 cm, R], as a function of development stage [0..2 -, R].

Attributes:

Name Type Description
DVS Series[float]

Development stage of the crop.

RD Series[float]

Rooting depth of the crop.

Source code in pyswap/plant/tables.py
class RDTB(BaseModel):
    """Rooting Depth [0..1000 cm, R], as a function of development stage [0..2 -, R].

    Attributes:
        DVS (Series[float]): Development stage of the crop.
        RD (Series[float]): Rooting depth of the crop.
    """

    DVS: Series[float] = pa.Field(**DVSRANGE)
    RD: Series[float] = pa.Field(ge=0.0, le=100.0)

RFSETB

Bases: BaseModel

reduction factor of senescence [-, R] as function of development stage [0..2 -, R]

Attributes:

Name Type Description
DVS Series[float]

Development stage of the crop.

RFSE Series[float]

Reduction factor of senescence.

Source code in pyswap/plant/tables.py
class RFSETB(BaseModel):
    """reduction factor of senescence [-, R] as function of development stage [0..2 -, R]

    Attributes:
        DVS (Series[float]): Development stage of the crop.
        RFSE (Series[float]): Reduction factor of senescence.
    """

    DVS: Series[float] = pa.Field(**DVSRANGE)
    RFSE: Series[float] = pa.Field(ge=0.0, le=1.0)

RFSETB_GRASS

Bases: BaseModel

reduction factor of senescence [-, R] as function of development stage [0..2 -, R]

Attributes:

Name Type Description
DNR Series[float]

Day number.

RFSE Series[float]

Reduction factor of senescence.

Source code in pyswap/plant/tables.py
class RFSETB_GRASS(BaseModel):
    """reduction factor of senescence [-, R] as function of development stage [0..2 -, R]

    Attributes:
        DNR (Series[float]): Day number.
        RFSE (Series[float]): Reduction factor of senescence.
    """

    DNR: Series[float] = pa.Field(**YEARRANGE)
    RFSE: Series[float] = pa.Field(**UNITRANGE)

RLWTB

Bases: BaseModel

rooting depth RL [0..5000 cm, R] as function of root weight RW [0..5000 kg DM/ha, R]

Attributes:

Name Type Description
RW Series[float]

rooting depth

RL Series[float]

root weight

Source code in pyswap/plant/tables.py
class RLWTB(BaseModel):
    """rooting depth RL [0..5000 cm, R] as function of root weight RW [0..5000 kg DM/ha, R]

    Attributes:
        RW (Series[float]): rooting depth
        RL (Series[float]): root weight
    """

    RW: Series[float] = pa.Field(ge=0.0, le=5000.0)
    RL: Series[float] = pa.Field(ge=0.0, le=5000.0)

SLATB

Bases: BaseModel

leaf area [0..1 ha/kg, R] as function of crop development stage [0..2 -, R]

Attributes:

Name Type Description
DVS Series[float]

Development stage of the crop.

SLA Series[float]

Leaf area.

Source code in pyswap/plant/tables.py
class SLATB(BaseModel):
    """leaf area [0..1 ha/kg, R] as function of crop development stage [0..2 -, R]

    Attributes:
        DVS (Series[float]): Development stage of the crop.
        SLA (Series[float]): Leaf area.
    """

    DVS: Series[float] = pa.Field(**DVSRANGE)
    SLA: Series[float] = pa.Field(ge=0.0, le=1.0)

SLATB_GRASS

Bases: BaseModel

leaf area [0..1 ha/kg, R] as function of crop development stage [0..2 -, R]

Attributes:

Name Type Description
DNR Series[float]

Day number.

SLA Series[float]

Leaf area.

Source code in pyswap/plant/tables.py
class SLATB_GRASS(BaseModel):
    """leaf area [0..1 ha/kg, R] as function of crop development stage [0..2 -, R]

    Attributes:
        DNR (Series[float]): Day number.
        SLA (Series[float]): Leaf area.
    """

    DNR: Series[float] = pa.Field(**YEARRANGE)
    SLA: Series[float] = pa.Field(ge=0.0, le=1.0)

TMNFTB

Bases: BaseModel

reduction factor of AMAX [-, R] as function of minimum day temperature [-10..50 oC, R]

Attributes:

Name Type Description
TMNR Series[float]

Minimum temperature.

TMNF Series[float]

Reduction factor of AMAX.

Source code in pyswap/plant/tables.py
class TMNFTB(BaseModel):
    """reduction factor of AMAX [-, R] as function of minimum day temperature [-10..50 oC, R]

    Attributes:
        TMNR (Series[float]): Minimum temperature.
        TMNF (Series[float]): Reduction factor of AMAX.
    """

    TMNR: Series[float] = pa.Field(ge=-10.0, le=50.0)
    TMNF: Series[float] = pa.Field(ge=0.0, le=1.0)

TMPFTB

Bases: BaseModel

reduction factor of AMAX [-, R] as function of average day temperature [-10..50 oC, R]

Attributes:

Name Type Description
TAVD Series[float]

Minimum temperature.

TMPF Series[float]

Reduction factor of AMAX.

Source code in pyswap/plant/tables.py
class TMPFTB(BaseModel):
    """reduction factor of AMAX [-, R] as function of average day temperature [-10..50 oC, R]

    Attributes:
        TAVD (Series[float]): Minimum temperature.
        TMPF (Series[float]): Reduction factor of AMAX.
    """

    TAVD: Series[float] = pa.Field(ge=-10.0, le=50.0)
    TMPF: Series[float] = pa.Field(ge=0.0, le=1.0)

WRTB

Bases: BaseModel

dry weight of roots at soil surface [0..10 kg/m3, R], as a function of development stage [0..2 -,R]

Attributes:

Name Type Description
DVS Series[float]

Development stage of the crop.

W_ROOT_SS Series[float]

Dry weight of roots at soil surface.

Source code in pyswap/plant/tables.py
class WRTB(BaseModel):
    """dry weight of roots at soil surface [0..10 kg/m3, R], as a function of development stage [0..2 -,R]

    Attributes:
        DVS (Series[float]): Development stage of the crop.
        W_ROOT_SS (Series[float]): Dry weight of roots at soil surface.
    """

    DVS: Series[float] = pa.Field(**DVSRANGE)
    W_ROOT_SS: Series[float] = pa.Field(ge=0.0, le=10.0)

Irrigation subpackage

Irrigation settings for the SWAP simulation.

Modules:

Name Description
irrigation

The irrigation settings.

irgfile

The irrigation file.

irgfile

Create the irrigation file for the SWAP model.

Classes:

Name Description
IrrigationFile

The irrigation file.

IrgFile

Bases: PySWAPBaseModel

The irrigation file.

Warning

The irrigation file is the first to have pandera validation. However, it is not yet complete. Some columns are set to non-required, but they might be required if solute transport is used.

Attributes:

Name Type Description
irgfil str

the name of the irgfile without .irg extension.

content DataFrame

The content of the irrigation file.

Source code in pyswap/irrigation/irgfile.py
class IrgFile(PySWAPBaseModel):
    """The irrigation file.

    !!! warning
        The irrigation file is the first to have pandera validation. However, 
        it is not yet complete. Some columns are set to non-required, but they
        might be required if solute transport is used.

    Attributes:
        irgfil (str): the name of the irgfile without .irg extension.
        content (DataFrame): The content of the irrigation file.
    """

    irgfil: str
    content: DataFrame = Field(exclude=True)

irg_from_csv(irgfil, path)

Load the irrigation file from a CSV file.

Parameters:

Name Type Description Default
irgfil str

the name of the irgfile without .irg extension.

required
path str

The path to the CSV file.

required

Returns:

Name Type Description
IrgFile IrgFile

The irrigation file.

Source code in pyswap/irrigation/irgfile.py
def irg_from_csv(irgfil: str, path: str) -> IrgFile:
    """Load the irrigation file from a CSV file.

    Parameters:
        irgfil (str): the name of the irgfile without .irg extension.
        path (str): The path to the CSV file.

    Returns:
        IrgFile: The irrigation file.
    """
    return IrgFile(content=read_csv(path), irgfil=irgfil)

irrigation

" Irrigation settings for the SWAP simuluation.

Classes:

Name Description
FixedIrrigation

Holds the settings for fixed irrigation.

ScheduledIrrigation

Holds the settings for scheduled irrigation.

Irrigation

Holds the irrigation settings of the simulation.

FixedIrrigation

Bases: PySWAPBaseModel

Fixed irrigation settings.

Note

This class is only used in the .swp file.

Attributes:

Name Type Description
swirfix Literal[0, 1]

Switch for fixed irrigation applications

swirgfil Literal[0, 1]

Switch for separate file with fixed irrigation applications

table_irrigevents Optional[Table]
irgfil Optional[str]
irrigationdata Optional[IrrigationFile]
Source code in pyswap/irrigation/irrigation.py
class FixedIrrigation(PySWAPBaseModel):
    """Fixed irrigation settings.

    !!! note
        This class is only used in the .swp file.

    Attributes:
        swirfix (Literal[0, 1]): Switch for fixed irrigation applications
        swirgfil (Literal[0, 1]): Switch for separate file with fixed irrigation applications
        table_irrigevents (Optional[Table]):
        irgfil (Optional[str]):
        irrigationdata (Optional[IrrigationFile]):
    """

    swirfix: Literal[0, 1]
    swirgfil: Optional[Literal[0, 1]] = None
    table_irrigevents: Optional[Table] = None
    irgfile: Optional[IrgFile] = Field(
        default=None, repr=False)

    @model_validator(mode='after')
    def _validate_fixed_irrigation(self) -> Self:
        if self.swirfix == 1:
            if self.swirgfil:
                assert self.irgfile is not None, "irgfile is required when swirgfil is True"
            else:
                assert self.table_irrigevents is not None, "irrigevents is required when swirgfil is False"

        return self

ScheduledIrrigation

Bases: PySWAPBaseModel

Irrigation scheduling settings.

Warning

The docstring needs to be updated.

Note

This class is only used in the .crp file.

Attributes:

Name Type Description
schedule Literal[0, 1]

Switch for application irrigation scheduling

startirr str

Specify day and month at which irrigation scheduling starts

endirr str

Specify day and month at which irrigation scheduling stops

cirrs float

Solute concentration of irrigation water

isuas int

Switch for type of irrigation method

  • 0 - Sprinkler irrigation
  • 1 - Surface irrigation
tcs int

Choose one of the following timing criteria options

  • 1 - Ratio actual/potential transpiration
  • 2 - Depletion of Readily Available Water
  • 3 - Depletion of Totally Available Water
  • 4 - Depletion of absolute Water Amount
  • 6 - Fixed weekly irrigation
  • 7 - Pressure head
  • 8 - Moisture content
phFieldCapacity float

Soil water pressure head at field capacity

irgthreshold Optional[float]

Threshold value for weekly irrigation

dcrit Optional[float]

Depth of the sensor

swcirrthres Optional[bool]

Switch for over-irrigation

cirrthres Optional[float]

Threshold salinity concentration above which over-irrigation occur

perirrsurp Optional[float]

Over-irrigation of the usually scheduled irrigation depth

tcsfix Optional[int]

Switch for minimum time interval between irrigation applications

irgdayfix Optional[int]

Minimum number of days between irrigation applications

phormc Optional[int]

Switch for the use of pressure head or water content

  • 0 - Pressure head
  • 1 - Water content
dvs_tc1 Optional[Table]
dvs_tc2 Optional[Table]
dvs_tc3 Optional[Table]
dvs_tc4 Optional[Table]
dvs_tc5 Optional[Table]
Source code in pyswap/irrigation/irrigation.py
class ScheduledIrrigation(PySWAPBaseModel):
    """Irrigation scheduling settings.

    !!! warning
        The docstring needs to be updated.

    !!! note
        This class is only used in the .crp file.

    Attributes:
        schedule (Literal[0, 1]): Switch for application irrigation scheduling
        startirr (str): Specify day and month at which irrigation scheduling starts
        endirr (str): Specify day and month at which irrigation scheduling stops
        cirrs (float): Solute concentration of irrigation water
        isuas (int): Switch for type of irrigation method

            * 0 - Sprinkler irrigation
            * 1 - Surface irrigation

        tcs (int): Choose one of the following timing criteria options

            * 1 - Ratio actual/potential transpiration
            * 2 - Depletion of Readily Available Water
            * 3 - Depletion of Totally Available Water
            * 4 - Depletion of absolute Water Amount
            * 6 - Fixed weekly irrigation
            * 7 - Pressure head
            * 8 - Moisture content

        phFieldCapacity (float): Soil water pressure head at field capacity
        irgthreshold (Optional[float]): Threshold value for weekly irrigation
        dcrit (Optional[float]): Depth of the sensor
        swcirrthres (Optional[bool]): Switch for over-irrigation
        cirrthres (Optional[float]): Threshold salinity concentration above which over-irrigation occur
        perirrsurp (Optional[float]): Over-irrigation of the usually scheduled irrigation depth
        tcsfix (Optional[int]): Switch for minimum time interval between irrigation applications
        irgdayfix (Optional[int]): Minimum number of days between irrigation applications
        phormc (Optional[int]): Switch for the use of pressure head or water content

            * 0 - Pressure head
            * 1 - Water content

        dvs_tc1 (Optional[Table]):
        dvs_tc2 (Optional[Table]):
        dvs_tc3 (Optional[Table]):
        dvs_tc4 (Optional[Table]):
        dvs_tc5 (Optional[Table]):
    """
    schedule: Literal[0, 1]
    startirr: Optional[DayMonth] = None
    endirr: Optional[DayMonth] = None
    cirrs: Optional[float] = Field(default=None, ge=0.0, le=100.0)
    isuas: Optional[Literal[0, 1]] = None
    tcs: Optional[Literal[1, 2, 3, 4, 6, 7, 8]] = None
    phfieldcapacity: Optional[float] = Field(default=None, ge=-1000.0, le=0.0)
    irgthreshold: Optional[float] = Field(default=None, ge=0.0, le=20.0)
    dcrit: Optional[float] = Field(default=None, ge=-100.0, le=0.0)
    swcirrthres: Optional[Literal[0, 1]] = None
    cirrthres: Optional[float] = Field(default=None, ge=0.0, le=100.0)
    perirrsurp: Optional[float] = Field(default=None, ge=0.0, le=100.0)
    tcsfix: Optional[Literal[0, 1]] = None
    irgdayfix: Optional[int] = Field(default=None, **YEARRANGE)
    dcs: Optional[Literal[0, 1]] = None
    dcslim: Optional[Literal[0, 1]] = None
    irgdepmin: Optional[float] = Field(default=None, ge=0.0, le=100.0)
    irgdepmax: Optional[float] = Field(default=None, ge=0.0, le=1.0e7)
    table_tc1tb: Optional[Table] = None
    table_tc2tb: Optional[Table] = None
    table_tc3tb: Optional[Table] = None
    table_tc4tb: Optional[Table] = None
    table_tc7tb: Optional[Table] = None
    table_tc8tb: Optional[Table] = None

    @model_validator(mode='after')
    def _validate_scheduled_irrigation(self) -> Self:

        if self.tcs == 1:
            self.dvs_tc1 = {'dvs_tc1': [0.0, 2.0],
                            'Trel': [0.95, 0.95]}
        elif self.tcs == 2:
            self.dvs_tc2 = {'dvs_tc2': [0.0, 2.0],
                            'RAW': [0.95, 0.95]}
        elif self.tcs == 3:
            self.dvs_tc3 = {'dvs_tc3': [0.0, 2.0],
                            'TAW': [0.50, 0.50]}
        elif self.tcs == 4:
            self.dvs_tc4 = {'dvs_tc4': [0.0, 2.0],
                            'DWA': [0.40, 0.40]}
        elif self.tcs == 5:
            self.dvs_tc5 = {'dvs_tc5': [0.0, 2.0],
                            'Value_tc5': [-1000.0, -1000.0]}
        elif self.tcs == 6:
            assert self.irgthreshold is not None, "irgthreshold is required when tcs is 6"
            assert self.tcsfix is not None, "tcsfix is required when tcs is 6"
            if self.tcsfix:
                assert self.irgdayfix is not None, "irgdayfix is required when tcsfix is True"

        return self

tables

Tables for the irrigation settings.

Classes:

IRRIGATION

Bases: BaseModel

information for each fixed irrigation event.

Attributes:

Name Type Description
IRDATE Series[datetime]

date of irrigation.

IRDEPTH Series[float]

amount of water [0..1000 mm, R].

IRCONC Series[float]

concentration of irrigation water [0..1000 mg/cm3, R].

IRTYPE Series[int]

type of irrigation

  • 0 - sprinkling
  • 1 - surface
Source code in pyswap/irrigation/tables.py
class IRRIGATION(BaseModel):
    """information for each fixed irrigation event.

    Attributes:
        IRDATE (Series[datetime]):date of irrigation.
        IRDEPTH (Series[float]): amount of water [0..1000 mm, R].
        IRCONC (Series[float]): concentration of irrigation water [0..1000 mg/cm3, R].
        IRTYPE (Series[int]): type of irrigation 

            * 0 - sprinkling 
            * 1 - surface

    """

    IRDATE: Series[pa.DateTime]
    IRDEPTH: Series[float] = pa.Field(ge=0.0, le=1000.0)
    IRCONC: Series[float] = pa.Field(ge=0.0, le=1000.0)
    IRTYPE: Series[int] = pa.Field(ge=0, le=1)

Soil-water subpackage

Settings related to soil and water.

Modules:

Name Description
evaporation

Evaporation settings.

soilmoisture

Soil moisture settings.

soilprofile

Soil profile settings.

surfaceflow

Surface flow settings.

snow

Snow and frost settings.

evaporation

Settings for evaporation.

Classes:

Name Description
Evaporation

Evaporation settings.

Evaporation

Bases: PySWAPBaseModel

Evaporation settings.

Attributes:

Name Type Description
swcfbs int

Switch for use of soil factor CFBS to calculate Epot from ETref

swredu int

Switch for the method for reduction of potential soil evaporation:

  • 0 - reduction to maximum Darcy flux.
  • 1 - reduction to maximum Darcy flux and to maximum Black (1969).
  • 2 - reduction to maximum Darcy flux and to maximum Boesten/Stroosnijder (1986).
cfevappond Optional[float]

hen ETref is used, evaporation coefficient in case of ponding.

cfbs Optional[float]

Coefficient for potential soil evaporation.

rsoil Optional[float]

Soil resistance of wet soil.

cofredbl Optional[float]

Soil evaporation coefficient of Black.

rsigni Optional[float]

Minimum rainfall to reset method of Black.

cofredbo Optional[float]

Soil evaporation coefficient of Boesten/Stroosnijder.

Source code in pyswap/soilwater/evaporation.py
class Evaporation(PySWAPBaseModel):
    """Evaporation settings.

    Attributes:
        swcfbs (int): Switch for use of soil factor CFBS to calculate Epot from ETref
        swredu (int): Switch for the method for reduction of potential soil evaporation:

            * 0 - reduction to maximum Darcy flux.
            * 1 - reduction to maximum Darcy flux and to maximum Black (1969).
            * 2 - reduction to maximum Darcy flux and to maximum Boesten/Stroosnijder (1986).

        cfevappond (Optional[float]): hen ETref is used, evaporation coefficient in case of ponding.
        cfbs (Optional[float]): Coefficient for potential soil evaporation.
        rsoil (Optional[float]): Soil resistance of wet soil.
        cofredbl (Optional[float]): Soil evaporation coefficient of Black.
        rsigni (Optional[float]): Minimum rainfall to reset method of Black.
        cofredbo (Optional[float]): Soil evaporation coefficient of Boesten/Stroosnijder.
    """
    swcfbs: Literal[0, 1]
    swredu: Literal[0, 1, 2]
    cfevappond: Optional[float] = None  # this is used if ETref is used
    cfbs: Optional[float] = None
    rsoil: Optional[float] = None
    cofredbl: Optional[float] = None
    rsigni: Optional[float] = None
    cofredbo: Optional[float] = None

    @model_validator(mode='after')
    def _validate_evaporation(self) -> Self:

        if self.swcfbs:
            assert self.cfbs is not None, "cfbs is required when swcfbs is True"

        if self.swredu == 1:
            assert self.cofredbl is not None, "cofredbl is required when swredu is 1"
            assert self.rsigni is not None, "rsigni is required when swredu is 1"

        elif self.swredu == 2:
            assert self.cofredbo is not None, "cofredbo is required when swredu is 2"

        return self

snow

SnowAndFrost

Bases: PySWAPBaseModel

Snow and frost settings for the model.

Attributes:

Name Type Description
swsnow Literal[0, 1]

Switch for calculation of snow accumulation and melt.

swfrost Literal[0, 1]

Switch, in case of frost reduce soil water flow

snowinco Optional[float]

Initial snow water equivalent

teprrain Optional[float]

Temperature above which all precipitation is rain

teprsnow Optional[float]

Temperature below which all precipitation is snow

snowcoef Optional[float]

Snowmelt calibration factor

tfroststa Optional[float]

Soil temperature (oC) where reduction of water fluxes starts

tfrostend Optional[float]

Soil temperature (oC) where reduction of water fluxes ends

Source code in pyswap/soilwater/snow.py
class SnowAndFrost(PySWAPBaseModel):
    """Snow and frost settings for the model.

    Attributes:
        swsnow (Literal[0, 1]): Switch for calculation of snow accumulation and melt.
        swfrost (Literal[0, 1]): Switch,  in case of frost reduce soil water flow
        snowinco (Optional[float]): Initial snow water equivalent
        teprrain (Optional[float]): Temperature above which all precipitation is rain
        teprsnow (Optional[float]): Temperature below which all precipitation is snow
        snowcoef (Optional[float]): Snowmelt calibration factor
        tfroststa (Optional[float]): Soil temperature (oC) where reduction of water fluxes starts
        tfrostend (Optional[float]): Soil temperature (oC) where reduction of water fluxes ends

    """

    swsnow: Literal[0, 1]
    swfrost: Literal[0, 1]
    snowinco: Optional[float] = None
    teprrain: Optional[float] = None
    teprsnow: Optional[float] = None
    snowcoef: Optional[float] = None
    tfrostst: Optional[float] = None
    tfrostend: Optional[float] = None

    @model_validator(mode='after')
    def _validate_snow_and_frost(self, v) -> Self:

        if self.swsnow == 1:
            assert self.snowinco is not None, "snowinco is required when swsnow is True"
            assert self.teprrain is not None, "teprrain is required when swsnow is True"
            assert self.teprsnow is not None, "teprsnow is required when swsnow is True"
            assert self.snowcoef is not None, "snowcoef is required when swsnow is True"

        if self.swfrost == 1:
            assert self.tfrostst is not None, "tfrostst is required when swfrost is True"
            assert self.tfrostend is not None, "tfrostend is required when swfrost is True"

        return self

soilmoisture

SoilMoisture

Bases: PySWAPBaseModel

Soil moisture content and water balance.

Warning

swinco = 3 is not yet implemented. The model will run, but the output will not be retrieved.

Attributes:

Name Type Description
swinco int

Switch for the type of initial soil moisture condition:

  • 1 - pressure head as function of soil depth.
  • 2 - pressure head of each compartment is in hydrostatic equilibrium with initial groundwater level.
  • 3 - read final pressure heads from output file of previous Swap simulation.
table_head_soildepth Optional[Table]

Table with head and soil depth data.

gwli Optional[float]

Initial groundwater level [cm].

inifil Optional[str]

name of output file *.END which contains initial values.

Source code in pyswap/soilwater/soilmoisture.py
class SoilMoisture(PySWAPBaseModel):
    """Soil moisture content and water balance.

    !!! warning
        swinco = 3 is not yet implemented. The model will run, but the output will not be
        retrieved.

    Attributes:
        swinco (int): Switch for the type of initial soil moisture condition:

            * 1 - pressure head as function of soil depth.
            * 2 - pressure head of each compartment is in hydrostatic equilibrium with initial groundwater level.
            * 3 - read final pressure heads from output file of previous Swap simulation.

        table_head_soildepth (Optional[Table]): Table with head and soil depth data.
        gwli (Optional[float]): Initial groundwater level [cm].
        inifil (Optional[str]): name of output file *.END which contains initial values.
    """

    swinco: Literal[1, 2, 3]
    table_head_soildepth: Optional[Table] = None
    gwli: Optional[float] = None
    inifil: Optional[str] = None

    @model_validator(mode='after')
    def _validate_soil_moisture(self) -> Self:

        if self.swinco == 1:
            assert self.table_head_soildepth is not None, "head_soildepth is required when swinco is 1"

        elif self.swinco == 2:
            assert self.gwli is not None, "gwli is required when swinco is 2"

        else:
            assert self.inifil is not None, "inifil is required when swinco is 3"

        return self

soilprofile

SoilProfile

Bases: PySWAPBaseModel

Vertical discretization of soil profile, soil hydraulic functions and hysteresis of soil water retention.

Covers parts 4, 5, 6 and 7 of the .swp file.

Attributes:

Name Type Description
swsophy Literal[0, 1]

Switch for analytical functions or tabular input

  • 0 - Analytical functions with input of Mualem - van Genuchten parameters
  • 1 - Soil physical tables
swhyst Literal[0, 1, 2]

Hysteresis of soil water retention function

  • 0 - No hysteresis
  • 1 - Hysteresis, initial conditions wetting
  • 2 - Hysteresis, initial conditions drying
filenamesophy Optional[str]

Names of input files with soil hydraulic tables for each soil layer

tau Optional[float]

Minimum pressure head difference to change wetting-drying

swmacro Literal[0, 1]

Switch for preferential flow due to macropores

table_soilprofile Table

Table with soil profile data

table_soilhydrfunc Optional[Table]

Table with soil hydraulic functions

Source code in pyswap/soilwater/soilprofile.py
class SoilProfile(PySWAPBaseModel):
    """Vertical discretization of soil profile, soil hydraulic functions and hysteresis of soil water retention.

    Covers parts 4, 5, 6 and 7 of the .swp file.

    Attributes:
        swsophy (Literal[0, 1]): Switch for analytical functions or tabular input

            * 0 - Analytical functions with input of Mualem - van Genuchten parameters
            * 1 - Soil physical tables

        swhyst (Literal[0, 1, 2]): Hysteresis of soil water retention function

            * 0 - No hysteresis
            * 1 - Hysteresis, initial conditions wetting
            * 2 - Hysteresis, initial conditions drying

        filenamesophy (Optional[str]): Names of input files with soil hydraulic tables for each soil layer
        tau (Optional[float]): Minimum pressure head difference to change wetting-drying
        swmacro (Literal[0, 1]): Switch for preferential flow due to macropores
        table_soilprofile (Table): Table with soil profile data
        table_soilhydrfunc (Optional[Table]): Table with soil hydraulic functions
    """
    swsophy: Literal[0, 1]
    swhyst: Literal[0, 1, 2]
    swmacro: Literal[0, 1]
    filenamesophy: Optional[str] = None
    tau: Optional[float] = None
    table_soilprofile: Table
    table_soilhydrfunc: Optional[Table] = None

    @model_validator(mode='after')
    def _validate_soil_profile(self) -> Self:

        if self.swsophy == 0:
            assert self.table_soilhydrfunc is not None, "table_soilhydrfunc is required when swsophy is True"
        else:
            assert self.filenamesophy is not None, "filenamesophy is required when swsophy is True"
        if self.swhyst in range(1, 3):
            assert self.tau is not None, "tau is required when swhyst is 1 or 2"

        return self

surfaceflow

SurfaceFlow

Bases: PySWAPBaseModel

Surface flow settings (ponding, runoff and runon).

Attributes:

Name Type Description
swpondmx Literal[0, 1]

Switch for variation ponding threshold for runoff

  • 0 - Ponding threshold for runoff is constant
  • 1 - Ponding threshold for runoff varies in time
swrunon Literal[0, 1]

Switch for runon

  • 0 - No runon
  • 1 - Use runon data
rsro float

Drainage resistance for surface runoff

rsroexp float

Exponent for drainage equation of surface runoff

pondmx Optional[float]

In case of ponding, minimum thickness for runoff

rufil Optional[str]

Name of the runon file

table_pondmxtb Optional[Table]

Minimum thickness for runoff as a function of time

Source code in pyswap/soilwater/surfaceflow.py
class SurfaceFlow(PySWAPBaseModel):
    """Surface flow settings (ponding, runoff and runon).

    Attributes:
        swpondmx (Literal[0, 1]): Switch for variation ponding threshold for runoff

            * 0 - Ponding threshold for runoff is constant
            * 1 - Ponding threshold for runoff varies in time

        swrunon (Literal[0, 1]): Switch for runon

            * 0 - No runon
            * 1 - Use runon data

        rsro (float): Drainage resistance for surface runoff
        rsroexp (float): Exponent for drainage equation of surface runoff
        pondmx (Optional[float]): In case of ponding, minimum thickness for runoff
        rufil (Optional[str]): Name of the runon file
        table_pondmxtb (Optional[Table]): Minimum thickness for runoff as a function of time
    """
    swpondmx: Literal[0, 1]
    swrunon: Literal[0, 1]
    rsro: float = 0.5
    rsroexp: float = 1.0
    pondmx: Optional[float] = None
    rufil: Optional[str] = None
    table_pondmxtb: Optional[Table] = None

    @model_validator(mode='after')
    def _validate_surface_flow(self) -> Self:

        if self.swpondmx == 0:
            assert self.pondmx is not None, "pondmx is required when swpondmx is 0"
        else:
            assert self.table_pondmxtb is not None, "pondmxtb is required when swpondmx is 1"

        if self.swrunon == 1:
            assert self.rufil is not None, "runfil is required when swrunon is 1"

        return self

tables

tables for the soil-water module

INIPRESSUREHEAD

Bases: BaseModel

Initial pressure head [cm, R] as a function of soil layer [1..N, I].

Attributes:

Name Type Description
ZI Series[int]

Series[int]: soil depth [-1.d5..0 cm, R].

H Series[float]

Series[float]: Initial soil water pressure head [-1.d10..1.d4 cm, R].

Source code in pyswap/soilwater/tables.py
class INIPRESSUREHEAD(BaseModel):
    """Initial pressure head [cm, R] as a function of soil layer [1..N, I].

    Attributes:
        ZI: Series[int]: soil depth [-1.d5..0 cm, R].
        H: Series[float]: Initial soil water pressure head [-1.d10..1.d4 cm, R].
    """

    ZI: Series[int] = pa.Field(ge=-1.0e5, le=0.0)
    H: Series[float] = pa.Field(ge=-1.0e10, le=1.0e4)

MXPONDTB

Bases: BaseModel

minimum thickness for runoff PONDMXTB [0..1000 cm, R] as function of time

Attributes:

Name Type Description
DATEPMX Series[DateTime]

Series[pa.DateTime]: Date of the ponding threshold for runoff.

PONDMXTB Series[float]

Series[float]: Minimum thickness for runoff.

Source code in pyswap/soilwater/tables.py
class MXPONDTB(BaseModel):
    """minimum thickness for runoff PONDMXTB [0..1000 cm, R] as function of time

    Attributes:
        DATEPMX: Series[pa.DateTime]: Date of the ponding threshold for runoff.
        PONDMXTB: Series[float]: Minimum thickness for runoff.
    """
    DATEPMX: Series[pa.DateTime]
    PONDMXTB: Series[float]

SOILHYDRFUNC

Bases: BaseModel

Soil hydraulic functions table.

Warning

ALFAW required only when the hysteresis option is set to 1 or 2. This column is set as optional column and (for now) is not checked.

Attributes:

Name Type Description
ORES Series[float]

Residual water content [0..1 cm3/cm3, R]

OSAT Series[float]

Saturated water content [0..1 cm3/cm3, R]

ALFA Series[float]

Parameter alfa of main drying curve [0.0001..100 /cm, R]

NPAR Series[float]

Parameter n [1.001..9 -, R]

LEXP Series[float]

Exponent in hydraulic conductivity function [-25..25 -, R]

KSATFIT Series[float]

Fitting parameter Ksat of hydraulic conductivity function [1.d-5..1d5 cm/d, R]

H_ENPR Series[float]

Air entry pressure head [-40.0..0.0 cm, R]

KSATEXM Series[float]

Measured hydraulic conductivity at saturated conditions [1.d-5..1d5 cm/d, R]

BDENS Series[float]

Dry soil bulk density [100..1d4 mg/cm3, R]

ALFAW Optional[Series[float]]

Alfa parameter of main wetting curve in case of hysteresis [0.0001..100 /cm, R]

Source code in pyswap/soilwater/tables.py
class SOILHYDRFUNC(BaseModel):
    """Soil hydraulic functions table.

    !!! warning
        ALFAW required only when the hysteresis option is set to 1 or 2. This column is set as optional column and (for now) is not checked.

    Attributes:
        ORES (Series[float]): Residual water content [0..1 cm3/cm3, R]
        OSAT (Series[float]): Saturated water content [0..1 cm3/cm3, R]
        ALFA (Series[float]): Parameter alfa of main drying curve [0.0001..100 /cm, R]
        NPAR (Series[float]): Parameter n [1.001..9 -, R]
        LEXP (Series[float]): Exponent in hydraulic conductivity function [-25..25 -, R]
        KSATFIT (Series[float]): Fitting parameter Ksat of hydraulic conductivity function [1.d-5..1d5 cm/d, R]
        H_ENPR (Series[float]): Air entry pressure head [-40.0..0.0 cm, R]
        KSATEXM (Series[float]): Measured hydraulic conductivity at saturated conditions [1.d-5..1d5 cm/d, R]
        BDENS (Series[float]): Dry soil bulk density [100..1d4 mg/cm3, R]
        ALFAW (Optional[Series[float]]): Alfa parameter of main wetting curve in case of hysteresis [0.0001..100 /cm, R]
    """

    ORES: Series[float] = pa.Field(ge=0.0, le=1.0)
    OSAT: Series[float] = pa.Field(ge=0.0, le=1.0)
    ALFA: Series[float] = pa.Field(ge=0.0001, le=100.0)
    NPAR: Series[float] = pa.Field(ge=1.001, le=9.0)
    LEXP: Series[float] = pa.Field(ge=-25.0, le=25.0)
    KSATFIT: Series[float] = pa.Field(ge=1.0e-5, le=1.0e5)
    H_ENPR: Series[float] = pa.Field(ge=-40.0, le=0.0)
    KSATEXM: Series[float] = pa.Field(ge=1.0e-5, le=1.0e5)
    BDENS: Series[float] = pa.Field(ge=100.0, le=1.0e4)
    ALFAW: Optional[Series[float]] = pa.Field(ge=0.0001, le=100.0)

SOILPROFILE

Bases: BaseModel

Vertical discretization of soil profile

Attributes:

Name Type Description
ISUBLAY Series[int]

Series[int]: number of sub layer, start with 1 at soil surface [1..MACP, I].

ISOILLAY Series[int]

Series[int]: number of soil physical layer, start with 1 at soil surface [1..MAHO, I].

HSUBLAY Series[float]

Series[float]: height of sub layer [0..1.d4 cm, R].

HCOMP Series[float]

Series[float]: height of compartments in the sub layer [0.0..1000.0 cm, R].

NCOMP Series[int]

Series[int]: number of compartments in the sub layer (Mind NCOMP = HSUBLAY/HCOMP) [1..MACP, I].

Source code in pyswap/soilwater/tables.py
class SOILPROFILE(BaseModel):
    """Vertical discretization of soil profile

    Attributes:
        ISUBLAY: Series[int]: number of sub layer, start with 1 at soil surface [1..MACP, I].
        ISOILLAY: Series[int]: number of soil physical layer, start with 1 at soil surface [1..MAHO, I].
        HSUBLAY: Series[float]: height of sub layer [0..1.d4 cm, R].
        HCOMP: Series[float]: height of compartments in the sub layer [0.0..1000.0 cm, R].
        NCOMP: Series[int]: number of compartments in the sub layer (Mind NCOMP = HSUBLAY/HCOMP) [1..MACP, I].
    """

    ISUBLAY: Series[int] = pa.Field(ge=1)
    ISOILLAY: Series[int] = pa.Field(ge=1)
    HSUBLAY: Series[float] = pa.Field(ge=0.0, le=1.0e4)
    HCOMP: Series[float] = pa.Field(ge=0.0, le=1.0e3)
    NCOMP: Series[int] = pa.Field(ge=1)

Drainage subpackage

Handling drainage settings and creation of the .dra file.

Modules:

Name Description
drainage

Holds the drainage settings for the SWAP model.

drafile

Create the .dra file.

drafile

Compose the .dra file for SWAP simulation.

Classes:

Name Description
DraFile

Class for the .dra file.

DraSettings

Class for the settings of the drainage module.

DrainageFluxTable

Class for the drainage flux table.

DrainageFormula

Class for the drainage formula.

DrainageInfiltrationResitance

Class for the drainage infiltration resistance.

Flux

Class for the flux.

DraFile

Bases: PySWAPBaseModel

Main class representing the drainage file (.dra) for SWAP.

Attributes:

Name Type Description
drfil str

Name of the file.

general Any

General settings.

fluxtable Optional[Any]

Flux table.

drainageformula Optional[Any]

Drainage formula.

drainageinfiltrationres Optional[Any]

Drainage infiltration resistance.

Source code in pyswap/drainage/drafile.py
class DraFile(PySWAPBaseModel):
    """Main class representing the drainage file (.dra) for SWAP.

    Attributes:
        drfil (str): Name of the file.
        general (Any): General settings.
        fluxtable (Optional[Any]): Flux table.
        drainageformula (Optional[Any]): Drainage formula.
        drainageinfiltrationres (Optional[Any]): Drainage infiltration resistance.
    """

    drfil: str
    general: DraSettings = Field(exclude=True)
    fluxtable: Optional[DrainageFluxTable] = Field(default=None, exclude=True)
    drainageformula: Optional[DrainageFormula] = Field(
        default=None, exclude=True)
    drainageinfiltrationres: Optional[DrainageInfiltrationResitance] = Field(
        default=None, exclude=True)

    @property
    def content(self):
        return self._concat_sections()

DraSettings

Bases: PySWAPBaseModel

General settings for the drainage file

Attributes:

Name Type Description
dramet Literal[1, 2, 3]

Method of lateral drainage calculation

  • 1 - Use table of drainage flux - groundwater level relation.
  • 2 - Use drainage formula of Hooghoudt or Ernst.
  • 3 - Use drainage/infiltration resistance, multi-level if needed.
swdivd Literal[1, 2]

Calculate vertical distribution of drainage flux in groundwater.

cofani Optional[FloatList]

specify anisotropy factor COFANI (horizontal/vertical saturated hydraulic conductivity) for each soil layer (maximum MAHO)

swdislay Literal[0, 1, 2, 3, '-']

Switch to adjust upper boundary of model discharge layer.

  • 0 - No adjustment
  • 1 - Adjusment based on depth of top of model discharge
  • 2 - Adjusment based on factor of top of model discharge
Source code in pyswap/drainage/drafile.py
class DraSettings(PySWAPBaseModel):
    """General settings for the drainage file

    Attributes:
        dramet (Literal[1, 2, 3]): Method of lateral drainage calculation

            * 1 - Use table of drainage flux - groundwater level relation.
            * 2 - Use drainage formula of Hooghoudt or Ernst.
            * 3 - Use drainage/infiltration resistance, multi-level if needed.

        swdivd (Literal[1, 2]): Calculate vertical distribution of drainage flux in groundwater.
        cofani (Optional[FloatList]): specify anisotropy factor COFANI (horizontal/vertical saturated hydraulic conductivity) for each soil layer (maximum MAHO)
        swdislay (Literal[0, 1, 2, 3, '-']): Switch to adjust upper boundary of model discharge layer.

            * 0 - No adjustment
            * 1 - Adjusment based on depth of top of model discharge
            * 2 - Adjusment based on factor of top of model discharge

    """
    dramet: Literal[1, 2, 3]
    swdivd: Literal[1, 2]
    cofani: Optional[FloatList]
    swdislay: Literal[0, 1, 2, 3, '-']

DrainageFluxTable

Bases: PySWAPBaseModel

Settings for the case when dramet is 1.

Attributes:

Name Type Description
lm1 float

Drain spacing

table_qdrntb Table

Table of drainage flux - groundwater level.

Source code in pyswap/drainage/drafile.py
class DrainageFluxTable(PySWAPBaseModel):
    """Settings for the case when dramet is 1.

    Attributes:
        lm1 (float): Drain spacing
        table_qdrntb (Table): Table of drainage flux - groundwater level.
    """
    lm1: float = Field(ge=1.0, le=1000.0)
    table_qdrntb: Table

DrainageFormula

Bases: PySWAPBaseModel

Settings for the case when dramet is 2.

Attributes:

Name Type Description
lm2 float

Drain spacing.

shape float

Shape factor to account for actual location between drain and water divide.

wetper float

Wet perimeter of the drain.

zbotdr float

Level of drain bottom.

entres float

Drain entry resistance.

ipos Literal[1, 2, 3, 4, 5]

Position of drain

  • 1 - On top of an impervious layer in a homogeneous profile
  • 2 - Above an impervious layer in a homogeneous profile
  • 3 - At the interface of a fine upper and a coarse lower soil layer
  • 4 - In the lower, more coarse soil layer
  • 5 - In the upper, more fine soil layer
basegw float

Level of impervious layer.

khtop float

Horizontal hydraulic conductivity of the top layer.

khbot Optional[float]

Horizontal hydraulic conductivity of the bottom layer.

zintf Optional[float]

Interface level of the coarse and fine soil layer.

kvtop Optional[float]

Vertical hydraulic conductivity of the top layer.

kvbot Optional[float]

Vertical hydraulic conductivity of the bottom layer.

geofac Optional[float]

Geometric factor of Ernst.

Source code in pyswap/drainage/drafile.py
class DrainageFormula(PySWAPBaseModel):
    """Settings for the case when dramet is 2.

    Attributes:
        lm2 (float): Drain spacing.
        shape (float): Shape factor to account for actual location between drain and water divide.
        wetper (float): Wet perimeter of the drain.
        zbotdr (float): Level of drain bottom.
        entres (float): Drain entry resistance.
        ipos (Literal[1, 2, 3, 4, 5]): Position of drain

            * 1 - On top of an impervious layer in a homogeneous profile
            * 2 - Above an impervious layer in a homogeneous profile
            * 3 - At the interface of a fine upper and a coarse lower soil layer
            * 4 - In the lower, more coarse soil layer
            * 5 - In the upper, more fine soil layer
        basegw (float): Level of impervious layer.
        khtop (float): Horizontal hydraulic conductivity of the top layer.
        khbot (Optional[float]): Horizontal hydraulic conductivity of the bottom layer.
        zintf (Optional[float]): Interface level of the coarse and fine soil layer.
        kvtop (Optional[float]): Vertical hydraulic conductivity of the top layer.
        kvbot (Optional[float]): Vertical hydraulic conductivity of the bottom layer.
        geofac (Optional[float]): Geometric factor of Ernst.
    """

    lm2: float = Field(ge=1.0, le=1000.0)
    shape: float = Field(**UNITRANGE)
    wetper: float = Field(ge=0.0, le=1000.0)
    zbotdr: float = Field(ge=-1000.0, le=0.0)
    entres: float = Field(ge=0.0, le=1000.0)
    ipos: Literal[1, 2, 3, 4, 5]
    basegw: float = Field(ge=-1.0e4, le=0.0)
    khtop: float = Field(ge=0.0, le=1000.0)
    khbot: Optional[float] = Field(default=None, ge=0.0, le=1000.0)
    zintf: Optional[float] = Field(default=None, ge=-1.0e4, le=0.0)
    kvtop: Optional[float] = Field(default=None, ge=0.0, le=1000.0)
    kvbot: Optional[float] = Field(default=None, ge=0.0, le=1000.0)
    geofac: Optional[float] = Field(default=None, ge=0.0, le=100.0)

    @model_validator(mode='after')
    def _validate_draformula(self) -> Self:
        if self.ipos in [3, 4, 5]:
            assert self.khbot is not None, 'khbot has to be provided if IPOS is 3.'
            assert self.zintf is not None, 'zintf has to be provided if IPOS is 3.'
        if self.ipos in [4, 5]:
            assert self.kvtop is not None, 'kvtop has to be provided if IPOS is 3.'
            assert self.kvbot is not None, 'kvbot has to be provided if IPOS is 3.'
        if self.ipos == 5:
            assert self.geofac is not None, 'geofac has to be provided if IPOS is 3.'

        return self

DrainageInfiltrationResitance

Bases: PySWAPBaseModel

Settings for the case when dramet is 3.

Attributes:

Name Type Description
nrlevs int

Number of drainage levels.

swintfl Literal[0, 1]

Option for interflow in highest drainage level (shallow system with short residence time).

cofintflb float

Coefficient for interflow relation.

expintflb float

Exponent for interflow relation.

swtopnrsrf Literal[0, 1]

Switch to enable adjustment of model discharge layer.

list_levelfluxes ObjectList

List of level fluxes.

Source code in pyswap/drainage/drafile.py
class DrainageInfiltrationResitance(PySWAPBaseModel):
    """Settings for the case when dramet is 3.

    Attributes:
        nrlevs (int): Number of drainage levels.
        swintfl (Literal[0, 1]): Option for interflow in highest drainage level (shallow system with short residence time).
        cofintflb (float): Coefficient for interflow relation.
        expintflb (float): Exponent for interflow relation.
        swtopnrsrf (Literal[0, 1]): Switch to enable adjustment of model discharge layer.
        list_levelfluxes (ObjectList): List of level fluxes.
    """
    nrlevs: int = Field(ge=1, le=5)
    swintfl: Literal[0, 1]
    cofintflb: Optional[float] = Field(default=None, ge=0.01, le=10.0)
    expintflb: Optional[float] = Field(default=None, ge=0.1, le=1.0)
    swtopnrsrf: Optional[Literal[0, 1]] = None
    list_levelfluxes: Optional[ObjectList] = None

    @model_validator(mode='after')
    def _validate_drainfiltrationres(self) -> Self:
        if self.swintfl == 1:
            assert self.cofintflb is not None, 'cofintflb has to be provided if swintfl is 1.'
            assert self.expintflb is not None, 'expintflb has to be provided if swintfl is 1.'

        return self

Flux

Bases: PySWAPBaseModel

These objects are needed for the DrainageInfiltrationResitance class. Flux object should be created for each level of drainage.

Attributes:

Name Type Description
level_number int

Number of the level.

drares float

Drainage resistance.

infres float

Infiltration resistance.

swallo Literal[1, 2]

Switch to allow drainage from this level.

l Optional[float]

Drain spacing.

zbotdr float

Level of the bottom of the drain.

swdtyp Literal[1, 2]

Drainage type.

  • 1 - drain tube.
  • 2 - open channel.
table_datowltb Table

date DATOWL [date] and channel water level LEVEL. Add suffix to the dataframe headers according to the level number.

Source code in pyswap/drainage/drafile.py
class Flux(PySWAPBaseModel):
    """These objects are needed for the DrainageInfiltrationResitance class. Flux object should be 
    created for each level of drainage.

    Attributes:
        level_number (int): Number of the level.
        drares (float): Drainage resistance.
        infres (float): Infiltration resistance.
        swallo (Literal[1, 2]): Switch to allow drainage from this level.
        l (Optional[float]): Drain spacing.
        zbotdr (float): Level of the bottom of the drain.
        swdtyp (Literal[1, 2]): Drainage type.

            * 1 - drain tube.
            * 2 - open channel.

        table_datowltb (Table): date DATOWL [date] and channel water level LEVEL. Add suffix to the 
            dataframe headers according to the level number.
    """
    level_number: int = Field(exclude=True, ge=1, le=5)
    drares: float = Field(ge=10.0, le=1.0e5)
    infres: float = Field(ge=10.0, le=1.0e5)
    swallo: Literal[1, 2, 3]
    l: Optional[float] = Field(ge=1.0, le=1.0e5)
    zbotdr: float = Field(ge=-1000.0, le=0.0)
    swdtyp: Literal[1, 2]
    table_datowltb: Table

    @model_validator(mode='after')
    def _validate_flux(self) -> Self:
        if self.swallo == 1:
            assert self.l is not None, 'l has to be provided if swallo is 1.'

        return self

    def model_dump(self, **kwargs):

        d = super().model_dump(**kwargs)

        # If level_number is set, modify the key names.
        if self.level_number is not None:
            new_d = {}
            suffix = str(self.level_number)
            for key, value in d.items():
                new_d[key + suffix] = value
            return new_d
        return d

drainage

Lateral drainage settings.

Classes:

Name Description
Drainage

The lateral drainage settings.

Drainage

Bases: PySWAPBaseModel

The lateral drainage settings of the simulation.

Attributes:

Name Type Description
swdra Literal[0, 1, 2]

Switch for lateral drainage.

  • 0 - No drainage.
  • 1 - Simulate with a basic drainage routine.
  • 2 - Simulate with surface water management.
drafile Optional[Any]

Content of the drainage file.

Source code in pyswap/drainage/drainage.py
class Drainage(PySWAPBaseModel):
    """The lateral drainage settings of the simulation.

    Attributes:
        swdra (Literal[0, 1, 2]): Switch for lateral drainage.

            * 0 - No drainage.
            * 1 - Simulate with a basic drainage routine.
            * 2 - Simulate with surface water management.

        drafile (Optional[Any]): Content of the drainage file.
    """

    swdra: Literal[0, 1, 2]
    drafile: Optional[DraFile] = Field(default=None)

    @model_validator(mode='after')
    def _validate_drainage(self) -> Self:
        if self.swdra > 0:
            assert self.drafile is not None, "drafile is required when swdra is 1 or 2"

        return self

    def write_dra(self, path: str):
        save_file(
            string=self.drafile.content,
            extension='dra',
            fname=self.drafile.drfil,
            path=path
        )

Boundary subpackage

This subpackage deals with boundary conditions for the SWAP model.

Modules:

Name Description
bottomboundary

Bottom boundary condition settings for the SWAP model.

bbcfile

Bottom boundary condition settings for the SWAP model.

Classes:

Name Description
BottomBoundary

Holds the settings of the bottom boundary conditions of the .swp file.

BBCFile

Bases: PySWAPBaseModel

Bottom boundary settings for SWAP model.

Attributes:

Name Type Description
swbotb Literal[1, 2, 3, 4, 5, 6, 7, 8]

Switch for type of bottom boundary.

  • 1 - prescribe groundwater level;
  • 2 - prescribe bottom flux;
  • 3 - calculate bottom flux from hydraulic head of deep aquifer;
  • 4 - calculate bottom flux as function of groundwater level;
  • 5 - prescribe soil water pressure head of bottom compartment;
  • 6 - bottom flux equals zero;
  • 7 - free drainage of soil profile;
  • 8 - free outflow at soil-air interface.
sw2 Optional[Literal[1, 2]]

Specify whether a sinus function or a table are used for the bottom flux.

  • 1 - sinus function;
  • 2 - table.
sw3 Optional[Literal[1, 2]]

Specify whether a sinus function or a table are used for the hydraulic head in the deep aquifer.

  • 1 - sinus function;
  • 2 - table.
sw4 Optional[Literal[0, 1]]

An extra groundwater flux can be specified which is added to above specified flux.

  • 0 - no extra flux;
  • 1 - extra flux.
swbotb3resvert Optional[Literal[0, 1]]

Switch for vertical hydraulic resistance between bottom boundary and groundwater level.

  • 0 - Include vertical hydraulic resistance
  • 1 - Suppress vertical hydraulic resistance
swbotb3impl Optional[Literal[0, 1]]

Switch for numerical solution of bottom flux.

  • 0 - Explicit solution (choose always when SHAPE < 1.0);
  • 1 - Implicit solution.
swqhbot Optional[Literal[1, 2]]

Specify whether an exponential relation or a table is used.

  • 1 - bottom flux is calculated with an exponential relation
  • 2 - bottom flux is derived from a table
bbcfile Optional[str]

Name of file with bottom boundary data (without .BBC extension).

sinave Optional[float]

Average value of bottom flux.

sinamp Optional[float]

Amplitude of bottom flux sine function.

sinmax Optional[float]

Time of the year with maximum bottom flux.

shape Optional[float]

Shape factor to derive average groundwater level.

hdrain Optional[float]

Mean drain base to correct for average groundwater level.

rimlay Optional[float]

Vertical resistance of aquitard.

aqave Optional[float]

Average hydraulic head in underlaying aquifer.

aqamp Optional[float]

Amplitude hydraulic head sinus wave.

aqtmax Optional[float]

First time of the year with maximum hydraulic head.

aqper Optional[float]

Period of hydraulic head sinus wave.

cofqha Optional[float]

Coefficient A for exponential relation for bottom flux.

cofqhb Optional[float]

Coefficient B for exponential relation for bottom flux.

cofqhc Optional[float]

Coefficient C for exponential relation for bottom flux.

gwlevel Optional[Table]

Table with groundwater level data.

table_qbot Optional[Table]

Table with bottom flux data.

table_haquif Optional[Table]

Table with average pressure head in underlaying aquifer.

table_qbot4 Optional[Table]

Table with bottom flux data.

table_qtab Optional[Table]

Table with groundwater level-bottom flux relation

table_hbot Optional[Table]

Table with the bottom compartment pressure head.

Source code in pyswap/boundary/bbcfile.py
class BBCFile(PySWAPBaseModel):
    """
    Bottom boundary settings for SWAP model.

    Attributes:
        swbotb (Literal[1, 2, 3, 4, 5, 6, 7, 8]): Switch for type of bottom boundary.

            * 1 - prescribe groundwater level;
            * 2 - prescribe bottom flux;
            * 3 - calculate bottom flux from hydraulic head of deep aquifer;
            * 4 - calculate bottom flux as function of groundwater level;
            * 5 - prescribe soil water pressure head of bottom compartment;
            * 6 - bottom flux equals zero;
            * 7 - free drainage of soil profile;
            * 8 - free outflow at soil-air interface.

        sw2 (Optional[Literal[1, 2]]): Specify whether a sinus function or a table are used for the bottom flux.

            * 1 - sinus function;
            * 2 - table.

        sw3 (Optional[Literal[1, 2]]): Specify whether a sinus function or a table are used for the hydraulic head in the deep aquifer.

            * 1 - sinus function;
            * 2 - table.

        sw4 (Optional[Literal[0, 1]]): An extra groundwater flux can be specified which is added to above specified flux.

            * 0 - no extra flux;
            * 1 - extra flux.

        swbotb3resvert (Optional[Literal[0, 1]]): Switch for vertical hydraulic resistance between bottom boundary and groundwater level.

            * 0 - Include vertical hydraulic resistance
            * 1 - Suppress vertical hydraulic resistance

        swbotb3impl (Optional[Literal[0, 1]]): Switch for numerical solution of bottom flux.

            * 0 - Explicit solution (choose always when SHAPE < 1.0);
            * 1 - Implicit solution.

        swqhbot (Optional[Literal[1, 2]]): Specify whether an exponential relation or a table is used.

            * 1 - bottom flux is calculated with an exponential relation
            * 2 - bottom flux is derived from a table

        bbcfile (Optional[str]): Name of file with bottom boundary data (without .BBC extension).
        sinave (Optional[float]): Average value of bottom flux.
        sinamp (Optional[float]): Amplitude of bottom flux sine function.
        sinmax (Optional[float]): Time of the year with maximum bottom flux.
        shape (Optional[float]): Shape factor to derive average groundwater level.
        hdrain (Optional[float]): Mean drain base to correct for average groundwater level.
        rimlay (Optional[float]): Vertical resistance of aquitard.
        aqave (Optional[float]): Average hydraulic head in underlaying aquifer.
        aqamp (Optional[float]): Amplitude hydraulic head sinus wave.
        aqtmax (Optional[float]): First time of the year with maximum hydraulic head.
        aqper (Optional[float]): Period of hydraulic head sinus wave.
        cofqha (Optional[float]): Coefficient A for exponential relation for bottom flux.
        cofqhb (Optional[float]): Coefficient B for exponential relation for bottom flux.
        cofqhc (Optional[float]): Coefficient C for exponential relation for bottom flux.
        gwlevel (Optional[Table]): Table with groundwater level data.
        table_qbot (Optional[Table]): Table with bottom flux data.
        table_haquif (Optional[Table]): Table with average pressure head in underlaying aquifer.
        table_qbot4 (Optional[Table]): Table with bottom flux data.
        table_qtab (Optional[Table]): Table with groundwater level-bottom flux relation
        table_hbot (Optional[Table]): Table with the bottom compartment pressure head.
    """

    swbotb: Literal[1, 2, 3, 4, 5, 6, 7, 8]
    sw2: Optional[Literal[1, 2]] = Field(default=None)
    sw3: Optional[Literal[1, 2]] = Field(default=None)
    sw4: Optional[Literal[0, 1]] = Field(default=None)
    swbotb3resvert: Optional[Literal[0, 1]] = Field(default=None)
    swbotb3impl: Optional[Literal[0, 1]] = Field(default=None)
    swqhbot: Optional[Literal[1, 2]] = Field(default=None)
    sinave: Optional[float] = Field(
        ge=-10.0, le=10.0, default=None)
    sinamp: Optional[float] = Field(
        ge=-10.0, le=10.0, default=None)
    sinmax: Optional[float] = Field(
        ge=0.0, le=366.0, default=None)
    shape: Optional[float] = Field(default=None)
    hdrain: Optional[float] = Field(default=None)
    rimlay: Optional[float] = Field(default=None)
    aqave: Optional[float] = Field(default=None)
    aqamp: Optional[float] = Field(default=None)
    aqtmax: Optional[float] = Field(default=None)
    aqper: Optional[float] = Field(default=None)
    cofqha: Optional[float] = Field(default=None)
    cofqhb: Optional[float] = Field(default=None)
    cofqhc: Optional[float] = Field(default=None)
    table_gwlevel: Optional[Table] = Field(default=None)
    table_qbot: Optional[Table] = Field(default=None)
    table_haquif: Optional[Table] = Field(default=None)
    table_qbot4: Optional[Table] = Field(default=None)
    table_qtab: Optional[Table] = Field(default=None)
    table_hbot5: Optional[Table] = Field(default=None)

    @model_validator(mode='after')
    def _check_swbotb(self) -> Self:
        if self.swbotb == 1:
            assert not self.table_gwlevel.empty, 'table_gwlevel must be provided if swbotb is 1'
        elif self.swbotb == 2:
            assert self.sw2, 'sw2 must be provided if swbotb is 2'
            if self.sw2 == 1:
                assert self.sinave, 'sinave must be provided if sw2 is 1'
                assert self.sinamp, 'sinamp must be provided if sw2 is 1'
                assert self.sinmax, 'sinmax must be provided if sw2 is 1'
            elif self.sw2 == 2:
                assert self.table_qbot, 'qbot must be provided if sw2 is 2'
        elif self.swbotb == 3:
            assert self.sw3, 'sw3 must be provided if swbotb is 3'
            if self.sw3 == 1:
                assert self.aqave, 'aqave must be provided if sw3 is 1'
                assert self.aqamp, 'auamp must be provided if sw3 is 1'
                assert self.aqtmax, 'aqtmax must be provided if sw3 is 1'
                assert self.aqper, 'aqper must be provided if sw3 is 1'
            elif self.sw3 == 2:
                assert self.table_haquif, 'haquif must be provided if sw3 is 2'
        elif self.swbotb == 4:
            assert self.swqhbot, 'swqhbot must be provided if swbotb is 4'
            if self.swqhbot == 1:
                assert self.cofqha, 'cofqha must be provided if swqhbot is 1'
                assert self.cofqhb, 'cofqhb must be provided if swqhbot is 1'
                assert self.cofqhc, 'cofqhc must be provided if swqhbot is 1'
            elif self.swqhbot == 2:
                assert self.table_qtab, 'qtab must be provided if swqhbot is 2'
        elif self.swbotb == 5:
            assert self.table_hbot5, 'hbot5 must be provided if swbotb is 5'

        return self

    @property
    def content(self):
        return self._concat_sections()

boundary

Bottom boundary condition settings for the SWAP model.

Classes:

Name Description
BottomBoundary

Holds the settings of the bottom boundary conditions of the .swp file.

BottomBoundary

Bases: PySWAPBaseModel

Bottom boundary settings for SWAP model.

Attributes:

Name Type Description
swbbcfile Literal[0, 1]

Switch for file with bottom boundary data:

  • 0 - data are specified in current file
  • 1 - data are specified in separate file
swbotb Literal[1, 2, 3, 4, 5, 6, 7, 8]

Switch for type of bottom boundary.

  • 1 - prescribe groundwater level;
  • 2 - prescribe bottom flux;
  • 3 - calculate bottom flux from hydraulic head of deep aquifer;
  • 4 - calculate bottom flux as function of groundwater level;
  • 5 - prescribe soil water pressure head of bottom compartment;
  • 6 - bottom flux equals zero;
  • 7 - free drainage of soil profile;
  • 8 - free outflow at soil-air interface.
sw2 Optional[Literal[1, 2]]

Specify whether a sinus function or a table are used for the bottom flux.

  • 1 - sinus function;
  • 2 - table.
sw3 Optional[Literal[1, 2]]

Specify whether a sinus function or a table are used for the hydraulic head in the deep aquifer.

  • 1 - sinus function;
  • 2 - table.
sw4 Optional[Literal[0, 1]]

An extra groundwater flux can be specified which is added to above specified flux.

  • 0 - no extra flux;
  • 1 - extra flux.
swbotb3resvert Optional[Literal[0, 1]]

Switch for vertical hydraulic resistance between bottom boundary and groundwater level.

  • 0 - Include vertical hydraulic resistance
  • 1 - Suppress vertical hydraulic resistance
swbotb3impl Optional[Literal[0, 1]]

Switch for numerical solution of bottom flux.

  • 0 - Explicit solution (choose always when SHAPE < 1.0);
  • 1 - Implicit solution.
swqhbot Optional[Literal[1, 2]]

Specify whether an exponential relation or a table is used.

  • 1 - bottom flux is calculated with an exponential relation
  • 2 - bottom flux is derived from a table
bbcfile Optional[str]

Name of file with bottom boundary data (without .BBC extension).

sinave Optional[float]

Average value of bottom flux.

sinamp Optional[float]

Amplitude of bottom flux sine function.

sinmax Optional[float]

Time of the year with maximum bottom flux.

shape Optional[float]

Shape factor to derive average groundwater level.

hdrain Optional[float]

Mean drain base to correct for average groundwater level.

rimlay Optional[float]

Vertical resistance of aquitard.

aqave Optional[float]

Average hydraulic head in underlaying aquifer.

aqamp Optional[float]

Amplitude hydraulic head sinus wave.

aqtmax Optional[float]

First time of the year with maximum hydraulic head.

aqper Optional[float]

Period of hydraulic head sinus wave.

cofqha Optional[float]

Coefficient A for exponential relation for bottom flux.

cofqhb Optional[float]

Coefficient B for exponential relation for bottom flux.

cofqhc Optional[float]

Coefficient C for exponential relation for bottom flux.

gwlevel Optional[Table]

Table with groundwater level data.

table_qbot Optional[Table]

Table with bottom flux data.

table_haquif Optional[Table]

Table with average pressure head in underlaying aquifer.

table_qbot4 Optional[Table]

Table with bottom flux data.

table_qtab Optional[Table]

Table with groundwater level-bottom flux relation

table_hbot Optional[Table]

Table with the bottom compartment pressure head.

Source code in pyswap/boundary/boundary.py
class BottomBoundary(PySWAPBaseModel):
    """
    Bottom boundary settings for SWAP model.

    Attributes:
        swbbcfile (Literal[0, 1]): Switch for file with bottom boundary data:

            * 0 - data are specified in current file
            * 1 - data are specified in separate file

        swbotb (Literal[1, 2, 3, 4, 5, 6, 7, 8]): Switch for type of bottom boundary.

            * 1 - prescribe groundwater level;
            * 2 - prescribe bottom flux;
            * 3 - calculate bottom flux from hydraulic head of deep aquifer;
            * 4 - calculate bottom flux as function of groundwater level;
            * 5 - prescribe soil water pressure head of bottom compartment;
            * 6 - bottom flux equals zero;
            * 7 - free drainage of soil profile;
            * 8 - free outflow at soil-air interface.

        sw2 (Optional[Literal[1, 2]]): Specify whether a sinus function or a table are used for the bottom flux.

            * 1 - sinus function;
            * 2 - table.

        sw3 (Optional[Literal[1, 2]]): Specify whether a sinus function or a table are used for the hydraulic head in the deep aquifer.

            * 1 - sinus function;
            * 2 - table.

        sw4 (Optional[Literal[0, 1]]): An extra groundwater flux can be specified which is added to above specified flux.

            * 0 - no extra flux;
            * 1 - extra flux.

        swbotb3resvert (Optional[Literal[0, 1]]): Switch for vertical hydraulic resistance between bottom boundary and groundwater level.

            * 0 - Include vertical hydraulic resistance
            * 1 - Suppress vertical hydraulic resistance

        swbotb3impl (Optional[Literal[0, 1]]): Switch for numerical solution of bottom flux.

            * 0 - Explicit solution (choose always when SHAPE < 1.0);
            * 1 - Implicit solution.

        swqhbot (Optional[Literal[1, 2]]): Specify whether an exponential relation or a table is used.

            * 1 - bottom flux is calculated with an exponential relation
            * 2 - bottom flux is derived from a table

        bbcfile (Optional[str]): Name of file with bottom boundary data (without .BBC extension).
        sinave (Optional[float]): Average value of bottom flux.
        sinamp (Optional[float]): Amplitude of bottom flux sine function.
        sinmax (Optional[float]): Time of the year with maximum bottom flux.
        shape (Optional[float]): Shape factor to derive average groundwater level.
        hdrain (Optional[float]): Mean drain base to correct for average groundwater level.
        rimlay (Optional[float]): Vertical resistance of aquitard.
        aqave (Optional[float]): Average hydraulic head in underlaying aquifer.
        aqamp (Optional[float]): Amplitude hydraulic head sinus wave.
        aqtmax (Optional[float]): First time of the year with maximum hydraulic head.
        aqper (Optional[float]): Period of hydraulic head sinus wave.
        cofqha (Optional[float]): Coefficient A for exponential relation for bottom flux.
        cofqhb (Optional[float]): Coefficient B for exponential relation for bottom flux.
        cofqhc (Optional[float]): Coefficient C for exponential relation for bottom flux.
        gwlevel (Optional[Table]): Table with groundwater level data.
        table_qbot (Optional[Table]): Table with bottom flux data.
        table_haquif (Optional[Table]): Table with average pressure head in underlaying aquifer.
        table_qbot4 (Optional[Table]): Table with bottom flux data.
        table_qtab (Optional[Table]): Table with groundwater level-bottom flux relation
        table_hbot (Optional[Table]): Table with the bottom compartment pressure head.
    """

    swbbcfile: Literal[0, 1]
    bbcfil: Optional[str] = None
    swbotb: Optional[Literal[1, 2, 3, 4, 5, 6, 7, 8]] = None
    sw2: Optional[Literal[1, 2]] = None
    sw3: Optional[Literal[1, 2]] = None
    sw4: Optional[Literal[0, 1]] = None
    swbotb3resvert: Optional[Literal[0, 1]] = None
    swbotb3impl: Optional[Literal[0, 1]] = None
    swqhbot: Optional[Literal[1, 2]] = None
    bbcfil: Optional[str] = None
    bbcfile: Optional[BBCFile] = None
    sinave: Optional[float] = Field(ge=-10.0, le=10.0, default=None)
    sinamp: Optional[float] = Field(ge=-10.0, le=10.0, default=None)
    sinmax: Optional[float] = Field(ge=0.0, le=366.0, default=None)
    shape: Optional[float] = None
    hdrain: Optional[float] = None
    rimlay: Optional[float] = None
    aqave: Optional[float] = None
    aqamp: Optional[float] = None
    aqtmax: Optional[float] = None
    aqper: Optional[float] = None
    cofqha: Optional[float] = None
    cofqhb: Optional[float] = None
    cofqhc: Optional[float] = None
    table_gwlevel: Optional[Table] = None
    table_qbot: Optional[Table] = None
    table_haquif: Optional[Table] = None
    table_qbot4: Optional[Table] = None
    table_qtab: Optional[Table] = None
    table_hbot5: Optional[Table] = None

    @model_validator(mode='after')
    def _check_swbotb(self) -> Self:
        if self.swbotb == 1:
            assert self.table_gwlevel, 'table_gwlevel must be provided if swbotb is 1'
        elif self.swbotb == 2:
            assert self.sw2, 'sw2 must be provided if swbotb is 2'
            if self.sw2 == 1:
                assert self.sinave, 'sinave must be provided if sw2 is 1'
                assert self.sinamp, 'sinamp must be provided if sw2 is 1'
                assert self.sinmax, 'sinmax must be provided if sw2 is 1'
            elif self.sw2 == 2:
                assert self.table_qbot, 'qbot must be provided if sw2 is 2'
        elif self.swbotb == 3:
            assert self.sw3, 'sw3 must be provided if swbotb is 3'
            if self.sw3 == 1:
                assert self.aqave, 'aqave must be provided if sw3 is 1'
                assert self.aqamp, 'auamp must be provided if sw3 is 1'
                assert self.aqtmax, 'aqtmax must be provided if sw3 is 1'
                assert self.aqper, 'aqper must be provided if sw3 is 1'
            elif self.sw3 == 2:
                assert self.table_haquif, 'haquif must be provided if sw3 is 2'
        elif self.swbotb == 4:
            assert self.swqhbot, 'swqhbot must be provided if swbotb is 4'
            if self.swqhbot == 1:
                assert self.cofqha, 'cofqha must be provided if swqhbot is 1'
                assert self.cofqhb, 'cofqhb must be provided if swqhbot is 1'
                assert self.cofqhc, 'cofqhc must be provided if swqhbot is 1'
            elif self.swqhbot == 2:
                assert self.table_qtab, 'qtab must be provided if swqhbot is 2'
        elif self.swbotb == 5:
            assert self.table_hbot5, 'hbot5 must be provided if swbotb is 5'

        return self

    def write_bbc(self, path: str):
        save_file(
            string=self.bbcfile.model_string(),
            extension='bbc',
            fname=self.bbcfil,
            path=path
        )

Additional settings

heatflow

Heat flow settings for SWAP simulation.

Classes:

Name Description
HeatFlow

Heat flow settings for SWAP simulation.

HeatFlow

Bases: PySWAPBaseModel

Heat flow settings for SWAP simulation.

Warning

table_initsoil is not validated because it depends of swinco setting.

Attributes:

Name Type Description
swhea Literal[0, 1]

Switch for heat flow.

swcalt Optional[Literal[1, 2]]
  • 1 - analytical method
  • 2 - numerical method
tampli Optional[float]

Amplitude of annual temperature wave at soil surface [0..50 oC, R]

tmean Optional[float]

Mean annual temperature at soil surface [-10..30 oC, R]

timref Optional[float]

Time at which the sinus temperature wave reaches it's top [0..366.0 d, R]

ddamp Optional[float]

Damping depth of soil temperature wave [1..500 cm, R]

swtopbhea Optional[Literal[1, 2]]

Define top boundary condition

  • 1 - use air temperature of meteo input file as top boundary
  • 2 - use measured top soil temperature as top boundary
tsoilfile Optional[str]

name of input file with soil surface temperatures without extension .TSS

swbotbhea Optional[Literal[1, 2]]

Define bottom boundary condition

  • 1 - no heat flux
  • 2 - prescribe bottom temperature
table_soiltextures Optional[Table]

for each physical soil layer the soil texture (g/g mineral parts) and the organic matter content (g/g dry soil)

table_initsoil Optional[Table]

initial temperature TSOIL [-50..50 oC, R] as function of soil depth ZH [-100000..0 cm, R]

table_bbctsoil Optional[Table]

bottom boundary temperature TBOT [-50..50 oC, R] as function of date DATET [date]

Source code in pyswap/extras/heatflow.py
class HeatFlow(PySWAPBaseModel):
    """Heat flow settings for SWAP simulation.

    !!! warning

        table_initsoil is not validated because it depends of swinco setting.

    Attributes:
        swhea (Literal[0, 1]): Switch for heat flow.
        swcalt (Optional[Literal[1, 2]]):

            * 1 - analytical method
            * 2 - numerical method

        tampli (Optional[float]): Amplitude of annual temperature wave at soil surface [0..50 oC, R]
        tmean (Optional[float]): Mean annual temperature at soil surface [-10..30 oC, R]
        timref (Optional[float]): Time at which the sinus temperature wave reaches it's top [0..366.0 d, R]
        ddamp (Optional[float]): Damping depth of soil temperature wave [1..500 cm, R]
        swtopbhea (Optional[Literal[1, 2]]): Define top boundary condition

            * 1 - use air temperature of meteo input file as top boundary
            * 2 - use measured top soil temperature as top boundary

        tsoilfile (Optional[str]): name of input file with soil surface temperatures without extension .TSS
        swbotbhea (Optional[Literal[1, 2]]): Define bottom boundary condition

            * 1 - no heat flux
            * 2 - prescribe bottom temperature

        table_soiltextures (Optional[Table]): for each physical soil layer the soil texture (g/g mineral parts) and the organic matter content (g/g dry soil)
        table_initsoil (Optional[Table]): initial temperature TSOIL [-50..50 oC, R] as function of soil depth ZH [-100000..0 cm, R]
        table_bbctsoil (Optional[Table]): bottom boundary temperature TBOT [-50..50 oC, R] as function of date DATET [date]
    """

    swhea: Literal[0, 1]
    swcalt: Optional[Literal[1, 2]] = None
    tampli: Optional[float] = None
    tmean: Optional[float] = None
    timref: Optional[float] = None
    ddamp: Optional[float] = None
    swtopbhea: Optional[Literal[1, 2]] = None
    tsoilfile: Optional[str] = None
    swbotbhea: Optional[Literal[1, 2]] = None
    table_soiltextures: Optional[Table] = None
    table_initsoil: Optional[Table] = None
    table_bbctsoil: Optional[Table] = None

    @model_validator(mode='after')
    def _check_heatflow(self) -> Self:
        if self.swhea == 1:
            assert self.swcalt is not None, "swcalt must be specified if swhea is 1"
            if self.swcalt == 1:
                assert self.tampli is not None, "tampli must be specified if swcalt is 1"
                assert self.tmean is not None, "tmean must be specified if swcalt is 1"
                assert self.timref is not None, "timref must be specified if swcalt is 1"
                assert self.ddamp is not None, "ddamp must be specified if swcalt is 1"
            elif self.swcalt == 2:
                assert self.table_soiltextures is not None, "table_soiltextures must be specified if swcalt is 2"
                if self.swtopbhea == 2:
                    assert self.tsoilfile is not None, "tsoilfile must be specified if swtopbhea is 2"
                if self.swbotbhea == 2:
                    assert self.table_bbctsoil is not None, "table_bbctsoil must be specified if swbotbhea is 2"
        return self

solutetransport

Solute transport settings for the SWAP simulation.

Classes:

Name Description
SoluteTransport

Solute transport settings.

SoluteTransport

Bases: PySWAPBaseModel

Solute transport settings.

Warning

Validation not yet included in the current release.

Attributes:

Name Type Description
swsolu Literal[0, 1]
cpre Optional[float]
cdrain Optional[float]
swbotbc Optional[Literal[0, 1]]
cseep Optional[float]
ddif Optional[float]
tscf Optional[float]
swsp Optional[Literal[0, 1]]
frexp Optional[float]
cref Optional[float]
swdc Optional[Literal[0, 1]]
gampar Optional[float]
rtheta Optional[float]
bexp Optional[float]
swbr Optional[Literal[0, 1]]
daquif Optional[float]
poros Optional[float]
kfsat Optional[float]
decsat Optional[float]
cdraini Optional[float]
table_cseeparrtb Optional[Table]
table_inissoil Optional[Table]
table_miscellaneous Optional[Table]
Source code in pyswap/extras/solutetransport.py
class SoluteTransport(PySWAPBaseModel):
    """
    Solute transport settings.

    !!! warning

        Validation not yet included in the current release.

    Attributes:
        swsolu (Literal[0, 1]): 
        cpre (Optional[float]):
        cdrain (Optional[float]):
        swbotbc (Optional[Literal[0, 1]]):
        cseep (Optional[float]):
        ddif (Optional[float]):
        tscf (Optional[float]):
        swsp (Optional[Literal[0, 1]]):
        frexp (Optional[float]):
        cref (Optional[float]):
        swdc (Optional[Literal[0, 1]]):
        gampar (Optional[float]):
        rtheta (Optional[float]):
        bexp (Optional[float]):
        swbr (Optional[Literal[0, 1]]):
        daquif (Optional[float]):
        poros (Optional[float]):
        kfsat (Optional[float]):
        decsat (Optional[float]):
        cdraini (Optional[float]):
        table_cseeparrtb (Optional[Table]):
        table_inissoil (Optional[Table]):
        table_miscellaneous (Optional[Table]):
    """
    swsolu: Literal[0, 1]
    cpre: Optional[float] = None
    cdrain: Optional[float] = None
    swbotbc: Optional[Literal[0, 1]] = None
    cseep: Optional[float] = None
    ddif: Optional[float] = None
    tscf: Optional[float] = None
    swsp: Optional[Literal[0, 1]] = None
    frexp: Optional[float] = None
    cref: Optional[float] = None
    swdc: Optional[Literal[0, 1]] = None
    gampar: Optional[float] = None
    rtheta: Optional[float] = None
    bexp: Optional[float] = None
    swbr: Optional[Literal[0, 1]] = None
    daquif: Optional[float] = None
    poros: Optional[float] = None
    kfsat: Optional[float] = None
    decsat: Optional[float] = None
    cdraini: Optional[float] = None
    table_cseeparrtb: Optional[Table] = None
    table_inissoil: Optional[Table] = None
    table_miscellaneous: Optional[Table] = None

tables

These are tables for the extras module

INITSOILTEMP

Bases: BaseModel

Table for initial soil temperature.

Attributes:

Name Type Description
ZH float

Depth of soil layer [cm, R]

TSOIL float

Initial temperature [oC, R]

Source code in pyswap/extras/tables.py
class INITSOILTEMP(BaseModel):
    """Table for initial soil temperature.

    Attributes:
        ZH (float): Depth of soil layer [cm, R]
        TSOIL (float): Initial temperature [oC, R]
    """
    ZH: float = pa.Field(ge=-100000, le=0)
    TSOIL: float = pa.Field(ge=-50, le=50)

SOILTEXTURES

Bases: BaseModel

Table for soil textures.

Attributes:

Name Type Description
PSAND float

Depth of soil layer [cm, R]

PSILT float

Sand content [g/g mineral parts, R]

PCLAY float

Clay content [g/g mineral parts, R]

ORGMAT float

Organic matter content [g/g dry soil, R]

Source code in pyswap/extras/tables.py
class SOILTEXTURES(BaseModel):
    """Table for soil textures.

    Attributes:
        PSAND (float): Depth of soil layer [cm, R]
        PSILT (float): Sand content [g/g mineral parts, R]
        PCLAY (float): Clay content [g/g mineral parts, R]
        ORGMAT (float): Organic matter content [g/g dry soil, R]
    """
    PSAND: float
    PSILT: float
    PCLAY: float
    ORGMAT: float

SWAP model package

The main subpackage for the SWAP model.

Modules:

Name Description
model

SWAP model class

result

SWAP result class

model

The main model class.

Classes:

Name Description
Model

Main class that runs the SWAP model.

Model

Bases: PySWAPBaseModel

Main class that runs the SWAP model.

The attributes must be valid pySWAP classes. For avoiding validation errors, for now the attributes are defined as Any.

Attributes:

Name Type Description
metadata Any

Metadata of the model.

general_settings Any

Simulation settings.

meteorology Any

Meteorological data.

crop Any

Crop data.

fixedirrigation Any

Fixed irrigation settings.

soilmoisture Any

Soil moisture data.

surfaceflow Any

Surface flow data.

evaporation Any

Evaporation data.

soilprofile Any

Soil profile data.

snowandfrost Optional[Any]

Snow and frost data. Default is SnowAndFrost(swsnow=0, swfrost=0).

richards Optional[Any]

Richards data.

lateraldrainage Any

Lateral drainage data.

bottomboundary Any

Bottom boundary data.

heatflow Optional[Any]

Heat flow data.

solutetransport Optional[Any]

Solute transport data.

Methods:

Name Description
write_swp

Write the .swp input file.

_copy_executable

Copy the appropriate SWAP executable to the temporary directory.

_run_swap

Run the SWAP executable.

_read_output

Read the output file.

_read_output_tz

Read the output file with time zone.

_read_vap

Read the .vap output file.

_write_inputs

Write the input files.

_identify_warnings

Identify warnings in the log file.

_raise_swap_warning

Raise a warning.

_read_output_old

Save the old output files.

run

Run the model.

Source code in pyswap/model/model.py
class Model(PySWAPBaseModel):
    """Main class that runs the SWAP model.

    The attributes must be valid pySWAP classes. For avoiding validation errors,
    for now the attributes are defined as Any.

    Attributes:
        metadata (Any): Metadata of the model.
        general_settings (Any): Simulation settings.
        meteorology (Any): Meteorological data.
        crop (Any): Crop data.
        fixedirrigation (Any): Fixed irrigation settings.
        soilmoisture (Any): Soil moisture data.
        surfaceflow (Any): Surface flow data.
        evaporation (Any): Evaporation data.
        soilprofile (Any): Soil profile data.
        snowandfrost (Optional[Any]): Snow and frost data. Default is `SnowAndFrost(swsnow=0, swfrost=0)`.
        richards (Optional[Any]): Richards data.
        lateraldrainage (Any): Lateral drainage data.
        bottomboundary (Any): Bottom boundary data.
        heatflow (Optional[Any]): Heat flow data.
        solutetransport (Optional[Any]): Solute transport data.

    Methods:
        write_swp: Write the .swp input file.
        _copy_executable: Copy the appropriate SWAP executable to the temporary directory.
        _run_swap: Run the SWAP executable.
        _read_output: Read the output file.
        _read_output_tz: Read the output file with time zone.
        _read_vap: Read the .vap output file.
        _write_inputs: Write the input files.
        _identify_warnings: Identify warnings in the log file.
        _raise_swap_warning: Raise a warning.
        _read_output_old: Save the old output files.
        run: Run the model.
    """

    metadata: Metadata
    version: str = Field(exclude=True, default='base')
    general_settings: GeneralSettings
    meteorology: Meteorology
    crop: Crop
    fixedirrigation: FixedIrrigation = FixedIrrigation(swirfix=0)
    soilmoisture: SoilMoisture
    surfaceflow: SurfaceFlow
    evaporation: Evaporation
    soilprofile: SoilProfile
    snowandfrost: Optional[SnowAndFrost] = SnowAndFrost(swsnow=0, swfrost=0)
    richards: Optional[RichardsSettings] = RichardsSettings(
        swkmean=1, swkimpl=0)
    lateraldrainage: Drainage
    bottomboundary: BottomBoundary
    heatflow: Optional[HeatFlow] = HeatFlow(swhea=0)
    solutetransport: Optional[SoluteTransport] = SoluteTransport(swsolu=0)

    def write_swp(self, path: str) -> None:
        """Write the .swp input file."""

        string = self._concat_sections()
        self.save_element(string=string, path=path,
                          filename='swap', extension='swp')
        print('swap.swp saved.')

    @staticmethod
    def _copy_executable(tempdir: Path):
        """Copy the appropriate SWAP executable to the temporary directory."""
        if IS_WINDOWS:
            exec_path = resources.files(
                "pyswap.libs.swap420-exe").joinpath("swap.exe")
            shutil.copy(str(exec_path), str(tempdir))
            print('Copying the windows version of SWAP into temporary directory...')
        else:
            exec_path = resources.files(
                "pyswap.libs.swap420-linux").joinpath("swap420")
            shutil.copy(str(exec_path), str(tempdir))
            print('Copying linux executable into temporary directory...')

    def _write_inputs(self, path: str) -> None:
        print('Preparing files...')
        self.write_swp(path)
        if self.lateraldrainage.drafile:
            self.lateraldrainage.write_dra(path)
        if self.crop.cropfiles:
            self.crop.write_crop(path)
        if self.meteorology.metfile:
            self.meteorology.write_met(path)
        if self.fixedirrigation.irgfile:
            self.irrigation.fixedirrig.write_irg(path)
        if self.bottomboundary.swbbcfile:
            self.bottomboundary.write_bbc(path)

    @staticmethod
    def _run_swap(tempdir: Path) -> str:
        """Run the SWAP executable.

        I do not decode the sterror because the SWAP executable
        writes errors to stdout. It will be easy to implement reading
        stderr later if needed.

        Returns:
            str: stdout.
        """

        swap_path = Path(tempdir, 'swap.exe') if IS_WINDOWS else './swap420'

        p = subprocess.Popen(swap_path,
                             stdout=subprocess.PIPE,
                             stdin=subprocess.PIPE,
                             stderr=subprocess.STDOUT,
                             cwd=tempdir)

        stdout = p.communicate(input=b'\n')[0]

        return stdout.decode()

    @staticmethod
    def _read_output(path: Path):
        """Read the output csv file."""
        df = read_csv(path, comment='*', index_col='DATETIME')
        df.index = to_datetime(df.index)

        return df

    @staticmethod
    def _read_output_tz(path: Path):
        """Read the output csv file with the depth information."""
        df = read_csv(path, comment='*', index_col='DATE')
        df.index = to_datetime(df.index)

        return df

    @staticmethod
    def _read_vap(path: Path):
        df = read_csv(path, skiprows=11, encoding_errors='replace')
        df.columns = df.columns.str.strip()
        df.replace(r'^\s*$', nan, regex=True, inplace=True)
        return df

    def _read_log_file(self, directory: Path) -> str:
        """Read the log file."""
        log_files = [f for f in Path(directory).glob(
            '*.log') if f.name != 'reruns.log']

        if len(log_files) == 0:
            raise FileNotFoundError("No .log file found in the directory.")
        elif len(log_files) > 1:
            raise FileExistsError(
                "Multiple .log files found in the directory.")

        log_file = log_files[0]

        with open(log_file, 'r') as file:
            log_content = file.read()

        return log_content

    @staticmethod
    def _identify_warnings(log: str) -> list[Warning]:
        """Read through the log file and catch warnings emitted by the SWAP executable."""
        lines = log.split('\n')
        warnings = [line for line in lines
                    if line.strip().lower().startswith('warning')]

        return warnings

    def _raise_swap_warning(self, message):
        warnings.warn(message, Warning, stacklevel=3)

    def _read_output_old(self, tempdir: Path):
        """Read all output files that are not in csv format as strings."""
        list_dir = os.listdir(tempdir)
        list_dir = [f for f in list_dir if not f.find(
            self.general_settings.outfil) and not f.endswith('.csv')]

        if list_dir:
            dict_files = {f.split('.')[1]: open_file(Path(tempdir, f))
                          for f in list_dir}

        return dict_files

    def run(self, path: str | Path, silence_warnings: bool = False, old_output: bool = False):
        """Main function that runs the model.

        Parameters:
            path (str): Path to the working directory.
            silence_warnings (bool): If True, warnings will not be printed.
            old_output (bool): If True, the old output files (like .vap) will be saved to a dictionary.

        !!! todo

            It would be nice to have a nice output string that will concatenate all output
            including warnings and/or errors.


        !!! warning

            Reruns are for now not supported. Multiple runs of the model can be achieved by running
            model.run() multiple times.
        """
        with tempfile.TemporaryDirectory(dir=path) as tempdir:

            self._copy_executable(tempdir)
            self._write_inputs(tempdir)

            result = self._run_swap(tempdir)

            if 'normal completion' not in result:
                raise Exception(
                    f'Model run failed. \n {result}')

            print(result)

            log = self._read_log_file(tempdir)
            warnings = self._identify_warnings(log)

            if warnings and not silence_warnings:
                print('Warnings:')
                for warning in warnings:
                    self._raise_swap_warning(message=warning)

            if old_output:
                dict_files = self._read_output_old(tempdir)

            result = Result(
                output=self._read_output(
                    Path(tempdir, f'{self.general_settings.outfil}_output.csv')) if self.general_settings.inlist_csv else None,
                output_tz=self._read_output_tz(
                    Path(tempdir, f'{self.general_settings.outfil}_output_tz.csv')) if self.general_settings.inlist_csv_tz else None,
                log=log,
                output_old=dict_files if old_output else None,
                warning=warnings
            )

            return result

run(path, silence_warnings=False, old_output=False)

Main function that runs the model.

Parameters:

Name Type Description Default
path str

Path to the working directory.

required
silence_warnings bool

If True, warnings will not be printed.

False
old_output bool

If True, the old output files (like .vap) will be saved to a dictionary.

False

Todo

It would be nice to have a nice output string that will concatenate all output including warnings and/or errors.

Warning

Reruns are for now not supported. Multiple runs of the model can be achieved by running model.run() multiple times.

Source code in pyswap/model/model.py
def run(self, path: str | Path, silence_warnings: bool = False, old_output: bool = False):
    """Main function that runs the model.

    Parameters:
        path (str): Path to the working directory.
        silence_warnings (bool): If True, warnings will not be printed.
        old_output (bool): If True, the old output files (like .vap) will be saved to a dictionary.

    !!! todo

        It would be nice to have a nice output string that will concatenate all output
        including warnings and/or errors.


    !!! warning

        Reruns are for now not supported. Multiple runs of the model can be achieved by running
        model.run() multiple times.
    """
    with tempfile.TemporaryDirectory(dir=path) as tempdir:

        self._copy_executable(tempdir)
        self._write_inputs(tempdir)

        result = self._run_swap(tempdir)

        if 'normal completion' not in result:
            raise Exception(
                f'Model run failed. \n {result}')

        print(result)

        log = self._read_log_file(tempdir)
        warnings = self._identify_warnings(log)

        if warnings and not silence_warnings:
            print('Warnings:')
            for warning in warnings:
                self._raise_swap_warning(message=warning)

        if old_output:
            dict_files = self._read_output_old(tempdir)

        result = Result(
            output=self._read_output(
                Path(tempdir, f'{self.general_settings.outfil}_output.csv')) if self.general_settings.inlist_csv else None,
            output_tz=self._read_output_tz(
                Path(tempdir, f'{self.general_settings.outfil}_output_tz.csv')) if self.general_settings.inlist_csv_tz else None,
            log=log,
            output_old=dict_files if old_output else None,
            warning=warnings
        )

        return result

write_swp(path)

Write the .swp input file.

Source code in pyswap/model/model.py
def write_swp(self, path: str) -> None:
    """Write the .swp input file."""

    string = self._concat_sections()
    self.save_element(string=string, path=path,
                      filename='swap', extension='swp')
    print('swap.swp saved.')

result

Capturing model results.

Tip

The Result class is now focusing on the output in CSV format and the log file. the other result files are also retrieved as a list of strings which user can access if needed.

Classes:

Name Description
Result

Stores the result of a model run.

Result

Bases: BaseModel

Class to store the result of a model run.

Attributes:

Name Type Description
log str

The log file of the model run.

summary str

The summary file of the model run.

output DataFrame

The output file of the model run.

output_tz DataFrame

The output file of the model run with timezone.

output_old Dict[str, str]

The old output files of the model run.

warning List[str]

The warnings of the model run.

model_config ConfigDict

The configuration for the model.

Methods:

Name Description
iteration_stats

The part of the log file that describes the iteration statistics.

blc_summary

The .blc file if it exists.

water_balance

The water balance of the model run.

Source code in pyswap/model/result.py
class Result(BaseModel):
    """Class to store the result of a model run.

    Attributes:
        log (str): The log file of the model run.
        summary (str, optional): The summary file of the model run.
        output (DataFrame, optional): The output file of the model run.
        output_tz (DataFrame, optional): The output file of the model run with timezone.
        output_old (Dict[str, str], optional): The old output files of the model run.
        warning (List[str], optional): The warnings of the model run.
        model_config (ConfigDict): The configuration for the model.

    Methods:
        iteration_stats (str): The part of the log file that describes the iteration statistics.
        blc_summary (str): The .blc file if it exists.
        water_balance (str): The water balance of the model run.
    """

    log: str
    output: Optional[DataFrame] = Field(default=None, repr=False)
    output_tz: Optional[DataFrame] = Field(default=None, repr=False)
    output_old: Optional[Dict[str, str]] = Field(default=None, repr=False)
    warning: Optional[List[str]] = Field(default=None, repr=False)

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
        validate_assignment=True,
        extra='forbid'
    )

    @computed_field(return_type=str)
    def iteration_stats(self):
        """Return the part of the string that describes the iteration statistics."""
        return re.search(r'.*(Iteration statistics\s*.*)$', self.log, re.DOTALL)[1]

    @computed_field(return_type=str)
    def blc_summary(self):
        """Return the .blc file if it exists."""
        return self.output_old.get('blc') if self.output_old else None

    def yearly_summary(self):
        """Return yearly sums of all output variables."""
        return self.output.resample('YE').sum()

blc_summary()

Return the .blc file if it exists.

Source code in pyswap/model/result.py
@computed_field(return_type=str)
def blc_summary(self):
    """Return the .blc file if it exists."""
    return self.output_old.get('blc') if self.output_old else None

iteration_stats()

Return the part of the string that describes the iteration statistics.

Source code in pyswap/model/result.py
@computed_field(return_type=str)
def iteration_stats(self):
    """Return the part of the string that describes the iteration statistics."""
    return re.search(r'.*(Iteration statistics\s*.*)$', self.log, re.DOTALL)[1]

yearly_summary()

Return yearly sums of all output variables.

Source code in pyswap/model/result.py
def yearly_summary(self):
    """Return yearly sums of all output variables."""
    return self.output.resample('YE').sum()

Core subpackage

Core package containing the main classes and functions for the SWAP model.

Modules:

Name Description
basemodel

Base model class for pySWAP.

fields

Field types for pyswap used for serialization.

files

Functions to interact with file system.

serializers

Functions to fine tune the serializatino of pySWAP objects.

valueranges

Objects containing value ranges used is validation of pySWAP objects.

model

Contains the classes for the model of the simulation.

result

Contains the classes for the results of the simulation.


basemodel

PySWAPBaseModel

Bases: BaseModel

Base class for PySWAP models.

Attributes:

Name Type Description
model_config ConfigDict

Overriding Pydantic model configuration.

Methods:

Name Description
save_element

Saves model element to a file.

model_string

Returns a custom model string representation that matches the requirements of .swp file.

_concat_sections

Concatenate a string from individual sections.

model_string

Returns a custom model string representation that matches the requirements of .swp file.

Source code in pyswap/core/basemodel.py
class PySWAPBaseModel(BaseModel):
    """Base class for PySWAP models.

    Attributes:
        model_config (ConfigDict): Overriding Pydantic model configuration.

    Methods:
        save_element: Saves model element to a file.
        model_string: Returns a custom model string representation that matches the requirements of .swp file.
        _concat_sections: Concatenate a string from individual sections.
        model_string: Returns a custom model string representation that matches the requirements of .swp file.
    """

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
        validate_assignment=True,
        extra='forbid'
    )

    @staticmethod
    def save_element(string: str, path: str, filename: str, extension: str | None = None) -> str:
        """Saves model element to a file.

        Args:
            string (str): String to be saved.
            path (str): Path to the file.
            filename (str): File name.

        Returns:
            str: Success message.
        """
        save_file(
            string=string,
            fname=filename,
            extension=extension,
            path=path
        )
        return f'{filename}.{extension} saved successfully.'

    def model_string(self) -> str:
        """Returns a custom model string representation that matches the requirements of .swp file.

        Note:
            If values are simple types, they are formatted as 'ATTR = VALUE'. If the valies are
            tables (in pySWAP pd.DataFrame are used), they are formatted simply as 'TABLE_VALUE'. Additionally,
            a custom serializer (pyswap.core.utils.serializers.quote_string) is used to quote strings.

        Returns:
            str: Custom model string representation.
        """
        string = ''

        def formatter(attr, value, string):
            if attr.startswith('table_') or attr.startswith('list_'):
                return string + value
            else:
                return string + f'{attr.upper()} = {quote_string(value)}\n'

        for attr, value in self.model_dump(
                mode='json', exclude_none=True).items():
            if isinstance(value, dict):
                for k, v in value.items():
                    string = formatter(k, v, string)
            else:
                string = formatter(attr, value, string)

        return string

    def _concat_sections(self) -> str:
        """Concatenate a string from individual sections.

        This method is meant to be used on models that collect other
        models, like DraFile, or Model.
        """

        string = ''
        for k, v in dict(self).items():
            if v is None or isinstance(v, str):
                continue
            string += v.model_string()
        return string

model_string()

Returns a custom model string representation that matches the requirements of .swp file.

Note

If values are simple types, they are formatted as 'ATTR = VALUE'. If the valies are tables (in pySWAP pd.DataFrame are used), they are formatted simply as 'TABLE_VALUE'. Additionally, a custom serializer (pyswap.core.utils.serializers.quote_string) is used to quote strings.

Returns:

Name Type Description
str str

Custom model string representation.

Source code in pyswap/core/basemodel.py
def model_string(self) -> str:
    """Returns a custom model string representation that matches the requirements of .swp file.

    Note:
        If values are simple types, they are formatted as 'ATTR = VALUE'. If the valies are
        tables (in pySWAP pd.DataFrame are used), they are formatted simply as 'TABLE_VALUE'. Additionally,
        a custom serializer (pyswap.core.utils.serializers.quote_string) is used to quote strings.

    Returns:
        str: Custom model string representation.
    """
    string = ''

    def formatter(attr, value, string):
        if attr.startswith('table_') or attr.startswith('list_'):
            return string + value
        else:
            return string + f'{attr.upper()} = {quote_string(value)}\n'

    for attr, value in self.model_dump(
            mode='json', exclude_none=True).items():
        if isinstance(value, dict):
            for k, v in value.items():
                string = formatter(k, v, string)
        else:
            string = formatter(attr, value, string)

    return string

save_element(string, path, filename, extension=None) staticmethod

Saves model element to a file.

Parameters:

Name Type Description Default
string str

String to be saved.

required
path str

Path to the file.

required
filename str

File name.

required

Returns:

Name Type Description
str str

Success message.

Source code in pyswap/core/basemodel.py
@staticmethod
def save_element(string: str, path: str, filename: str, extension: str | None = None) -> str:
    """Saves model element to a file.

    Args:
        string (str): String to be saved.
        path (str): Path to the file.
        filename (str): File name.

    Returns:
        str: Success message.
    """
    save_file(
        string=string,
        fname=filename,
        extension=extension,
        path=path
    )
    return f'{filename}.{extension} saved successfully.'

database

All modules related to the database connection and models.

Warning

This module is in review and may be significantly changed or removed in the future.

Tip

pySWAP is meant to enable saving model runs to a SQLite database. That way, the users will be able to easily exchange models they built and run them on their own machines.

connection

This module is responsible for creating a connection to the database.

Classes:

Name Description
DatabaseConnection

Creates a connection to the database.

DatabaseConnection

Creates a connection to the database.

Upon calling the connect method, a check is made to see if the database file exists. If it does not, the file is created. The tables are then checked and created if they do not exist.

Attributes:

Name Type Description
engine

The database engine.

session

The database session.

db_path

The path to the database file.

Methods:

Name Description
connect

Connect to the database.

Source code in pyswap/core/database/connection.py
class DatabaseConnection:
    """Creates a connection to the database.

    Upon calling the connect method, a check is made to see if the database file
    exists. If it does not, the file is created. The tables are then checked
    and created if they do not exist.

    Attributes:
        engine: The database engine.
        session: The database session.
        db_path: The path to the database file.

    Methods:
        connect: Connect to the database.
    """

    def __init__(self, db_path: str = 'pyswap.db'):
        self.engine = None
        self.session = None
        self.db_path = db_path
        self.connect()

    def connect(self):
        """Connect to the dabase or create a new one."""
        if os.path.exists(self.db_path):
            if os.path.isfile(self.db_path):
                print("Database exists. Connecting...")

        else:
            print("Database does not exist. Creating...")

        self.engine = create_engine(f'sqlite:///{self.db_path}')
        session = sessionmaker(bind=self.engine)
        self.session = session()

        # Check if tables exist in the database
        inspector = inspect(self.engine)
        table_names = inspector.get_table_names()

        for table in Base.metadata.tables.keys():
            if table not in table_names:
                print(
                    f"Table {table} does not exist in the database. Creating...")
                Base.metadata.tables[table].create(self.engine)
            else:
                print(f"Table {table} exists in the database.")
connect()

Connect to the dabase or create a new one.

Source code in pyswap/core/database/connection.py
def connect(self):
    """Connect to the dabase or create a new one."""
    if os.path.exists(self.db_path):
        if os.path.isfile(self.db_path):
            print("Database exists. Connecting...")

    else:
        print("Database does not exist. Creating...")

    self.engine = create_engine(f'sqlite:///{self.db_path}')
    session = sessionmaker(bind=self.engine)
    self.session = session()

    # Check if tables exist in the database
    inspector = inspect(self.engine)
    table_names = inspector.get_table_names()

    for table in Base.metadata.tables.keys():
        if table not in table_names:
            print(
                f"Table {table} does not exist in the database. Creating...")
            Base.metadata.tables[table].create(self.engine)
        else:
            print(f"Table {table} exists in the database.")

hdf5

This file contains classes and functions for handling HDF5 integration.

HDF5

Bases: BaseModel

Source code in pyswap/core/database/hdf5.py
class HDF5(BaseModel):

    filename: str
    models: Optional[dict] = Field(default_factory=dict)

    @computed_field(return_type=dict)
    def list_projects(self):
        with h5py.File(self.filename, 'a') as f:
            # Use the visititems method to traverse the file structure
            projects = list(f.keys())
        return projects

    @staticmethod
    def _get_or_create_group(f, group_name):
        if group_name not in f:
            try:
                f.create_group(group_name)
            except ValueError:
                raise ValueError(
                    f'Cannot create group {group_name}. It may already exist. If you want to overwrite it, set overwrite=True.')
        return f[group_name]

    def save_model(self,
                   model: Model,
                   result: Optional[Result] = None,
                   overwrite_datasets: bool = False,
                   overwrite_project: bool = False,
                   mode: Literal['python', 'json', 'yaml'] = 'python'):
        """Sava a model and its results to an HDF5 file.

        Each model in its metadata attribute stores the project name. That is used as the name for the main group. If that name already exists,
        a new group is not created. Then the check is made if the version of the model already exists. If it does, the group is not created.
        """

        def _overwrite_datasets(group):
            for key in list(group.keys()):
                try:
                    del group[key]
                except KeyError:
                    pass

        def _overwrite_project(f, project_name):
            try:
                del f[project_name]
            except KeyError:
                pass

        def _save_pickled(group, name, data):
            try:
                pickle_data = pickle.dumps(data)
                group.create_dataset(name, data=np.void(pickle_data))
            except ValueError:
                raise ValueError(
                    f'Cannot create dataset {name}. It may already exist. If you want to overwrite it, set overwrite=True.')

        with h5py.File(self.filename, 'a') as f:

            if overwrite_project:
                _overwrite_project(f, model.metadata.project)
            # create a project and add attributes
            project_group = self._get_or_create_group(
                f, model.metadata.project)
            project_attrs = model.metadata.__dict__
            project_attrs = {k: v for k,
                             v in project_attrs.items() if v is not None}

            project_group.attrs.update(project_attrs)

            # create a model group with input and output datasets
            model_group = self._get_or_create_group(
                project_group, model.version)

            if overwrite_datasets:
                _overwrite_datasets(model_group)

            if mode == 'python':
                # For the python option there is no need for an additional group
                _save_pickled(model_group, 'input', model)
                if result:
                    _save_pickled(model_group, 'output', result)

            if mode == 'json':
                raise NotImplementedError('JSON mode is not yet implemented')

            if mode == 'yaml':
                raise NotImplementedError('YAML mode is not yet implemented')

    def load(self, project: str, model: Optional[str] = None, load_results: bool = False, mode: Literal['python', 'json', 'yaml'] = 'python') -> Tuple[Model, Result]:
        """Load a single model or all models within a specific project."""

        def _load_pickled(group: h5py.Group, name: str, load_results: bool) -> Tuple[Model, Result]:
            pickle_in = group[name]['input'][()].tobytes()
            pickle_out = group[name]['output'][()].tobytes(
            ) if load_results else None

            model = pickle.loads(pickle_in)
            result = pickle.loads(pickle_out) if load_results else None
            return model, result

        with h5py.File(self.filename, 'r') as f:
            project_grp = f[project]

            if mode == 'python':
                if model is None:
                    all_models = list(project_grp.keys())
                    for item in all_models:
                        self.models[item] = _load_pickled(
                            group=project_grp, name=item, load_results=load_results)

                else:
                    self.models[model] = _load_pickled(
                        group=project_grp, name=model, load_results=load_results)

            if mode == 'json':
                raise NotImplementedError('JSON mode is not yet implemented')

            if mode == 'yaml':
                raise NotImplementedError('YAML mode is not yet implemented')
load(project, model=None, load_results=False, mode='python')

Load a single model or all models within a specific project.

Source code in pyswap/core/database/hdf5.py
def load(self, project: str, model: Optional[str] = None, load_results: bool = False, mode: Literal['python', 'json', 'yaml'] = 'python') -> Tuple[Model, Result]:
    """Load a single model or all models within a specific project."""

    def _load_pickled(group: h5py.Group, name: str, load_results: bool) -> Tuple[Model, Result]:
        pickle_in = group[name]['input'][()].tobytes()
        pickle_out = group[name]['output'][()].tobytes(
        ) if load_results else None

        model = pickle.loads(pickle_in)
        result = pickle.loads(pickle_out) if load_results else None
        return model, result

    with h5py.File(self.filename, 'r') as f:
        project_grp = f[project]

        if mode == 'python':
            if model is None:
                all_models = list(project_grp.keys())
                for item in all_models:
                    self.models[item] = _load_pickled(
                        group=project_grp, name=item, load_results=load_results)

            else:
                self.models[model] = _load_pickled(
                    group=project_grp, name=model, load_results=load_results)

        if mode == 'json':
            raise NotImplementedError('JSON mode is not yet implemented')

        if mode == 'yaml':
            raise NotImplementedError('YAML mode is not yet implemented')
save_model(model, result=None, overwrite_datasets=False, overwrite_project=False, mode='python')

Sava a model and its results to an HDF5 file.

Each model in its metadata attribute stores the project name. That is used as the name for the main group. If that name already exists, a new group is not created. Then the check is made if the version of the model already exists. If it does, the group is not created.

Source code in pyswap/core/database/hdf5.py
def save_model(self,
               model: Model,
               result: Optional[Result] = None,
               overwrite_datasets: bool = False,
               overwrite_project: bool = False,
               mode: Literal['python', 'json', 'yaml'] = 'python'):
    """Sava a model and its results to an HDF5 file.

    Each model in its metadata attribute stores the project name. That is used as the name for the main group. If that name already exists,
    a new group is not created. Then the check is made if the version of the model already exists. If it does, the group is not created.
    """

    def _overwrite_datasets(group):
        for key in list(group.keys()):
            try:
                del group[key]
            except KeyError:
                pass

    def _overwrite_project(f, project_name):
        try:
            del f[project_name]
        except KeyError:
            pass

    def _save_pickled(group, name, data):
        try:
            pickle_data = pickle.dumps(data)
            group.create_dataset(name, data=np.void(pickle_data))
        except ValueError:
            raise ValueError(
                f'Cannot create dataset {name}. It may already exist. If you want to overwrite it, set overwrite=True.')

    with h5py.File(self.filename, 'a') as f:

        if overwrite_project:
            _overwrite_project(f, model.metadata.project)
        # create a project and add attributes
        project_group = self._get_or_create_group(
            f, model.metadata.project)
        project_attrs = model.metadata.__dict__
        project_attrs = {k: v for k,
                         v in project_attrs.items() if v is not None}

        project_group.attrs.update(project_attrs)

        # create a model group with input and output datasets
        model_group = self._get_or_create_group(
            project_group, model.version)

        if overwrite_datasets:
            _overwrite_datasets(model_group)

        if mode == 'python':
            # For the python option there is no need for an additional group
            _save_pickled(model_group, 'input', model)
            if result:
                _save_pickled(model_group, 'output', result)

        if mode == 'json':
            raise NotImplementedError('JSON mode is not yet implemented')

        if mode == 'yaml':
            raise NotImplementedError('YAML mode is not yet implemented')

models

SQLAlchemy models for the pySWAP database.

Important assumptions
  1. There is always an initial SWAP model which is saved in the SWAPModel table.
  2. Each SWAPModel belongs to only one project.
  3. Each SWAPModel has one or mode runs which are saved in the ModelRun table.
  4. One ModelRun has exactly one ModelOutput

Data

Bases: Base

Stores data files like meteo data or crop data.

Source code in pyswap/core/database/models.py
class Data(Base):
    """Stores data files like meteo data or crop data."""

    __tablename__ = 'data'

    did = Column(Integer, primary_key=True)
    fname = Column(String(50), nullable=False, unique=True)
    datafile = Column(LargeBinary)
    swapruns = relationship(
        'SWAPRun', secondary=data_swaprun_association, back_populates='data')

SWAPRun

Bases: Base

This model stores sections of the .swp file

Source code in pyswap/core/database/models.py
class SWAPRun(Base):
    """This model stores sections of the .swp file"""

    __tablename__ = 'swapmodel'

    mid = Column(String(50), primary_key=True)
    rid = Column(String(50), default='initial', primary_key=True)
    swp = Column(LargeBinary, nullable=True)
    data = relationship(
        'Data', secondary=data_swaprun_association, back_populates='swapruns')
    result = Column(LargeBinary, nullable=True)

    __table_args__ = (UniqueConstraint('mid', 'rid', name='uq_model_run'),)

defaults

Default values for the Richards' equation

fields

Custom field types used for serilization in the model_dump(mode='json').

Other Parameters:

Name Type Description
Table DataFrame

A DataFrame object serialized as a string with just the headers and the data.

Arrays DataFrame

A DataFrame object serialized as a string with just the columns of data (no headers), but with the variable name in front (e.g., FLUXTB = 0.0 0.0/n 1.0 1.0 )

CSVTable DataFrame

A DataFrame object serialized as a string with the headers and data in CSV format, specifically tailored for the .met file format.

DayMonth date

A date object serialized as a string with just the day and month (e.g., '01 01').

StringList List[str]

A list of strings serialized as a string with the elements separated by commas, enclosed in quotation marks (e.g., 'string1, string2, string3').

FloatList List[float]

A list of floats serialized as a string with the elements separated by spaces.

DateList List[date]

A list of date objects serialized as a string with the elements separated by newlines.

Switch bool | int

A boolean or integer serialized as an integer (0 or 1).

ObjectList list

A list of objects serialized as a string with the elements separated by newlines.

files

Simple module to interact with files.

open_file(file_path)

Open file and detect encoding.

Parameters:

Name Type Description Default
file_path str

Path to the file to be opened.

required
Source code in pyswap/core/files.py
def open_file(file_path: str) -> str:
    """Open file and detect encoding.

    Arguments:
        file_path (str): Path to the file to be opened.
    """
    with open(file_path, 'rb') as f:
        raw_data = f.read()
    encoding = chardet.detect(raw_data)['encoding']

    return raw_data.decode(encoding)

save_file(string, fname, path, mode='w', extension=None, encoding='ascii')

Saves a string to a file.

Parameters:

Name Type Description Default
string str

The string to be saved to a file.

required
extension str | None

The extension that the file should have (e.g. 'txt', 'csv', etc.).

None
fname str

The name of the file.

required
path str

The path where the file should be saved.

required
mode str

The mode in which the file should be opened (e.g. 'w' for write, 'a' for append, etc.).

'w'
encoding str

The encoding to use for the file (default is 'ascii').

'ascii'

Returns:

Type Description
None

None

Source code in pyswap/core/files.py
def save_file(string: str,
              fname: str,
              path: str,
              mode: str = 'w',
              extension: str | None = None,
              encoding: str = 'ascii') -> None:
    """
    Saves a string to a file.

    Arguments:
        string: The string to be saved to a file.
        extension: The extension that the file should have (e.g. 'txt', 'csv', etc.).
        fname: The name of the file.
        path: The path where the file should be saved.
        mode: The mode in which the file should be opened (e.g. 'w' for write, 'a' for append, etc.).
        encoding: The encoding to use for the file (default is 'ascii').

    Returns:
        None
    """

    if extension is not None:
        fname = f'{fname}.{extension}'

    with open(f'{path}/{fname}', f'{mode}', encoding=f'{encoding}') as f:
        f.write(string)

plot

Plotting functionality for pySWAP.

Modules:

Name Description
evapotranspiration

Functions for plotting evapotranspiration data.

gwl

Functions for plotting groundwater level data.

watercontent

Functions for plotting water content data.

evapotranspiration(potential, actual, title='Evapotranspiration')

Plot evapotranspiration (potential vs actual) and compute the RMSE.

Paremeters

potential (DataFrame): DataFrame containing dates and values for potential evapotranspiration. actual (DataFrame): DataFrame containing dates and values for actual evapotranspiration. title (str, optional): Title of the plot. Defaults to 'Evapotranspiration'.

Source code in pyswap/core/plot/evapotranspiration.py
def evapotranspiration(potential: DataFrame, actual: DataFrame, title: str = 'Evapotranspiration'):
    """Plot evapotranspiration (potential vs actual) and compute the RMSE.

    Paremeters:
        potential (DataFrame): DataFrame containing dates and values for potential evapotranspiration.
        actual (DataFrame): DataFrame containing dates and values for actual evapotranspiration.
        title (str, optional): Title of the plot. Defaults to 'Evapotranspiration'.
    """

    sns.set_context('poster')

    fig, ax = plt.subplots(figsize=(34, 8))
    sns.lineplot(data=potential, ax=ax, label='Potential',
                 color='black', linewidth=1)
    sns.lineplot(data=actual, ax=ax, label='Actual',
                 color='orange', linewidth=1, linestyle='--')

    ax.set_title(title, pad=20)
    ax.set_xlabel('Date')
    ax.set_ylabel('Evapotranspiration')

    ax.tick_params(axis='x', rotation=45)
    ax.legend()
    plt.tight_layout()
    plt.show()

gwl(simulated, observed, title='Groundwater levels')

Plot groundwater levels (observed vs simulated) and compute the RMSE.

Parameters:

Name Type Description Default
simulated DataFrame

Simulated groundwater levels

required
observed DataFrame

Observed groundwater levels

required
title str

Title of the plot. Defaults to 'Groundwater levels'.

'Groundwater levels'
Source code in pyswap/core/plot/gwl.py
def gwl(simulated: DataFrame, observed: DataFrame, title: str = 'Groundwater levels'):
    """Plot groundwater levels (observed vs simulated) and compute the RMSE.

    Parameters:
        simulated (DataFrame): Simulated groundwater levels
        observed (DataFrame): Observed groundwater levels
        title (str, optional): Title of the plot. Defaults to 'Groundwater levels'.
    """

    rmse = ((simulated - observed) ** 2).mean() ** 0.5

    fig, ax = plt.subplots(figsize=(10, 3))
    ax.plot(simulated, label='Simulated', color='black', linewidth=0.5)
    ax.plot(observed, label='Observed', marker='o',
            linestyle='None', markersize=2)
    ax.set_title(title)
    ax.set_xlabel('Date')
    ax.set_ylabel('Groundwater level')
    ax.tick_params(axis='x', rotation=45)

    # add RMSE to the plot in the right lower corner
    ax.text(0.95, 0.05, f'RMSE: {rmse:.2f}', verticalalignment='bottom',
            horizontalalignment='right', transform=ax.transAxes)
    ax.legend()
    plt.tight_layout()
    plt.show()

water_content(df_vap, title='Water content')

Plot water content as heatmap with time on the x-axis and depth on the y-axis.

Parameters:

Name Type Description Default
df_vap DataFrame

DataFrame containing the water content data

required
title str

Title of the plot. Defaults to 'Water content'.

'Water content'
Source code in pyswap/core/plot/watercontent.py
def water_content(df_vap: pd.DataFrame, title: str = 'Water content'):
    """Plot water content as heatmap with time on the x-axis and depth on the y-axis.

    Parameters:
        df_vap (pd.DataFrame): DataFrame containing the water content data
        title (str, optional): Title of the plot. Defaults to 'Water content'.
    """
    sns.set_context('poster')

    df_wcont = df_vap[['depth', 'date', 'wcontent']]
    df_wcont['date'] = pd.to_datetime(
        df_wcont['date'])
    df_wcont['date'] = df_wcont['date'].dt.strftime('%Y-%m')
    df_wcont['depth'] = df_wcont['depth'].astype(float)
    df_wcont['wcontent'] = df_wcont['wcontent'].astype(float)
    pivot_table = df_wcont.pivot(
        columns='date', index='depth', values='wcontent')

    pivot_table.sort_index(ascending=True, inplace=True)
    plt.figure(figsize=(34, 8))
    ax = sns.heatmap(pivot_table, cmap="YlGnBu")
    plt.title(title)
    plt.xlabel('Date')
    plt.ylabel('Depth (cm)')

    plt.gca().invert_yaxis()

    plt.xticks(rotation=45)
    plt.tight_layout(pad=20)
    plt.show()

watercontent

water_content(df_vap, title='Water content')

Plot water content as heatmap with time on the x-axis and depth on the y-axis.

Parameters:

Name Type Description Default
df_vap DataFrame

DataFrame containing the water content data

required
title str

Title of the plot. Defaults to 'Water content'.

'Water content'
Source code in pyswap/core/plot/watercontent.py
def water_content(df_vap: pd.DataFrame, title: str = 'Water content'):
    """Plot water content as heatmap with time on the x-axis and depth on the y-axis.

    Parameters:
        df_vap (pd.DataFrame): DataFrame containing the water content data
        title (str, optional): Title of the plot. Defaults to 'Water content'.
    """
    sns.set_context('poster')

    df_wcont = df_vap[['depth', 'date', 'wcontent']]
    df_wcont['date'] = pd.to_datetime(
        df_wcont['date'])
    df_wcont['date'] = df_wcont['date'].dt.strftime('%Y-%m')
    df_wcont['depth'] = df_wcont['depth'].astype(float)
    df_wcont['wcontent'] = df_wcont['wcontent'].astype(float)
    pivot_table = df_wcont.pivot(
        columns='date', index='depth', values='wcontent')

    pivot_table.sort_index(ascending=True, inplace=True)
    plt.figure(figsize=(34, 8))
    ax = sns.heatmap(pivot_table, cmap="YlGnBu")
    plt.title(title)
    plt.xlabel('Date')
    plt.ylabel('Depth (cm)')

    plt.gca().invert_yaxis()

    plt.xticks(rotation=45)
    plt.tight_layout(pad=20)
    plt.show()

serializers

Functions serializing obects to the appropriate format used in the custom pyswap fields (pyswap.core.utils.fields)

is_scientific_notation(s)

Check if a string represents a number in scientific notation.

The pattern matches strings in scientific notation, e.g., '1.23e-4', '2E+2'

Parameters:

Name Type Description Default
s str

The string to be checked.

required
Source code in pyswap/core/serializers.py
def is_scientific_notation(s: str) -> str:
    """Check if a string represents a number in scientific notation.

    The pattern matches strings in scientific notation, e.g., '1.23e-4', '2E+2'

    Args:
        s: The string to be checked.
    """
    pattern = r'^[+-]?(\d+(\.\d*)?|\.\d+)[eE][+-]?\d+$'
    return re.match(pattern, s) is not None

quote_string(string)

Quote the string if it contains alphabetic characters or './', except for scientific notation.

Source code in pyswap/core/serializers.py
def quote_string(string) -> str:
    """Quote the string if it contains alphabetic characters or './', except for scientific notation."""

    string = str(string)

    # Check for scientific notation first
    if is_scientific_notation(string):
        return string.upper()

    if re.search("[a-zA-Z/]", string):
        return f"'{string}'"
    if re.search(r".\\", string):
        return f"'{string}'"
    else:
        return string

serialize_arrays(table)

Convert the DataFrame to a string without headers and newline in front

Parameters:

Name Type Description Default
table DataFrame

The DataFrame to be serialized.

required
Source code in pyswap/core/serializers.py
def serialize_arrays(table: DataFrame) -> str:
    """Convert the DataFrame to a string without headers and newline in front

    Args:
        table: The DataFrame to be serialized.
    """
    return f'\n{table.to_string(index=False, header=False)}\n'

serialize_csv_table(table)

Convert the DataFrame to a string in CSV format.

This serializer is specifically tailored to output the data in the format of the ,met files used in SWAP.

Parameters:

Name Type Description Default
table DataFrame

The DataFrame to be serialized.

required
Source code in pyswap/core/serializers.py
def serialize_csv_table(table: DataFrame) -> str:
    """Convert the DataFrame to a string in CSV format.

    This serializer is specifically tailored to output the data in the
    format of the ,met files used in SWAP.

    Args:
        table: The DataFrame to be serialized.
    """
    if isinstance(table.index, DatetimeIndex):
        table['DD'] = table.index.day
        table['MM'] = table.index.month
        table['YYYY'] = table.index.year
        required_order = ['Station', 'DD', 'MM', 'YYYY', 'RAD',
                          'Tmin', 'Tmax', 'HUM', 'WIND', 'RAIN', 'ETref', 'WET']
        table = table[required_order]

    table.loc[:, 'Station'] = table.Station.apply(
        lambda x: f"'{x}'" if not str(x).startswith("'") else x)
    return table.to_csv(index=False, lineterminator='\n')

serialize_object_list(list)

Serialize a list of objects to a string.

Source code in pyswap/core/serializers.py
def serialize_object_list(list) -> str:
    """Serialize a list of objects to a string."""
    string = ''
    for item in list:
        string += item.model_string()

    return string

serialize_table(table)

Convert the DataFrame to a string with the headers in uppercase.

Parameters:

Name Type Description Default
table DataFrame

The DataFrame to be serialized.

required
Source code in pyswap/core/serializers.py
def serialize_table(table: DataFrame) -> str:
    """Convert the DataFrame to a string with the headers in uppercase.

    Args:
        table: The DataFrame to be serialized.
    """
    table.columns = [header.upper() for header in table.columns]
    return f'{table.to_string(index=False)}\n'

tablevalidation

Pandera schemas for validating tables in pySWAP.

The schemas are used to validate pandas DataFrames used in the pySWAP models. They also help to enforce the appropriate data types required by the SWAP model.

Warning

This is an experimental feature and is currently only implemented in the irrigation subpackage. If no bugs are found, it will be implemented in the other subpackages.

Classes:

Name Description
BaseModel

Base class for all pySWAP schemas.

BaseModel

Bases: DataFrameModel

Base model with create method for preprocessing and validation.

Source code in pyswap/core/tablevalidation.py
class BaseModel(pa.DataFrameModel):
    """Base model with create method for preprocessing and validation."""

    class Config:
        coerce = True

    @classmethod
    def create(cls, data: dict) -> DataFrame:
        df = pd.DataFrame(data)
        df.columns = df.columns.str.upper()
        validated_df = cls.validate(df)
        return validated_df

valueranges

Commonly used ranges of values for pydantic Field() objects.

Other Parameters:

Name Type Description
UNITRANGE dict

Range of values between 0.0 and 1.0.

YEARRANGE dict

Range of values for year (0 <= x <= 366).

DVSRANGE dict

Range of values for development stage (0 <= x <= 2).

DVSRANGE = {'ge': 0.0, 'le': 2.0} module-attribute

Range of values for development stage (0 <= x <= 2).

UNITRANGE = {'ge': 0.0, 'le': 1.0} module-attribute

Range of values between 0.0 and 1.0.

YEARRANGE = {'ge': 0, 'le': 366} module-attribute

Range of values for year (0 <= x <= 366).

Plotting functionality for pySWAP.

Modules:

Name Description
evapotranspiration

Functions for plotting evapotranspiration data.

gwl

Functions for plotting groundwater level data.

watercontent

Functions for plotting water content data.

evapotranspiration(potential, actual, title='Evapotranspiration')

Plot evapotranspiration (potential vs actual) and compute the RMSE.

Paremeters

potential (DataFrame): DataFrame containing dates and values for potential evapotranspiration. actual (DataFrame): DataFrame containing dates and values for actual evapotranspiration. title (str, optional): Title of the plot. Defaults to 'Evapotranspiration'.

Source code in pyswap/core/plot/evapotranspiration.py
def evapotranspiration(potential: DataFrame, actual: DataFrame, title: str = 'Evapotranspiration'):
    """Plot evapotranspiration (potential vs actual) and compute the RMSE.

    Paremeters:
        potential (DataFrame): DataFrame containing dates and values for potential evapotranspiration.
        actual (DataFrame): DataFrame containing dates and values for actual evapotranspiration.
        title (str, optional): Title of the plot. Defaults to 'Evapotranspiration'.
    """

    sns.set_context('poster')

    fig, ax = plt.subplots(figsize=(34, 8))
    sns.lineplot(data=potential, ax=ax, label='Potential',
                 color='black', linewidth=1)
    sns.lineplot(data=actual, ax=ax, label='Actual',
                 color='orange', linewidth=1, linestyle='--')

    ax.set_title(title, pad=20)
    ax.set_xlabel('Date')
    ax.set_ylabel('Evapotranspiration')

    ax.tick_params(axis='x', rotation=45)
    ax.legend()
    plt.tight_layout()
    plt.show()

gwl(simulated, observed, title='Groundwater levels')

Plot groundwater levels (observed vs simulated) and compute the RMSE.

Parameters:

Name Type Description Default
simulated DataFrame

Simulated groundwater levels

required
observed DataFrame

Observed groundwater levels

required
title str

Title of the plot. Defaults to 'Groundwater levels'.

'Groundwater levels'
Source code in pyswap/core/plot/gwl.py
def gwl(simulated: DataFrame, observed: DataFrame, title: str = 'Groundwater levels'):
    """Plot groundwater levels (observed vs simulated) and compute the RMSE.

    Parameters:
        simulated (DataFrame): Simulated groundwater levels
        observed (DataFrame): Observed groundwater levels
        title (str, optional): Title of the plot. Defaults to 'Groundwater levels'.
    """

    rmse = ((simulated - observed) ** 2).mean() ** 0.5

    fig, ax = plt.subplots(figsize=(10, 3))
    ax.plot(simulated, label='Simulated', color='black', linewidth=0.5)
    ax.plot(observed, label='Observed', marker='o',
            linestyle='None', markersize=2)
    ax.set_title(title)
    ax.set_xlabel('Date')
    ax.set_ylabel('Groundwater level')
    ax.tick_params(axis='x', rotation=45)

    # add RMSE to the plot in the right lower corner
    ax.text(0.95, 0.05, f'RMSE: {rmse:.2f}', verticalalignment='bottom',
            horizontalalignment='right', transform=ax.transAxes)
    ax.legend()
    plt.tight_layout()
    plt.show()

water_content(df_vap, title='Water content')

Plot water content as heatmap with time on the x-axis and depth on the y-axis.

Parameters:

Name Type Description Default
df_vap DataFrame

DataFrame containing the water content data

required
title str

Title of the plot. Defaults to 'Water content'.

'Water content'
Source code in pyswap/core/plot/watercontent.py
def water_content(df_vap: pd.DataFrame, title: str = 'Water content'):
    """Plot water content as heatmap with time on the x-axis and depth on the y-axis.

    Parameters:
        df_vap (pd.DataFrame): DataFrame containing the water content data
        title (str, optional): Title of the plot. Defaults to 'Water content'.
    """
    sns.set_context('poster')

    df_wcont = df_vap[['depth', 'date', 'wcontent']]
    df_wcont['date'] = pd.to_datetime(
        df_wcont['date'])
    df_wcont['date'] = df_wcont['date'].dt.strftime('%Y-%m')
    df_wcont['depth'] = df_wcont['depth'].astype(float)
    df_wcont['wcontent'] = df_wcont['wcontent'].astype(float)
    pivot_table = df_wcont.pivot(
        columns='date', index='depth', values='wcontent')

    pivot_table.sort_index(ascending=True, inplace=True)
    plt.figure(figsize=(34, 8))
    ax = sns.heatmap(pivot_table, cmap="YlGnBu")
    plt.title(title)
    plt.xlabel('Date')
    plt.ylabel('Depth (cm)')

    plt.gca().invert_yaxis()

    plt.xticks(rotation=45)
    plt.tight_layout(pad=20)
    plt.show()

watercontent

water_content(df_vap, title='Water content')

Plot water content as heatmap with time on the x-axis and depth on the y-axis.

Parameters:

Name Type Description Default
df_vap DataFrame

DataFrame containing the water content data

required
title str

Title of the plot. Defaults to 'Water content'.

'Water content'
Source code in pyswap/core/plot/watercontent.py
def water_content(df_vap: pd.DataFrame, title: str = 'Water content'):
    """Plot water content as heatmap with time on the x-axis and depth on the y-axis.

    Parameters:
        df_vap (pd.DataFrame): DataFrame containing the water content data
        title (str, optional): Title of the plot. Defaults to 'Water content'.
    """
    sns.set_context('poster')

    df_wcont = df_vap[['depth', 'date', 'wcontent']]
    df_wcont['date'] = pd.to_datetime(
        df_wcont['date'])
    df_wcont['date'] = df_wcont['date'].dt.strftime('%Y-%m')
    df_wcont['depth'] = df_wcont['depth'].astype(float)
    df_wcont['wcontent'] = df_wcont['wcontent'].astype(float)
    pivot_table = df_wcont.pivot(
        columns='date', index='depth', values='wcontent')

    pivot_table.sort_index(ascending=True, inplace=True)
    plt.figure(figsize=(34, 8))
    ax = sns.heatmap(pivot_table, cmap="YlGnBu")
    plt.title(title)
    plt.xlabel('Date')
    plt.ylabel('Depth (cm)')

    plt.gca().invert_yaxis()

    plt.xticks(rotation=45)
    plt.tight_layout(pad=20)
    plt.show()