Source code for powersimdata.network.europe_tub.model
import os
import pypsa
from powersimdata.input.converter.helpers import (
add_interconnect_to_grid_data_frames,
add_zone_to_grid_data_frames,
)
from powersimdata.input.converter.pypsa_to_grid import FromPyPSA
from powersimdata.input.converter.pypsa_to_profiles import (
get_pypsa_demand_profile,
get_pypsa_gen_profile,
)
from powersimdata.input.profile_input import ProfileInput
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,
)
from powersimdata.network.model import ModelImmutables
from powersimdata.network.zenodo import Zenodo
[docs]class PyPSABase(FromPyPSA):
"""Arbitrary PyPSA network.
:param str/iterable interconnect: interconnect name(s).
:param str grid_model: the grid model
:param pypsa.Network network: a PyPSA network object
:param bool add_pypsa_cols: PyPSA data frames with renamed columns appended to
Grid object data frames.
"""
def __init__(self, interconnect, grid_model, network=None, add_pypsa_cols=True):
"""Constructor."""
super().__init__(network, add_pypsa_cols)
self.grid_model = grid_model
self.interconnect = check_and_format_interconnect(
interconnect, model=self.grid_model
)
[docs] def build_eur(self):
self.id2zone = {i: l for i, l in enumerate(self.network.loads.index)}
self.zone2id = {l: i for i, l in self.id2zone.items()}
if self.interconnect != ["Europe"]:
geo = get_geography(self.grid_model)
filter = list( # noqa: F841
geo["interconnect2abv"][
interconnect_to_name(self.interconnect, model=self.grid_model)
]
)
self.network = self.network[
self.network.buses.query("country == @filter").index
]
self.zone2id = {l: self.zone2id[l] for l in self.network.loads.index}
self.id2zone = {i: l for l, i in self.zone2id.items()}
zone = (
self.network.buses.loc[self.network.loads["bus"]]["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),
)
[docs] def build(self):
"""Build network"""
if self.grid_model == "europe_tub":
self.build_eur()
else:
self.model_immutables = ModelImmutables(
self.grid_model, interconnect=self.interconnect
)
super().build()
[docs]class TUB(PyPSABase):
"""PyPSA Europe network.
:param str/iterable interconnect: interconnect name(s).
:param str zenodo_record_id: the zenodo record id. If set to None, v0.6.1 will
be used. If set to latest, the latest version will be used.
:param int reduction: reduction parameter (number of nodes in network). If None,
the full network is loaded.
"""
def __init__(self, interconnect, zenodo_record_id=None, reduction=None):
super().__init__(interconnect, "europe_tub")
self.network = self.from_zenodo(zenodo_record_id, reduction)
[docs] def from_zenodo(self, zenodo_record_id, reduction):
"""Create network from zenodo data
:param str zenodo_record_id: the zenodo record id. If set to None, v0.6.1 will
be used. If set to latest, the latest version will be used.
:param int reduction: reduction parameter (number of nodes in network). If None,
the full network is loaded.
:return: (*pypsa.Network*) -- a PyPSA network object
"""
if zenodo_record_id is None:
z = Zenodo("7251657")
elif zenodo_record_id == "latest":
z = Zenodo("3601881")
else:
z = Zenodo(zenodo_record_id)
z.load_data(os.path.dirname(__file__))
self.data_loc = os.path.join(z.dir, "networks")
self.version = z.version
self.reduction = reduction
return self._get_network()
def _get_network(self):
"""Create a PyPSA network with the given reduction
:return: (*pypsa.Network*) -- a PyPSA network object
"""
path = os.path.join(self.data_loc, "elec_s")
self._check_reduction()
if self.reduction is None:
return pypsa.Network(path + ".nc")
return pypsa.Network(path + f"_{self.reduction}.nc")
def _check_reduction(self):
"""Validate reduction parameter
:raises ValueError: if ``reduction`` is not available.
"""
if self.reduction is None:
return
prefix = [f.split(".")[0] for f in os.listdir(self.data_loc)]
available = {s for f in prefix for s in f.split("_") if s.isdigit()}
if str(self.reduction) not in available:
raise ValueError(f"Available reduced network: {' | '.join(available)}")
@property
def _profile_version(self):
"""Get the profile version given the current record version from zenodo and the
reduction.
:return: (*str*) -- the version which is used to construct a file name
"""
append = f"_{self.reduction}" if self.reduction is not None else ""
return f"{self.version}" + append
def _profile_exists(self, kind):
"""Check if a profile has been uploaded for the given version
:param str kind: the kind of profile to check
:return: (*bool*) -- True if the profile is available
"""
_profile_input = ProfileInput()
available = _profile_input.get_profile_version(self.grid_model, kind)
return self._profile_version in available
def _add_information(self):
"""Add zone and interconnect columns to data frames"""
bus = self.bus
bus2sub = self.bus2sub
bus["zone_name"] = bus.index.map(bus2sub.sub_id)
bus.zone_id = bus.zone_name.map(self.zone2id)
add_zone_to_grid_data_frames(self)
zone2ic = self.model_immutables.zones["loadzone2interconnect"]
bus2sub.interconnect = bus2sub.sub_id.map(zone2ic)
self.sub.interconnect = self.sub.index.map(zone2ic)
add_interconnect_to_grid_data_frames(self)
def _extract_profiles(self):
"""Extract and upload profiles if necessary"""
profiles = {}
if not self._profile_exists("demand"):
demand = get_pypsa_demand_profile(self.network)
demand.columns = demand.columns.astype(str).map(self.zone2id)
profiles[f"demand_{self._profile_version}"] = demand
p2c = dict(self.model_immutables.plants["group_profile_resources"])
p2c["hydro"] = {"ror", "hydro"}
for k in p2c:
if not self._profile_exists(k):
profile = get_pypsa_gen_profile(self.network, {k: p2c[k]})
profiles[f"{k}_{self._profile_version}"] = profile[k]
if any(profiles):
print(f"Uploading profiles: {list(profiles.keys())}")
_profile_input = ProfileInput()
for k, v in profiles.items():
_profile_input.upload(self.grid_model, k, v)
def _update_cols(self):
"""Update columns with default values"""
pd_mask = self.bus.index.isin(self.zone2id)
self.bus["Pd"] = [int(x) for x in pd_mask]
self.plant.status = 1
[docs] def build(self):
"""Construct the network used to build a grid object and extract/upload the
profiles if necessary.
"""
super().build()
self._extract_profiles()
self._update_cols()
self._add_information()