Source code for climada.entity.impact_funcs.trop_cyclone

"""
This file is part of CLIMADA.

Copyright (C) 2017 ETH Zurich, CLIMADA contributors listed in AUTHORS.

CLIMADA is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free
Software Foundation, version 3.

CLIMADA is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with CLIMADA. If not, see <https://www.gnu.org/licenses/>.

---

Define impact functions for tropical cyclnes .
"""

__all__ = ["ImpfTropCyclone", "ImpfSetTropCyclone", "IFTropCyclone"]

import logging
from enum import Enum

import numpy as np
import pandas as pd
from deprecation import deprecated

from climada.entity.impact_funcs.base import ImpactFunc
from climada.entity.impact_funcs.impact_func_set import ImpactFuncSet
from climada.util import coordinates
from climada.util.constants import SYSTEM_DIR

LOGGER = logging.getLogger(__name__)


class CountryCode(Enum):
    """
    Enum class that links ISO country codes (both ISO3A and ISO3N) to specific regions
    and associated impact function IDs.

    Attributes
    ----------
        ISO3A: dict
             A mapping of region names to lists of 3-letter ISO country codes (iso3a).
        ISO3N: dict
             A mapping of region names to lists of numeric ISO country codes (iso3n).
        IMPF_ID: dict
            A mapping of region names to corresponding impact function IDs.
        REGION_NAME: dict
            A mapping of region names to their descriptive names.
    """

    # fmt: off
    ALPHA3 = {
        "NA1": [
            "ABW", "AIA", "ARG", "ATG", "BHS", "BLZ", "BMU", "BOL", "BRB", "CHL", "COL",
            "CPV", "CRI", "CUB", "CYM", "DMA", "DOM", "ECU", "FLK", "GLP", "GRD", "GTM",
            "GUF", "GUY", "HND", "HTI", "JAM", "KNA", "LCA", "MEX", "MSR", "MTQ", "NIC",
            "PAN", "PER", "PRI", "PRY", "SHN", "SLV", "SUR", "SXM", "TCA", "TTO", "URY",
            "VCT", "VEN", "VGB", "VIR",
        ],
        "NA2": ["CAN", "USA"],
        "NI": [
            "AFG", "ARE", "ARM", "AZE", "BGD", "BHR", "BTN", "DJI", "ERI", "ETH", "GEO",
            "IND", "IRN", "IRQ", "ISR", "JOR", "KAZ", "KGZ", "KWT", "LBN", "LKA", "MDV",
            "MMR", "MNG", "NPL", "OMN", "PAK", "QAT", "SAU", "SOM", "SYR", "TJK", "TKM",
            "UGA", "UZB", "YEM",
        ],
        "OC": [
            "ASM", "AUS", "COK", "FJI", "FSM", "GUM", "KIR", "MHL", "MNP", "NCL", "NFK",
            "NIU", "NRU", "NZL", "PCN", "PLW", "PNG", "PYF", "SLB", "TKL", "TLS", "TON",
            "TUV", "VUT", "WLF", "WSM",
        ],
        "SI": [
            "COD", "COM", "MDG", "MLI", "MOZ", "MUS", "MWI", "SWZ", "TZA", "ZAF", "ZWE",
        ],
        "WP1": ["KHM", "IDN", "LAO", "MYS", "THA", "VNM"],
        "WP2": ["PHL"],
        "WP3": ["CHN"],
        "WP4": ["HKG", "JPN", "KOR", "MAC", "TWN"],
        "ROW": [
            "AGO", "ALA", "ALB", "AND", "ATA", "ATF", "AUT", "BDI", "BEL", "BEN", "BES",
            "BFA", "BGR", "BIH", "BLM", "BLR", "BRA", "BRN", "BVT", "BWA", "CAF", "CCK",
            "CHE", "CIV", "CMR", "COG", "CUW", "CXR", "CYP", "CZE", "DEU", "DNK", "DZA",
            "EGY", "ESH", "ESP", "EST", "FIN", "FRA", "FRO", "GAB", "GBR", "GGY", "GHA",
            "GIB", "GIN", "GMB", "GNB", "GNQ", "GRC", "GRL", "HMD", "HRV", "HUN", "IMN",
            "IOT", "IRL", "ISL", "ITA", "JEY", "KEN", "LBR", "LBY", "LIE", "LSO", "LTU",
            "LUX", "LVA", "MAF", "MAR", "MCO", "MDA", "MKD", "MLT", "MNE", "MRT", "MYT",
            "NAM", "NER", "NGA", "NLD", "NOR", "POL", "PRK", "PRT", "PSE", "REU", "ROU",
            "RUS", "RWA", "SDN", "SEN", "SGP", "SGS", "SJM", "SLE", "SMR", "SPM", "SRB",
            "SSD", "STP", "SVK", "SVN", "SWE", "SYC", "TCD", "TGO", "TUN", "TUR", "UKR",
            "UMI", "VAT", "XKX", "ZMB",
        ],
    }

    # fmt: on
    IMPF_ID = {
        "NA1": 1,
        "NA2": 2,
        "NI": 3,
        "OC": 4,
        "SI": 5,
        "WP1": 6,
        "WP2": 7,
        "WP3": 8,
        "WP4": 9,
        "ROW": 10,
    }

    REGION_NAME = {
        "NA1": "Caribbean and Mexico",
        "NA2": "USA and Canada",
        "NI": "North Indian",
        "OC": "Oceania",
        "SI": "South Indian",
        "WP1": "South East Asia",
        "WP2": "Philippines",
        "WP3": "China Mainland",
        "WP4": "North West Pacific",
        "ROW": "Rest of The World",
    }


[docs] class ImpfTropCyclone(ImpactFunc): """Impact functions for tropical cyclones."""
[docs] def __init__(self): ImpactFunc.__init__(self) self.haz_type = "TC"
[docs] def set_emanuel_usa(self, *args, **kwargs): """This function is deprecated, use from_emanuel_usa() instead.""" LOGGER.warning( "The use of ImpfTropCyclone.set_emanuel_usa is deprecated." "Use ImpfTropCyclone.from_emanuel_usa instead." ) self.__dict__ = ImpfTropCyclone.from_emanuel_usa(*args, **kwargs).__dict__
[docs] @classmethod def from_emanuel_usa( cls, impf_id=1, intensity=np.arange(0, 121, 5), v_thresh=25.7, v_half=74.7, scale=1.0, ): """ Init TC impact function using the formula of Kerry Emanuel, 2011: 'Global Warming Effects on U.S. Hurricane Damage', https://doi.org/10.1175/WCAS-D-11-00007.1 Parameters ---------- impf_id : int, optional impact function id. Default: 1 intensity : np.array, optional intensity array in m/s. Default: 5 m/s step array from 0 to 120m/s v_thresh : float, optional first shape parameter, wind speed in m/s below which there is no damage. Default: 25.7(Emanuel 2011) v_half : float, optional second shape parameter, wind speed in m/s at which 50% of max. damage is expected. Default: v_threshold + 49 m/s (mean value of Sealy & Strobl 2017) scale : float, optional scale parameter, linear scaling of MDD. 0<=scale<=1. Default: 1.0 Raises ------ ValueError Returns ------- impf : ImpfTropCyclone TC impact function instance based on formula by Emanuel (2011) """ if v_half <= v_thresh: raise ValueError("Shape parameters out of range: v_half <= v_thresh.") if v_thresh < 0 or v_half < 0: raise ValueError("Negative shape parameter.") if scale > 1 or scale <= 0: raise ValueError("Scale parameter out of range.") impf = cls() impf.name = "Emanuel 2011" impf.id = impf_id impf.intensity_unit = "m/s" impf.intensity = intensity impf.paa = np.ones(intensity.shape) v_temp = (impf.intensity - v_thresh) / (v_half - v_thresh) v_temp[v_temp < 0] = 0 impf.mdd = v_temp**3 / (1 + v_temp**3) impf.mdd *= scale return impf
[docs] class ImpfSetTropCyclone(ImpactFuncSet): """Impact function set (ImpfS) for tropical cyclones."""
[docs] def __init__(self): ImpactFuncSet.__init__(self)
[docs] def set_calibrated_regional_ImpfSet(self, *args, **kwargs): """This function is deprecated, use from_calibrated_regional_ImpfSet() instead.""" LOGGER.warning( "ImpfSetTropCyclone.set_calibrated_regional_ImpfSet is deprecated." "Use ImpfSetTropCyclone.from_calibrated_regional_ImpfSet instead." ) self.__dict__ = ImpfSetTropCyclone.from_calibrated_regional_ImpfSet( *args, **kwargs ).__dict__ return ImpfSetTropCyclone.calibrated_regional_vhalf(*args, **kwargs)
[docs] @classmethod def from_calibrated_regional_ImpfSet( cls, calibration_approach="TDR", q=0.5, input_file_path=None, version=1 ): """Calibrated regional TC wind impact functions Based on Eberenz et al. 2021: https://doi.org/10.5194/nhess-21-393-2021 Parameters ---------- calibration_approach : str, optional The following values are supported: 'TDR' (default) Total damage ratio (TDR) optimization with TDR=1.0 (simulated damage = reported damage from EM-DAT) 'TDR1.5' Total damage ratio (TDR) optimization with TDR=1.5 (simulated damage = 1.5*reported damage from EM-DAT) 'RMSF' Root-mean-squared fraction (RMSF) optimization 'EDR' quantile from individually fitted v_half per event, i.e. v_half fitted to get EDR=1.0 for each event q : float, optional Quantile between 0 and 1.0 to select (EDR only). Default: 0.5, i.e. median v_half input_file_path : str or DataFrame, optional full path to calibration result file to be used instead of default file in repository (expert users only) Returns ------- impf_set : ImpfSetTropCyclone TC Impact Function Set based on Eberenz et al, 2021. """ reg_v_half = ImpfSetTropCyclone.calibrated_regional_vhalf( calibration_approach=calibration_approach, q=q, input_file_path=input_file_path, version=version, ) # define regions and parameters: v_0 = 25.7 # v_threshold based on Emanuel (2011) scale = 1.0 # init impact function set impf_set = cls() for idx, region in enumerate(reg_v_half.keys()): impf_tc = ImpfTropCyclone.from_emanuel_usa( impf_id=int(idx + 1), v_thresh=v_0, v_half=reg_v_half[region], scale=scale, ) impf_tc.name = CountryCode.REGION_NAME.value[region] impf_set.append(impf_tc) return impf_set
[docs] @staticmethod def calibrated_regional_vhalf( calibration_approach="TDR", q=0.5, input_file_path=None, version=1 ): """Calibrated TC wind impact function slope parameter v_half per region Based on Eberenz et al., 2021: https://doi.org/10.5194/nhess-21-393-2021 Parameters ---------- calibration_approach : str, optional The following values are supported: 'TDR' (default) Total damage ratio (TDR) optimization with TDR=1.0 (simulated damage = reported damage from EM-DAT) 'TDR1.5' Total damage ratio (TDR) optimization with TDR=1.5 (simulated damage = 1.5*reported damage from EM-DAT) 'RMSF' Root-mean-squared fraction (RMSF) optimization 'EDR' quantile from individually fitted v_half per event, i.e. v_half fitted to get EDR=1.0 for each event q : float, optional Quantile between 0 and 1.0 to select (EDR only). Default: 0.5, i.e. median v_half input_file_path : str or DataFrame, optional full path to calibration result file to be used instead of default file in repository (expert users only) Raises ------ ValueError Returns ------- v_half : dict TC impact function slope parameter v_half per region """ calibration_approach = calibration_approach.upper() if calibration_approach not in ["TDR", "TDR1.0", "TDR1.5", "RMSF", "EDR"]: raise ValueError("calibration_approach is invalid") if "EDR" in calibration_approach and (q < 0.0 or q > 1.0): raise ValueError("Quantile q out of range [0, 1]") if calibration_approach == "TDR": calibration_approach = "TDR1.0" # load calibration results depending on approach: if isinstance(input_file_path, str): df_calib_results = pd.read_csv( input_file_path, encoding="ISO-8859-1", header=0 ) elif isinstance(input_file_path, pd.DataFrame): df_calib_results = input_file_path else: df_calib_results = pd.read_csv( SYSTEM_DIR.joinpath( "tc_impf_cal_v%02.0f_%s.csv" % (version, calibration_approach) ), encoding="ISO-8859-1", header=0, ) regions_short = list(CountryCode.REGION_NAME.value.keys()) regions_short.remove("ROW") # loop over calibration regions (column cal_region2 in df): reg_v_half = dict() for region in regions_short: df_reg = df_calib_results.loc[df_calib_results.cal_region2 == region] df_reg = df_reg.reset_index(drop=True) reg_v_half[region] = np.round(df_reg["v_half"].quantile(q=q), 5) # rest of the world (ROW), calibrated by all data: regions_short = regions_short + ["ROW"] if calibration_approach == "EDR": reg_v_half[regions_short[-1]] = np.round( df_calib_results["v_half"].quantile(q=q), 5 ) else: df_reg = df_calib_results.loc[df_calib_results.cal_region2 == "GLB"] df_reg = df_reg.reset_index(drop=True) reg_v_half[regions_short[-1]] = np.round(df_reg["v_half"].values[0], 5) return reg_v_half
[docs] @staticmethod def get_countries_per_region(region=None): """Returns dictionaries with numerical (numeric) and alphabetical (alpha3) ISO3 codes of all countries associated to a calibration region. Only contains countries that were affected by tropical cyclones between 1980 and 2017 according to EM-DAT. Parameters ---------- region : str regional abbreviation (default='all'), either 'NA1', 'NA2', 'NI', 'OC', 'SI', 'WP1', 'WP2', 'WP3', 'WP4', or 'all'. Returns ------- region_name : dict or str long name per region impf_id : dict or int impact function ID per region numeric : dict or list numerical ISO3codes (=region_id) per region alpha3 : dict or list numerical ISO3codes (=region_id) per region """ if not region: region = "all" if region == "all": return ( CountryCode.REGION_NAME.value, CountryCode.IMPF_ID.value, coordinates.country_to_iso( CountryCode.ALPHA3.value, representation="numeric" ), CountryCode.ALPHA3.value, ) return ( CountryCode.REGION_NAME.value[region], CountryCode.IMPF_ID.value[region], coordinates.country_to_iso( CountryCode.ALPHA3.value[region], representation="numeric" ), CountryCode.ALPHA3.value[region], )
[docs] @staticmethod def get_impf_id_regions_per_countries(countries: list = None) -> tuple: """Return the impact function id and the region corresponding to some countries Parameters: ----------- countries : list List containing the ISO codes of the country, which should be either in string format if the code is "ISO 3166-1 alpha-3" abbreviated as "alpha3", or an integer if the code is in "ISO 3166-1 numeric" abbreviated as "numeric", which is a three-digit country code, the numeric version of "ISO 3166-1 alpha-3". For example, the "alpha3" code of Switzerland is "CHE" and the "numeric" is 756. Returns: -------- impf_ids : list List of impact function ids matching the countries. regions_ids : list List of the region ids. Regions are a container of countries as defined in: https://nhess.copernicus.org/articles/21/393/2021/nhess-21-393-2021.pdf, and implemented in the CountryCode Enum Class. Example: "NA1", "NA2", ... regions_names : list List of the regions names. Example: "Caribbean and Mexico", "USA and Canada", ... """ # Find region regions_ids = [] for country in countries: if isinstance(country, int): country = coordinates.country_to_iso( country, representation="alpha3", fillvalue=None ) for region_id, countr_in_region_id in CountryCode.ALPHA3.value.items(): if country in countr_in_region_id: regions_ids.append(region_id) # Find impact function id impf_ids = [CountryCode.IMPF_ID.value[region] for region in regions_ids] regions_name = [CountryCode.REGION_NAME.value[region] for region in regions_ids] return impf_ids, regions_ids, regions_name
[docs] @deprecated( details="The class name IFTropCyclone is deprecated and won't be supported in a future " + "version. Use ImpfTropCyclone instead" ) class IFTropCyclone(ImpfTropCyclone): """Is ImpfTropCyclone now"""