Skip to content

Commit

Permalink
Merge branch 'dev' into features/#377-grid-separation
Browse files Browse the repository at this point in the history
  • Loading branch information
khelfen committed May 3, 2023
2 parents ea77207 + 97c9d3d commit 825109b
Show file tree
Hide file tree
Showing 6 changed files with 1,072 additions and 3 deletions.
28 changes: 28 additions & 0 deletions edisgo/flex_opt/reinforce_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,9 @@ def enhanced_reinforce_grid(
kwargs["copy_grid"] = False
kwargs.pop("skip_mv_reinforcement", False)

num_lv_grids_standard_lines = 0
num_lv_grids_aggregated = 0

try:
logger.info("Try initial enhanced reinforcement.")
edisgo_obj.reinforce(mode=None, catch_convergence_problems=True, **kwargs)
Expand Down Expand Up @@ -874,6 +877,7 @@ def enhanced_reinforce_grid(
logger.warning(
f"Change all lines to standard type in {lv_grid=}."
)
num_lv_grids_standard_lines += 1
lv_standard_line_type = edisgo_obj.config[
"grid_expansion_standard_equipment"
]["lv_line"]
Expand All @@ -894,6 +898,7 @@ def enhanced_reinforce_grid(
logger.warning(
f"Aggregate all nodes to station bus in {lv_grid=}."
)
num_lv_grids_aggregated += 1
try:
edisgo_obj.topology.aggregate_lv_grid_at_station(
lv_grid_id=lv_grid.id
Expand All @@ -911,4 +916,27 @@ def enhanced_reinforce_grid(
logger.info("Enhanced reinforcement failed.")
raise e

if activate_cost_results_disturbing_mode is True:
if num_lv_grids_standard_lines > 0:
msg = (
f"In {num_lv_grids_standard_lines} LV grid(s) all lines were "
f"exchanged by standard lines."
)
logger.warning(msg)
edisgo_obj.results.measures = msg
else:
msg = (
"Enhanced reinforcement: No exchange of lines with standard lines or "
"aggregation at MV/LV station needed."
)
logger.info(msg)
edisgo_obj.results.measures = msg
if num_lv_grids_aggregated > 0:
msg = (
f"Enhanced reinforcement: In {num_lv_grids_aggregated} LV grid(s) all "
f"components were aggregated at the MV/LV station."
)
logger.warning(msg)
edisgo_obj.results.measures = msg

return edisgo_obj
186 changes: 184 additions & 2 deletions edisgo/network/overlying_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import logging
import os

from copy import deepcopy
from zipfile import ZipFile

import pandas as pd

# from edisgo import EDisGo
from edisgo.tools.tools import resample

logger = logging.getLogger(__name__)
Expand All @@ -24,14 +26,21 @@ class OverlyingGrid:
Curtailment of fluctuating renewables per time step in MW.
storage_units_active_power : :pandas:`pandas.Series<Series>`
Aggregated dispatch of storage units per time step in MW.
storage_units_soc : :pandas:`pandas.Series<Series>`
State of charge of storage units per time step in p.u..
dsm_active_power : :pandas:`pandas.Series<Series>`
Aggregated demand side management utilisation per time step in MW.
electromobility_active_power : :pandas:`pandas.Series<Series>`
Aggregated charging demand at flexible charging sites per time step in MW.
Aggregated charging demand at all charging sites in grid per time step in MW.
heat_pump_decentral_active_power : :pandas:`pandas.Series<Series>`
Aggregated demand of flexible decentral heat pumps per time step in MW.
thermal_storage_units_decentral_soc : :pandas:`pandas.Series<Series>`
State of charge of decentral thermal storage units in p.u..
heat_pump_central_active_power : :pandas:`pandas.Series<Series>`
Aggregated demand of flexible central heat pumps per time step in MW.
thermal_storage_units_central_soc : :pandas:`pandas.DataFrame<DataFrame>`
State of charge of central thermal storage units per district heating area (in
columns) and time step (in index) in p.u..
feedin_district_heating : :pandas:`pandas.DataFrame<DataFrame>`
Other thermal feed-in into district heating per district heating area (in
columns) and time step (in index) in MW.
Expand All @@ -42,10 +51,12 @@ def __init__(self, **kwargs):
self.renewables_curtailment = kwargs.get(
"renewables_curtailment", pd.Series(dtype="float64")
)

self.storage_units_active_power = kwargs.get(
"storage_units_active_power", pd.Series(dtype="float64")
)
self.storage_units_soc = kwargs.get(
"storage_units_soc", pd.Series(dtype="float64")
)
self.dsm_active_power = kwargs.get(
"dsm_active_power", pd.Series(dtype="float64")
)
Expand All @@ -55,9 +66,15 @@ def __init__(self, **kwargs):
self.heat_pump_decentral_active_power = kwargs.get(
"heat_pump_decentral_active_power", pd.Series(dtype="float64")
)
self.thermal_storage_units_decentral_soc = kwargs.get(
"thermal_storage_units_decentral_soc", pd.Series(dtype="float64")
)
self.heat_pump_central_active_power = kwargs.get(
"heat_pump_central_active_power", pd.Series(dtype="float64")
)
self.thermal_storage_units_central_soc = kwargs.get(
"thermal_storage_units_central_soc", pd.DataFrame(dtype="float64")
)
self.feedin_district_heating = kwargs.get(
"feedin_district_heating", pd.DataFrame(dtype="float64")
)
Expand All @@ -67,10 +84,13 @@ def _attributes(self):
return [
"renewables_curtailment",
"storage_units_active_power",
"storage_units_soc",
"dsm_active_power",
"electromobility_active_power",
"heat_pump_decentral_active_power",
"thermal_storage_units_decentral_soc",
"heat_pump_central_active_power",
"thermal_storage_units_central_soc",
"feedin_district_heating",
]

Expand Down Expand Up @@ -238,3 +258,165 @@ def resample(self, method: str = "ffill", freq: str | pd.Timedelta = "15min"):

freq_orig = timeindex[1] - timeindex[0]
resample(self, freq_orig, method, freq)


def distribute_overlying_grid_requirements(edisgo_obj):
"""
Distributes overlying grid requirements to components in grid.
Overlying grid requirements for e.g. electromobility charging are distributed to
all charging points where cars are parked, and for DSM to all DSM loads based
on their available load increase and decrease at each time step.
Parameters
-----------
edisgo_obj : :class:`~.EDisGo`
The eDisGo API object
Returns
--------
:class:`~.EDisGo`
New EDisGo object with only the topology data and adjusted time series data.
"""

edisgo_copy = edisgo_obj.__class__()
edisgo_copy.topology = deepcopy(edisgo_obj.topology)
edisgo_copy.timeseries = deepcopy(edisgo_obj.timeseries)

# electromobility - distribute charging time series from overlying grid to all
# charging points based on upper power flexibility band
if not edisgo_obj.overlying_grid.electromobility_active_power.empty:
cp_loads = edisgo_obj.topology.loads_df.index[
edisgo_obj.topology.loads_df.type == "charging_point"
]
# scale flexibility band upper power timeseries
scaling_df = edisgo_obj.electromobility.flexibility_bands[
"upper_power"
].transpose() / edisgo_obj.electromobility.flexibility_bands["upper_power"].sum(
axis=1
)
edisgo_copy.timeseries._loads_active_power.loc[:, cp_loads] = (
scaling_df * edisgo_obj.overlying_grid.electromobility_active_power
).transpose()

# storage units - distribute charging/discharging time series from overlying grid
# to all storage units based on their installed capacity
if not edisgo_obj.overlying_grid.storage_units_active_power.empty:
scaling_factor = (
edisgo_obj.topology.storage_units_df.p_nom
/ edisgo_obj.topology.storage_units_df.p_nom.sum()
)
scaling_df = pd.DataFrame(
index=scaling_factor.index,
columns=edisgo_copy.timeseries.timeindex,
data=pd.concat(
[scaling_factor] * len(edisgo_copy.timeseries.timeindex), axis=1
).values,
)
edisgo_copy.timeseries._storage_units_active_power = (
scaling_df * edisgo_obj.overlying_grid.storage_units_active_power
).transpose()

# central PtH - distribute dispatch time series from overlying grid
# to all central PtH units based on their installed capacity
if not edisgo_obj.overlying_grid.heat_pump_central_active_power.empty:
hp_district = edisgo_obj.topology.loads_df[
(edisgo_obj.topology.loads_df.type == "heat_pump")
& (
edisgo_obj.topology.loads_df.sector.isin(
["district_heating", "district_heating_resistive_heater"]
)
)
]
scaling_factor = hp_district.p_set / hp_district.p_set.sum()
scaling_df = pd.DataFrame(
index=scaling_factor.index,
columns=edisgo_copy.timeseries.timeindex,
data=pd.concat(
[scaling_factor] * len(edisgo_copy.timeseries.timeindex), axis=1
).values,
)
edisgo_copy.timeseries._loads_active_power.loc[:, hp_district.index] = (
scaling_df
* edisgo_obj.overlying_grid.heat_pump_central_active_power.sum(axis=1)[0]
).transpose()

# decentral PtH - distribute dispatch time series from overlying grid
# to all decentral PtH units based on their installed capacity
if not edisgo_obj.overlying_grid.heat_pump_decentral_active_power.empty:
hp_individual = edisgo_obj.topology.loads_df.index[
(edisgo_obj.topology.loads_df.type == "heat_pump")
& (
edisgo_obj.topology.loads_df.sector.isin(
["individual_heating", "individual_heating_resistive_heater"]
)
)
]
# scale with heat pump upper power
scaling_factor = (
edisgo_obj.topology.loads_df.p_set.loc[hp_individual]
/ edisgo_obj.topology.loads_df.p_set.loc[hp_individual].sum()
)
scaling_df = pd.DataFrame(
index=scaling_factor.index,
columns=edisgo_copy.timeseries.timeindex,
data=pd.concat(
[scaling_factor] * len(edisgo_copy.timeseries.timeindex), axis=1
).values,
)
edisgo_copy.timeseries._loads_active_power.loc[:, hp_individual] = (
scaling_df * edisgo_obj.overlying_grid.heat_pump_decentral_active_power
).transpose()

# DSM - distribute dispatch time series from overlying grid to all DSM loads based
# on their maximum load increase (in case of positive dispatch values) or
# their maximum load decrease (in case of negative dispatch values)
if not edisgo_obj.overlying_grid.dsm_active_power.empty:
dsm_loads = edisgo_obj.dsm.p_max.columns
if len(dsm_loads) > 0:
scaling_df_max = (
edisgo_obj.dsm.p_max.transpose() / edisgo_obj.dsm.p_max.sum(axis=1)
)
scaling_df_min = (
edisgo_obj.dsm.p_min.transpose() / edisgo_obj.dsm.p_min.sum(axis=1)
)
edisgo_copy.timeseries._loads_active_power.loc[:, dsm_loads] = (
edisgo_obj.timeseries._loads_active_power.loc[:, dsm_loads]
+ (
scaling_df_min
* edisgo_obj.overlying_grid.dsm_active_power.clip(upper=0)
).transpose()
+ (
scaling_df_max
* edisgo_obj.overlying_grid.dsm_active_power.clip(lower=0)
).transpose()
)
else:
logger.warning(
"EDisGo object has no attribute 'dsm'. DSM timeseries from "
"overlying grid cannot be distributed."
)

# curtailment
if not edisgo_obj.overlying_grid.renewables_curtailment.empty:
gens = edisgo_obj.topology.generators_df[
edisgo_obj.topology.generators_df.type.isin(["solar", "wind"])
].index
gen_per_ts = edisgo_obj.timeseries.generators_active_power.loc[:, gens].sum(
axis=1
)
scaling_factor = (
edisgo_obj.timeseries.generators_active_power.loc[:, gens].transpose()
/ gen_per_ts
).fillna(0)
curtailment = (
scaling_factor * edisgo_obj.overlying_grid.renewables_curtailment
).transpose()
edisgo_copy.timeseries._generators_active_power.loc[:, gens] = (
edisgo_obj.timeseries.generators_active_power.loc[:, gens] - curtailment
)

# reset reactive power time series
edisgo_copy.set_time_series_reactive_power_control()
return edisgo_copy
Loading

0 comments on commit 825109b

Please sign in to comment.