Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explicit demand response peak load #484

Open
wants to merge 63 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
32b689c
Add residential componnets and convert DST to classes
nick-harder Oct 17, 2024
01acad7
-add release notes
nick-harder Oct 17, 2024
9cd2e93
-create empty dict for dst componnets to avoid import error
nick-harder Oct 18, 2024
e61c116
-adjust structure
nick-harder Oct 18, 2024
0511490
-fix error in min_down_time and up_time constraints
nick-harder Oct 18, 2024
7e23e78
-missing docstring
nick-harder Oct 18, 2024
3df0bb3
switch back to glpk for now in the tests
nick-harder Oct 18, 2024
10fe4d6
Add check for natural gas price profile in DRIPlant class
nick-harder Oct 18, 2024
c165563
-fix tests
nick-harder Oct 18, 2024
d4e427a
Merge branch 'main' of https://github.com/assume-framework/assume int…
Manish-Khanra Oct 27, 2024
2a4b240
input files for experiment
Manish-Khanra Oct 28, 2024
8c1d606
gg
Manish-Khanra Oct 29, 2024
74e0401
grid congestion forcast included
Manish-Khanra Oct 30, 2024
a10a5bf
Merge branch 'main' of https://github.com/assume-framework/assume int…
Manish-Khanra Oct 30, 2024
d7b4802
line co2 factor fixed
Manish-Khanra Oct 30, 2024
7375c9a
Merge branch 'main' of https://github.com/assume-framework/assume int…
Manish-Khanra Oct 31, 2024
3ec01dc
Merge branch 'main' of https://github.com/assume-framework/assume int…
Manish-Khanra Oct 31, 2024
6f8dc86
test
Manish-Khanra Nov 6, 2024
1d03896
Merge branch 'main' of https://github.com/assume-framework/assume int…
Manish-Khanra Nov 6, 2024
fa7043c
test
Manish-Khanra Nov 7, 2024
8b0c84e
split load_shift into positive and negative
nick-harder Nov 8, 2024
59bac27
test
Manish-Khanra Nov 10, 2024
6c289d4
flex_power_requirement fixed
Manish-Khanra Nov 11, 2024
7e1c509
Grid congestion based flexibility finalised
Manish-Khanra Nov 11, 2024
11154d6
peak load shift
Manish-Khanra Nov 12, 2024
7a23fd8
Merge branch 'main' of https://github.com/assume-framework/assume int…
Manish-Khanra Nov 12, 2024
b183e83
...
Manish-Khanra Nov 13, 2024
da9ee4d
fix optimal values initialization
nick-harder Nov 13, 2024
ccb349b
def peak_load_shifting_flexibility implemented
Manish-Khanra Nov 14, 2024
05de6d7
Refactores poeal_load_cap
Manish-Khanra Nov 14, 2024
7edf4cd
renewable utilisation included
Manish-Khanra Nov 16, 2024
a4618ab
tests inlcuded
Manish-Khanra Nov 16, 2024
b6edc0c
Removed input
Manish-Khanra Nov 16, 2024
df54e4e
removed plot
Manish-Khanra Nov 16, 2024
8ad6a53
removed example
Manish-Khanra Nov 16, 2024
747280e
node added
Manish-Khanra Nov 16, 2024
3b8a2d3
solver fixed
Manish-Khanra Nov 16, 2024
a7d9d27
solver
Manish-Khanra Nov 17, 2024
54da44f
nodes added
Manish-Khanra Nov 17, 2024
53d0915
added lines and buses
Manish-Khanra Nov 17, 2024
5237327
lines and nodes added
Manish-Khanra Nov 17, 2024
30cf785
Changes to highs
Manish-Khanra Nov 17, 2024
96aace5
highs
Manish-Khanra Nov 17, 2024
580a2c8
solver fixed
Manish-Khanra Nov 17, 2024
19f0f51
removies plot function
Manish-Khanra Nov 17, 2024
222370b
fix solver options in steel plant when not using gurobi
maurerle Nov 18, 2024
9266b0f
test fixed
Manish-Khanra Nov 18, 2024
84217f2
iloc fixed
Manish-Khanra Nov 19, 2024
d4c3f4d
Big M for load shifting implemented
Manish-Khanra Nov 21, 2024
d423abb
Merge branch 'main' of https://github.com/assume-framework/assume int…
Manish-Khanra Nov 21, 2024
8a2631a
deleted wrong licence
Manish-Khanra Nov 21, 2024
75d1849
Merge branch 'main' into explicit_demand_response_peak_load
maurerle Nov 25, 2024
159b7f7
fix tests
maurerle Nov 25, 2024
16f3e29
Merge branch 'main' of https://github.com/assume-framework/assume int…
Manish-Khanra Nov 26, 2024
dead787
inputs added
Manish-Khanra Nov 26, 2024
d448285
commented out plot
Manish-Khanra Nov 27, 2024
c3f0e72
Merge branch 'main' of https://github.com/assume-framework/assume int…
Manish-Khanra Nov 27, 2024
5ebd0b4
-remove changes in example_01a
nick-harder Dec 3, 2024
71563ce
-remove unrequired changes
nick-harder Dec 3, 2024
dedbd4e
Merge branch 'main' into explicit_demand_response_peak_load
nick-harder Dec 3, 2024
56d8527
-fix hydrogen plant
nick-harder Dec 3, 2024
dc34ee9
-fix tests
nick-harder Dec 3, 2024
1d6627f
Merge branch 'main' into explicit_demand_response_peak_load
nick-harder Dec 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 161 additions & 3 deletions assume/common/forecasts.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,13 @@
def __init__(
self,
index: pd.Series,
powerplants_units: pd.DataFrame,
demand_units: pd.DataFrame,
market_configs: dict = {},
powerplants_units: dict[str, pd.Series] = {},
demand_units: dict[str, pd.Series] = {},
dsm_units: dict[str, pd.Series] = {},
buses: dict[str, pd.Series] = {},
lines: dict[str, pd.Series] = {},
demand_df: dict[str, pd.Series] = {},
market_configs: dict[str, pd.Series] = {},
save_path: str = "",
*args,
**kwargs,
Expand All @@ -121,6 +125,10 @@
self.logger = logging.getLogger(__name__)
self.powerplants_units = powerplants_units
self.demand_units = demand_units
self.dsm_units = dsm_units
self.buses = buses
self.lines = lines
self.demand_df = demand_df
self.market_configs = market_configs
self.forecasts = pd.DataFrame(index=index)
self.save_path = save_path
Expand Down Expand Up @@ -217,6 +225,31 @@
self.calculate_residual_load_forecast(market_id=market_id)
)

# Calculate node-specific congestion signal
node_congestion_signal_df = self.calculate_node_specific_congestion_forecast()

for col in node_congestion_signal_df.columns:
if col not in self.forecasts.columns:
self.forecasts[col] = node_congestion_signal_df[col]

utilisation_columns = [
f"{node}_renewable_utilisation"
for node in self.demand_units["node"].unique()
]
utilisation_columns.append("all_nodes_renewable_utilisation")

# If any of the utilisation columns are missing, calculate and add them
if not all(col in self.forecasts.columns for col in utilisation_columns):
# Calculate renewable utilisation forecast if any columns are missing
renewable_utilisation_forecast = (
self.calculate_renewable_utilisation_forecast()
)

# Add each column from the renewable utilisation forecast to self.forecasts
for col in renewable_utilisation_forecast.columns:
if col not in self.forecasts.columns:
self.forecasts[col] = renewable_utilisation_forecast[col]

def get_registered_market_participants(self, market_id):
"""
Retrieves information about market participants to make accurate price forecasts.
Expand Down Expand Up @@ -375,6 +408,131 @@

return marginal_cost

def calculate_node_specific_congestion_forecast(self) -> pd.DataFrame:
"""
Calculates a collective node-specific congestion signal by aggregating the congestion severity of all
transmission lines connected to each node, taking into account powerplant load based on availability factors.

Returns:
pd.DataFrame: A DataFrame with columns for each node, where each column represents the collective
congestion signal time series for that node.
"""
# Step 1: Calculate powerplant load using availability factors
availability_factor_df = pd.DataFrame(
index=self.index, columns=self.powerplants_units.index, data=0.0
)

# Calculate load for each powerplant based on availability factor and max power
for pp, max_power in self.powerplants_units["max_power"].items():
availability_factor_df[pp] = (
self.forecasts[f"availability_{pp}"] * max_power
)

# Step 2: Calculate net load for each node (demand - generation)
net_load_by_node = {}

for node in self.demand_units["node"].unique():
# Calculate total demand for this node
node_demand_units = self.demand_units[
self.demand_units["node"] == node
].index
node_demand = self.demand_df[node_demand_units].sum(axis=1)

# Calculate total generation for this node by summing powerplant loads
node_generation_units = self.powerplants_units[
self.powerplants_units["node"] == node
].index
node_generation = availability_factor_df[node_generation_units].sum(axis=1)

# Calculate net load (demand - generation)
net_load_by_node[node] = node_demand - node_generation

# Step 3: Calculate line-specific congestion severity
line_congestion_severity = pd.DataFrame(index=self.index)

for line_id, line_data in self.lines.iterrows():
node1, node2 = line_data["bus0"], line_data["bus1"]
line_capacity = line_data["s_nom"]

# Calculate net load for the line as the sum of net loads from both connected nodes
line_net_load = net_load_by_node[node1] + net_load_by_node[node2]
congestion_severity = line_net_load / line_capacity

# Store the line-specific congestion severity in DataFrame
line_congestion_severity[f"{line_id}_congestion_severity"] = (
congestion_severity
)

# Step 4: Calculate node-specific congestion signal by aggregating connected lines
node_congestion_signal = pd.DataFrame(index=self.index)

for node in self.demand_units["node"].unique():
# Find all lines connected to this node
connected_lines = self.lines[
(self.lines["bus0"] == node) | (self.lines["bus1"] == node)
].index

# Collect all relevant line congestion severities
relevant_lines = [
f"{line_id}_congestion_severity" for line_id in connected_lines
]

# Ensure only existing columns are used to avoid KeyError
relevant_lines = [
line
for line in relevant_lines
if line in line_congestion_severity.columns
]

# Aggregate congestion severities for this node (use max or mean)
if relevant_lines:
node_congestion_signal[f"{node}_congestion_severity"] = (
line_congestion_severity[relevant_lines].max(axis=1)
)

return node_congestion_signal

def calculate_renewable_utilisation_forecast(self) -> pd.DataFrame:
"""
Calculates the renewable utilisation forecast by summing the available renewable generation
for each node and an overall 'all_nodes' summary.

Returns:
pd.DataFrame: A DataFrame with columns for each node, where each column represents the renewable
utilisation signal time series for that node and a column for total utilisation across all nodes.
"""
# Initialize a DataFrame to store renewable utilisation for each node
renewable_utilisation = pd.DataFrame(index=self.index)

# Identify renewable power plants by filtering `powerplants_units` DataFrame
renewable_plants = self.powerplants_units[
self.powerplants_units["fuel_type"] == "renewable"
]

# Calculate utilisation based on availability and max power for each renewable plant
for node in self.demand_units["node"].unique():
node_renewable_sum = pd.Series(0, index=self.index)

# Filter renewable plants in this specific node
node_renewable_plants = renewable_plants[renewable_plants["node"] == node]

for pp in node_renewable_plants.index:
max_power = node_renewable_plants.loc[pp, "max_power"]
availability_col = f"availability_{pp}"

Check warning on line 521 in assume/common/forecasts.py

View check run for this annotation

Codecov / codecov/patch

assume/common/forecasts.py#L520-L521

Added lines #L520 - L521 were not covered by tests

# Calculate renewable power based on availability and max capacity
if availability_col in self.forecasts.columns:
node_renewable_sum += self.forecasts[availability_col] * max_power

Check warning on line 525 in assume/common/forecasts.py

View check run for this annotation

Codecov / codecov/patch

assume/common/forecasts.py#L524-L525

Added lines #L524 - L525 were not covered by tests

# Store the node-specific renewable utilisation
renewable_utilisation[f"{node}_renewable_utilisation"] = node_renewable_sum

# Calculate the total renewable utilisation across all nodes
all_nodes_sum = renewable_utilisation.sum(axis=1)
renewable_utilisation["all_nodes_renewable_utilisation"] = all_nodes_sum

return renewable_utilisation

def save_forecasts(self, path=None):
"""
Saves the forecasts to a csv file located at the specified path.
Expand Down
10 changes: 10 additions & 0 deletions assume/scenario/loader_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ def load_dsm_units(
"unit_type",
"node",
"flexibility_measure",
"congestion_threshold",
"peak_load_cap",
]
# Filter the common columns to only include those that exist in the DataFrame
common_columns = [col for col in common_columns if col in dsm_units.columns]
Expand Down Expand Up @@ -444,6 +446,8 @@ def load_config_and_create_forecaster(
)

powerplant_units = load_file(path=path, config=config, file_name="powerplant_units")
buses = load_file(path=path, config=config, file_name="buses")
lines = load_file(path=path, config=config, file_name="lines")
storage_units = load_file(path=path, config=config, file_name="storage_units")
demand_units = load_file(path=path, config=config, file_name="demand_units")

Expand Down Expand Up @@ -501,6 +505,10 @@ def load_config_and_create_forecaster(
index=index,
powerplants_units=powerplant_units,
demand_units=demand_units,
dsm_units=dsm_units,
buses=buses,
lines=lines,
demand_df=demand_df,
market_configs=config["markets_config"],
)

Expand All @@ -526,6 +534,8 @@ def load_config_and_create_forecaster(
"storage_units": storage_units,
"demand_units": demand_units,
"dsm_units": dsm_units,
"buses": buses,
"lines": lines,
"forecaster": forecaster,
}

Expand Down
7 changes: 5 additions & 2 deletions assume/strategies/naive_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#
# SPDX-License-Identifier: AGPL-3.0-or-later


from assume.common.base import BaseStrategy, SupportsMinMax
from assume.common.market_objects import MarketConfig, Order, Orderbook, Product

Expand Down Expand Up @@ -151,7 +152,7 @@
**kwargs,
) -> Orderbook:
# calculate the optimal operation of the unit
unit.calculate_optimal_operation_if_needed()
unit.determine_optimal_operation_without_flex()

Check warning on line 155 in assume/strategies/naive_strategies.py

View check run for this annotation

Codecov / codecov/patch

assume/strategies/naive_strategies.py#L155

Added line #L155 was not covered by tests

bids = []
for product in product_tuples:
Expand All @@ -173,6 +174,8 @@
}
)

# Plot the power requirements after calculating bids

return bids


Expand All @@ -185,7 +188,7 @@
**kwargs,
) -> Orderbook:
# calculate the optimal operation of the unit according to the objective function
unit.calculate_optimal_operation_if_needed()
unit.determine_optimal_operation_with_flex()

Check warning on line 191 in assume/strategies/naive_strategies.py

View check run for this annotation

Codecov / codecov/patch

assume/strategies/naive_strategies.py#L191

Added line #L191 was not covered by tests

bids = []
for product in product_tuples:
Expand Down
Loading
Loading