Source code for pymagicc.definitions

"""
This module contains all of the relevant definitions for handling MAGICC data.

When we store the data in csv's, we use `Data Packages <https://frictionlessdata.io/
docs/creating-tabular-data-packages-in-python/>`_. These store the data in an easy to
read csv file whilst providing comprehensive metadata describing the data (column
meanings and expected types) in the accompanying ``datapackage.json`` file. Please see
this metadata for further details.

For more details about how these constants are used, see the documentation of
``pymagicc.io``. In particular, the documentation of
``pymagicc.io.get_special_scen_code``, ``pymagicc.io.get_dattype_regionmode`` and
``pymagicc.io.get_region_order`` in :ref:`pymagicc.io`.
"""
import functools
import warnings
from pathlib import Path

import pandas as pd
from pandas_datapackage_reader import read_datapackage

from pymagicc.utils import apply_string_substitutions

DATA_HIERARCHY_SEPARATOR = "|"
"""str: String used to define different levels in our data hierarchies.

For example, "Emissions|CO2|Energy|Coal".

We copy this straight from pyam_ to maintain easy compatibility.
"""

path = Path(__file__).parent


_dtrm = read_datapackage(path, "magicc_dattype_regionmode_regions")

_region_cols = _dtrm.columns.to_series().apply(lambda x: x.startswith("region"))

DATTYPE_REGIONMODE_REGIONS = _dtrm.loc[:, ~_region_cols].copy()
""":obj:`pandas.DataFrame` Mapping between regions and whether a file is SCEN7 or not and the expected values of THISFILE_DATTYPE and THISFILE_REGIONMODE flags in MAGICC.
"""

DATTYPE_REGIONMODE_REGIONS["regions"] = [
    [r for r in raw if not pd.isnull(r)]
    for raw in _dtrm.loc[:, _region_cols].values.tolist()
]

MAGICC7_EMISSIONS_UNITS = read_datapackage(path, "magicc_emissions_units")
""":obj:`pandas.DataFrame` Definitions of emissions variables and their expected units in MAGICC7.
"""

PART_OF_SCENFILE_WITH_EMISSIONS_CODE_0 = MAGICC7_EMISSIONS_UNITS[
    MAGICC7_EMISSIONS_UNITS["part_of_scenfile_with_emissions_code_0"]
]["magicc_variable"].tolist()
"""list: The emissions which are included in a SCEN file if the SCEN emms code is 0.

See documentation of ``pymagicc.io.get_special_scen_code`` for more details.
"""

PART_OF_SCENFILE_WITH_EMISSIONS_CODE_1 = MAGICC7_EMISSIONS_UNITS[
    MAGICC7_EMISSIONS_UNITS["part_of_scenfile_with_emissions_code_1"]
]["magicc_variable"].tolist()

"""list: The emissions which are included in a SCEN file if the SCEN emms code is 1.

See documentation of ``pymagicc.io.get_special_scen_code`` for more details.
"""

PART_OF_PRNFILE = MAGICC7_EMISSIONS_UNITS[MAGICC7_EMISSIONS_UNITS["part_of_prnfile"]][
    "magicc_variable"
].tolist()
"""list: The emissions which are included in a ``.prn`` file.
"""

MAGICC7_CONCENTRATIONS_UNITS = read_datapackage(path, "magicc_concentrations_units")
""":obj:`pandas.DataFrame` Definitions of concentrations variables and their expected units in MAGICC7.
"""

AR6_REGION_ABBREVIATIONS = [
    "GIC",
    "NWN",
    "NEN",
    "WNA",
    "CNA",
    "ENA",
    "NCA",
    "SCA",
    "CAR",
    "NWS",
    "NSA",
    "NES",
    "SAM",
    "SWS",
    "SES",
    "SSA",
    "NEU",
    "WCE",
    "EEU",
    "MED",
    "SAH",
    "WAF",
    "CAF",
    "NEAF",
    "SEAF",
    "WSAF",
    "ESAF",
    "MDG",
    "RAR",
    "WSB",
    "ESB",
    "RFE",
    "WCA",
    "ECA",
    "TIB",
    "EAS",
    "ARP",
    "SAS",
    "SEA",
    "NAU",
    "CAU",
    "EAU",
    "SAU",
    "NZ",
    "EAN",
    "WAN",
    "ARO",
    "NPO",
    "EPO",
    "SPO",
    "NAO",
    "EAO",
    "SAO",
    "ARS",
    "BOB",
    "EIO",
    "SIO",
    "SOO",
]
"""list: Abbreviations for the AR6 regions as defined in `Iturbide et al (2020) <https://doi.org/10.5194/essd-2019-258>`_
"""


[docs]def get_magicc_region_to_openscm_region_mapping(inverse=False): """Get the mappings from MAGICC to OpenSCM regions. This is not a pure inverse of the other way around. For example, we never provide "GLOBAL" as a MAGICC return value because it's unnecesarily confusing when we also have "World". Fortunately MAGICC doesn't ever read the name "GLOBAL" so this shouldn't matter. Parameters ---------- inverse : bool If True, return the inverse mappings i.e. MAGICC to OpenSCM mappings Returns ------- dict Dictionary of mappings """ world = "World" def get_openscm_replacement(in_region): if in_region in ("WORLD", "GLOBAL"): return world if in_region in ("BUNKERS",): return DATA_HIERARCHY_SEPARATOR.join([world, "Bunkers"]) if in_region in ("OCEAN",): return DATA_HIERARCHY_SEPARATOR.join([world, "Ocean"]) if in_region in ("LAND",): return DATA_HIERARCHY_SEPARATOR.join([world, "Land"]) if in_region in ("N34",): return DATA_HIERARCHY_SEPARATOR.join([world, "El Nino N3.4"]) if in_region in ("AMV",): return DATA_HIERARCHY_SEPARATOR.join([world, "North Atlantic Ocean"]) elif in_region.startswith(("NH", "SH")): in_region = in_region.replace("-", "") hem = "Northern Hemisphere" if "NH" in in_region else "Southern Hemisphere" if in_region in ("NH", "SH"): return DATA_HIERARCHY_SEPARATOR.join([world, hem]) land_ocean = "Land" if "LAND" in in_region else "Ocean" return DATA_HIERARCHY_SEPARATOR.join([world, hem, land_ocean]) else: return DATA_HIERARCHY_SEPARATOR.join([world, in_region]) # we generate the mapping dynamically, the first name in the list # is the one which will be used for inverse mappings _magicc_regions = [ "WORLD", "GLOBAL", "OECD90", "ALM", "REF", "ASIA", "R5ASIA", "R5OECD", "R5REF", "R5MAF", "R5LAM", "R5.2ASIA", "R5.2OECD", "R5.2REF", "R5.2MAF", "R5.2LAM", "NHOCEAN", "SHOCEAN", "NHLAND", "SHLAND", "NH-OCEAN", "SH-OCEAN", "NH-LAND", "SH-LAND", "SH", "NH", "BUNKERS", "N34", "AMV", "OCEAN", "LAND", ] replacements = {} for magicc_region in _magicc_regions: openscm_region = get_openscm_replacement(magicc_region) # i.e. if we've already got a value for the inverse, we don't want to overwrite if (openscm_region in replacements.values()) and inverse: continue replacements[magicc_region] = openscm_region for region in AR6_REGION_ABBREVIATIONS: replacements["AR6-" + region] = "World|AR6|" + region if inverse: return {v: k for k, v in replacements.items()} else: # R6 doesn't really exist, they're supposed to be the SSP database's R5.2 # regions. The R6 regions appeared in a few SSP scenario files (but will # hopefully never be released) to this is just in case. for r6 in ["R6OECD90", "R6REF", "R6LAM", "R6MAF", "R6ASIA"]: replacements[r6] = "{}|{}".format( world, r6.replace("R6", "R5.2").replace("90", "") ) return replacements
MAGICC_REGION_TO_OPENSCM_REGION_MAPPING = get_magicc_region_to_openscm_region_mapping() """dict: Mappings from MAGICC regions to OpenSCM regions""" OPENSCM_REGION_TO_MAGICC_REGION_MAPPING = get_magicc_region_to_openscm_region_mapping( inverse=True ) """dict: Mappings from OpenSCM regions to MAGICC regions """ @functools.lru_cache(None) def _apply_convert_magicc_to_openscm_regions(regions, inverse): if inverse: return apply_string_substitutions( regions, OPENSCM_REGION_TO_MAGICC_REGION_MAPPING, unused_substitutions="ignore", # TODO: make this warn and see what happens ) else: return apply_string_substitutions( regions, MAGICC_REGION_TO_OPENSCM_REGION_MAPPING, unused_substitutions="ignore", # TODO: make this warn and see what happens case_insensitive=True, # MAGICC regions are case insensitive )
[docs]def convert_magicc_to_openscm_regions(regions, inverse=False): """ Convert MAGICC regions to OpenSCM regions Parameters ---------- regions : list_like, str Regions to convert inverse : bool If True, convert the other way i.e. convert OpenSCM regions to MAGICC7 regions Returns ------- ``type(regions)`` Set of converted regions """ if isinstance(regions, (list, pd.Index)): return [_apply_convert_magicc_to_openscm_regions(r, inverse) for r in regions] else: return _apply_convert_magicc_to_openscm_regions(regions, inverse)
[docs]def get_magicc7_to_openscm_variable_mapping(inverse=False): """Get the mappings from MAGICC7 to OpenSCM variables. Parameters ---------- inverse : bool If True, return the inverse mappings i.e. OpenSCM to MAGICC7 mappings Returns ------- dict Dictionary of mappings """ def get_openscm_replacement(in_var): if in_var.endswith("_INVERSE_EMIS"): prefix = "Inverse Emissions" elif in_var.endswith("_EMIS"): prefix = "Emissions" elif in_var.endswith("_CONC"): prefix = "Atmospheric Concentrations" elif in_var.endswith("_ERF"): prefix = "Effective Radiative Forcing" elif in_var.endswith("_RF"): prefix = "Radiative Forcing" elif in_var.endswith("_OT"): prefix = "Optical Thickness" else: raise ValueError("This shouldn't happen") variable = in_var.split("_")[0] if variable.endswith("EQ"): variable = variable.replace("EQ", " Equivalent") if "GHG" in variable: variable = variable.replace("GHG", "Greenhouse Gases") if "CO2CH4N2O" in variable: variable = variable.replace("CO2CH4N2O", "CO2, CH4 and N2O") aggregate_indicators = { "KYOTO": "Kyoto Gases", "FGASSUM": "F-Gases", "MHALOSUM": "Montreal Protocol Halogen Gases", } for agg_indicator, long_name in aggregate_indicators.items(): if variable.startswith(agg_indicator): stripped_var = variable.replace(agg_indicator, "") if stripped_var: variable = DATA_HIERARCHY_SEPARATOR.join([stripped_var, long_name]) else: variable = long_name edge_case_B = variable.upper() in ("HCFC141B", "HCFC142B") if variable.endswith("I"): variable = DATA_HIERARCHY_SEPARATOR.join( [variable[:-1], "MAGICC Fossil and Industrial"] ) elif variable.endswith("B") and not edge_case_B: variable = DATA_HIERARCHY_SEPARATOR.join([variable[:-1], "MAGICC AFOLU"]) elif variable.endswith("T") and variable != "MINERALDUST": variable = variable[:-1] dir_aerosols = [ "OC", "BC", "SOX", "NOX", # Deprecated "NH3", # Deprecated "NO3", "MINERALDUST", "BIOMASSAER", ] for aer in dir_aerosols: if variable.startswith(aer) and "Radiative Forcing" in prefix: variable = "Aerosols|Direct Effect|" + variable case_adjustments = { "SOX": "SOx", "NOX": "NOx", "CC4F8": "cC4F8", "HFC134A": "HFC134a", "HFC143A": "HFC143a", "HFC152A": "HFC152a", "HFC227EA": "HFC227ea", "HFC236FA": "HFC236fa", "HFC245FA": "HFC245fa", "HFC365MFC": "HFC365mfc", "HCFC141B": "HCFC141b", "HCFC142B": "HCFC142b", "CH3CCL3": "CH3CCl3", "CCL4": "CCl4", "CH3CL": "CH3Cl", "CH2CL2": "CH2Cl2", "CHCL3": "CHCl3", "CH3BR": "CH3Br", "HALON1211": "Halon1211", "HALON1301": "Halon1301", "HALON2402": "Halon2402", "HALON1202": "Halon1202", "SOLAR": "Solar", "VOLCANIC": "Volcanic", "EXTRA": "Extra", "MINERALDUST": "Mineral Dust", "BIOMASSAER": "MAGICC AFOLU", } variable = apply_string_substitutions(variable, case_adjustments) return DATA_HIERARCHY_SEPARATOR.join([prefix, variable]) magicc7_suffixes = ["_EMIS", "_CONC", "_ERF", "_RF", "_OT", "_INVERSE_EMIS"] magicc7_base_vars = MAGICC7_EMISSIONS_UNITS.magicc_variable.tolist() + [ "SOLAR", "VOLCANIC", "CO2EQ", "KYOTOCO2EQ", "FGASSUMHFC134AEQ", "MHALOSUMCFC12EQ", "GHG", "KYOTOGHG", "FGASSUM", "MHALOSUM", "BIOMASSAER", "MINERALDUST", "CO2CH4N2O", "EXTRA", ] magicc7_vars = [ base_var + suffix for base_var in magicc7_base_vars for suffix in magicc7_suffixes ] replacements = {m7v: get_openscm_replacement(m7v) for m7v in magicc7_vars} rf_updates = { "TOTAL_INCLVOLCANIC_RF": "Radiative Forcing", "VOLCANIC_ANNUAL_RF": "Radiative Forcing|Volcanic", "TOTAL_ANTHRO_RF": "Radiative Forcing|Anthropogenic", "TOTAER_DIR_RF": "Radiative Forcing|Aerosols|Direct Effect", "CLOUD_TOT_RF": "Radiative Forcing|Aerosols|Indirect Effect", "AEROSOL_RF": "Radiative Forcing|Aerosols", "OZTOTAL_RF": "Radiative Forcing|Ozone", "STRATOZ_RF": "Radiative Forcing|Stratospheric Ozone", "TROPOZ_RF": "Radiative Forcing|Tropospheric Ozone", "CH4OXSTRATH2O_RF": "Radiative Forcing|CH4 Oxidation Stratospheric H2O", "LANDUSE_RF": "Radiative Forcing|Land-use Change", "BCSNOW_RF": "Radiative Forcing|Black Carbon on Snow", "AIR_CIRRUS_RF": "Radiative Forcing|Aviation|Cirrus", "AIR_CONTRAIL_RF": "Radiative Forcing|Aviation|Contrail", "AIR_CONTRAILANDCIRRUS_RF": "Radiative Forcing|Aviation|Contrail and Cirrus", "AIR_H2O_RF": "Radiative Forcing|Aviation|H2O", } rf_updates_with_erf = { **rf_updates, **{ k.replace("_RF", "_ERF"): v.replace( "Radiative Forcing", "Effective Radiative Forcing" ) for k, v in rf_updates.items() }, } replacements.update(rf_updates_with_erf) replacements.update( { "SURFACE_TEMP": "Surface Temperature", "SURFACE_MIXEDLAYERTEMP": "Surface Air Ocean Blended Temperature Change", "CO2_AIR2LAND_FLUX": "Net Atmosphere to Land Flux|CO2", "CO2_AIR2OCEAN_FLUX": "Net Atmosphere to Ocean Flux|CO2", "CO2PF_EMIS": "Net Land to Atmosphere Flux|CO2|Earth System Feedbacks|Permafrost", "CH4PF_EMIS": "Net Land to Atmosphere Flux|CH4|Earth System Feedbacks|Permafrost", "SLR_TOT": "Sea Level Rise", } ) agg_ocean_heat_top = "Heat Content|Ocean" heat_content_aggreg_depths = { "HEATCONTENT_AGGREG_DEPTH{}".format(i): "{}{}Depth {}".format( agg_ocean_heat_top, DATA_HIERARCHY_SEPARATOR, i ) for i in range(1, 4) } replacements.update(heat_content_aggreg_depths) replacements.update( { "HEATCONTENT_AGGREG_TOTAL": agg_ocean_heat_top, "HEATUPTK_AGGREG": "Heat Uptake|Ocean", "HEAT_EARTH": "Heat Content", "HEATUPTK_EARTH": "Heat Uptake", "HEAT_NONOCEAN": "Heat Content|Non-Ocean", "HEATUPTK_NONOCEAN": "Heat Uptake|Non-Ocean", } ) ocean_temp_layer = { "OCEAN_TEMP_LAYER_{0:03d}".format(i): "Ocean Temperature{}Layer {}".format( DATA_HIERARCHY_SEPARATOR, i ) for i in range(1, 999) } replacements.update(ocean_temp_layer) if inverse: replacements = {v: k for k, v in replacements.items()} # these variables need a specific inverse conversion, different # to going the other way total_erf_variables = [ "BC", "OC", "SOX", ] suffixes = ["RF", "ERF"] one_way_replacements = {} for k, v in replacements.items(): toks = v.split("_") if toks[0] in total_erf_variables and toks[1] in suffixes: one_way_replacements[k] = "{}T_{}".format(toks[0], "_".join(toks[1:])) total_emissions_variables = ["CO2", "CH4", "N2O"] for k, v in replacements.items(): toks = v.split("_") if toks[0] in total_emissions_variables and toks[1] == "EMIS": one_way_replacements[k] = "{}T_{}".format(toks[0], "EMIS") else: # these come from MAGICC's output one_way_replacements = { "SURFACE_TEMP_SUBANNUAL": "Surface Temperature", } total_variables = [ "BC", "OC", "SOX", "NOX", "CO2", "N2O", "CH4", "NO3", ] for k in replacements: toks = k.split("_") if toks[0] in total_variables: one_way_replacements[ "{}T_{}".format(toks[0], "_".join(toks[1:])) ] = replacements[k] replacements.update(one_way_replacements) return replacements
MAGICC7_TO_OPENSCM_VARIABLES_MAPPING = get_magicc7_to_openscm_variable_mapping() """dict: Mappings from MAGICC7 variables to OpenSCM variables """ OPENSCM_TO_MAGICC7_VARIABLES_MAPPING = get_magicc7_to_openscm_variable_mapping( inverse=True ) """dict: Mappings from OpenSCM variables to MAGICC7 variables """ @functools.lru_cache(None) def _apply_convert_magicc7_to_openscm_variables(v, inverse): if inverse: return apply_string_substitutions( v, OPENSCM_TO_MAGICC7_VARIABLES_MAPPING, unused_substitutions="ignore", # TODO: make this warn and see what happens ) else: return apply_string_substitutions( v, MAGICC7_TO_OPENSCM_VARIABLES_MAPPING, unused_substitutions="ignore", # TODO: make this warn and see what happens case_insensitive=True, # MAGICC variables are case insensitive )
[docs]def convert_magicc7_to_openscm_variables(variables, inverse=False): """ Convert MAGICC7 variables to OpenSCM variables Parameters ---------- variables : list_like, str Variables to convert inverse : bool If True, convert the other way i.e. convert OpenSCM variables to MAGICC7 variables Returns ------- ``type(variables)`` Set of converted variables """ if isinstance(variables, (list, pd.Index)): return [ _apply_convert_magicc7_to_openscm_variables(v, inverse) for v in variables ] else: return _apply_convert_magicc7_to_openscm_variables(variables, inverse)
[docs]def get_magicc6_to_magicc7_variable_mapping(inverse=False): """Get the mappings from MAGICC6 to MAGICC7 variables. Note that this mapping is not one to one. For example, "HFC4310", "HFC43-10" and "HFC-43-10" in MAGICC6 both map to "HFC4310" in MAGICC7 but "HFC4310" in MAGICC7 maps back to "HFC4310". Note that HFC-245fa was mistakenly labelled as HFC-245ca in MAGICC6. In reality, they are not the same thing. However, the MAGICC6 labelling was merely a typo so the mapping between the two is one-to-one. Parameters ---------- inverse : bool If True, return the inverse mappings i.e. MAGICC7 to MAGICC6 mappings Returns ------- dict Dictionary of mappings """ # we generate the mapping dynamically, the first name in the list # is the one which will be used for inverse mappings magicc6_simple_mapping_vars = [ "KYOTO-CO2EQ", "CO2I", "CO2B", "CH4", "N2O", "BC", "OC", "SOx", "NOx", "NMVOC", "CO", "SF6", "NH3", "CF4", "C2F6", "HFC4310", "HFC43-10", "HFC-43-10", "HFC4310", "HFC134a", "HFC143a", "HFC227ea", "CCl4", "CH3CCl3", "HFC245fa", "Halon 1211", "Halon 1202", "Halon 1301", "Halon 2402", "Halon1211", "Halon1202", "Halon1301", "Halon2402", "CH3Br", "CH3Cl", "C6F14", ] magicc6_sometimes_hyphen_vars = [ "CFC-11", "CFC-12", "CFC-113", "CFC-114", "CFC-115", "HCFC-22", "HFC-23", "HFC-32", "HFC-125", "HFC-134a", "HFC-143a", "HCFC-141b", "HCFC-142b", "HFC-227ea", "HFC-245fa", ] magicc6_sometimes_hyphen_vars = [ v.replace("-", "") for v in magicc6_sometimes_hyphen_vars ] + magicc6_sometimes_hyphen_vars magicc6_sometimes_underscore_vars = [ "HFC43_10", "CFC_11", "CFC_12", "CFC_113", "CFC_114", "CFC_115", "HCFC_22", "HCFC_141b", "HCFC_142b", ] magicc6_sometimes_underscore_replacements = { v: v.replace("_", "") for v in magicc6_sometimes_underscore_vars } special_case_replacements = { "FossilCO2": "CO2I", "OtherCO2": "CO2B", "MCF": "CH3CCL3", "CARB_TET": "CCL4", "MHALOSUMCFC12EQ": "MHALOSUMCFC12EQ", # special case to avoid confusion with MCF } one_way_replacements = {"HFC-245ca": "HFC245FA", "HFC245ca": "HFC245FA"} all_possible_magicc6_vars = ( magicc6_simple_mapping_vars + magicc6_sometimes_hyphen_vars + magicc6_sometimes_underscore_vars + list(special_case_replacements.keys()) + list(one_way_replacements.keys()) ) replacements = {} for m6v in all_possible_magicc6_vars: if m6v in special_case_replacements: replacements[m6v] = special_case_replacements[m6v] elif ( m6v in magicc6_sometimes_underscore_vars and not inverse ): # underscores one way replacements[m6v] = magicc6_sometimes_underscore_replacements[m6v] elif (m6v in one_way_replacements) and not inverse: replacements[m6v] = one_way_replacements[m6v] else: m7v = m6v.replace("-", "").replace(" ", "").upper() # i.e. if we've already got a value for the inverse, we don't # want to overwrite it if (m7v in replacements.values()) and inverse: continue replacements[m6v] = m7v if inverse: return {v: k for k, v in replacements.items()} else: return replacements
MAGICC6_TO_MAGICC7_VARIABLES_MAPPING = get_magicc6_to_magicc7_variable_mapping() """dict: Mappings from MAGICC6 variables to MAGICC7 variables """ MAGICC7_TO_MAGICC6_VARIABLES_MAPPING = get_magicc6_to_magicc7_variable_mapping( inverse=True ) """dict: Mappings from MAGICC7 variables to MAGICC6 variables """ @functools.lru_cache(None) def _apply_convert_magicc6_to_magicc7_variables(variables, inverse): def hfc245ca_included(variables): variables = [variables] if isinstance(variables, str) else variables return any([v.replace("-", "").lower() == "hfc245ca" for v in variables]) if hfc245ca_included(variables): error_msg = ( "HFC245ca wasn't meant to be included in MAGICC6. Renaming to HFC245fa." ) warnings.warn(error_msg) if inverse: return apply_string_substitutions( variables, MAGICC7_TO_MAGICC6_VARIABLES_MAPPING, unused_substitutions="ignore", # TODO: make this warn and see what happens case_insensitive=True, # MAGICC variables are case insensitive ) else: return apply_string_substitutions( variables, MAGICC6_TO_MAGICC7_VARIABLES_MAPPING, unused_substitutions="ignore", # TODO: make this warn and see what happens case_insensitive=True, # MAGICC variables are case insensitive )
[docs]def convert_magicc6_to_magicc7_variables(variables, inverse=False): """ Convert MAGICC6 variables to MAGICC7 variables Parameters ---------- variables : list_like, str Variables to convert inverse : bool If True, convert the other way i.e. convert MAGICC7 variables to MAGICC6 variables Raises ------ ValueError If you try to convert HFC245ca, or some variant thereof, you will get a ValueError. The reason is that this variable was never meant to be included in MAGICC6, it was just an accident. See, for example, the text in the description section of ``pymagicc/MAGICC6/run/HISTRCP_HFC245fa_CONC.IN``: "...HFC245fa, rather than HFC245ca, is the actually used isomer.". Returns ------- ``type(variables)`` Set of converted variables """ if isinstance(variables, (list, pd.Index)): return [ _apply_convert_magicc6_to_magicc7_variables(v, inverse) for v in variables ] else: return _apply_convert_magicc6_to_magicc7_variables(variables, inverse)
[docs]def get_pint_to_fortran_safe_units_mapping(inverse=False): """Get the mappings from Pint to Fortran safe units. Fortran can't handle special characters like "^" or "/" in names, but we need these in Pint. Conversely, Pint stores variables with spaces by default e.g. "Mt CO2 / yr" but we don't want these in the input files as Fortran is likely to think the whitespace is a delimiter. Parameters ---------- inverse : bool If True, return the inverse mappings i.e. Fortran safe to Pint mappings Returns ------- dict Dictionary of mappings """ replacements = {"^": "super", "/": "per", " ": "_", "**": "super"} if inverse: replacements = {v: k for k, v in replacements.items() if k not in ["**"]} return replacements
PINT_TO_FORTRAN_SAFE_UNITS_MAPPING = get_pint_to_fortran_safe_units_mapping() """dict: mappings required to make Pint units Fortran safe. """ FORTRAN_SAFE_TO_PINT_UNITS_MAPPING = get_pint_to_fortran_safe_units_mapping( inverse=True ) """dict: mappings required to convert our Fortran safe units to Pint. """
[docs]def convert_pint_to_fortran_safe_units(units, inverse=False): """ Convert Pint units to Fortran safe units Parameters ---------- units : list_like, str Units to convert inverse : bool If True, convert the other way i.e. convert Fortran safe units to Pint units Returns ------- ``type(units)`` Set of converted units """ if inverse: return apply_string_substitutions(units, FORTRAN_SAFE_TO_PINT_UNITS_MAPPING) else: return apply_string_substitutions(units, PINT_TO_FORTRAN_SAFE_UNITS_MAPPING)