Skip to content

Commit

Permalink
Merge pull request #707 from Breakthrough-Energy/jen/tub
Browse files Browse the repository at this point in the history
Enable creating a grid object for europe model
  • Loading branch information
jenhagg authored Dec 7, 2022
2 parents da12a76 + a33e3b1 commit c3a806c
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 113 deletions.
70 changes: 34 additions & 36 deletions powersimdata/input/converter/pypsa_to_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from powersimdata.input.abstract_grid import AbstractGrid
from powersimdata.input.const import grid_const
from powersimdata.input.const.pypsa_const import pypsa_const
from powersimdata.input.grid import Grid
from powersimdata.network.constants.carrier.storage import storage as storage_const


Expand Down Expand Up @@ -132,31 +131,43 @@ def _get_storage_gen(df, storage_type):
class FromPyPSA(AbstractGrid):
"""Grid builder for PyPSA network object.
:param pypsa.Network network: Network to read in.
:param pypsa.Network network: PyPSA network to read in.
:param bool add_pypsa_cols: PyPSA data frames with renamed columns appended to
Grid object data frames.
"""

def __init__(self, network, add_pypsa_cols=True):
"""Constructor"""
super().__init__()
self._read_network(network, add_pypsa_cols=add_pypsa_cols)
self.network = network
self.add_pypsa_cols = add_pypsa_cols

def _read_network(self, n, add_pypsa_cols=True):
"""PyPSA Network reader.
def _set_interconnect(self):
if self.interconnect is None:
self.interconnect = self.network.name.split(", ")

:param pypsa.Network n: PyPSA network to read in.
:param bool add_pypsa_cols: PyPSA data frames with renamed columns appended to
Grid object data frames
"""
def _set_data_loc(self):
if len(self.interconnect) > 1:
self.data_loc = self.interconnect[0]
else:
self.data_loc = "pypsa"

# Interconnect and data location
def _set_zone_mapping(self):
n = self.network
if any(self.id2zone) and any(self.zone2id):
return
# only relevant if the PyPSA network was originally created from PSD
interconnect = n.name.split(", ")
if len(interconnect) > 1:
data_loc = interconnect.pop(0)
else:
data_loc = "pypsa"
if "zone_id" in n.buses and "zone_name" in n.buses:
uniques = ~n.buses.zone_id.duplicated() * n.buses.zone_id.notnull()
self.zone2id = (
n.buses[uniques].set_index("zone_name").zone_id.astype(int).to_dict()
)
self.id2zone = _invert_dict(self.zone2id)

def build(self):
"""PyPSA Network reader."""
n = self.network
add_pypsa_cols = self.add_pypsa_cols

# Read in data from PyPSA
bus_pypsa = n.buses
Expand All @@ -181,18 +192,6 @@ def _read_network(self, n, add_pypsa_cols=True):
["(?i)PQ", "(?i)PV", "(?i)Slack", ""], [1, 2, 3, 4], regex=True
).astype(int)

# zones mapping
# only relevant if the PyPSA network was originally created from PSD
if "zone_id" in n.buses and "zone_name" in n.buses:
uniques = ~n.buses.zone_id.duplicated() * n.buses.zone_id.notnull()
zone2id = (
n.buses[uniques].set_index("zone_name").zone_id.astype(int).to_dict()
)
id2zone = _invert_dict(zone2id)
else:
zone2id = {}
id2zone = {}

# substations
# only relevant if the PyPSA network was originally created from PSD
sub_cols = ["name", "interconnect_sub_id", "lat", "lon", "interconnect"]
Expand Down Expand Up @@ -456,15 +455,18 @@ def add_suffix(s):
df["pypsa_component"] = "stores"

# Build PSD grid object
self.data_loc = data_loc
self.interconnect = interconnect

# Interconnect and data location
# only relevant if the PyPSA network was originally created from PSD
self._set_interconnect()
self._set_data_loc()
self._set_zone_mapping()

self.bus = data["bus"]
self.sub = data["sub"]
self.bus2sub = data["bus2sub"]
self.branch = data["branch"].sort_index()
self.dcline = data["dcline"]
self.zone2id = zone2id
self.id2zone = id2zone
self.plant = data["plant"]
self.gencost["before"] = data["gencost"]
self.gencost["after"] = data["gencost"]
Expand Down Expand Up @@ -498,6 +500,7 @@ def add_suffix(s):
self.storage["gen"].index.name = "storage_id"
self.storage["gencost"].index.name = "storage_id"
self.storage["StorageData"].index.name = "storage_id"
return self

def _translate_pnl(self, pnl, key):
"""Translate time-dependent data frames with one time step from PyPSA to static
Expand All @@ -513,8 +516,3 @@ def _translate_pnl(self, pnl, key):
{v: pnl[k].iloc[0] for k, v in translators.items() if k in pnl}, axis=1
)
return df

@property
def __class__(self):
"""If anyone asks, I'm a Grid object!"""
return Grid
21 changes: 10 additions & 11 deletions powersimdata/input/converter/tests/test_pypsa_to_grid.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from importlib.util import find_spec

import pytest
import pypsa
from pandas.testing import assert_series_equal

from powersimdata.input.change_table import ChangeTable
Expand All @@ -10,23 +8,19 @@
from powersimdata.input.transform_grid import TransformGrid


@pytest.mark.skipif(find_spec("pypsa") is None, reason="Package PyPSA not available.")
def test_import_arbitrary_network_from_pypsa_to_grid():
import pypsa

n = pypsa.examples.ac_dc_meshed()
grid = FromPyPSA(n)
grid = FromPyPSA(n).build()

assert not grid.bus.empty
assert len(n.buses) == len(grid.bus)


@pytest.mark.skipif(find_spec("pypsa") is None, reason="Package PyPSA not available.")
def test_import_network_including_storages_from_pypsa_to_grid():
import pypsa

n = pypsa.examples.storage_hvdc()
grid = FromPyPSA(n)
grid = FromPyPSA(n).build()

inflow = n.get_switchable_as_dense("StorageUnit", "inflow")
has_inflow = inflow.any()
Expand All @@ -45,7 +39,6 @@ def test_import_network_including_storages_from_pypsa_to_grid():
assert not grid.storage["StorageData"].empty


@pytest.mark.skipif(find_spec("pypsa") is None, reason="Package PyPSA not available.")
def test_import_exported_network():

grid = Grid("Western")
Expand All @@ -60,7 +53,13 @@ def test_import_exported_network():

kwargs = dict(add_substations=True, add_load_shedding=False, add_all_columns=True)
n = export_to_pypsa(ref, **kwargs)
test = FromPyPSA(n, add_pypsa_cols=False)
test = Grid(
"Western",
source="pypsa",
grid_model="usa_tamu",
network=n,
add_pypsa_cols=False,
)

# Only a scaled version of linear cost term is exported to pypsa
# Test whether the exported marginal cost is in the same order of magnitude
Expand Down
34 changes: 26 additions & 8 deletions powersimdata/input/grid.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from powersimdata.network.constants.carrier.storage import storage
from powersimdata.network.europe_tub.model import TUB, PyPSABase
from powersimdata.network.hifld.model import HIFLD
from powersimdata.network.usa_tamu.model import TAMU
from powersimdata.utility.helpers import MemoryCache, cache_key
Expand All @@ -8,7 +9,8 @@

class Grid:

SUPPORTED_MODELS = {"usa_tamu"}
SUPPORTED_IMPORTS = {"pypsa"}
SUPPORTED_MODELS = {"usa_tamu", "europe_tub"}

"""Grid
Expand All @@ -18,18 +20,17 @@ class Grid:
is defined in :mod:`powersimdata.network.constants.model.model2interconnect`.
:param str source: model used to build the network. Can be one of the supported
models
:raises TypeError: if source and engine are not both strings.
:raises ValueError: if source or engine does not exist.
:raises TypeError: if source is not a string.
:raises ValueError: if source is not a supported grid model.
"""

def __init__(self, interconnect, source="usa_tamu"):
def __init__(self, interconnect, source="usa_tamu", **kwargs):
"""Constructor."""
if not isinstance(source, str):
raise TypeError("source must be a str")
if source not in self.SUPPORTED_MODELS:
raise ValueError(
f"Source must be one of {','.join(self.SUPPORTED_MODELS)} "
)
supported = self.SUPPORTED_MODELS | self.SUPPORTED_IMPORTS
if source not in supported:
raise ValueError(f"Source must be one of {','.join(supported)} ")

key = cache_key(interconnect, source)
cached = _cache.get(key)
Expand All @@ -39,6 +40,10 @@ def __init__(self, interconnect, source="usa_tamu"):
network = TAMU(interconnect)
elif source == "hifld":
network = HIFLD(interconnect)
elif source == "europe_tub":
network = TUB(interconnect, **kwargs)
elif source == "pypsa":
network = PyPSABase(interconnect, **kwargs)
else:
raise ValueError(f"Unknown source: {source}")

Expand All @@ -61,6 +66,19 @@ def __init__(self, interconnect, source="usa_tamu"):

_cache.put(key, network)

def __repr__(self):
result = self.__class__.__name__
result += f"\ninterconnect: {self.interconnect}\n"
result += f"model: {self.model_immutables.model}\n"
result += f"data_loc: {self.data_loc}\n"
df_name = ["sub", "plant", "dcline", "bus2sub", "bus", "branch"]
for n in df_name:
shape = getattr(self, n).shape
result += f"{n}: {shape}\n"
result += f"id2zone: {len(self.id2zone)}\n"
result += f"zone2id: {len(self.zone2id)}"
return result

def __eq__(self, other):
"""Used when 'self == other' is evaluated.
Expand Down
Loading

0 comments on commit c3a806c

Please sign in to comment.