def pdsi(precip_time_series: np.ndarray, pet_time_series: np.ndarray, awc, data_start_year: int, calibration_start_year: int, calibration_end_year: int): """ This function computes the Palmer Drought Severity Index (PDSI), Palmer Hydrological Drought Index (PHDI), and Palmer Z-Index. :param precip_time_series: time series of monthly precipitation values, in inches :param pet_time_series: time series of monthly PET values, in inches :param awc: available water capacity (soil constant), in inches :param data_start_year: initial year of the input precipitation and PET datasets, both of which are assumed to start in January of this year :param calibration_start_year: initial year of the calibration period :param calibration_end_year: final year of the calibration period :return: four numpy arrays containing PDSI, PHDI, PMDI, and Z-Index values respectively """ return palmer.pdsi(precip_time_series, pet_time_series, awc, data_start_year, calibration_start_year, calibration_end_year)
def test_pdsi(self): pdsi, phdi, pmdi, zindex = palmer.pdsi( self.fixture_precips_mm_monthly, self.fixture_pet_mm, self.fixture_awc_inches, self.fixture_data_year_start_monthly, self.fixture_calibration_year_start_monthly, self.fixture_calibration_year_end_monthly) np.testing.assert_allclose( pdsi, self.fixture_palmer_pdsi_monthly, atol=0.001, equal_nan=True, err_msg='PDSI not computed as expected from monthly inputs') np.testing.assert_allclose( phdi, self.fixture_palmer_phdi_monthly, atol=0.001, equal_nan=True, err_msg='PHDI not computed as expected from monthly inputs') np.testing.assert_allclose( pmdi, self.fixture_palmer_pmdi_monthly, atol=0.001, equal_nan=True, err_msg='PMDI not computed as expected from monthly inputs') np.testing.assert_allclose( zindex, self.fixture_palmer_zindex_monthly, atol=0.001, equal_nan=True, err_msg='Z-Index not computed as expected from monthly inputs')
def test_pdsi( precips_mm_monthly, pet_thornthwaite_mm, awc_inches, data_year_start_monthly, calibration_year_start_monthly, calibration_year_end_monthly, palmer_pdsi_monthly, palmer_phdi_monthly, palmer_pmdi_monthly, palmer_zindex_monthly, ): pdsi, phdi, pmdi, zindex = palmer.pdsi( precips_mm_monthly, pet_thornthwaite_mm, awc_inches, data_year_start_monthly, calibration_year_start_monthly, calibration_year_end_monthly, ) np.testing.assert_allclose( pdsi, palmer_pdsi_monthly, atol=0.001, equal_nan=True, err_msg="PDSI not computed as expected from monthly inputs", ) np.testing.assert_allclose( phdi, palmer_phdi_monthly, atol=0.001, equal_nan=True, err_msg="PHDI not computed as expected from monthly inputs", ) np.testing.assert_allclose( pmdi, palmer_pmdi_monthly, atol=0.001, equal_nan=True, err_msg="PMDI not computed as expected from monthly inputs", ) np.testing.assert_allclose( zindex, palmer_zindex_monthly, atol=0.001, equal_nan=True, err_msg="Z-Index not computed as expected from monthly inputs", )
def pdsi( ppt: pd.Series, pet: pd.Series, awc: float, pad_years: int = 10, y1: int = CLIMATE_NORMAL_PERIOD[0], y2: int = CLIMATE_NORMAL_PERIOD[1], ) -> np.ndarray: """Calculate the Palmer Drought Severity Index (PDSI) This is a simple wrapper of the climate_idicies package implementation of pdsi. The wrapper includes a spin up period (`pad_years`) using a repeated climatology calculated between `y1` and `y2`. Note that this is not a perfect reproduction of the Terraclimate PDSI implementation. See https://github.com/carbonplan/cmip6-downscaling/issues/4 for more details. Parameters ---------- ppt : pd.Series Monthly precipitation timeseries (mm) pet : pd.Series Monthly PET timeseries (mm) awc : float Soil water capacity (mm) pad_years : int Number of years of the climatology to prepend to the timeseries of ppt and pet y1 : int Start year for climate normal period y2 : int End year for climate normal period Returns ------- pdsi : pd.Series Timeseries of PDSI (unitless) """ pad_months = pad_years * MONTHS_PER_YEAR assert len(ppt) > pad_months y0 = ppt.index.year[0] - pad_years # start year (with pad) awc_in = awc / MM_PER_IN # calculate the climatology for ppt and pet (for only the climate normal period) df = pd.concat([pet, ppt], axis=1) climatology = df.loc[str(y1) : str(y2)].groupby(by=by_month).mean() # repeat climatology for pad_years, then begine the time series ppt_extended = np.concatenate([np.tile(climatology['ppt'].values, pad_years), ppt.values]) ppt_extended_in = ppt_extended / MM_PER_IN pet_extended = np.concatenate([np.tile(climatology['pet'].values, pad_years), pet.values]) pet_extended_in = pet_extended / MM_PER_IN # set all zero ppt months to SMALL_PPT: this gets around a divide by zero # in the pdsi function below. ppt_zeros = ppt_extended_in <= 0 if ppt_zeros.any(): ppt_extended_in[ppt_zeros] = SMALL_PPT pdsi_vals = palmer.pdsi(ppt_extended_in, pet_extended_in, awc_in, y0, y1, y2)[0] pdsi_vals = pdsi_vals.clip(-16, 16) out = pd.Series(pdsi_vals[pad_months:], index=ppt.index) return out