Aller au contenu

Hydrologie (kadi.weather.hydrology)

La classe Hydrology modélise le bilan hydrique du sol pour une parcelle agricole. Elle calcule l'évapotranspiration de référence (ET0), le ruissellement journalier (SCS-CN) et le bilan hydrique complet selon la norme FAO-56.

Elle est utilisée en interne par la façade Weather, mais peut aussi être instanciée directement pour des analyses spécifiques.


Importation directe

from kadi.weather import Hydrology

Initialisation

Dans le cas courant, Hydrology est créée automatiquement par la façade Weather lors du premier appel à water_balance() ou et0_hargreaves(). Elle est alors accessible via weather.hydrology.

import kadi as kd

weather = kd.Weather(lat=9.3, lon=2.3, name="Parakou")

# Hydrology chargée à la demande
bilan = weather.water_balance(crop="maize", soil_type="ferrugineux")
hydrology = weather.hydrology   # Instance disponible

Pour une instanciation directe :

from kadi.weather import Hydrology, Location
import pandas as pd

location = Location(lat=9.3, lon=2.3, name="Parakou")

# rainfall    : pd.Series journalière de précipitations, indexée par date
# temperature : pd.DataFrame avec colonnes 'temperature_min' et 'temperature_max'

hydrology = Hydrology(
    location,
    rainfall,
    temperature,
    soil_type="ferrugineux",
    crop="maize",
)

Attributs publics :

Attribut Type Description
location Location Localisation de la parcelle
rainfall pd.Series Série de précipitations quotidiennes
temperature pd.DataFrame Données de température (temperature_min, temperature_max)
crop str Type de culture
soil_type str Type de sol
soil dict Paramètres physiques du sol
balance pd.DataFrame Résultat du bilan hydrique (None avant calcul)

Méthodes

water_balance()

Simule le bilan hydrique quotidien du sol selon la méthode FAO-56.

Le calcul comprend :

  1. ET0 journalier par Hargreaves-Samani.
  2. Ruissellement par la méthode SCS-CN avec ajustement AMC (Antecedent Moisture Condition) sur les 5 jours précédents.
  3. Évapotranspiration de la culture (ETc = ET0 x Kc).
  4. Bilan séquentiel : pluie efficace - ETc, plafonné à TAW (Total Available Water).
bilan = weather.water_balance(crop="maize", soil_type="ferrugineux")

# Derniers 7 jours
print(bilan.tail(7)[[
    "precip", "et0", "pluie_eff",
    "evapotransp", "deficit_eau", "reserve_utile", "stress_hydrique_index"
]])

Ce calcul est déclenché via la façade Weather. Pour l'appeler directement sur l'instance Hydrology (par exemple après avoir modifié la culture) :

# Modification de la culture et du sol
weather.hydrology.crop = "rice"
weather.hydrology.soil_type = "ferrallitique"
weather.hydrology.soil = weather.hydrology.soil_params("ferrallitique")

# Recalcul
bilan = weather.hydrology.water_balance()

Retour : pd.DataFrame avec DatetimeIndex.

Colonnes :

Colonne Description
precip Précipitations observées (mm)
et0 ET0 par Hargreaves-Samani (mm/jour)
pluie_eff Pluie efficace après déduction du ruissellement (mm)
evapotransp ETc = ET0 x Kc de la culture (mm/jour)
deficit_eau Déficit hydrique journalier dans le sol (mm)
reserve_utile Eau disponible dans le sol = TAW - déficit (mm)
stress_hydrique_index Indice de stress hydrique = déficit / TAW (0 a 1)

Exceptions :

  • DataError : données de précipitation ou de température manquantes.

et0_hargreaves(tmin, tmax, day_of_year)

Calcule l'évapotranspiration de référence (ET0) par la méthode Hargreaves-Samani. Alternative à Penman-Monteith lorsque les données d'humidité, de vent et de rayonnement solaire sont indisponibles.

# Calcul pour un jour donné
et0 = weather.et0_hargreaves(tmin=22.0, tmax=35.0, day_of_year=180)
print(f"ET0 : {et0:.2f} mm/jour")

# Appel direct sur l'instance Hydrology (accepte aussi des arrays numpy)
import numpy as np
tmin_arr = np.array([20.0, 21.0, 19.0])
tmax_arr = np.array([33.0, 35.0, 32.0])
doy_arr  = np.array([150, 151, 152])
et0_arr = weather.hydrology.et0_hargreaves(tmin_arr, tmax_arr, doy_arr)

Paramètres :

Nom Type Description
tmin float ou np.ndarray Température minimale (degC)
tmax float ou np.ndarray Température maximale (degC)
day_of_year int ou np.ndarray Jour de l'année (1 a 365)

Retour : float ou np.ndarray - ET0 en mm/jour.


et0_fao56_penman(tmin, tmax, humidity, wind_speed, solar_rad)

Calcule l'ET0 par la méthode FAO-56 Penman-Monteith. Plus précise que Hargreaves car elle intègre l'humidité relative, la vitesse du vent et le rayonnement solaire mesuré.

et0_pm = weather.hydrology.et0_fao56_penman(
    tmin=22.0,
    tmax=35.0,
    humidity=65.0,     # Humidité relative (%)
    wind_speed=2.5,    # Vitesse du vent a 2 m (m/s)
    solar_rad=18.0,    # Rayonnement solaire (MJ/m2/jour)
)
print(f"ET0 Penman-Monteith : {et0_pm:.2f} mm/jour")

Paramètres :

Nom Type Description
tmin float Température minimale (degC)
tmax float Température maximale (degC)
humidity float Humidité relative moyenne (%, entre 0 et 100)
wind_speed float Vitesse du vent à 2 m de hauteur (m/s)
solar_rad float Rayonnement solaire incident (MJ/m2/jour)

Retour : float - ET0 en mm/jour.


runoff_cn(precipitation, prior_5d_rain)

Calcule le ruissellement quotidien par la méthode révisée SCS-CN, avec ajustement selon l'humidité antécédente (AMC).

runoff = weather.hydrology.runoff_cn(
    precipitation=35.0,
    prior_5d_rain=20.0,
)
print(f"Ruissellement : {runoff:.2f} mm")

Paramètres :

Nom Type Défaut Description
precipitation float requis Précipitation du jour (mm)
prior_5d_rain float 0.0 Cumul des 5 jours précédents (mm) pour l'ajustement AMC

Ajustement AMC :

Condition Pluie 5j précédents CN ajusté
AMC I (sec) < 12.5 mm CN réduit
AMC II (moyen) 12.5 a 35.5 mm CN de base
AMC III (humide) > 35.5 mm CN augmenté

Retour : float - Ruissellement en mm (0.0 si la pluie ne dépasse pas l'abstraction initiale).


soil_params(soil_type)

Retourne les paramètres physiques d'un sol béninois.

params = weather.hydrology.soil_params("ferrallitique")
print(f"TAW   : {params['taw']} mm")
print(f"CN    : {params['cn_amc2']}")
print(f"Ksat  : {params['ksat']} mm/j")

Paramètres :

Nom Type Description
soil_type str Type de sol parmi les valeurs supportées

Types de sols supportés :

Type TAW (mm) CN AMC II Ksat (mm/j)
'ferrugineux' 100 82 15
'ferrallitique' 130 75 35
'sableux' 60 65 100
'limoneux' 150 78 10

Retour : dict avec taw, cn_amc2, ksat.

Exceptions :

  • ValidationError : type de sol non pris en charge.

crop_kc(crop, stage)

Retourne le coefficient cultural (Kc) selon le stade phénologique. Le Kc représente le rapport ETc / ET0 selon la norme FAO-56.

kc = weather.hydrology.crop_kc("maize", "mid")
print(f"Kc milieu de cycle : {kc}")

Paramètres :

Nom Type Description
crop str Nom de la culture
stage str Stade parmi 'ini', 'mid', 'end'

Coefficients Kc par culture et stade :

Culture Kc ini Kc mid Kc end
'maize' 0.30 1.20 0.35
'rice' 1.05 1.20 0.90
'manioc' 0.30 0.80 0.30
'sorghum' 0.30 1.00 0.55
'tomato' 0.60 1.15 0.70

Retour : float - Valeur du Kc.

Exceptions :

  • CropError : culture non reconnue.

Méthodes dépréciées

Ancienne méthode Remplacée par Depuis
compute_water_balance() water_balance() v1.2.0
get_soil_params(soil_type) soil_params(soil_type) v1.2.0
get_crop_coefficients(crop, stage) crop_kc(crop, stage) v1.2.0

Attributs dépréciés

Ancien attribut Remplacé par Depuis
rainfall_data rainfall v1.2.0
temperature_data temperature v1.2.0
balance_result balance v1.2.0

Exemple complet

import kadi as kd

weather = kd.Weather(lat=9.3, lon=2.3, name="Parakou")

# Bilan hydrique complet
bilan = weather.water_balance(crop="sorghum", soil_type="sableux")

# Analyse du stress hydrique sur les 30 derniers jours
derniers_30j = bilan.tail(30)
stress_moyen = derniers_30j["stress_hydrique_index"].mean()
print(f"Stress hydrique moyen (30j) : {stress_moyen:.2f}")

# ET0 ponctuel
et0 = weather.et0_hargreaves(tmin=21.0, tmax=34.0, day_of_year=200)
print(f"ET0 du jour : {et0:.2f} mm/jour")

# Paramètres du sol
params = weather.hydrology.soil_params("ferrugineux")
print(f"Réserve utile max : {params['taw']} mm")

# Kc en plein cycle
kc = weather.hydrology.crop_kc("sorghum", "mid")
print(f"ETc = ET0 x Kc = {et0:.2f} x {kc} = {et0 * kc:.2f} mm/jour")

Hydrology

Gère la modélisation hydrologique (bilan hydrique du sol) pour une parcelle.

Attributs

location (Location): Localisation de la parcelle. rainfall (pd.Series): Série de précipitations quotidiennes. temperature (pd.DataFrame): DataFrame avec temperature_min et temperature_max. crop (str): Type de culture. soil_type (str): Type de sol. soil (dict): Paramètres physiques du sol. balance (pd.DataFrame): Résultat du bilan hydrique (None avant calcul).

Source code in kadi/weather/hydrology.py
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
class Hydrology:
    """
    Gère la modélisation hydrologique (bilan hydrique du sol) pour une
    parcelle.

    Attributs:
        location (Location): Localisation de la parcelle.
        rainfall (pd.Series): Série de précipitations quotidiennes.
        temperature (pd.DataFrame): DataFrame avec temperature_min et
            temperature_max.
        crop (str): Type de culture.
        soil_type (str): Type de sol.
        soil (dict): Paramètres physiques du sol.
        balance (pd.DataFrame): Résultat du bilan hydrique (None avant calcul).
    """

    def __init__(
        self,
        location: Location,
        rainfall_data: pd.Series,
        temperature_data: pd.DataFrame,
        soil_type: Optional[str] = None,
        crop: str = "maize",
    ):
        """
        Initialise l'analyseur hydrologique.

        Args:
            location (Location): Instance de Location.
            rainfall_data (pd.Series): Série de précipitations quotidiennes.
            temperature_data (pd.DataFrame): DataFrame avec les colonnes
                'temperature_min' et 'temperature_max'.
            soil_type (str): Type de sol ('ferrugineux', 'ferrallitique',
                'sableux', 'limoneux'). Si None, résolu depuis le cache.
            crop (str): Type de culture.
        """
        self.location = location
        # Nouveaux noms d'attributs
        self.rainfall = rainfall_data
        self.temperature = temperature_data
        self.crop = crop
        self.soil_type = soil_type or self._resolve_soil(location)
        # Résultat du bilan hydrique (None avant le premier calcul)
        self.balance: Optional[pd.DataFrame] = None

        # Paramètres physiques du sol associé
        self.soil = self.soil_params(self.soil_type)

    def _resolve_soil(self, location: Location) -> str:
        """
        Détermine le type de sol depuis le cache local pré-téléchargé.

        Args:
            location (Location): Localisation de la parcelle.

        Returns:
            str: Identifiant du type de sol le plus probable.
        """
        from kadi._sources.soilgrids import fetch_soil_type
        return fetch_soil_type(location.lat, location.lon)

    def et0_hargreaves(self, tmin, tmax, day_of_year) -> float:
        """
        Calcule l'évapotranspiration de référence (ETo) par Hargreaves-Samani.

        Méthode alternative à Penman-Monteith, utilisée en l'absence de
        données d'humidité, de vent et de rayonnement solaire.

        Args:
            tmin: Température minimale (degC).
            tmax: Température maximale (degC).
            day_of_year: Jour de l'année (1 à 365).

        Returns:
            float: ETo en mm/jour.
        """
        # 1. Calcul du rayonnement extraterrestre Ra
        lat_rad = np.radians(self.location.lat)
        dr = 1 + 0.033 * np.cos(2 * np.pi * day_of_year / 365.0)
        delta = 0.409 * np.sin(2 * np.pi * day_of_year / 365.0 - 1.39)

        cos_omega_s = -np.tan(lat_rad) * np.tan(delta)
        cos_omega_s = np.clip(cos_omega_s, -1.0, 1.0)
        omega_s = np.arccos(cos_omega_s)

        # Constante solaire (MJ/m2/min)
        gsc = 0.0820
        ra = (24 * 60 / np.pi) * gsc * dr * (
            omega_s * np.sin(lat_rad) * np.sin(delta)
            + np.cos(lat_rad) * np.cos(delta) * np.sin(omega_s)
        )

        # 2. Formule Hargreaves-Samani
        tmean = (tmax + tmin) / 2.0
        tdiff = (
            np.maximum(0.0, tmax - tmin)
            if isinstance(tmax, np.ndarray)
            else max(0.0, tmax - tmin)
        )
        k_rs = 0.0023

        eto = 0.408 * k_rs * ra * (tmean + 17.8) * (tdiff ** 0.5)
        if isinstance(eto, np.ndarray):
            return np.maximum(0.0, eto)
        return float(max(0.0, eto))

    def et0_fao56_penman(
        self,
        tmin: float,
        tmax: float,
        humidity: float,
        wind_speed: float,
        solar_rad: float,
    ) -> float:
        """
        Calcule l'évapotranspiration de référence (ETo) par FAO-56 Penman-Monteith.

        Méthode de référence internationale (Allen et al., 1998, FAO-56).
        Plus précise que Hargreaves car elle intègre l'humidité relative,
        la vitesse du vent et le rayonnement solaire mesuré.

        Args:
            tmin (float): Température minimale (degC).
            tmax (float): Température maximale (degC).
            humidity (float): Humidité relative moyenne (%, entre 0 et 100).
            wind_speed (float): Vitesse du vent mesurée à 2 m de hauteur (m/s).
            solar_rad (float): Rayonnement solaire incident (MJ/m2/jour).

        Returns:
            float: ETo en mm/jour.
        """
        # Altitude moyenne estimée au Bénin (m) pour la pression atmosphérique
        z = 200.0
        tmean = (tmax + tmin) / 2.0

        # Pression atmosphérique (kPa) selon l'équation standard (FAO-56 Eq. 7)
        p_atm = 101.3 * ((293.0 - 0.0065 * z) / 293.0) ** 5.26

        # Constante psychrométrique gamma (kPa/degC) (FAO-56 Eq. 8)
        gamma = 0.000665 * p_atm

        # Pente de la courbe de pression de vapeur saturante (kPa/degC) (FAO-56 Eq. 13)
        delta = (
            4098.0
            * (0.6108 * np.exp(17.27 * tmean / (tmean + 237.3)))
            / (tmean + 237.3) ** 2
        )

        # Pression de vapeur saturante (kPa) : moyenne Tmin/Tmax (FAO-56 Eq. 11-12)
        es_tmax = 0.6108 * np.exp(17.27 * tmax / (tmax + 237.3))
        es_tmin = 0.6108 * np.exp(17.27 * tmin / (tmin + 237.3))
        es = (es_tmax + es_tmin) / 2.0

        # Pression de vapeur réelle ea (kPa) depuis l'humidité relative (FAO-56 Eq. 17)
        ea = (humidity / 100.0) * es

        # Déficit de pression de vapeur (kPa)
        vpd = es - ea

        # Rayonnement net (MJ/m2/jour) : simplification Rns - Rnl
        rns = (1.0 - 0.23) * solar_rad  # albédo = 0.23 pour culture de référence
        rnl = 0.2 * solar_rad            # approximation simplifiée
        rn = rns - rnl

        # Flux de chaleur du sol G = 0 à l'échelle journalière (FAO-56 hypothèse)
        g = 0.0

        # Équation FAO-56 Penman-Monteith (FAO-56 Eq. 6)
        num = 0.408 * delta * (rn - g) + gamma * (900.0 / (tmean + 273.0)) * wind_speed * vpd
        den = delta + gamma * (1.0 + 0.34 * wind_speed)

        eto = num / den
        return float(max(0.0, eto))

    def runoff_cn(self, precipitation: float, prior_5d_rain: float = 0.0) -> float:
        """
        Calcule le ruissellement quotidien par la méthode révisée SCS-CN.

        Args:
            precipitation (float): Précipitation du jour (mm).
            prior_5d_rain (float): Pluie des 5 jours précédents (pour
                ajustement AMC, Antecedent Moisture Condition).

        Returns:
            float: Ruissellement en mm.
        """
        if precipitation <= 0.0:
            return 0.0

        base_cn = self.soil["cn_amc2"]

        # Ajustement AMC (Antecedent Moisture Condition)
        if prior_5d_rain < 12.5:
            cn = base_cn / (2.281 - 0.0128 * base_cn)  # AMC I (sec)
        elif prior_5d_rain > 35.5:
            cn = base_cn / (0.427 + 0.00573 * base_cn)  # AMC III (humide)
        else:
            cn = base_cn  # AMC II (moyen)

        s = (25400.0 / cn) - 254.0
        ia = 0.2 * s  # Abstraction initiale

        if precipitation > ia:
            runoff = ((precipitation - ia) ** 2) / (precipitation + 0.8 * s)
            return float(runoff)
        return 0.0

    def water_balance(self) -> pd.DataFrame:
        """
        Simule le bilan hydrique quotidien du sol selon FAO-56.

        Returns:
            pd.DataFrame: DataFrame contenant l'évolution journalière du bilan
                hydrique (précipitation, ET0, pluie effective, évapotranspiration
                réelle, déficit, réserve utile, stress hydrique).

        Raises:
            DataError: Si les données météorologiques sont absentes.
        """
        if self.rainfall.empty or self.temperature.empty:
            raise DataError(
                "Données météorologiques historiques manquantes pour le "
                "calcul du bilan hydrique."
            )

        taw = self.soil["taw"]  # Total Available Water
        base_cn = self.soil["cn_amc2"]
        kc = self.crop_kc(self.crop, "mid")

        precip_arr = self.rainfall.to_numpy()
        tmin_arr = self.temperature["temperature_min"].to_numpy()
        tmax_arr = self.temperature["temperature_max"].to_numpy()
        dates = self.rainfall.index
        dayofyear_arr = dates.dayofyear.to_numpy()

        # Calcul des pluies accumulées sur les 5 jours précédents
        prior_5d_arr = (
            self.rainfall.shift(1)
            .rolling(window=5, min_periods=0)
            .sum()
            .fillna(0.0)
            .to_numpy()
        )

        # Vectorisation ETo Hargreaves-Samani
        eto_arr = self.et0_hargreaves(tmin_arr, tmax_arr, dayofyear_arr)
        etc_arr = eto_arr * kc

        # Vectorisation du ruissellement SCS-CN
        cn_1 = base_cn / (2.281 - 0.0128 * base_cn)
        cn_3 = base_cn / (0.427 + 0.00573 * base_cn)
        cn_arr = np.where(
            prior_5d_arr < 12.5, cn_1,
            np.where(prior_5d_arr > 35.5, cn_3, base_cn),
        )
        s_arr = (25400.0 / cn_arr) - 254.0
        ia_arr = 0.2 * s_arr

        runoff_arr = np.where(
            (precip_arr > 0) & (precip_arr > ia_arr),
            ((precip_arr - ia_arr) ** 2) / (precip_arr + 0.8 * s_arr),
            0.0,
        )
        pluie_eff_arr = np.maximum(0.0, precip_arr - runoff_arr)

        # Calcul séquentiel du stock d'eau du sol
        n = len(dates)
        dr_arr = np.zeros(n)
        reserve_arr = np.zeros(n)
        stress_arr = np.zeros(n)
        dr = 0.0

        for i in range(n):
            temp_dr = dr - pluie_eff_arr[i]
            if temp_dr < 0:
                dr = 0.0
            else:
                dr = min(taw, temp_dr + etc_arr[i])
            dr_arr[i] = dr
            reserve_arr[i] = taw - dr
            stress_arr[i] = dr / taw if taw > 0 else 0.0

        df = pd.DataFrame(
            {
                "precip": precip_arr,
                "et0": np.round(eto_arr, 2),
                "pluie_eff": np.round(pluie_eff_arr, 2),
                "evapotransp": np.round(etc_arr, 2),
                "deficit_eau": np.round(dr_arr, 2),
                "reserve_utile": np.round(reserve_arr, 2),
                "stress_hydrique_index": np.round(stress_arr, 2),
            },
            index=dates,
        )

        self.balance = df
        return df

    def soil_params(self, soil_type: str) -> dict:
        """
        Retourne les paramètres physiques du sol béninois.

        AWC : Available Water Capacity.
        CN  : Curve Number (SCS-CN).
        Ksat: Conductivité hydraulique saturée.

        Args:
            soil_type (str): Type de sol parmi 'ferrugineux', 'ferrallitique',
                'sableux', 'limoneux'.

        Returns:
            dict: Dictionnaire des paramètres (taw, cn_amc2, ksat).

        Raises:
            ValidationError: Si le type de sol n'est pas pris en charge.
        """
        soils = {
            "ferrugineux":  {"taw": 100.0, "cn_amc2": 82.0, "ksat": 15.0},
            "ferrallitique": {"taw": 130.0, "cn_amc2": 75.0, "ksat": 35.0},
            "sableux":       {"taw": 60.0,  "cn_amc2": 65.0, "ksat": 100.0},
            "limoneux":      {"taw": 150.0, "cn_amc2": 78.0, "ksat": 10.0},
        }
        if soil_type not in soils:
            raise ValidationError(
                f"Type de sol non pris en charge : {soil_type}"
            )
        return soils[soil_type]

    def crop_kc(self, crop: str, stage: str) -> float:
        """
        Retourne le coefficient cultural (Kc) selon le stade phenologique.

        La valeur Kc représente le rapport ETC / ET0 selon la norme FAO-56
        (Allen et al., 1998).

        Args:
            crop (str): Nom de la culture.
            stage (str): Stade phenologique parmi 'ini', 'mid', 'end'.

        Returns:
            float: Coefficient Kc pour le stade spécifié.

        Raises:
            CropError: Si la culture n'est pas reconnue.
        """
        kcs = {
            "maize":   {"ini": 0.3,  "mid": 1.2,  "end": 0.35},
            "rice":    {"ini": 1.05, "mid": 1.2,  "end": 0.9},
            "manioc":  {"ini": 0.3,  "mid": 0.8,  "end": 0.3},
            "sorghum": {"ini": 0.3,  "mid": 1.0,  "end": 0.55},
            "tomato":  {"ini": 0.6,  "mid": 1.15, "end": 0.7},
        }
        if crop not in kcs:
            raise CropError(
                f"Culture non reconnue pour le coefficient FAO-56 : {crop}"
            )
        return kcs[crop].get(stage, 1.0)

    # ----------------------------------------------------------------
    # Méthodes dépréciées (anciens noms publics)
    # ----------------------------------------------------------------

    def compute_water_balance(self) -> pd.DataFrame:
        """
        Ancienne méthode publique. Dépréciée depuis v1.2.0.

        Utilisez ``water_balance()`` à la place.

        Returns:
            pd.DataFrame: Résultat du bilan hydrique.
        """
        warnings.warn(
            "Hydrology.compute_water_balance() est obsolète et sera supprimé "
            "dans KadiPy v2.0. Utilisez water_balance() à la place.",
            category=DeprecationWarning,
            stacklevel=2,
        )
        return self.water_balance()

    def get_soil_params(self, soil_type: str) -> dict:
        """
        Ancienne méthode publique. Dépréciée depuis v1.2.0.

        Utilisez ``soil_params(soil_type)`` à la place.

        Args:
            soil_type (str): Type de sol.

        Returns:
            dict: Paramètres du sol.
        """
        warnings.warn(
            "Hydrology.get_soil_params() est obsolète et sera supprimé dans "
            "KadiPy v2.0. Utilisez soil_params() à la place.",
            category=DeprecationWarning,
            stacklevel=2,
        )
        return self.soil_params(soil_type)

    def get_crop_coefficients(self, crop: str, stage: str) -> float:
        """
        Ancienne méthode publique. Dépréciée depuis v1.2.0.

        Utilisez ``crop_kc(crop, stage)`` à la place.

        Args:
            crop (str): Nom de la culture.
            stage (str): Stade phenologique.

        Returns:
            float: Coefficient Kc.
        """
        warnings.warn(
            "Hydrology.get_crop_coefficients() est obsolète et sera supprimé "
            "dans KadiPy v2.0. Utilisez crop_kc() à la place.",
            category=DeprecationWarning,
            stacklevel=2,
        )
        return self.crop_kc(crop, stage)

    # ----------------------------------------------------------------
    # Propriétés de rétrocompatibilité (anciens noms d'attributs)
    # ----------------------------------------------------------------

    @property
    def rainfall_data(self) -> pd.Series:
        """Ancien nom de ``rainfall``. Déprécié depuis v1.2.0."""
        warnings.warn(
            "Hydrology.rainfall_data est obsolète et sera supprimé dans "
            "KadiPy v2.0. Utilisez Hydrology.rainfall à la place.",
            category=DeprecationWarning,
            stacklevel=2,
        )
        return self.rainfall

    @rainfall_data.setter
    def rainfall_data(self, value: pd.Series) -> None:
        """Ancien setter de ``rainfall``. Déprécié depuis v1.2.0."""
        warnings.warn(
            "Hydrology.rainfall_data est obsolète et sera supprimé dans "
            "KadiPy v2.0. Utilisez Hydrology.rainfall à la place.",
            category=DeprecationWarning,
            stacklevel=2,
        )
        self.rainfall = value

    @property
    def temperature_data(self) -> pd.DataFrame:
        """Ancien nom de ``temperature``. Déprécié depuis v1.2.0."""
        warnings.warn(
            "Hydrology.temperature_data est obsolète et sera supprimé dans "
            "KadiPy v2.0. Utilisez Hydrology.temperature à la place.",
            category=DeprecationWarning,
            stacklevel=2,
        )
        return self.temperature

    @temperature_data.setter
    def temperature_data(self, value: pd.DataFrame) -> None:
        """Ancien setter de ``temperature``. Déprécié depuis v1.2.0."""
        warnings.warn(
            "Hydrology.temperature_data est obsolète et sera supprimé dans "
            "KadiPy v2.0. Utilisez Hydrology.temperature à la place.",
            category=DeprecationWarning,
            stacklevel=2,
        )
        self.temperature = value

    @property
    def balance_result(self) -> Optional[pd.DataFrame]:
        """Ancien nom de ``balance``. Déprécié depuis v1.2.0."""
        warnings.warn(
            "Hydrology.balance_result est obsolète et sera supprimé dans "
            "KadiPy v2.0. Utilisez Hydrology.balance à la place.",
            category=DeprecationWarning,
            stacklevel=2,
        )
        return self.balance

    @balance_result.setter
    def balance_result(self, value: Optional[pd.DataFrame]) -> None:
        """Ancien setter de ``balance``. Déprécié depuis v1.2.0."""
        warnings.warn(
            "Hydrology.balance_result est obsolète et sera supprimé dans "
            "KadiPy v2.0. Utilisez Hydrology.balance à la place.",
            category=DeprecationWarning,
            stacklevel=2,
        )
        self.balance = value

    @property
    def soil_params_attr(self) -> dict:
        """Ancien attribut ``soil_params`` (maintenant méthode). Interne."""
        return self.soil

    def _resolve_soil_type_from_cache(self, location: Location) -> str:
        """
        Ancien nom de ``_resolve_soil``. Déprécié depuis v1.2.0.

        Args:
            location (Location): Instance de Location.

        Returns:
            str: Type de sol.
        """
        warnings.warn(
            "Hydrology._resolve_soil_type_from_cache est obsolète et sera supprimé dans "
            "KadiPy v2.0. Utilisez Hydrology._resolve_soil à la place.",
            category=DeprecationWarning,
            stacklevel=2,
        )
        return self._resolve_soil(location)

rainfall_data property writable

rainfall_data: Series

Ancien nom de rainfall. Déprécié depuis v1.2.0.

temperature_data property writable

temperature_data: DataFrame

Ancien nom de temperature. Déprécié depuis v1.2.0.

balance_result property writable

balance_result: Optional[DataFrame]

Ancien nom de balance. Déprécié depuis v1.2.0.

soil_params_attr property

soil_params_attr: dict

Ancien attribut soil_params (maintenant méthode). Interne.

__init__

__init__(location: Location, rainfall_data: Series, temperature_data: DataFrame, soil_type: Optional[str] = None, crop: str = 'maize')

Initialise l'analyseur hydrologique.

Parameters:

Name Type Description Default
location Location

Instance de Location.

required
rainfall_data Series

Série de précipitations quotidiennes.

required
temperature_data DataFrame

DataFrame avec les colonnes 'temperature_min' et 'temperature_max'.

required
soil_type str

Type de sol ('ferrugineux', 'ferrallitique', 'sableux', 'limoneux'). Si None, résolu depuis le cache.

None
crop str

Type de culture.

'maize'
Source code in kadi/weather/hydrology.py
def __init__(
    self,
    location: Location,
    rainfall_data: pd.Series,
    temperature_data: pd.DataFrame,
    soil_type: Optional[str] = None,
    crop: str = "maize",
):
    """
    Initialise l'analyseur hydrologique.

    Args:
        location (Location): Instance de Location.
        rainfall_data (pd.Series): Série de précipitations quotidiennes.
        temperature_data (pd.DataFrame): DataFrame avec les colonnes
            'temperature_min' et 'temperature_max'.
        soil_type (str): Type de sol ('ferrugineux', 'ferrallitique',
            'sableux', 'limoneux'). Si None, résolu depuis le cache.
        crop (str): Type de culture.
    """
    self.location = location
    # Nouveaux noms d'attributs
    self.rainfall = rainfall_data
    self.temperature = temperature_data
    self.crop = crop
    self.soil_type = soil_type or self._resolve_soil(location)
    # Résultat du bilan hydrique (None avant le premier calcul)
    self.balance: Optional[pd.DataFrame] = None

    # Paramètres physiques du sol associé
    self.soil = self.soil_params(self.soil_type)

et0_hargreaves

et0_hargreaves(tmin, tmax, day_of_year) -> float

Calcule l'évapotranspiration de référence (ETo) par Hargreaves-Samani.

Méthode alternative à Penman-Monteith, utilisée en l'absence de données d'humidité, de vent et de rayonnement solaire.

Parameters:

Name Type Description Default
tmin

Température minimale (degC).

required
tmax

Température maximale (degC).

required
day_of_year

Jour de l'année (1 à 365).

required

Returns:

Name Type Description
float float

ETo en mm/jour.

Source code in kadi/weather/hydrology.py
def et0_hargreaves(self, tmin, tmax, day_of_year) -> float:
    """
    Calcule l'évapotranspiration de référence (ETo) par Hargreaves-Samani.

    Méthode alternative à Penman-Monteith, utilisée en l'absence de
    données d'humidité, de vent et de rayonnement solaire.

    Args:
        tmin: Température minimale (degC).
        tmax: Température maximale (degC).
        day_of_year: Jour de l'année (1 à 365).

    Returns:
        float: ETo en mm/jour.
    """
    # 1. Calcul du rayonnement extraterrestre Ra
    lat_rad = np.radians(self.location.lat)
    dr = 1 + 0.033 * np.cos(2 * np.pi * day_of_year / 365.0)
    delta = 0.409 * np.sin(2 * np.pi * day_of_year / 365.0 - 1.39)

    cos_omega_s = -np.tan(lat_rad) * np.tan(delta)
    cos_omega_s = np.clip(cos_omega_s, -1.0, 1.0)
    omega_s = np.arccos(cos_omega_s)

    # Constante solaire (MJ/m2/min)
    gsc = 0.0820
    ra = (24 * 60 / np.pi) * gsc * dr * (
        omega_s * np.sin(lat_rad) * np.sin(delta)
        + np.cos(lat_rad) * np.cos(delta) * np.sin(omega_s)
    )

    # 2. Formule Hargreaves-Samani
    tmean = (tmax + tmin) / 2.0
    tdiff = (
        np.maximum(0.0, tmax - tmin)
        if isinstance(tmax, np.ndarray)
        else max(0.0, tmax - tmin)
    )
    k_rs = 0.0023

    eto = 0.408 * k_rs * ra * (tmean + 17.8) * (tdiff ** 0.5)
    if isinstance(eto, np.ndarray):
        return np.maximum(0.0, eto)
    return float(max(0.0, eto))

et0_fao56_penman

et0_fao56_penman(tmin: float, tmax: float, humidity: float, wind_speed: float, solar_rad: float) -> float

Calcule l'évapotranspiration de référence (ETo) par FAO-56 Penman-Monteith.

Méthode de référence internationale (Allen et al., 1998, FAO-56). Plus précise que Hargreaves car elle intègre l'humidité relative, la vitesse du vent et le rayonnement solaire mesuré.

Parameters:

Name Type Description Default
tmin float

Température minimale (degC).

required
tmax float

Température maximale (degC).

required
humidity float

Humidité relative moyenne (%, entre 0 et 100).

required
wind_speed float

Vitesse du vent mesurée à 2 m de hauteur (m/s).

required
solar_rad float

Rayonnement solaire incident (MJ/m2/jour).

required

Returns:

Name Type Description
float float

ETo en mm/jour.

Source code in kadi/weather/hydrology.py
def et0_fao56_penman(
    self,
    tmin: float,
    tmax: float,
    humidity: float,
    wind_speed: float,
    solar_rad: float,
) -> float:
    """
    Calcule l'évapotranspiration de référence (ETo) par FAO-56 Penman-Monteith.

    Méthode de référence internationale (Allen et al., 1998, FAO-56).
    Plus précise que Hargreaves car elle intègre l'humidité relative,
    la vitesse du vent et le rayonnement solaire mesuré.

    Args:
        tmin (float): Température minimale (degC).
        tmax (float): Température maximale (degC).
        humidity (float): Humidité relative moyenne (%, entre 0 et 100).
        wind_speed (float): Vitesse du vent mesurée à 2 m de hauteur (m/s).
        solar_rad (float): Rayonnement solaire incident (MJ/m2/jour).

    Returns:
        float: ETo en mm/jour.
    """
    # Altitude moyenne estimée au Bénin (m) pour la pression atmosphérique
    z = 200.0
    tmean = (tmax + tmin) / 2.0

    # Pression atmosphérique (kPa) selon l'équation standard (FAO-56 Eq. 7)
    p_atm = 101.3 * ((293.0 - 0.0065 * z) / 293.0) ** 5.26

    # Constante psychrométrique gamma (kPa/degC) (FAO-56 Eq. 8)
    gamma = 0.000665 * p_atm

    # Pente de la courbe de pression de vapeur saturante (kPa/degC) (FAO-56 Eq. 13)
    delta = (
        4098.0
        * (0.6108 * np.exp(17.27 * tmean / (tmean + 237.3)))
        / (tmean + 237.3) ** 2
    )

    # Pression de vapeur saturante (kPa) : moyenne Tmin/Tmax (FAO-56 Eq. 11-12)
    es_tmax = 0.6108 * np.exp(17.27 * tmax / (tmax + 237.3))
    es_tmin = 0.6108 * np.exp(17.27 * tmin / (tmin + 237.3))
    es = (es_tmax + es_tmin) / 2.0

    # Pression de vapeur réelle ea (kPa) depuis l'humidité relative (FAO-56 Eq. 17)
    ea = (humidity / 100.0) * es

    # Déficit de pression de vapeur (kPa)
    vpd = es - ea

    # Rayonnement net (MJ/m2/jour) : simplification Rns - Rnl
    rns = (1.0 - 0.23) * solar_rad  # albédo = 0.23 pour culture de référence
    rnl = 0.2 * solar_rad            # approximation simplifiée
    rn = rns - rnl

    # Flux de chaleur du sol G = 0 à l'échelle journalière (FAO-56 hypothèse)
    g = 0.0

    # Équation FAO-56 Penman-Monteith (FAO-56 Eq. 6)
    num = 0.408 * delta * (rn - g) + gamma * (900.0 / (tmean + 273.0)) * wind_speed * vpd
    den = delta + gamma * (1.0 + 0.34 * wind_speed)

    eto = num / den
    return float(max(0.0, eto))

runoff_cn

runoff_cn(precipitation: float, prior_5d_rain: float = 0.0) -> float

Calcule le ruissellement quotidien par la méthode révisée SCS-CN.

Parameters:

Name Type Description Default
precipitation float

Précipitation du jour (mm).

required
prior_5d_rain float

Pluie des 5 jours précédents (pour ajustement AMC, Antecedent Moisture Condition).

0.0

Returns:

Name Type Description
float float

Ruissellement en mm.

Source code in kadi/weather/hydrology.py
def runoff_cn(self, precipitation: float, prior_5d_rain: float = 0.0) -> float:
    """
    Calcule le ruissellement quotidien par la méthode révisée SCS-CN.

    Args:
        precipitation (float): Précipitation du jour (mm).
        prior_5d_rain (float): Pluie des 5 jours précédents (pour
            ajustement AMC, Antecedent Moisture Condition).

    Returns:
        float: Ruissellement en mm.
    """
    if precipitation <= 0.0:
        return 0.0

    base_cn = self.soil["cn_amc2"]

    # Ajustement AMC (Antecedent Moisture Condition)
    if prior_5d_rain < 12.5:
        cn = base_cn / (2.281 - 0.0128 * base_cn)  # AMC I (sec)
    elif prior_5d_rain > 35.5:
        cn = base_cn / (0.427 + 0.00573 * base_cn)  # AMC III (humide)
    else:
        cn = base_cn  # AMC II (moyen)

    s = (25400.0 / cn) - 254.0
    ia = 0.2 * s  # Abstraction initiale

    if precipitation > ia:
        runoff = ((precipitation - ia) ** 2) / (precipitation + 0.8 * s)
        return float(runoff)
    return 0.0

water_balance

water_balance() -> pd.DataFrame

Simule le bilan hydrique quotidien du sol selon FAO-56.

Returns:

Type Description
DataFrame

pd.DataFrame: DataFrame contenant l'évolution journalière du bilan hydrique (précipitation, ET0, pluie effective, évapotranspiration réelle, déficit, réserve utile, stress hydrique).

Raises:

Type Description
DataError

Si les données météorologiques sont absentes.

Source code in kadi/weather/hydrology.py
def water_balance(self) -> pd.DataFrame:
    """
    Simule le bilan hydrique quotidien du sol selon FAO-56.

    Returns:
        pd.DataFrame: DataFrame contenant l'évolution journalière du bilan
            hydrique (précipitation, ET0, pluie effective, évapotranspiration
            réelle, déficit, réserve utile, stress hydrique).

    Raises:
        DataError: Si les données météorologiques sont absentes.
    """
    if self.rainfall.empty or self.temperature.empty:
        raise DataError(
            "Données météorologiques historiques manquantes pour le "
            "calcul du bilan hydrique."
        )

    taw = self.soil["taw"]  # Total Available Water
    base_cn = self.soil["cn_amc2"]
    kc = self.crop_kc(self.crop, "mid")

    precip_arr = self.rainfall.to_numpy()
    tmin_arr = self.temperature["temperature_min"].to_numpy()
    tmax_arr = self.temperature["temperature_max"].to_numpy()
    dates = self.rainfall.index
    dayofyear_arr = dates.dayofyear.to_numpy()

    # Calcul des pluies accumulées sur les 5 jours précédents
    prior_5d_arr = (
        self.rainfall.shift(1)
        .rolling(window=5, min_periods=0)
        .sum()
        .fillna(0.0)
        .to_numpy()
    )

    # Vectorisation ETo Hargreaves-Samani
    eto_arr = self.et0_hargreaves(tmin_arr, tmax_arr, dayofyear_arr)
    etc_arr = eto_arr * kc

    # Vectorisation du ruissellement SCS-CN
    cn_1 = base_cn / (2.281 - 0.0128 * base_cn)
    cn_3 = base_cn / (0.427 + 0.00573 * base_cn)
    cn_arr = np.where(
        prior_5d_arr < 12.5, cn_1,
        np.where(prior_5d_arr > 35.5, cn_3, base_cn),
    )
    s_arr = (25400.0 / cn_arr) - 254.0
    ia_arr = 0.2 * s_arr

    runoff_arr = np.where(
        (precip_arr > 0) & (precip_arr > ia_arr),
        ((precip_arr - ia_arr) ** 2) / (precip_arr + 0.8 * s_arr),
        0.0,
    )
    pluie_eff_arr = np.maximum(0.0, precip_arr - runoff_arr)

    # Calcul séquentiel du stock d'eau du sol
    n = len(dates)
    dr_arr = np.zeros(n)
    reserve_arr = np.zeros(n)
    stress_arr = np.zeros(n)
    dr = 0.0

    for i in range(n):
        temp_dr = dr - pluie_eff_arr[i]
        if temp_dr < 0:
            dr = 0.0
        else:
            dr = min(taw, temp_dr + etc_arr[i])
        dr_arr[i] = dr
        reserve_arr[i] = taw - dr
        stress_arr[i] = dr / taw if taw > 0 else 0.0

    df = pd.DataFrame(
        {
            "precip": precip_arr,
            "et0": np.round(eto_arr, 2),
            "pluie_eff": np.round(pluie_eff_arr, 2),
            "evapotransp": np.round(etc_arr, 2),
            "deficit_eau": np.round(dr_arr, 2),
            "reserve_utile": np.round(reserve_arr, 2),
            "stress_hydrique_index": np.round(stress_arr, 2),
        },
        index=dates,
    )

    self.balance = df
    return df

soil_params

soil_params(soil_type: str) -> dict

Retourne les paramètres physiques du sol béninois.

AWC : Available Water Capacity. CN : Curve Number (SCS-CN). Ksat: Conductivité hydraulique saturée.

Parameters:

Name Type Description Default
soil_type str

Type de sol parmi 'ferrugineux', 'ferrallitique', 'sableux', 'limoneux'.

required

Returns:

Name Type Description
dict dict

Dictionnaire des paramètres (taw, cn_amc2, ksat).

Raises:

Type Description
ValidationError

Si le type de sol n'est pas pris en charge.

Source code in kadi/weather/hydrology.py
def soil_params(self, soil_type: str) -> dict:
    """
    Retourne les paramètres physiques du sol béninois.

    AWC : Available Water Capacity.
    CN  : Curve Number (SCS-CN).
    Ksat: Conductivité hydraulique saturée.

    Args:
        soil_type (str): Type de sol parmi 'ferrugineux', 'ferrallitique',
            'sableux', 'limoneux'.

    Returns:
        dict: Dictionnaire des paramètres (taw, cn_amc2, ksat).

    Raises:
        ValidationError: Si le type de sol n'est pas pris en charge.
    """
    soils = {
        "ferrugineux":  {"taw": 100.0, "cn_amc2": 82.0, "ksat": 15.0},
        "ferrallitique": {"taw": 130.0, "cn_amc2": 75.0, "ksat": 35.0},
        "sableux":       {"taw": 60.0,  "cn_amc2": 65.0, "ksat": 100.0},
        "limoneux":      {"taw": 150.0, "cn_amc2": 78.0, "ksat": 10.0},
    }
    if soil_type not in soils:
        raise ValidationError(
            f"Type de sol non pris en charge : {soil_type}"
        )
    return soils[soil_type]

crop_kc

crop_kc(crop: str, stage: str) -> float

Retourne le coefficient cultural (Kc) selon le stade phenologique.

La valeur Kc représente le rapport ETC / ET0 selon la norme FAO-56 (Allen et al., 1998).

Parameters:

Name Type Description Default
crop str

Nom de la culture.

required
stage str

Stade phenologique parmi 'ini', 'mid', 'end'.

required

Returns:

Name Type Description
float float

Coefficient Kc pour le stade spécifié.

Raises:

Type Description
CropError

Si la culture n'est pas reconnue.

Source code in kadi/weather/hydrology.py
def crop_kc(self, crop: str, stage: str) -> float:
    """
    Retourne le coefficient cultural (Kc) selon le stade phenologique.

    La valeur Kc représente le rapport ETC / ET0 selon la norme FAO-56
    (Allen et al., 1998).

    Args:
        crop (str): Nom de la culture.
        stage (str): Stade phenologique parmi 'ini', 'mid', 'end'.

    Returns:
        float: Coefficient Kc pour le stade spécifié.

    Raises:
        CropError: Si la culture n'est pas reconnue.
    """
    kcs = {
        "maize":   {"ini": 0.3,  "mid": 1.2,  "end": 0.35},
        "rice":    {"ini": 1.05, "mid": 1.2,  "end": 0.9},
        "manioc":  {"ini": 0.3,  "mid": 0.8,  "end": 0.3},
        "sorghum": {"ini": 0.3,  "mid": 1.0,  "end": 0.55},
        "tomato":  {"ini": 0.6,  "mid": 1.15, "end": 0.7},
    }
    if crop not in kcs:
        raise CropError(
            f"Culture non reconnue pour le coefficient FAO-56 : {crop}"
        )
    return kcs[crop].get(stage, 1.0)

compute_water_balance

compute_water_balance() -> pd.DataFrame

Ancienne méthode publique. Dépréciée depuis v1.2.0.

Utilisez water_balance() à la place.

Returns:

Type Description
DataFrame

pd.DataFrame: Résultat du bilan hydrique.

Source code in kadi/weather/hydrology.py
def compute_water_balance(self) -> pd.DataFrame:
    """
    Ancienne méthode publique. Dépréciée depuis v1.2.0.

    Utilisez ``water_balance()`` à la place.

    Returns:
        pd.DataFrame: Résultat du bilan hydrique.
    """
    warnings.warn(
        "Hydrology.compute_water_balance() est obsolète et sera supprimé "
        "dans KadiPy v2.0. Utilisez water_balance() à la place.",
        category=DeprecationWarning,
        stacklevel=2,
    )
    return self.water_balance()

get_soil_params

get_soil_params(soil_type: str) -> dict

Ancienne méthode publique. Dépréciée depuis v1.2.0.

Utilisez soil_params(soil_type) à la place.

Parameters:

Name Type Description Default
soil_type str

Type de sol.

required

Returns:

Name Type Description
dict dict

Paramètres du sol.

Source code in kadi/weather/hydrology.py
def get_soil_params(self, soil_type: str) -> dict:
    """
    Ancienne méthode publique. Dépréciée depuis v1.2.0.

    Utilisez ``soil_params(soil_type)`` à la place.

    Args:
        soil_type (str): Type de sol.

    Returns:
        dict: Paramètres du sol.
    """
    warnings.warn(
        "Hydrology.get_soil_params() est obsolète et sera supprimé dans "
        "KadiPy v2.0. Utilisez soil_params() à la place.",
        category=DeprecationWarning,
        stacklevel=2,
    )
    return self.soil_params(soil_type)

get_crop_coefficients

get_crop_coefficients(crop: str, stage: str) -> float

Ancienne méthode publique. Dépréciée depuis v1.2.0.

Utilisez crop_kc(crop, stage) à la place.

Parameters:

Name Type Description Default
crop str

Nom de la culture.

required
stage str

Stade phenologique.

required

Returns:

Name Type Description
float float

Coefficient Kc.

Source code in kadi/weather/hydrology.py
def get_crop_coefficients(self, crop: str, stage: str) -> float:
    """
    Ancienne méthode publique. Dépréciée depuis v1.2.0.

    Utilisez ``crop_kc(crop, stage)`` à la place.

    Args:
        crop (str): Nom de la culture.
        stage (str): Stade phenologique.

    Returns:
        float: Coefficient Kc.
    """
    warnings.warn(
        "Hydrology.get_crop_coefficients() est obsolète et sera supprimé "
        "dans KadiPy v2.0. Utilisez crop_kc() à la place.",
        category=DeprecationWarning,
        stacklevel=2,
    )
    return self.crop_kc(crop, stage)