From 59f3af4b16dd3e7d7fdd3ef3ed248019c099ee8a Mon Sep 17 00:00:00 2001 From: Ben RdO Date: Thu, 13 Oct 2022 22:51:21 -0700 Subject: [PATCH] refactor: refactor geographical part of model immutables --- powersimdata/input/tests/test_check.py | 13 +- .../network/constants/region/division.py | 58 +++ .../network/constants/region/europe.py | 252 ------------ .../network/constants/region/geography.py | 362 ++++++++++++++++++ .../network/constants/region/interconnect.py | 45 +++ .../network/constants/region/loadzone.py | 54 +++ .../network/constants/region/mapping.py | 69 ++++ powersimdata/network/constants/region/usa.py | 237 ------------ .../network/constants/region/zones.py | 47 ++- powersimdata/network/europe_tub/model.py | 75 +--- powersimdata/network/model.py | 24 +- powersimdata/network/tests/test_model.py | 6 +- 12 files changed, 663 insertions(+), 579 deletions(-) create mode 100644 powersimdata/network/constants/region/division.py delete mode 100644 powersimdata/network/constants/region/europe.py create mode 100644 powersimdata/network/constants/region/geography.py create mode 100644 powersimdata/network/constants/region/interconnect.py create mode 100644 powersimdata/network/constants/region/loadzone.py create mode 100644 powersimdata/network/constants/region/mapping.py delete mode 100644 powersimdata/network/constants/region/usa.py diff --git a/powersimdata/input/tests/test_check.py b/powersimdata/input/tests/test_check.py index b6fb758ae..be999021a 100644 --- a/powersimdata/input/tests/test_check.py +++ b/powersimdata/input/tests/test_check.py @@ -25,7 +25,6 @@ check_grid, ) from powersimdata.network.europe_tub.model import TUB -from powersimdata.network.model import ModelImmutables from powersimdata.tests.mock_scenario import MockScenario @@ -264,11 +263,11 @@ def test_check_resources_and_format_argument_value(): _check_resources_and_format(a) -def test_check_resources_and_format(): +def test_check_resources_and_format(europe): _check_resources_and_format(["dfo", "wind", "solar", "ng"]) - _check_resources_and_format("offwind-ac", mi=ModelImmutables("europe_tub")) + _check_resources_and_format("offwind-ac", mi=europe.model_immutables) _check_resources_and_format({"nuclear"}) - _check_resources_and_format("geothermal", mi=ModelImmutables("europe_tub")) + _check_resources_and_format("geothermal", mi=europe.model_immutables) def test_check_resources_are_renewable_and_format_argument_value(): @@ -276,13 +275,11 @@ def test_check_resources_are_renewable_and_format_argument_value(): _check_resources_are_renewable_and_format({"solar", "nuclear"}) -def test_check_resources_are_renewable_and_format(): +def test_check_resources_are_renewable_and_format(europe): _check_resources_are_renewable_and_format(["wind_offshore", "wind"]) _check_resources_are_renewable_and_format("solar") _check_resources_are_renewable_and_format({"wind"}) - _check_resources_are_renewable_and_format( - {"solar"}, mi=ModelImmutables("europe_tub") - ) + _check_resources_are_renewable_and_format({"solar"}, mi=europe.model_immutables) def test_check_areas_are_in_grid_and_format_argument_type(mock_grid): diff --git a/powersimdata/network/constants/region/division.py b/powersimdata/network/constants/region/division.py new file mode 100644 index 000000000..2cfda5e1b --- /dev/null +++ b/powersimdata/network/constants/region/division.py @@ -0,0 +1,58 @@ +class DivisionMapping: + """State/Country mapping for grid models. + + :param pandas.DataFrame zone: information on zones of a grid model. + """ + + def __init__(self, zone): + self.abv = set(zone["abv"]) + self.abv2loadzone = zone.groupby("abv")["zone_name"].apply(set).to_dict() + self.abv2id = zone.reset_index().groupby("abv")["zone_id"].apply(set).to_dict() + self.id2abv = zone["abv"].to_dict() + self.abv2interconnect = dict(zip(zone["abv"], zone["interconnect"])) + + +class USADivisionMapping(DivisionMapping): + """State mapping for USA grid models + + :param pandas.DataFrame zone: information on zones of a grid model. + """ + + def __init__(self, zone): + super().__init__(zone) + self.state = set(zone["state"]) + self.state_abbr = set(zone["abv"]) + self.state2loadzone = zone.groupby("state")["zone_name"].apply(set).to_dict() + self.state2abv = dict(zip(zone["state"], zone["abv"])) + self.abv2state = dict(zip(zone["abv"], zone["state"])) + + +class EUDivisionMapping(DivisionMapping): + """Country mapping for EU grid models + + :param pandas.DataFrame zone: information on zones of a grid model. + """ + + def __init__(self, zone): + super().__init__(zone) + self.country = set(zone["country"]) + self.country_abbr = set(zone["country"]) + self.country2loadzone = ( + zone.groupby("country")["zone_name"].apply(set).to_dict() + ) + self.country2abv = dict(zip(zone["country"], zone["abv"])) + self.abv2country = dict(zip(zone["abv"], zone["country"])) + + +def get_division_mapping(model, zone): + """Return division mappings for a grid model. + + :param str model: grid model. + :param pandas.DataFrame zone: information on zones of a grid model. + """ + _lookup = { + "usa_tamu": USADivisionMapping, + "hifld": USADivisionMapping, + "europe_tub": EUDivisionMapping, + } + return _lookup[model](zone).__dict__ diff --git a/powersimdata/network/constants/region/europe.py b/powersimdata/network/constants/region/europe.py deleted file mode 100644 index 4ab4aafb3..000000000 --- a/powersimdata/network/constants/region/europe.py +++ /dev/null @@ -1,252 +0,0 @@ -from itertools import chain - -import pandas as pd - -from powersimdata.network.constants.model import model2interconnect -from powersimdata.network.helpers import interconnect_to_name, powerset - -abv2country = { - "AL": "Albania", - "AT": "Austria", - "BA": "Bosnia And Herzegovina", - "BE": "Belgium", - "BG": "Bulgaria", - "CH": "Switzerland", - "CZ": "Czech Republic", - "DE": "Germany", - "DK": "Danemark", - "EE": "Estonia", - "ES": "Spain", - "FI": "Finland", - "FR": "France", - "GB": "Great Britain", - "GR": "Greece", - "HR": "Croatia", - "HU": "Hungary", - "IE": "Ireland", - "IT": "Italy", - "LT": "Lithuania", - "LU": "Luxembourg", - "LV": "Latvia", - "ME": "Montenegro", - "MK": "Macedonia", - "NL": "Netherlands", - "NO": "Norway", - "PL": "Poland", - "PT": "Portugal", - "RO": "Romania", - "RS": "Serbia", - "SE": "Sweden", - "SI": "Slovenia", - "SK": "Slovakia", -} - -abv2timezone = { - "AL": "ETC/GMT-1", - "AT": "ETC/GMT-1", - "BA": "ETC/GMT-1", - "BE": "ETC/GMT-1", - "BG": "ETC/GMT-2", - "CH": "ETC/GMT-1", - "CZ": "ETC/GMT-1", - "DE": "ETC/GMT-1", - "DK": "ETC/GMT-1", - "EE": "ETC/GMT-2", - "ES": "ETC/GMT-1", - "FI": "ETC/GMT-2", - "FR": "ETC/GMT-1", - "GB": "ETC/GMT", - "GR": "ETC/GMT-2", - "HR": "ETC/GMT-1", - "HU": "ETC/GMT-1", - "IE": "ETC/GMT", - "IT": "ETC/GMT-1", - "LT": "ETC/GMT-2", - "LU": "ETC/GMT-1", - "LV": "ETC/GMT-2", - "ME": "ETC/GMT-1", - "MK": "ETC/GMT-1", - "NL": "ETC/GMT-1", - "NO": "ETC/GMT-1", - "PL": "ETC/GMT-1", - "PT": "ETC/GMT", - "RO": "ETC/GMT-2", - "RS": "ETC/GMT-1", - "SE": "ETC/GMT-1", - "SI": "ETC/GMT-1", - "SK": "ETC/GMT-1", -} - -interconnect2abv = { - "ContinentalEurope": { - "AL", - "AT", - "BA", - "BE", - "BG", - "CH", - "CZ", - "DE", - "DK", - "ES", - "FR", - "GR", - "HR", - "HU", - "IT", - "LU", - "ME", - "MK", - "NL", - "PL", - "PT", - "RO", - "RS", - "SI", - "SK", - }, - "Nordic": {"FI", "NO", "SE"}, - "GreatBritain": {"GB"}, - "Ireland": {"IE"}, - "Baltic": {"EE", "LT", "LV"}, -} -for c in powerset(model2interconnect["europe_tub"], 2): - interconnect2abv[interconnect_to_name(c, model="europe_tub")] = set( - chain(*[interconnect2abv[i] for i in c]) - ) - -name2interconnect = { - interconnect_to_name(c, model="europe_tub"): set(c) - for c in powerset(model2interconnect["europe_tub"], 1) -} - -name2component = name2interconnect.copy() -name2component.update({"Europe": set(name2interconnect) - {"Europe"}}) - -interconnect2timezone = { - interconnect_to_name(c, model="europe_tub"): "ETC/GMT-1" - for c in powerset(model2interconnect["europe_tub"], 1) -} -interconnect2timezone.update( - { - interconnect_to_name("GreatBritain", model="europe_tub"): "ETC/GMT", - interconnect_to_name("Ireland", model="europe_tub"): "ETC/GMT", - interconnect_to_name( - ["GreatBritain", "Ireland"], model="europe_tub" - ): "ETC/GMT", - interconnect_to_name("Baltic", model="europe_tub"): "ETC/GMT-2", - interconnect_to_name(["Nordic", "Baltic"], model="europe_tub"): "ETC/GMT-2", - } -) - - -def get_interconnect_mapping(zone, model): - """Return interconnect mapping. - - :param pandas.DataFrame zone: information on zones of a grid model. - :param str model: the grid model. - :return: (*dict*) -- mappings of interconnect to other areas. - """ - mapping = dict() - - name = interconnect_to_name(zone["interconnect"], model=model) - - mapping["interconnect"] = name2component[name] | {name} - mapping["name2interconnect"] = { - i: name2interconnect[i] for i in mapping["interconnect"] - } - mapping["name2component"] = {i: name2component[i] for i in mapping["interconnect"]} - mapping["interconnect2timezone"] = { - i: interconnect2timezone[i] for i in mapping["interconnect"] - } - mapping["interconnect2abv"] = { - i: interconnect2abv[i] for i in mapping["interconnect"] - } - if model == "europe_tub": - mapping["interconnect2loadzone"] = {i: set() for i in mapping["interconnect"]} - mapping["interconnect2id"] = {i: set() for i in mapping["interconnect"]} - - return mapping - - -def get_country_mapping(zone, model): - """Return country mapping. - - :param pandas.DataFrame zone: information on zones of a grid model. - :param str model: the grid model. - :return: (*dict*) -- mappings of countries to other areas. - """ - mapping = dict() - - mapping["country"] = set(zone["country"]) - mapping["abv"] = set(zone["abv"]) - mapping["country_abbr"] = set(zone["abv"]) - mapping["country2abv"] = dict(zip(zone["country"], zone["abv"])) - mapping["abv2country"] = dict(zip(zone["abv"], zone["country"])) - mapping["abv2interconnect"] = dict(zip(zone["abv"], zone["interconnect"])) - - if model == "europe_tub": - mapping["country2loadzone"] = {c: set() for c in set(zone["country"])} - mapping["abv2loadzone"] = {a: set() for a in set(zone["abv"])} - mapping["abv2id"] = {a: set() for a in set(zone["abv"])} - mapping["id2abv"] = dict() - - return mapping - - -def get_loadzone_mapping(zone, model): - """Return loadzone mapping - - :param pandas.DataFrame zone: information on zones of a grid model. - :param str model: the grid model. - :return: (*dict*) -- mappings of loadzones to other areas. - """ - mapping = dict() - - if model == "europe_tub": - mapping["loadzone"] = set() - mapping["id2timezone"] = dict() - mapping["id2loadzone"] = dict() - mapping["timezone2id"] = dict() - mapping["loadzone2id"] = dict() - mapping["loadzone2country"] = dict() - mapping["loadzone2abv"] = dict() - mapping["loadzone2interconnect"] = dict() - - return mapping - - -def get_zones(interconnect, model): - """Return zone constants. - - :para list interconnect: interconnect(s). - :param str model: the grid model. - :return: (*dict*) -- zones information. - """ - zones = dict() - zones["mappings"] = {"loadzone", "country", "country_abbr", "interconnect"} - zones["division"] = "country" - - interconnect = ( - model2interconnect[model] if "Europe" in interconnect else interconnect - ) - if model == "europe_tub": - # geographical information will be enclosed in the PyPSA Network object - zone_info = pd.DataFrame( - {"abv": [a for i in interconnect for a in interconnect2abv[i]]} - ) - zone_info["country"] = zone_info["abv"].map(abv2country) - zone_info["time_zone"] = zone_info["abv"].map( - {a: t for a, t in abv2timezone.items()} - ) - zone_info["interconnect"] = zone_info["abv"].map( - {a: i for i in interconnect for a in interconnect2abv[i]} - ) - else: - raise ValueError("Invalid model") - - zones.update(get_loadzone_mapping(zone_info, model)) - zones.update(get_country_mapping(zone_info, model)) - zones.update(get_interconnect_mapping(zone_info, model)) - - return zones diff --git a/powersimdata/network/constants/region/geography.py b/powersimdata/network/constants/region/geography.py new file mode 100644 index 000000000..d2c122ccf --- /dev/null +++ b/powersimdata/network/constants/region/geography.py @@ -0,0 +1,362 @@ +import ast + +from powersimdata.network.helpers import powerset + + +class USA: + """Geographical and timezone information for USA grid models""" + + def __init__(self): + self.division = "state" + self.abv2state = { + "AK": "Alaska", + "AL": "Alabama", + "AR": "Arkansas", + "AZ": "Arizona", + "CA": "California", + "CO": "Colorado", + "CT": "Connecticut", + "DE": "Delaware", + "FL": "Florida", + "GA": "Georgia", + "HI": "Hawaii", + "IA": "Iowa", + "ID": "Idaho", + "IL": "Illinois", + "IN": "Indiana", + "KS": "Kansas", + "KY": "Kentucky", + "LA": "Louisiana", + "MA": "Massachusetts", + "MD": "Maryland", + "ME": "Maine", + "MI": "Michigan", + "MN": "Minnesota", + "MO": "Missouri", + "MS": "Mississippi", + "MT": "Montana", + "NC": "North Carolina", + "ND": "North Dakota", + "NE": "Nebraska", + "NH": "New Hampshire", + "NJ": "New Jersey", + "NM": "New Mexico", + "NV": "Nevada", + "NY": "New York", + "OH": "Ohio", + "OK": "Oklahoma", + "OR": "Oregon", + "PA": "Pennsylvania", + "RI": "Rhode Island", + "SC": "South Carolina", + "SD": "South Dakota", + "TN": "Tennessee", + "TX": "Texas", + "UT": "Utah", + "VA": "Virginia", + "VT": "Vermont", + "WA": "Washington", + "WI": "Wisconsin", + "WV": "West Virginia", + "WY": "Wyoming", + } + self.state2abv = {n: a for a, n in self.abv2state.items()} + self.interconnect2abv = { + "Eastern": { + "ME", + "NH", + "VT", + "MA", + "RI", + "CT", + "NY", + "NJ", + "PA", + "DE", + "MD", + "VA", + "NC", + "SC", + "GA", + "FL", + "AL", + "MS", + "TN", + "KY", + "WV", + "OH", + "MI", + "IN", + "IL", + "WI", + "MN", + "IA", + "MO", + "AR", + "LA", + "OK", + "KS", + "NE", + "SD", + "ND", + }, + "ERCOT": {"TX"}, + "Western": { + "WA", + "OR", + "CA", + "NV", + "AZ", + "UT", + "NM", + "CO", + "WY", + "ID", + "MT", + }, + } + self.abv2interconnect = { + a: i for i, abv in self.interconnect2abv.items() for a in abv + } + self.abv2timezone = { + "ME": "ETC/GMT+5", + "NH": "ETC/GMT+5", + "VT": "ETC/GMT+5", + "MA": "ETC/GMT+5", + "RI": "ETC/GMT+5", + "CT": "ETC/GMT+5", + "NY": "ETC/GMT+5", + "NJ": "ETC/GMT+5", + "PA": "ETC/GMT+5", + "DE": "ETC/GMT+5", + "MD": "ETC/GMT+5", + "VA": "ETC/GMT+5", + "NC": "ETC/GMT+5", + "SC": "ETC/GMT+5", + "GA": "ETC/GMT+5", + "FL": "ETC/GMT+5", + "AL": "ETC/GMT+6", + "MS": "ETC/GMT+6", + "TN": "ETC/GMT+6", + "KY": "ETC/GMT+5", + "WV": "ETC/GMT+5", + "OH": "ETC/GMT+5", + "MI": "ETC/GMT+5", + "IN": "ETC/GMT+5", + "IL": "ETC/GMT+6", + "WI": "ETC/GMT+6", + "MN": "ETC/GMT+6", + "IA": "ETC/GMT+6", + "MO": "ETC/GMT+6", + "AR": "ETC/GMT+6", + "LA": "ETC/GMT+6", + "TX": "ETC/GMT+6", + "NM": "ETC/GMT+7", + "OK": "ETC/GMT+6", + "KS": "ETC/GMT+6", + "NE": "ETC/GMT+6", + "SD": "ETC/GMT+6", + "ND": "ETC/GMT+6", + "MT": "ETC/GMT+7", + "WA": "ETC/GMT+8", + "OR": "ETC/GMT+8", + "CA": "ETC/GMT+8", + "NV": "ETC/GMT+8", + "AZ": "ETC/GMT+7", + "UT": "ETC/GMT+7", + "CO": "ETC/GMT+7", + "WY": "ETC/GMT+7", + "ID": "ETC/GMT+7", + } + self.interconnect2timezone = { + "USA": "ETC/GMT+6", + "Eastern": "ETC/GMT+5", + "ERCOT": "ETC/GMT+6", + "Western": "ETC/GMT+8", + format(["ERCOT", "Western"]): "ETC/GMT+7", + format(["ERCOT", "Eastern"]): "ETC/GMT+5", + format(["Eastern", "Western"]): "ETC/GMT+6", + } + + self.sub = {"USA": format(self.interconnect2abv)} + self.name2interconnect = { + format(c): set(c) for c in powerset(self.interconnect2abv, 1) + } + self.name2interconnect["USA"] = self.name2interconnect.pop(self.sub.get("USA")) + self.name2component = self.name2interconnect.copy() + self.name2component.update({"USA": set(self.name2interconnect) - {"USA"}}) + + def substitute(self): + """Replace ERCOT with Texas in all attributes. + + :return: (*dict*) -- updated attributes. + """ + old = powerset(["ERCOT", "Eastern", "Western"], 1) + old.reverse() + new = powerset(["Texas", "Eastern", "Western"], 1) + new.reverse() + + for o, n in {format(o): format(n) for o, n in zip(old, new)}.items(): + self.__dict__.update(ast.literal_eval(repr(self.__dict__).replace(o, n))) + + return self + + +class EU: + """Geographical and timezone information for USA grid models""" + + def __init__(self): + self.division = "country" + self.abv2country = { + "AL": "Albania", + "AT": "Austria", + "BA": "Bosnia And Herzegovina", + "BE": "Belgium", + "BG": "Bulgaria", + "CH": "Switzerland", + "CZ": "Czech Republic", + "DE": "Germany", + "DK": "Danemark", + "EE": "Estonia", + "ES": "Spain", + "FI": "Finland", + "FR": "France", + "GB": "Great Britain", + "GR": "Greece", + "HR": "Croatia", + "HU": "Hungary", + "IE": "Ireland", + "IT": "Italy", + "LT": "Lithuania", + "LU": "Luxembourg", + "LV": "Latvia", + "ME": "Montenegro", + "MK": "Macedonia", + "NL": "Netherlands", + "NO": "Norway", + "PL": "Poland", + "PT": "Portugal", + "RO": "Romania", + "RS": "Serbia", + "SE": "Sweden", + "SI": "Slovenia", + "SK": "Slovakia", + } + self.country2abv = {n: a for a, n in self.abv2country.items()} + self.interconnect2abv = { + "ContinentalEurope": { + "AL", + "AT", + "BA", + "BE", + "BG", + "CH", + "CZ", + "DE", + "DK", + "ES", + "FR", + "GR", + "HR", + "HU", + "IT", + "LU", + "ME", + "MK", + "NL", + "PL", + "PT", + "RO", + "RS", + "SI", + "SK", + }, + "Nordic": {"FI", "NO", "SE"}, + "GreatBritain": {"GB"}, + "Ireland": {"IE"}, + "Baltic": {"EE", "LT", "LV"}, + } + self.abv2interconnect = { + a: i for i, abv in self.interconnect2abv.items() for a in abv + } + self.abv2timezone = { + "AL": "ETC/GMT-1", + "AT": "ETC/GMT-1", + "BA": "ETC/GMT-1", + "BE": "ETC/GMT-1", + "BG": "ETC/GMT-2", + "CH": "ETC/GMT-1", + "CZ": "ETC/GMT-1", + "DE": "ETC/GMT-1", + "DK": "ETC/GMT-1", + "EE": "ETC/GMT-2", + "ES": "ETC/GMT-1", + "FI": "ETC/GMT-2", + "FR": "ETC/GMT-1", + "GB": "ETC/GMT", + "GR": "ETC/GMT-2", + "HR": "ETC/GMT-1", + "HU": "ETC/GMT-1", + "IE": "ETC/GMT", + "IT": "ETC/GMT-1", + "LT": "ETC/GMT-2", + "LU": "ETC/GMT-1", + "LV": "ETC/GMT-2", + "ME": "ETC/GMT-1", + "MK": "ETC/GMT-1", + "NL": "ETC/GMT-1", + "NO": "ETC/GMT-1", + "PL": "ETC/GMT-1", + "PT": "ETC/GMT", + "RO": "ETC/GMT-2", + "RS": "ETC/GMT-1", + "SE": "ETC/GMT-1", + "SI": "ETC/GMT-1", + "SK": "ETC/GMT-1", + } + self.sub = {"Europe": format(self.interconnect2abv)} + self.interconnect2timezone = { + format(c): "ETC/GMT-1" for c in powerset(self.interconnect2abv, 1) + } + self.interconnect2timezone.update( + { + "GreatBritain": "ETC/GMT", + "Ireland": "ETC/GMT", + format(["GreatBritain", "Ireland"]): "ETC/GMT", + "Baltic": "ETC/GMT-2", + format(["Nordic", "Baltic"]): "ETC/GMT-2", + } + ) + self.interconnect2timezone["Europe"] = self.interconnect2timezone.pop( + self.sub.get("Europe") + ) + self.name2interconnect = { + format(c): set(c) for c in powerset(self.interconnect2abv, 1) + } + self.name2interconnect["Europe"] = self.name2interconnect.pop( + self.sub.get("Europe") + ) + self.name2component = self.name2interconnect.copy() + self.name2component.update({"Europe": set(self.name2interconnect) - {"Europe"}}) + + +def format(name): + """Format a list of words. + + :param list name: list of words + """ + return "_".join(sorted([i.replace(" ", "") for i in name])) + + +def get_geography(model): + """Return geographical and time zone information for a grid model. + + :param str model: grid model + """ + + _lookup = { + "usa_tamu": USA().substitute(), + "hifld": USA(), + "europe_tub": EU(), + } + return _lookup[model].__dict__ diff --git a/powersimdata/network/constants/region/interconnect.py b/powersimdata/network/constants/region/interconnect.py new file mode 100644 index 000000000..9426ae960 --- /dev/null +++ b/powersimdata/network/constants/region/interconnect.py @@ -0,0 +1,45 @@ +import ast + +from powersimdata.network.constants.model import model2region +from powersimdata.network.constants.region.geography import format, get_geography +from powersimdata.network.helpers import powerset + + +class InterconnectMapping: + """Interconnect mapping for grid models + + :param str model: grid model. + :param pandas.DataFrame zone: information on zones of a grid model. + """ + + def __init__(self, model, zone): + geo = get_geography(model) + region = model2region[model] + + self.interconnect = { + ast.literal_eval(repr(format(c)).replace(geo["sub"][region], region)) + for c in powerset(zone["interconnect"].unique(), 1) + } + self.interconnect2timezone = { + i: geo["interconnect2timezone"][i] for i in self.interconnect + } + self.name2interconnect = { + i: geo["name2interconnect"][i] for i in self.interconnect + } + self.name2component = {i: geo["name2component"][i] for i in self.interconnect} + self.interconnect2loadzone = ( + zone.groupby("interconnect")["zone_name"].apply(set).to_dict() + ) + self.interconnect2id = ( + zone.reset_index().groupby("interconnect")["zone_id"].apply(set).to_dict() + ) + self.interconnect2abv = zone.groupby("interconnect")["abv"].apply(set).to_dict() + + +def get_interconnect_mapping(model, zone): + """Return interconnect mappings for a grid model. + + :param str model: grid model + :param pandas.DataFrame zone: information on zones of a grid model. + """ + return InterconnectMapping(model, zone).__dict__ diff --git a/powersimdata/network/constants/region/loadzone.py b/powersimdata/network/constants/region/loadzone.py new file mode 100644 index 000000000..42938295b --- /dev/null +++ b/powersimdata/network/constants/region/loadzone.py @@ -0,0 +1,54 @@ +class LoadzoneMapping: + """Loadzone mapping for grid models + + :param pandas.DataFrame zone: information on zones of a grid model. + """ + + def __init__(self, zone): + self.loadzone = set(zone["zone_name"]) + self.id2timezone = zone["time_zone"].to_dict() + self.id2loadzone = zone["zone_name"].to_dict() + self.timezone2id = ( + zone.reset_index().groupby("time_zone")["zone_id"].apply(set).to_dict() + ) + self.loadzone2id = ( + zone.reset_index().groupby("zone_name")["zone_id"].apply(int).to_dict() + ) + self.loadzone2abv = dict(zip(zone["zone_name"], zone["abv"])) + self.loadzone2interconnect = dict(zip(zone["zone_name"], zone["interconnect"])) + + +class USALoadzoneMapping(LoadzoneMapping): + """Loadzone mapping for USA grid models + + :param pandas.DataFrame zone: information on zones of a grid model. + """ + + def __init__(self, zone): + super().__init__(zone) + self.loadzone2state = dict(zip(zone["zone_name"], zone["state"])) + + +class EULoadzoneMapping(LoadzoneMapping): + """Loadzone mapping for EU grid models + + :param pandas.DataFrame zone: information on zones of a grid model. + """ + + def __init__(self, zone): + super().__init__(zone) + self.loadzone2country = dict(zip(zone["zone_name"], zone["country"])) + + +def get_loadzone_mapping(model, zone): + """Return loadzone mappings for a grid model. + + :param str model: grid model + :param pandas.DataFrame zone: information on zones of a grid model. + """ + _lookup = { + "usa_tamu": USALoadzoneMapping, + "hifld": USALoadzoneMapping, + "europe_tub": EULoadzoneMapping, + } + return _lookup[model](zone).__dict__ diff --git a/powersimdata/network/constants/region/mapping.py b/powersimdata/network/constants/region/mapping.py new file mode 100644 index 000000000..335bc82ab --- /dev/null +++ b/powersimdata/network/constants/region/mapping.py @@ -0,0 +1,69 @@ +from powersimdata.network.constants.model import model2interconnect, model2region +from powersimdata.network.constants.region.division import get_division_mapping +from powersimdata.network.constants.region.interconnect import get_interconnect_mapping +from powersimdata.network.constants.region.loadzone import get_loadzone_mapping + + +class Mapping: + """Geographical/time mapping for USA grid models + + :param str model: grid model. + :param list interconnect: interconnect(s) + :param pandas.DataFrame info: information on zones of a grid model. + """ + + def __init__(self, model, interconnect, info): + self.zones = dict() + + interconnect = ( # noqa + model2interconnect[model] + if model2region[model] in interconnect + else interconnect + ) + zone = info.query("interconnect == @interconnect") + self.zones.update(get_loadzone_mapping(model, zone)) + self.zones.update(get_division_mapping(model, zone)) + self.zones.update(get_interconnect_mapping(model, zone)) + + +class USAMapping(Mapping): + """Geographical/time mapping for USA grid models + + :param str model: grid model. + :param list interconnect: interconnect(s) + :param pandas.DataFrame info: information on zones of a grid model. + """ + + def __init__(self, model, interconnect, info): + super().__init__(model, interconnect, info) + self.zones["mappings"] = {"state_abbr", "state", "loadzone", "interconnect"} + self.zones["division"] = "state" + + +class EUMapping(Mapping): + """Geographical/time mapping for EU grid models + + :param str model: grid model. + :param list interconnect: interconnect(s) + :param pandas.DataFrame info: information on zones of a grid model. + """ + + def __init__(self, model, interconnect, info): + super().__init__(model, interconnect, info) + self.zones["mappings"] = {"country_abbr", "country", "loadzone", "interconnect"} + self.zones["division"] = "country" + + +def get_mapping(model, interconnect, info): + """Return interconnect mappings for a grid model. + + :param str model: grid model. + :param list interconnect: interconnect(s) + :param pandas.DataFrame info: information on zones of a grid model. + """ + _lookup = { + "usa_tamu": USAMapping, + "hifld": USAMapping, + "europe_tub": EUMapping, + } + return _lookup[model](model, interconnect, info).zones diff --git a/powersimdata/network/constants/region/usa.py b/powersimdata/network/constants/region/usa.py deleted file mode 100644 index 30f8964b4..000000000 --- a/powersimdata/network/constants/region/usa.py +++ /dev/null @@ -1,237 +0,0 @@ -import ast -from itertools import chain - -from powersimdata.network.constants.model import model2interconnect -from powersimdata.network.helpers import get_zone_info, interconnect_to_name, powerset - -abv2state = { - "AK": "Alaska", - "AL": "Alabama", - "AR": "Arkansas", - "AZ": "Arizona", - "CA": "California", - "CO": "Colorado", - "CT": "Connecticut", - "DE": "Delaware", - "FL": "Florida", - "GA": "Georgia", - "HI": "Hawaii", - "IA": "Iowa", - "ID": "Idaho", - "IL": "Illinois", - "IN": "Indiana", - "KS": "Kansas", - "KY": "Kentucky", - "LA": "Louisiana", - "MA": "Massachusetts", - "MD": "Maryland", - "ME": "Maine", - "MI": "Michigan", - "MN": "Minnesota", - "MO": "Missouri", - "MS": "Mississippi", - "MT": "Montana", - "NC": "North Carolina", - "ND": "North Dakota", - "NE": "Nebraska", - "NH": "New Hampshire", - "NJ": "New Jersey", - "NM": "New Mexico", - "NV": "Nevada", - "NY": "New York", - "OH": "Ohio", - "OK": "Oklahoma", - "OR": "Oregon", - "PA": "Pennsylvania", - "RI": "Rhode Island", - "SC": "South Carolina", - "SD": "South Dakota", - "TN": "Tennessee", - "TX": "Texas", - "UT": "Utah", - "VA": "Virginia", - "VT": "Vermont", - "WA": "Washington", - "WI": "Wisconsin", - "WV": "West Virginia", - "WY": "Wyoming", -} - -interconnect2abv = { - "Eastern": { - "ME", - "NH", - "VT", - "MA", - "RI", - "CT", - "NY", - "NJ", - "PA", - "DE", - "MD", - "VA", - "NC", - "SC", - "GA", - "FL", - "AL", - "MS", - "TN", - "KY", - "WV", - "OH", - "MI", - "IN", - "IL", - "WI", - "MN", - "IA", - "MO", - "AR", - "LA", - "OK", - "KS", - "NE", - "SD", - "ND", - }, - "ERCOT": {"TX"}, - "Western": {"WA", "OR", "CA", "NV", "AZ", "UT", "NM", "CO", "WY", "ID", "MT"}, -} -for c in powerset(model2interconnect["hifld"], 2): - interconnect2abv[interconnect_to_name(c, model="hifld")] = set( - chain(*[interconnect2abv[i] for i in c]) - ) - -name2interconnect = { - interconnect_to_name(c, model="hifld"): set(c) - for c in powerset(model2interconnect["hifld"], 1) -} - -name2component = name2interconnect.copy() -name2component.update({"USA": set(name2interconnect) - {"USA"}}) - -interconnect2timezone = { - interconnect_to_name("USA"): "ETC/GMT+6", - interconnect_to_name("Eastern"): "ETC/GMT+5", - interconnect_to_name("ERCOT"): "ETC/GMT+6", - interconnect_to_name("Western"): "ETC/GMT+8", - interconnect_to_name(["ERCOT", "Western"]): "ETC/GMT+7", - interconnect_to_name(["ERCOT", "Eastern"]): "ETC/GMT+5", - interconnect_to_name(["Eastern", "Western"]): "ETC/GMT+6", -} - - -def get_interconnect_mapping(zone, model): - """Return interconnect mapping. - - :param pandas.DataFrame zone: information on zones of a grid model. - :param str model: the grid model. - :return: (*dict*) -- mappings of interconnect to other areas. - """ - - def _substitute(entry): - return { - i: ast.literal_eval(repr(entry).replace("ERCOT", sub))[i] - for i in mapping["interconnect"] - } - - mapping = dict() - sub = "Texas" if model == "usa_tamu" else "ERCOT" - - name = interconnect_to_name(zone["interconnect"].unique(), model=model) - mapping["interconnect"] = ast.literal_eval( - repr(name2component).replace("ERCOT", sub) - )[name] | {name} - mapping["name2interconnect"] = _substitute(name2interconnect) - mapping["name2component"] = _substitute(name2component) - mapping["interconnect2timezone"] = _substitute(interconnect2timezone) - mapping["interconnect2abv"] = _substitute(interconnect2abv) - mapping["interconnect2loadzone"] = { - i: set(l) - for i, l in zone.set_index("zone_name").groupby("interconnect").groups.items() - } - mapping["interconnect2id"] = { - i: set(id) for i, id in zone.groupby("interconnect").groups.items() - } - - return mapping - - -def get_state_mapping(zone): - """Return state mapping. - - :param pandas.DataFrame zone: information on zones of a grid model. - :return: (*dict*) -- mappings of states to other areas. - """ - mapping = dict() - - mapping["state"] = set(zone["state"]) - mapping["abv"] = set(zone["abv"]) - mapping["state_abbr"] = set(zone["abv"]) - mapping["state2loadzone"] = { - k: set(v) - for k, v in zone.groupby("state")["zone_name"].unique().to_dict().items() - } - mapping["abv2loadzone"] = { - k: set(v) - for k, v in zone.groupby("abv")["zone_name"].unique().to_dict().items() - } - mapping["abv2id"] = {k: set(v) for k, v in zone.groupby("abv").groups.items()} - mapping["id2abv"] = {k: v for k, v in zone["abv"].to_dict().items()} - mapping["state2abv"] = dict(zip(zone["state"], zone["abv"])) - mapping["abv2state"] = dict(zip(zone["abv"], zone["state"])) - mapping["abv2interconnect"] = dict(zip(zone["abv"], zone["interconnect"])) - - return mapping - - -def get_loadzone_mapping(zone): - """Return loadzone mapping - - :param pandas.DataFrame zone: information on zones of a grid model. - :return: (*dict*) -- mappings of loadzones to other areas - """ - mapping = dict() - - mapping["loadzone"] = set(zone["zone_name"]) - mapping["id2timezone"] = zone["time_zone"].to_dict() - mapping["id2loadzone"] = zone["zone_name"].to_dict() - mapping["timezone2id"] = { - t: set(i) for t, i in zone.groupby("time_zone").groups.items() - } - mapping["loadzone2id"] = { - l: i[0] for l, i in zone.groupby("zone_name").groups.items() - } - mapping["loadzone2state"] = dict(zip(zone["zone_name"], zone["state"])) - mapping["loadzone2abv"] = dict(zip(zone["zone_name"], zone["abv"])) - mapping["loadzone2interconnect"] = dict( - zip(zone["zone_name"], zone["interconnect"]) - ) - - return mapping - - -def get_zones(interconnect, model): - """Return zone constants. - - :para list interconnect: interconnect(s). - :param str model: the grid model. - :return: (*dict*) -- zones information. - """ - interconnect = ( # noqa - model2interconnect[model] if "USA" in interconnect else interconnect - ) - zone_info = get_zone_info(model=model).query("interconnect == @interconnect") - zone_info["abv"] = zone_info["state"].map({s: a for a, s in abv2state.items()}) - - zones = dict() - zones["mappings"] = {"loadzone", "state", "state_abbr", "interconnect"} - zones["division"] = "state" - - zones.update(get_loadzone_mapping(zone_info)) - zones.update(get_state_mapping(zone_info)) - zones.update(get_interconnect_mapping(zone_info, model)) - - return zones diff --git a/powersimdata/network/constants/region/zones.py b/powersimdata/network/constants/region/zones.py index 2cc968f09..36c60bf03 100644 --- a/powersimdata/network/constants/region/zones.py +++ b/powersimdata/network/constants/region/zones.py @@ -1,21 +1,40 @@ -from importlib import import_module +from powersimdata.network.constants.region.geography import get_geography +from powersimdata.network.helpers import get_zone_info -from powersimdata.network.constants.model import model2region -from powersimdata.network.helpers import check_model +def from_csv(model): + """Returns geographical and timezone information of a grid model from a CSV file. -def get_zones(interconnect, model): - """Return zone constants. + :param str model: grid model. + :return: (*pandas.DataFrame*) -- a data frame with loadzone name (*'zone_name'*), + division name (e.g. *'state'* name for USA grid models), interconnect name + (*'interconnect'*), time zone of loadzone (*'time_zone'*), division abbreviation + (*'abv'*) as columns and loadzone id (*'zone_id'*) as indices. + """ + geo = get_geography(model) + info = get_zone_info(model=model) + info["abv"] = info[geo["division"]].map(geo[f"{geo['division']}2abv"]) + + return info + + +def from_pypsa(model, info): + """Returns geographical and timezone information of a grid model from a PyPSA + Network object. - :para list interconnect: interconnect(s). - :param str model: the grid model. - :return: (*func*) -- function returning information on zones for a given model. + :param str model: grid model. + :param pd.DataFrame info: a data frame with loadzone id as index and loadzone name + (*'zone_name'*) and division abbreviation (*'abv'*) as columns. + :return: (*pandas.DataFrame*) -- a data frame with loadzone name (*'zone_name'*), + division name (e.g. *'country'* name for EU grid models), interconnect name + (*'interconnect'*), time zone of loadzone (*'time_zone'*), division abbreviation + (*'abv'*) as columns and loadzone id (*'zone_id'*) as indices. """ - check_model(model) + geo = get_geography(model) + info[geo["division"]] = info["abv"].map(geo[f"abv2{geo['division']}"]) + info["interconnect"] = info["abv"].map(geo["abv2interconnect"]) + info["time_zone"] = info["abv"].map(geo["abv2timezone"]) - mod = import_module( - f"powersimdata.network.constants.region.{model2region[model].lower()}" - ) - zones = getattr(mod, "get_zones") + info.rename_axis(index="zone_id") - return zones(interconnect, model) + return info diff --git a/powersimdata/network/europe_tub/model.py b/powersimdata/network/europe_tub/model.py index cc8cbe48d..449cb1a27 100644 --- a/powersimdata/network/europe_tub/model.py +++ b/powersimdata/network/europe_tub/model.py @@ -2,11 +2,8 @@ import shutil from zipfile import ZipFile -from powersimdata.network.constants.region.europe import ( - abv2country, - abv2timezone, - interconnect2abv, -) +from powersimdata.network.constants.region.geography import get_geography +from powersimdata.network.constants.region.zones import from_pypsa from powersimdata.network.helpers import ( check_and_format_interconnect, interconnect_to_name, @@ -73,8 +70,9 @@ def build(self): self.id2zone = id2zone self.zone2id = zone2id else: + geo = get_geography(self.model) filter = list( # noqa: F841 - interconnect2abv[ + geo["interconnect2abv"][ interconnect_to_name(self.interconnect, model=self.grid_model) ] ) @@ -82,58 +80,15 @@ def build(self): self.zone2id = {l: zone2id[l] for l in self.network.buses.index} self.id2zone = {i: l for l, i in self.zone2id.items()} - self.model_immutables = self._generate_model_immutables() - - def _generate_model_immutables(self): - """Generate the model immutables""" - mapping = ModelImmutables(self.grid_model, interconnect=self.interconnect) - - # loadzone - mapping.zones["loadzone"] = set(self.zone2id) - mapping.zones["id2loadzone"] = self.id2zone - mapping.zones["loadzone2id"] = self.zone2id - mapping.zones["loadzone2abv"] = self.network.buses["country"].to_dict() - mapping.zones["loadzone2country"] = ( - self.network.buses["country"].map(abv2country).to_dict() + zone = ( + self.network.buses["country"] + .reset_index() + .set_axis(self.id2zone) + .rename(columns={"Bus": "zone_name", "country": "abv"}) + .rename_axis(index="zone_id") + ) + self.model_immutables = ModelImmutables( + self.grid_model, + interconnect=self.interconnect, + zone=from_pypsa(self.grid_model, zone), ) - mapping.zones["loadzone2interconnect"] = { - l: mapping.zones["abv2interconnect"][a] - for l, a in mapping.zones["loadzone2abv"].items() - } - mapping.zones["id2timezone"] = { - self.zone2id[l]: abv2timezone[a] - for l, a in mapping.zones["loadzone2abv"].items() - } - mapping.zones["timezone2id"] = { - t: i for i, t in mapping.zones["id2timezone"].items() - } - - # country - mapping.zones["country2loadzone"] = { - abv2country[a]: set(l) - for a, l in self.network.buses.groupby("country").groups.items() - } - mapping.zones["abv2loadzone"] = { - a: set(l) for a, l in self.network.buses.groupby("country").groups.items() - } - mapping.zones["abv2id"] = { - a: {self.zone2id[l] for l in l_in_country} - for a, l_in_country in mapping.zones["abv2loadzone"].items() - } - mapping.zones["id2abv"] = { - i: mapping.zones["loadzone2abv"][l] for i, l in self.id2zone.items() - } - - # interconnect - mapping.zones["interconnect2loadzone"] = { - i: set().union( - *(mapping.zones["abv2loadzone"][a] for a in a_in_interconnect) - ) - for i, a_in_interconnect in mapping.zones["interconnect2abv"].items() - } - mapping.zones["interconnect2id"] = { - i: set().union(*({self.zone2id[l]} for l in l_in_interconnect)) - for i, l_in_interconnect in mapping.zones["interconnect2loadzone"].items() - } - - return mapping diff --git a/powersimdata/network/model.py b/powersimdata/network/model.py index 217fd533f..2bee7f393 100644 --- a/powersimdata/network/model.py +++ b/powersimdata/network/model.py @@ -1,7 +1,8 @@ from powersimdata.network.constants.carrier.plants import get_plants from powersimdata.network.constants.carrier.storage import get_storage from powersimdata.network.constants.model import model2region -from powersimdata.network.constants.region.zones import get_zones +from powersimdata.network.constants.region.mapping import get_mapping +from powersimdata.network.constants.region.zones import from_csv from powersimdata.network.helpers import ( check_and_format_interconnect, check_model, @@ -14,9 +15,15 @@ class ModelImmutables: :param str model: grid model name. :param str interconnect: interconnect of grid model. + :param pandas.DataFrame zone: a data frame with loadzone name (*'zone_name'*), + division name (e.g. *'state'* name for USA grid models), interconnect name + (*'interconnect'*), time zone of loadzone (*'time_zone'*) and division + abbreviation (*'abv'*) as columns; and loadzone id (*'zone_id'*) as indices. If + None, it will be assumed that the data frame can be built from a CSV file + stored on disk. """ - def __init__(self, model, interconnect=None): + def __init__(self, model, interconnect=None, zone=None): """Constructor.""" check_model(model) self.model = model @@ -25,10 +32,11 @@ def __init__(self, model, interconnect=None): if interconnect is None else check_and_format_interconnect(interconnect, model=model) ) + zone = from_csv(self.model) if zone is None else zone self.plants = get_plants(model) self.storage = get_storage(model) - self.zones = get_zones(interconnect, model) + self.zones = get_mapping(model, interconnect, zone) self.check_and_format_interconnect = check_and_format_interconnect self.interconnect_to_name = interconnect_to_name @@ -38,7 +46,7 @@ def area_to_loadzone(self, *args, **kwargs): return area_to_loadzone(self.model, *args, **kwargs) -def area_to_loadzone(model, area, area_type=None): +def area_to_loadzone(model, area, area_type=None, zone=None): """Map the query area to a list of loadzones. :param str model: grid model to use to look up constants for mapping. @@ -48,6 +56,12 @@ def area_to_loadzone(model, area, area_type=None): *'state_abbr'*/'*country_abbr*', *'interconnect'*. If None, ``area`` will be searched successively into *'state'*/*'country'*, *'loadzone'*, *'state abbreviation'*/*'country abbreviation'*, *'interconnect'* and *'all'*. + :param pandas.DataFrame zone: a data frame with loadzone name (*'zone_name'*), + division name (e.g. *'state'* name for USA grid models), interconnect name + (*'interconnect'*), time zone of loadzone (*'time_zone'*) and division + abbreviation (*'abv'*) as columns; and loadzone id (*'zone_id'*) as indices. If + None, it will be assumed that the data frame can be built from a CSV file + stored on disk. :return: (*set*) -- set of loadzone names located in the query area. :raises TypeError: if ``area`` is not a str. @@ -56,7 +70,7 @@ def area_to_loadzone(model, area, area_type=None): if ``area`` is invalid if combination of ``area`` and ``area_type`` is invalid. """ - zones = ModelImmutables(model).zones + zones = ModelImmutables(model, zone=zone).zones mappings = zones["mappings"] division = zones["division"] diff --git a/powersimdata/network/tests/test_model.py b/powersimdata/network/tests/test_model.py index 8d119f80d..25fffa929 100644 --- a/powersimdata/network/tests/test_model.py +++ b/powersimdata/network/tests/test_model.py @@ -5,10 +5,10 @@ def test_area_to_loadzone_argument_type(): with pytest.raises(TypeError, match="area must be a str"): - area_to_loadzone("europe_tub", 3) + area_to_loadzone("usa_tamu", 3) with pytest.raises(TypeError, match="area_type must be either None or str"): - area_to_loadzone("europe_tub", "all", area_type=["interconnect"]) + area_to_loadzone("usa_tamu", "all", area_type=["interconnect"]) def test_area_to_loadzone_argument_value(): @@ -22,7 +22,7 @@ def test_area_to_loadzone_argument_value(): area_to_loadzone("usa_tamu", "WA", area_type="interconnect") with pytest.raises(ValueError, match="Invalid area / area_type combination"): - area_to_loadzone("europe_tub", "France", area_type="country_abbr") + area_to_loadzone("usa_tamu", "Utah", area_type="state_abbr") def test_area_to_loadzone():