Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import math
from typing import List, Optional, Sequence
from .agri_growing_degree_days_types import DailyTemperature, DegreeDayMethod, DegreeDays
from .math_round_div import round_div ← from math.round-div ^1.0.0 · built alongside by fune
from .math_round_float import round_float ← from math.round-float ^1.0.0 · built alongside by fune
def _tenths(value: float, message: str) -> int:
if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value) or value < -100 or value > 100:
raise ValueError(message)
return int(round(round_float(float(value), 1) * 10))
def growing_degree_days(
days: Sequence[DailyTemperature],
base_celsius: float,
upper_celsius: Optional[float],
method: DegreeDayMethod,
) -> DegreeDays:
"""Growing degree days, McMaster and Wilhelm (1997), in tenths of a degree.
Each day is (max + min) / 2 - base with the temperatures taken to 0.1 C,
so it is an exact multiple of 0.05 and the total is summed exactly and
rounded once. Rounding each day to 0.1 first and adding drifts: three days
of 0.05 are 0.15 (0.2 to one place), not 0.3.
"""
base = _tenths(base_celsius, "baseCelsius must be a finite number from -100 to 100")
upper: Optional[int] = None
if upper_celsius is not None:
upper = _tenths(upper_celsius, "upperCelsius must be a finite number from -100 to 100")
if upper <= base:
raise ValueError("upperCelsius must be above baseCelsius")
if method not in ("average", "clamped"):
raise ValueError('unknown method "%s": use average or clamped' % (method,))
def cap(t: int) -> int:
return upper if upper is not None and t > upper else t
daily: List[float] = []
total = 0
for i, day in enumerate(days):
message = "day %d: temperatures must be finite numbers from -100 to 100 °C" % (i + 1,)
lo = _tenths(day.min, message)
hi = _tenths(day.max, message)
if lo > hi:
raise ValueError("day %d: min must not be above max" % (i + 1,))
lo = cap(lo)
hi = cap(hi)
if method == "clamped":
twice = max(lo, base) + max(hi, base) - 2 * base
else:
twice = max(lo + hi - 2 * base, 0)
# twice is 2 x (mean - base) in tenths, so the day is twice / 20 degrees.
daily.append(twice / 20)
total += twice
return DegreeDays(total=round_div(total, 2, "half-up") / 10, daily=daily)