"""
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)