Skip to content

Commit

Permalink
Consistent parameter labels
Browse files Browse the repository at this point in the history
  • Loading branch information
burggraaff committed Mar 27, 2024
1 parent c437887 commit aebf5ec
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 61 deletions.
64 changes: 64 additions & 0 deletions fpcup/_typing.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Combines abstract base classes from various places and generates some useful aliases.
"""
from dataclasses import dataclass
from numbers import Number, Real as RealNumber
from os import PathLike
from typing import Callable, Iterable, Optional, Type
Expand All @@ -21,3 +22,66 @@
# Geographic data
AreaDict = dict[str, Polygon]
BoundaryDict = dict[str, GeoSeries]

# PCSE parameters
@dataclass
class _PCSEParameterBase:
name: str
description: str

def __str__(self) -> str:
return f"{self.name}: {self.description}"

@dataclass
class _PCSEParameterPlottable:
plotname: Optional[str] = None

def __post_init__(self):
# Uses the description as a default plotname; assumes a description exists
if self.plotname is None:
self.plotname = self.description

@dataclass
class PCSEFlag(_PCSEParameterBase):
def __str__(self) -> str:
return f"[FLAG] {self.name}: {self.description}"

@dataclass
class PCSELabel(_PCSEParameterPlottable, _PCSEParameterBase):
def __str__(self) -> str:
return str(self.plotname)

@dataclass
class PCSENumericParameter(_PCSEParameterPlottable, _PCSEParameterBase):
unit: Optional[str] = None
bounds: Iterable[Number] = None
dtype: type = float

def __str__(self) -> str:
base = f"{self.name}: {self.plotname}"
if self.unit is not None:
base += f" [{self.unit}]"
return base

@dataclass
class PCSEDateParameter(_PCSEParameterPlottable, _PCSEParameterBase):
def __str__(self) -> str:
return f"{self.name}: {self.plotname}"

@dataclass
class PCSETabularParameter(_PCSEParameterBase):
x: str
x_unit: Optional[str] = None
unit: Optional[str] = None

def __str__(self) -> str:
base = f"[TABLE] {self.name}: {self.description}"
if self.unit is not None:
base += f" [{self.unit}]"
base += f" as function of {self.x}"
if self.x_unit is not None:
base += f" [{self.x_unit}]"
return base


PCSEParameter = PCSEFlag | PCSELabel | PCSENumericParameter | PCSETabularParameter
8 changes: 7 additions & 1 deletion fpcup/aggregate.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
import pandas as pd
from pandas.api.types import is_datetime64_any_dtype as is_datetime

from ._typing import Callable, FuncDict, Iterable
from ._typing import Callable, FuncDict, Iterable, PCSENumericParameter
from .tools import parameterdict

# Parameters for plotting
area = PCSENumericParameter(name="area", description="Total plot area", unit="ha")
n = PCSENumericParameter(name="area", description="Number of sites")
parameters = parameterdict(area, n)

# Columns in summary data that should be averaged over for aggregates
# H3pandas does not support the tuple-dict system, e.g. {"n": ("DVS", "count")}, so it has to be done in an ugly way
Expand Down
32 changes: 20 additions & 12 deletions fpcup/crop.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,27 @@
from pcse.fileinput import CABOFileReader, YAMLCropDataProvider

from ._brp_dictionary import brp_crops_NL2EN
from ._typing import PathOrStr, PCSEFlag, PCSELabel, PCSENumericParameter, PCSETabularParameter
from .tools import parameterdict

# Parameter information from "A gentle introduction to WOFOST" (De Wit & Boogaard 2021) and YAML file descriptions
C = "°C"
Cday = f"{C} day"

TSUMEM = PCSENumericParameter(name="TSUMEM", description="Temperature sum from sowing to emergence", unit=Cday, bounds=(0, 170))
TSUM1 = PCSENumericParameter(name="TSUM1", description="Temperature sum from emergence to anthesis", unit=Cday, bounds=(150, 1050))
TSUM2 = PCSENumericParameter(name="TSUM2", description="Temperature sum from anthesis to maturity", unit=Cday, bounds=(600, 1550))
TBASEM = PCSENumericParameter(name="TBASEM", description="Lower threshold temperature for emergence", unit=C, bounds=(-10, 8))
TEFFMX = PCSENumericParameter(name="TEFFMX", description="Maximum effective temperature for emergence", unit=C, bounds=(18, 32))
RDI = PCSENumericParameter(name="RDI", description="Initial rooting depth", unit="cm", bounds=(10, 50))
RRI = PCSENumericParameter(name="RRI", description="Maximum daily increase in rooting depth", unit="cm / day", bounds=(0, 3))
RDMCR = PCSENumericParameter(name="RDMCR", description="Maximum rooting depth", unit="cm", bounds=(50, 400))

SLATB = PCSETabularParameter(name="SLATB", description="Specific leaf area", x="DVS", x_unit=None, unit="ha / kg")
AMAXTB = PCSETabularParameter(name="AMAXTB", description="Maximum leaf CO2 assimilation rate", x="DVS", x_unit=None, unit="kg / ha / hr")

parameters = parameterdict(TSUMEM, TSUM1, TSUM2, TBASEM, TEFFMX, RDI, RRI, RDMCR, SLATB, AMAXTB)

# Parameter names are from "A gentle introduction to WOFOST" (De Wit & Boogaard 2021) and from the YAML crop parameter files
parameter_names = {"TSUMEM": "Temperature sum from sowing to emergence [°C day]",
"TSUM1": "Temperature sum from emergence to anthesis [°C day]",
"TSUM2": "Temperature sum from anthesis to maturity [°C day]",
"TBASEM": "Lower threshold temperature for emergence [°C]",
"TEFFMX": "Maximum effective temperature for emergence [°C]",
"SLATB": "Specific leaf area as a function of DVS [; ha/kg]",
"AMAXTB": "Maximum leaf CO2 assimilation rate as function of DVS [;kg/ha/hr]",
"RDI": "Initial rooting depth [cm]",
"RRI": "Maximum daily increase in rooting depth [cm/day]",
"RDMCR": "Maximum rooting depth [cm]",
}

default = YAMLCropDataProvider()

Expand Down
51 changes: 26 additions & 25 deletions fpcup/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,42 +16,43 @@
from pcse.models import Engine, Wofost72_WLP_FD
from pcse.util import _GenericSiteDataProvider as PCSESiteDataProvider

from ._typing import Callable, Coordinates, Iterable, Optional, PathOrStr
from ._typing import Callable, Coordinates, Iterable, Optional, PathOrStr, PCSEDateParameter, PCSENumericParameter
from .agro import AgromanagementData
from .constants import CRS_AMERSFOORT
from .multiprocessing import multiprocess_file_io, multiprocess_pcse
from .soil import SoilType
from .tools import copy, indent2, make_iterable
from .tools import copy, indent2, parameterdict

### Constants
_SUFFIX_RUNDATA = ".wrun"
_SUFFIX_OUTPUTS = ".wout"
_SUFFIX_SUMMARY = ".wsum"

# Parameter names are from "A gentle introduction to WOFOST", De Wit & Boogaard 2021
parameter_names = {"DVS": "Crop development stage",
"LAI": "Leaf area index [ha/ha]",
"TAGP": "Total above-ground production [kg/ha]",
"TWSO": "Total weight - storage organs [kg/ha]",
"TWLV": "Total weight - leaves [kg/ha]",
"TWST": "Total weight - stems [kg/ha]",
"TWRT": "Total weight - roots [kg/ha]",
"TRA": "Crop transpiration [cm/day]",
"RD": "Crop rooting depth [cm]",
"SM": "Soil moisture index",
"WWLOW": "Total water [cm]",
"LAIMAX": "Maximum leaf area index [ha/ha]",
"CTRAT": "Cumulative crop transpiration [cm]",
"DOS": "Date of sowing",
"DOE": "Date of emergence",
"DOA": "Date of anthesis",
"DOM": "Date of maturity",
"DOH": "Date of harvest",
"DOV": "Date of vernalisation",
"CEVST": "Cumulative soil evaporation [cm]",
"area": "Total area [ha]",
"n": "Number of sites",
}
kgperha = "kg / ha"

DVS = PCSENumericParameter(name="DVS", description="Crop development state (-0.1 = sowing; 0 = emergence; 1 = flowering; 2 = maturity)", plotname="Crop development state", bounds=(-0.1, 2))
LAI = PCSENumericParameter(name="LAI", description="Leaf area index", unit="ha / ha", bounds=(0, 12))
TAGP = PCSENumericParameter(name="TAGP", description="Total above-ground production", unit=kgperha, bounds=(0, 150000))
TWSO = PCSENumericParameter(name="TWSO", description="Total weight of storage organs", unit=kgperha, bounds=(0, 100000))
TWLV = PCSENumericParameter(name="TWLV", description="Total weight of leaves", unit=kgperha, bounds=(0, 100000))
TWST = PCSENumericParameter(name="TWST", description="Total weight of stems", unit=kgperha, bounds=(0, 100000))
TWRT = PCSENumericParameter(name="TWSO", description="Total weight of roots", unit=kgperha, bounds=(0, 100000))
TRA = PCSENumericParameter(name="TRA", description="Crop transpiration", unit="cm / day")
RD = PCSENumericParameter(name="RD", description="Rooting depth", unit="cm", bounds=(10, 150))
SM = PCSENumericParameter(name="SM", description="Actual soil moisture content in rooted zone", plotname="Soil moisture index", bounds=(0.01, 0.9))
WWLOW = PCSENumericParameter(name="WWLOW", description="Amount of water in whole rootable zone", plotname="Water in rootable zone", unit="cm", bounds=(0, 150))
LAIMAX = PCSENumericParameter(name="LAIMAX", description="Maximum LAI reached during growth cycle", plotname="Maximum leaf area index", unit="ha / ha")
CTRAT = PCSENumericParameter(name="CTRAT", description="Cumulative crop transpiration", unit="cm", bounds=(0, 100))
CEVST = PCSENumericParameter(name="CEVST", description="Cumulative soil transpiration", unit="cm")
DOS = PCSEDateParameter(name="DOS", description="Date of sowing")
DOE = PCSEDateParameter(name="DOE", description="Date of emergence")
DOA = PCSEDateParameter(name="DOA", description="Date of anthesis")
DOM = PCSEDateParameter(name="DOM", description="Date of maturity")
DOH = PCSEDateParameter(name="DOH", description="Date of harvest")
DOV = PCSEDateParameter(name="DOV", description="Date of vernalisation")

parameters = parameterdict(DVS, LAI, TAGP, TWSO, TWLV, TWST, TWRT, TRA, RD, SM, WWLOW, LAIMAX, CTRAT, CEVST, DOS, DOE, DOA, DOM, DOH, DOV)

_GEOMETRY_DEFAULT = (0, 0)

Expand Down
9 changes: 7 additions & 2 deletions fpcup/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@

from ._brp_dictionary import brp_categories_colours, brp_crops_colours
from ._typing import Aggregator, Callable, Iterable, Optional, PathOrStr, RealNumber, StringDict
from .aggregate import KEYS_AGGREGATE
from .aggregate import KEYS_AGGREGATE, parameters as agg_parameters
from .constants import CRS_AMERSFOORT, WGS84
from .crop import parameters as crop_parameters
from .geo import PROVINCE_NAMES, area, area_coarse, boundary, boundary_coarse, aggregate_h3, entries_in_province
from .model import Summary, parameter_names
from .model import Summary, parameters as model_parameters
from .site import parameters as site_parameters
from .soil import parameters as soil_parameters
from .tools import make_iterable

# Constants
Expand All @@ -36,6 +39,8 @@

KEYS_AGGREGATE_PLOT = ("n", "area", *KEYS_AGGREGATE)

parameter_names = {**agg_parameters, **crop_parameters, **model_parameters, **site_parameters, **soil_parameters}

def plot_outline(ax: plt.Axes, province: str="Netherlands", *,
coarse: bool=False, crs: str=CRS_AMERSFOORT, **kwargs) -> None:
"""
Expand Down
13 changes: 12 additions & 1 deletion fpcup/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,22 @@
from pcse.util import WOFOST72SiteDataProvider, WOFOST80SiteDataProvider
from pcse.util import _GenericSiteDataProvider as PCSESiteDataProvider

from ._typing import Callable, Coordinates, Iterable, RealNumber
from ._typing import Callable, Coordinates, Iterable, PCSEFlag, PCSENumericParameter, RealNumber
from .constants import CRS_AMERSFOORT, WGS84
from .geo import area, _generate_random_point_in_geometry, _generate_random_point_in_geometry_batch, coverage_of_bounding_box, transform_geometry
from .multiprocessing import multiprocess_site_generation
from .tools import parameterdict

# Parameter information from "A gentle introduction to WOFOST" (De Wit & Boogaard 2021)
WAV = PCSENumericParameter(name="WAV", description="Initial amount of water in rootable zone in excess of wilting point", plotname="Initial amount of water", unit="cm", bounds=(0, 50))
NOTINF = PCSENumericParameter(name="NOTINF", description="Non-infiltrating fraction", bounds=(0, 1))
SMLIM = PCSENumericParameter(name="SMLIM", description="Maximum initial soil moisture in rooted zone", plotname="Maximum initial soil moisture", unit="cm", bounds=(0, 10))
SSI = PCSENumericParameter(name="SSI", description="Initial surface storage", unit="cm", bounds=(0, 2))
SSMAX = PCSENumericParameter(name="SSMAX", description="Maximum surface storage capacity", unit="cm", bounds=(0, 2))

IFUNRN = PCSEFlag(name="IFUNRN", description="Flag indicating the way the non-infiltrating fraction of rainfall is determined")

parameters = parameterdict(WAV, NOTINF, SMLIM, SSI, SSMAX, IFUNRN)

def example(*args, **kwargs) -> PCSESiteDataProvider:
"""
Expand Down
44 changes: 25 additions & 19 deletions fpcup/soil.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,37 @@
from pcse.fileinput import CABOFileReader
from pcse.util import DummySoilDataProvider

from ._typing import PathOrStr
from ._typing import PathOrStr, PCSELabel, PCSENumericParameter, PCSETabularParameter
from .settings import DEFAULT_DATA
from .tools import parameterdict

SoilType = CABOFileReader | DummySoilDataProvider

DEFAULT_SOIL_DATA = DEFAULT_DATA / "soil"

# Parameter names are from "A gentle introduction to WOFOST" (De Wit & Boogaard 2021) and from the CABO file descriptions
parameter_names = {"SOLNAM": "Soil name",
"CRAIRC": "Critical soil air content for aeration [cm^3 / cm^3]",
"SM0": "Soil moisture content of saturated soil [cm^3 / cm^3]",
"SMTAB": "Vol. soil moisture content as function of pF [log(cm) ; cm^3 / cm^3]",
"SMFCF": "Soil moisture content at field capacity [cm^3 / cm^3]",
"SMW": "Soil moisture content at wilting point [cm^3 / cm^3]",
"RDMSOL": "Maximum rootable depth of soil [cm]",
"K0": "Hydraulic conductivity of saturated soil [cm / day]",
"KSUB": "Maximum percolation rate of water to subsoil [cm / day]",
"SOPE": "Maximum percolation rate of water through the root zone [cm / day]",
"SPADS": "1st topsoil seepage parameter deep seedbed",
"SPODS": "2nd topsoil seepage parameter deep seedbed",
"SPASS": "1st topsoil seepage parameter shallow seedbed",
"SPOSS": "2nd topsoil seepage parameter shallow seedbed",
"DEFLIM": "required moisture deficit deep seedbed",
"CONTAB": "10-log hydraulic conductivity as function of pF [log(cm) ; log(cm/day)]",
}
# Parameter information from "A gentle introduction to WOFOST" (De Wit & Boogaard 2021) and CABO file descriptions
cm3percm3 = "cm^3 / cm^3"
cmperday = "cm / day"
SOLNAM = PCSELabel(name="SOLNAM", description="Soil name")

CRAIRC = PCSENumericParameter(name="CRAIRC", description="Critical soil air content for aeration (used when IOX = 1)", plotname="Critical soil air content for aeration", unit=cm3percm3, bounds=(0.04, 0.1))
SM0 = PCSENumericParameter(name="SM0", description="Soil moisture content of saturated soil", plotname="Saturated soil moisture content", unit=cm3percm3, bounds=(0.3, 0.9))
SMFCF = PCSENumericParameter(name="SMFCF", description="Soil moisture content at field capacity", unit=cm3percm3, bounds=(0.05, 0.74))
SMW = PCSENumericParameter(name="SMW", description="Soil moisture content at wilting point", unit=cm3percm3, bounds=(0.01, 0.35))
RDMSOL = PCSENumericParameter(name="RDMSOL", description="Maximum rootable depth of soil", plotname="Maximum rootable depth", unit="cm", bounds=(10, 150))
K0 = PCSENumericParameter(name="K0", description="Hydraulic conductivity of saturated soil", unit=cmperday, bounds=(0.1, 14))
KSUB = PCSENumericParameter(name="KSUB", description="Maximum percolation rate of water to subsoil", unit=cmperday, bounds=(0.1, 14))
SOPE = PCSENumericParameter(name="SOPE", description="Maximum percolation rate of water through the root zone", unit=cmperday, bounds=(0, 10))
SPADS = PCSENumericParameter(name="SPADS", description="1st topsoil seepage parameter deep seedbed")
SPODS = PCSENumericParameter(name="SPODS", description="2nd topsoil seepage parameter deep seedbed")
SPASS = PCSENumericParameter(name="SPADS", description="1st topsoil seepage parameter shallow seedbed")
SPOSS = PCSENumericParameter(name="SPADS", description="2nd topsoil seepage parameter shallow seedbed")
DEFLIM = PCSENumericParameter(name="DEFLIM", description="Required moisture deficit deep seedbed")

SMTAB = PCSETabularParameter(name="SMTAB", description="Volumetric soil moisture content", x="pF", x_unit="log(cm)", unit=cm3percm3)
CONTAB = PCSETabularParameter(name="CONTAB", description="10-log hydraulic conductivity", x="pF", x_unit="log(cm)", unit=f"log({cmperday})")

parameters = parameterdict(SOLNAM, CRAIRC, SM0, SMFCF, SMW, RDMSOL, K0, KSUB, SOPE, SPADS, SPODS, SPASS, SPOSS, DEFLIM, SMTAB, CONTAB)

# Dummy with default settings
dummy = DummySoilDataProvider()
Expand Down
10 changes: 9 additions & 1 deletion fpcup/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from textwrap import indent
indent2 = partial(indent, prefix=" ")

from ._typing import Iterable
from ._typing import Iterable, PCSEParameter

try:
get_ipython()
Expand All @@ -31,6 +31,7 @@ def make_iterable(x: object, exclude: Iterable[type]=[str]) -> Iterable:
else:
return [x]


def dict_product(d: dict) -> list[dict]:
"""
For a dict `d` with iterable values, return the Cartesian product, i.e. all combinations of values in those iterables.
Expand All @@ -41,3 +42,10 @@ def dict_product(d: dict) -> list[dict]:
d_all_iterable = {key: make_iterable(value) for key, value in d.items()}
d_product = [dict(zip(d_all_iterable.keys(), i)) for i in product(*d_all_iterable.values())]
return d_product


def parameterdict(*params: Iterable[PCSEParameter]) -> dict[str, PCSEParameter]:
"""
Combines an iterable of PCSEParameter objects into a dictionary, with their name as their key.
"""
return {p.name: p for p in params}

0 comments on commit aebf5ec

Please sign in to comment.