From 943e145be651c271309d70934a4a11bdfb9dce00 Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Wed, 10 Dec 2025 17:59:40 +0100
Subject: [PATCH 01/24] renamed max_soc to capacity in StorageSupportsMinMax
class and Storage class
---
assume/common/base.py | 7 +++---
assume/units/storage.py | 48 +++++++++++++++++++++++------------------
2 files changed, 31 insertions(+), 24 deletions(-)
diff --git a/assume/common/base.py b/assume/common/base.py
index 73553835d..02c8b3ac5 100644
--- a/assume/common/base.py
+++ b/assume/common/base.py
@@ -473,6 +473,7 @@ class SupportsMinMaxCharge(BaseUnit):
"""
initial_soc: float
+ # float between 0 and 1 - initial state of charge
min_power_charge: float
# negative float - if this storage is charging, what is the minimum charging power (the negative non-zero power closest to zero) (resulting in negative current power)
max_power_charge: float
@@ -489,7 +490,7 @@ class SupportsMinMaxCharge(BaseUnit):
# negative
ramp_down_charge: float | None
# ramp_down_charge is negative
- max_soc: float
+ capacity: float
efficiency_charge: float
efficiency_discharge: float
@@ -509,7 +510,7 @@ def calculate_min_max_charge(
Args:
start (datetime.datetime): The start time of the dispatch.
end (datetime.datetime): The end time of the dispatch.
- soc (float, optional): The current state-of-charge. Defaults to None.
+ soc (float, optional): The current state-of-charge (between 0 and 1). Defaults to None.
Returns:
tuple[np.ndarray, np.ndarray]: The min and max charging power for the given time period.
@@ -524,7 +525,7 @@ def calculate_min_max_discharge(
Args:
start (datetime.datetime): The start time of the dispatch.
end (datetime.datetime): The end time of the dispatch.
- soc (float, optional): The current state-of-charge. Defaults to None.
+ soc (float, optional): The current state-of-charge (between 0 and 1). Defaults to None.
Returns:
tuple[np.ndarray, np.ndarray]: The min and max discharging power for the given time period.
diff --git a/assume/units/storage.py b/assume/units/storage.py
index 9e81804ef..4b7eac654 100644
--- a/assume/units/storage.py
+++ b/assume/units/storage.py
@@ -28,9 +28,10 @@ class Storage(SupportsMinMaxCharge):
min_power_charge (float): The minimum power input of the storage unit in MW (negative value).
max_power_discharge (float): The maximum power output of the storage unit in MW.
min_power_discharge (float): The minimum power output of the storage unit in MW.
- max_soc (float): The maximum state of charge of the storage unit in MWh (equivalent to capacity).
- min_soc (float): The minimum state of charge of the storage unit in MWh.
- initial_soc (float): The initial state of charge of the storage unit in MWh.
+ capacity (float): The capacity of the storage unit in MWh.
+ max_soc (float): The maximum state of charge of the storage unit as a fraction (typically 1).
+ min_soc (float): The minimum state of charge of the storage unit as a fraction (typically 0).
+ initial_soc (float): The initial state of charge of the storage unit as a fraction (between 0 and 1).
efficiency_charge (float): The efficiency of the storage unit while charging.
efficiency_discharge (float): The efficiency of the storage unit while discharging.
additional_cost_charge (float, optional): Additional costs associated with power consumption, in EUR/MWh. Defaults to 0.
@@ -60,10 +61,11 @@ def __init__(
forecaster: UnitForecaster,
max_power_charge: float,
max_power_discharge: float,
- max_soc: float,
+ capacity: float,
min_power_charge: float = 0.0,
min_power_discharge: float = 0.0,
min_soc: float = 0.0,
+ max_soc: float = 1.0,
initial_soc: float | None = 0.0,
soc_tick: float = 0.01,
efficiency_charge: float = 1,
@@ -95,18 +97,21 @@ def __init__(
location=location,
**kwargs,
)
- if max_soc < 0:
- raise ValueError(f"{max_soc=} must be >= 0 for unit {self.id}")
- if min_soc < 0:
- raise ValueError(f"{min_soc=} must be >= 0 for unit {self.id}")
+ if capacity < 0:
+ raise ValueError(f"{capacity=} must be >= 0 for unit {self.id}")
+ if not 0 <= min_soc <= 1:
+ raise ValueError(f"{min_soc=} must be between 0 and 1 for unit {self.id}")
+ if not 0 <= max_soc <= 1:
+ raise ValueError(f"{max_soc=} must be between 0 and 1 for unit {self.id}")
if max_soc < min_soc:
raise ValueError(f"{max_soc=} must be >= {min_soc=} for unit {self.id}")
+ self.capacity = capacity
self.max_soc = max_soc
self.min_soc = min_soc
if initial_soc is None:
- initial_soc = max_soc / 2
- if initial_soc < 0:
- raise ValueError(f"{initial_soc=} must be >= 0 for unit {self.id}")
+ initial_soc = 0.5
+ if not 0 <= initial_soc <= 1:
+ raise ValueError(f"{initial_soc=} must be between 0 and 1 for unit {self.id}")
self.initial_soc = initial_soc
if max_power_charge > 0:
@@ -283,15 +288,15 @@ def calculate_soc_max_discharge(self, soc) -> float:
Calculates the maximum discharge power depending on the current state of charge.
Args:
- soc (float): The current state of charge.
+ soc (float): The current state of charge (between 0 and 1).
Returns:
- float: The maximum discharge power.
+ float: The maximum discharge power in MW.
"""
duration = self.index.freq / timedelta(hours=1)
power = max(
0,
- ((soc - self.min_soc) * self.efficiency_discharge / duration),
+ ((soc - self.min_soc) * self.capacity * self.efficiency_discharge / duration),
)
return power
@@ -303,15 +308,15 @@ def calculate_soc_max_charge(
Calculates the maximum charge power depending on the current state of charge.
Args:
- soc (float): The current state of charge.
+ soc (float): The current state of charge (between 0 and 1).
Returns:
- float: The maximum charge power.
+ float: The maximum charge power in MW.
"""
duration = self.index.freq / timedelta(hours=1)
power = min(
0,
- ((soc - self.max_soc) / self.efficiency_charge / duration),
+ ((soc - self.max_soc) * self.capacity / self.efficiency_charge / duration),
)
return power
@@ -326,7 +331,7 @@ def calculate_min_max_charge(
Args:
start (datetime.datetime): The start of the current dispatch.
end (datetime.datetime): The end of the current dispatch.
- soc (float): The current state-of-charge. Defaults to None, then using soc at given start time.
+ soc (float): The current state-of-charge (between 0 and 1). Defaults to None, then using soc at given start time.
Returns:
tuple[np.ndarray, np.ndarray]: The minimum and maximum charge power levels of the storage unit in MW.
@@ -368,7 +373,7 @@ def calculate_min_max_discharge(
Args:
start (datetime.datetime): The start of the current dispatch.
end (datetime.datetime): The end of the current dispatch.
- soc (float): The current state-of-charge. Defaults to None, then using soc at given start time.
+ soc (float): The current state-of-charge (between 0 and 1). Defaults to None, then using soc at given start time.
Returns:
tuple[np.ndarray, np.ndarray]: The minimum and maximum discharge power levels of the storage unit in MW.
@@ -415,7 +420,7 @@ def calculate_ramp_discharge(
Adjusts the discharging power to the ramping constraints.
Args:
- soc (float): The current state of charge.
+ soc (float): The current state of charge (between 0 and 1).
previous_power (float): The previous power output of the unit.
power_discharge (float): The discharging power output of the unit.
current_power (float, optional): The current power output of the unit. Defaults to 0.
@@ -450,7 +455,7 @@ def calculate_ramp_charge(
Adjusts the charging power to the ramping constraints.
Args:
- soc (float): The current state of charge.
+ soc (float): The current state of charge (between 0 and 1).
previous_power (float): The previous power output of the unit.
power_charge (float): The charging power output of the unit.
current_power (float, optional): The current power output of the unit. Defaults to 0.
@@ -504,6 +509,7 @@ def as_dict(self) -> dict:
unit_dict = super().as_dict()
unit_dict.update(
{
+ "capacity": self.capacity,
"max_soc": self.max_soc,
"min_soc": self.min_soc,
"max_power_charge": self.max_power_charge,
From ccf53aecaac651c8cfa3970763f1abfe07a24412 Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Wed, 10 Dec 2025 18:00:36 +0100
Subject: [PATCH 02/24] adjusted input files to contain capacity, min_soc and
max_soc
---
examples/inputs/example_01c/storage_units.csv | 4 +-
examples/inputs/example_01g/storage_units.csv | 6 +--
examples/inputs/example_02e/storage_units.csv | 6 +--
examples/inputs/example_03/storage_units.csv | 52 +++++++++----------
examples/inputs/example_03c/storage_units.csv | 52 +++++++++----------
5 files changed, 60 insertions(+), 60 deletions(-)
diff --git a/examples/inputs/example_01c/storage_units.csv b/examples/inputs/example_01c/storage_units.csv
index 66e51f8be..ba0522c85 100644
--- a/examples/inputs/example_01c/storage_units.csv
+++ b/examples/inputs/example_01c/storage_units.csv
@@ -1,2 +1,2 @@
-name,technology,bidding_EOM,bidding_CRM_pos,bidding_CRM_neg,max_power_charge,max_power_discharge,efficiency_charge,efficiency_discharge,min_soc,max_soc,additional_cost_charge,additional_cost_discharge,natural_inflow,unit_operator
-Storage 1,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,1000.0,992.0,0.86,0.9,0.0,6076.0,0.28,0.28,0.0,Operator 1
+name,technology,bidding_EOM,bidding_CRM_pos,bidding_CRM_neg,max_power_charge,max_power_discharge,efficiency_charge,efficiency_discharge,min_soc,max_soc,capacity,additional_cost_charge,additional_cost_discharge,natural_inflow,unit_operator
+Storage 1,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,1000.0,992.0,0.86,0.9,0.0,1.0,6076.0,0.28,0.28,0.0,Operator 1
diff --git a/examples/inputs/example_01g/storage_units.csv b/examples/inputs/example_01g/storage_units.csv
index 9d57e587d..735a9adbb 100644
--- a/examples/inputs/example_01g/storage_units.csv
+++ b/examples/inputs/example_01g/storage_units.csv
@@ -1,3 +1,3 @@
-name,technology,bidding_energy,bidding_capacity_pos,bidding_capacity_neg,max_power_charge,max_power_discharge,efficiency_charge,efficiency_discharge,min_soc,max_soc,variable_cost_charge,variable_cost_discharge,natural_inflow,unit_operator
-storage_unit,PSPP,storage_energy_heuristic_flexable,-,-,490,452.9,0.87,0.92,1,3528.8,0.28,0.28,0,storage_operator
-battery_unit,li-ion_battery,storage_energy_heuristic_flexable,-,-,100,90,0.95,0.95,1,190,30,30,0,battery_operator
+name,technology,bidding_energy,bidding_capacity_pos,bidding_capacity_neg,max_power_charge,max_power_discharge,efficiency_charge,efficiency_discharge,min_soc,max_soc,capacity,variable_cost_charge,variable_cost_discharge,natural_inflow,unit_operator
+storage_unit,PSPP,storage_energy_heuristic_flexable,-,-,490,452.9,0.87,0.92,0.0,1.0,3528.8,0.28,0.28,0,storage_operator
+battery_unit,li-ion_battery,storage_energy_heuristic_flexable,-,-,100,90,0.95,0.95,0.0,1.0,190,30,30,0,battery_operator
\ No newline at end of file
diff --git a/examples/inputs/example_02e/storage_units.csv b/examples/inputs/example_02e/storage_units.csv
index b03ec11b9..6e7764298 100644
--- a/examples/inputs/example_02e/storage_units.csv
+++ b/examples/inputs/example_02e/storage_units.csv
@@ -1,3 +1,3 @@
-name,technology,bidding_EOM,max_power_charge,max_power_discharge,efficiency_charge,efficiency_discharge,min_soc,max_soc,initial_soc,additional_cost_charge,additional_cost_discharge,natural_inflow,unit_operator
-Storage 1,PSPP,storage_energy_learning,150,150,0.85,0.95,0.0,5000,0.0,0.28,0.28,0.0,Operator 1
-Storage 2,PSPP,storage_energy_learning,150,150,0.89,0.91,0.0,5000,0.0,0.28,0.28,0.0,Operator 1
+name,technology,bidding_EOM,max_power_charge,max_power_discharge,efficiency_charge,efficiency_discharge,min_soc,max_soc,capacity,initial_soc,additional_cost_charge,additional_cost_discharge,natural_inflow,unit_operator
+Storage 1,PSPP,storage_energy_learning,150,150,0.85,0.95,0.0,1.0,5000,0.0,0.28,0.28,0.0,Operator 1
+Storage 2,PSPP,storage_energy_learning,150,150,0.89,0.91,0.0,1.0,5000,0.0,0.28,0.28,0.0,Operator 1
diff --git a/examples/inputs/example_03/storage_units.csv b/examples/inputs/example_03/storage_units.csv
index 50df22c5a..b857cafa9 100644
--- a/examples/inputs/example_03/storage_units.csv
+++ b/examples/inputs/example_03/storage_units.csv
@@ -1,26 +1,26 @@
-name,technology,bidding_EOM,bidding_CRM_pos,bidding_CRM_neg,max_power_charge,max_power_discharge,efficiency_charge,efficiency_discharge,min_soc,max_soc,additional_cost_charge,additional_cost_discharge,natural_inflow,unit_operator
-Tanzmühle - Rabenleite,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,25,35,0.81,0.88,1,404,0.28,0.28,0,GDF SUEZ ENERGIE DEUTSCHLAND
-Schwarzenbachwerk,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,20,46,0.73,0.82,1,198,0.28,0.28,1.2,ENBW ENERGIE BADEN-WURTTEMBERG
-Leitzach II,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,38,49,0.86,0.9,1,550,0.28,0.28,0.7,STADTWERKE MUENCHEN GMBH
-Leitzach I,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,40,51,0.86,0.9,1,550,0.28,0.28,1.8,STADTWERKE MUENCHEN GMBH
-Hohenwarte I,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,34,63,0.8,0.87,1,504,0.28,0.28,1.9,VATTENFALL EUROPE AG
-Bleiloch,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,32,80,0.8,0.87,1,640,0.28,0.28,1.3,VATTENFALL EUROPE AG
-Wendefurth,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,72,80,0.82,0.88,1,523,0.28,0.28,0,VATTENFALL EUROPE AG
-Glems,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,68,90,0.84,0.89,1,560,0.28,0.28,0,ENBW ENERGIE BADEN-WURTTEMBERG
-Reisach - Rabenleite,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,84,100,0.85,0.9,1,630,0.28,0.28,0,GDF SUEZ ENERGIE DEUTSCHLAND
-Geesthacht,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,96,105,0.81,0.87,1,600,0.28,0.28,0,VATTENFALL EUROPE AG
-Rönkhausen,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,140,140,0.85,0.9,1,690,0.28,0.28,0,ENERVIE - SUEDWESTFALEN ENERGIE
-Waldeck I,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,96,140,0.87,0.92,1,478,0.28,0.28,0,UNIPER
-Häusern,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,104,144,0.82,0.88,1,514,0.28,0.28,0,ENBW ENERGIE BADEN-WURTTEMBERG
-Koepchenwerk Herdecke II,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,153,153,0.85,0.9,1,590,0.28,0.28,0,RWE POWER AG
-Happurg,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,126,160,0.83,0.89,1,900,0.28,0.28,0,UNIPER
-Langenprozelten,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,154,168,0.85,0.9,1,950,0.28,0.28,0,UNIPER
-Waldshut,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,80,176,0.79,0.86,1,581,0.28,0.28,0,ENBW ENERGIE BADEN-WURTTEMBERG
-Erzhausen,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,230,220,0.84,0.9,1,940,0.28,0.28,0,STATKRAFT AS
-Witznau,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,128,220,0.77,0.84,1,880,0.28,0.28,0,ENBW ENERGIE BADEN-WURTTEMBERG
-Hohenwarte II,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,310,320,0.8,0.87,1,2087,0.28,0.28,0,VATTENFALL EUROPE AG
-Säckingen,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,301,353,0.86,0.91,1,2064,0.28,0.28,1.8,ENBW ENERGIE BADEN-WURTTEMBERG
-Waldeck II,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,476,440,0.87,0.92,1,3428,0.28,0.28,0,UNIPER
-Wehr,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,1000,992,0.86,0.9,1,6076,0.28,0.28,0,RWE POWER AG
-Markersbach,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,1140,1050,0.84,0.89,1,4018,0.28,0.28,0,VATTENFALL EUROPE AG
-Goldisthal,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,1140,1060,0.88,0.92,1,8480,0.28,0.28,0,VATTENFALL EUROPE AG
+name,technology,bidding_EOM,bidding_CRM_pos,bidding_CRM_neg,max_power_charge,max_power_discharge,efficiency_charge,efficiency_discharge,min_soc,max_soc,capacity,additional_cost_charge,additional_cost_discharge,natural_inflow,unit_operator
+Tanzmühle - Rabenleite,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,25,35,0.81,0.88,0,1,404,0.28,0.28,0,GDF SUEZ ENERGIE DEUTSCHLAND
+Schwarzenbachwerk,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,20,46,0.73,0.82,0,1,198,0.28,0.28,1.2,ENBW ENERGIE BADEN-WURTTEMBERG
+Leitzach II,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,38,49,0.86,0.9,0,1,550,0.28,0.28,0.7,STADTWERKE MUENCHEN GMBH
+Leitzach I,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,40,51,0.86,0.9,0,1,550,0.28,0.28,1.8,STADTWERKE MUENCHEN GMBH
+Hohenwarte I,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,34,63,0.8,0.87,0,1,504,0.28,0.28,1.9,VATTENFALL EUROPE AG
+Bleiloch,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,32,80,0.8,0.87,0,1,640,0.28,0.28,1.3,VATTENFALL EUROPE AG
+Wendefurth,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,72,80,0.82,0.88,0,1,523,0.28,0.28,0,VATTENFALL EUROPE AG
+Glems,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,68,90,0.84,0.89,0,1,560,0.28,0.28,0,ENBW ENERGIE BADEN-WURTTEMBERG
+Reisach - Rabenleite,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,84,100,0.85,0.9,0,1,630,0.28,0.28,0,GDF SUEZ ENERGIE DEUTSCHLAND
+Geesthacht,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,96,105,0.81,0.87,0,1,600,0.28,0.28,0,VATTENFALL EUROPE AG
+Rönkhausen,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,140,140,0.85,0.9,0,1,690,0.28,0.28,0,ENERVIE - SUEDWESTFALEN ENERGIE
+Waldeck I,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,96,140,0.87,0.92,0,1,478,0.28,0.28,0,UNIPER
+Häusern,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,104,144,0.82,0.88,0,1,514,0.28,0.28,0,ENBW ENERGIE BADEN-WURTTEMBERG
+Koepchenwerk Herdecke II,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,153,153,0.85,0.9,0,1,590,0.28,0.28,0,RWE POWER AG
+Happurg,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,126,160,0.83,0.89,0,1,900,0.28,0.28,0,UNIPER
+Langenprozelten,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,154,168,0.85,0.9,0,1,950,0.28,0.28,0,UNIPER
+Waldshut,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,80,176,0.79,0.86,0,1,581,0.28,0.28,0,ENBW ENERGIE BADEN-WURTTEMBERG
+Erzhausen,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,230,220,0.84,0.9,0,1,940,0.28,0.28,0,STATKRAFT AS
+Witznau,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,128,220,0.77,0.84,0,1,880,0.28,0.28,0,ENBW ENERGIE BADEN-WURTTEMBERG
+Hohenwarte II,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,310,320,0.8,0.87,0,1,2087,0.28,0.28,0,VATTENFALL EUROPE AG
+Säckingen,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,301,353,0.86,0.91,0,1,2064,0.28,0.28,1.8,ENBW ENERGIE BADEN-WURTTEMBERG
+Waldeck II,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,476,440,0.87,0.92,0,1,3428,0.28,0.28,0,UNIPER
+Wehr,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,1000,992,0.86,0.9,0,1,6076,0.28,0.28,0,RWE POWER AG
+Markersbach,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,1140,1050,0.84,0.89,0,1,4018,0.28,0.28,0,VATTENFALL EUROPE AG
+Goldisthal,PSPP,storage_energy_heuristic_flexable,storage_capacity_heuristic_balancing_pos,storage_capacity_heuristic_balancing_neg,1140,1060,0.88,0.92,0,1,8480,0.28,0.28,0,VATTENFALL EUROPE AG
diff --git a/examples/inputs/example_03c/storage_units.csv b/examples/inputs/example_03c/storage_units.csv
index 86b6375a3..7afb4dee6 100644
--- a/examples/inputs/example_03c/storage_units.csv
+++ b/examples/inputs/example_03c/storage_units.csv
@@ -1,26 +1,26 @@
-name,technology,bidding_EOM,max_power_charge,max_power_discharge,efficiency_charge,efficiency_discharge,min_soc,max_soc,additional_cost_charge,additional_cost_discharge,unit_operator
-Tanzmühle - Rabenleite,PSPP,storage_energy_learning,25,35,0.81,0.88,1,404,0.28,0.28,GDF SUEZ ENERGIE DEUTSCHLAND
-Schwarzenbachwerk,PSPP,storage_energy_learning,20,46,0.73,0.82,1,198,0.28,0.28,ENBW ENERGIE BADEN-WURTTEMBERG
-Leitzach II,PSPP,storage_energy_learning,38,49,0.86,0.9,1,550,0.28,0.28,STADTWERKE MUENCHEN GMBH
-Leitzach I,PSPP,storage_energy_learning,40,51,0.86,0.9,1,550,0.28,0.28,STADTWERKE MUENCHEN GMBH
-Hohenwarte I,PSPP,storage_energy_learning,34,63,0.8,0.87,1,504,0.28,0.28,VATTENFALL EUROPE AG
-Bleiloch,PSPP,storage_energy_learning,32,80,0.8,0.87,1,640,0.28,0.28,VATTENFALL EUROPE AG
-Wendefurth,PSPP,storage_energy_learning,72,80,0.82,0.88,1,523,0.28,0.28,VATTENFALL EUROPE AG
-Glems,PSPP,storage_energy_learning,68,90,0.84,0.89,1,560,0.28,0.28,ENBW ENERGIE BADEN-WURTTEMBERG
-Reisach - Rabenleite,PSPP,storage_energy_learning,84,100,0.85,0.9,1,630,0.28,0.28,GDF SUEZ ENERGIE DEUTSCHLAND
-Geesthacht,PSPP,storage_energy_learning,96,105,0.81,0.87,1,600,0.28,0.28,VATTENFALL EUROPE AG
-Rönkhausen,PSPP,storage_energy_learning,140,140,0.85,0.9,1,690,0.28,0.28,ENERVIE - SUEDWESTFALEN ENERGIE
-Waldeck I,PSPP,storage_energy_learning,96,140,0.87,0.92,1,478,0.28,0.28,UNIPER
-Häusern,PSPP,storage_energy_learning,104,144,0.82,0.88,1,514,0.28,0.28,ENBW ENERGIE BADEN-WURTTEMBERG
-Koepchenwerk Herdecke II,PSPP,storage_energy_learning,153,153,0.85,0.9,1,590,0.28,0.28,RWE POWER AG
-Happurg,PSPP,storage_energy_learning,126,160,0.83,0.89,1,900,0.28,0.28,UNIPER
-Langenprozelten,PSPP,storage_energy_learning,154,168,0.85,0.9,1,950,0.28,0.28,UNIPER
-Waldshut,PSPP,storage_energy_learning,80,176,0.79,0.86,1,581,0.28,0.28,ENBW ENERGIE BADEN-WURTTEMBERG
-Erzhausen,PSPP,storage_energy_learning,230,220,0.84,0.9,1,940,0.28,0.28,STATKRAFT AS
-Witznau,PSPP,storage_energy_learning,128,220,0.77,0.84,1,880,0.28,0.28,ENBW ENERGIE BADEN-WURTTEMBERG
-Hohenwarte II,PSPP,storage_energy_learning,310,320,0.8,0.87,1,2087,0.28,0.28,VATTENFALL EUROPE AG
-Säckingen,PSPP,storage_energy_learning,301,353,0.86,0.91,1,2064,0.28,0.28,ENBW ENERGIE BADEN-WURTTEMBERG
-Waldeck II,PSPP,storage_energy_learning,476,440,0.87,0.92,1,3428,0.28,0.28,UNIPER
-Wehr,PSPP,storage_energy_learning,1000,992,0.86,0.9,1,6076,0.28,0.28,RWE POWER AG
-Markersbach,PSPP,storage_energy_learning,1140,1050,0.84,0.89,1,4018,0.28,0.28,VATTENFALL EUROPE AG
-Goldisthal,PSPP,storage_energy_learning,1140,1060,0.88,0.92,1,8480,0.28,0.28,VATTENFALL EUROPE AG
+name,technology,bidding_EOM,max_power_charge,max_power_discharge,efficiency_charge,efficiency_discharge,min_soc,max_soc,capacity,additional_cost_charge,additional_cost_discharge,unit_operator
+Tanzmühle - Rabenleite,PSPP,storage_energy_learning,25,35,0.81,0.88,0,1,404,0.28,0.28,GDF SUEZ ENERGIE DEUTSCHLAND
+Schwarzenbachwerk,PSPP,storage_energy_learning,20,46,0.73,0.82,0,1,198,0.28,0.28,ENBW ENERGIE BADEN-WURTTEMBERG
+Leitzach II,PSPP,storage_energy_learning,38,49,0.86,0.9,0,1,550,0.28,0.28,STADTWERKE MUENCHEN GMBH
+Leitzach I,PSPP,storage_energy_learning,40,51,0.86,0.9,0,1,550,0.28,0.28,STADTWERKE MUENCHEN GMBH
+Hohenwarte I,PSPP,storage_energy_learning,34,63,0.8,0.87,0,1,504,0.28,0.28,VATTENFALL EUROPE AG
+Bleiloch,PSPP,storage_energy_learning,32,80,0.8,0.87,0,1,640,0.28,0.28,VATTENFALL EUROPE AG
+Wendefurth,PSPP,storage_energy_learning,72,80,0.82,0.88,0,1,523,0.28,0.28,VATTENFALL EUROPE AG
+Glems,PSPP,storage_energy_learning,68,90,0.84,0.89,0,1,560,0.28,0.28,ENBW ENERGIE BADEN-WURTTEMBERG
+Reisach - Rabenleite,PSPP,storage_energy_learning,84,100,0.85,0.9,0,1,630,0.28,0.28,GDF SUEZ ENERGIE DEUTSCHLAND
+Geesthacht,PSPP,storage_energy_learning,96,105,0.81,0.87,0,1,600,0.28,0.28,VATTENFALL EUROPE AG
+Rönkhausen,PSPP,storage_energy_learning,140,140,0.85,0.9,0,1,690,0.28,0.28,ENERVIE - SUEDWESTFALEN ENERGIE
+Waldeck I,PSPP,storage_energy_learning,96,140,0.87,0.92,0,1,478,0.28,0.28,UNIPER
+Häusern,PSPP,storage_energy_learning,104,144,0.82,0.88,0,1,514,0.28,0.28,ENBW ENERGIE BADEN-WURTTEMBERG
+Koepchenwerk Herdecke II,PSPP,storage_energy_learning,153,153,0.85,0.9,0,1,590,0.28,0.28,RWE POWER AG
+Happurg,PSPP,storage_energy_learning,126,160,0.83,0.89,0,1,900,0.28,0.28,UNIPER
+Langenprozelten,PSPP,storage_energy_learning,154,168,0.85,0.9,0,1,950,0.28,0.28,UNIPER
+Waldshut,PSPP,storage_energy_learning,80,176,0.79,0.86,0,1,581,0.28,0.28,ENBW ENERGIE BADEN-WURTTEMBERG
+Erzhausen,PSPP,storage_energy_learning,230,220,0.84,0.9,0,1,940,0.28,0.28,STATKRAFT AS
+Witznau,PSPP,storage_energy_learning,128,220,0.77,0.84,0,1,880,0.28,0.28,ENBW ENERGIE BADEN-WURTTEMBERG
+Hohenwarte II,PSPP,storage_energy_learning,310,320,0.8,0.87,0,1,2087,0.28,0.28,VATTENFALL EUROPE AG
+Säckingen,PSPP,storage_energy_learning,301,353,0.86,0.91,0,1,2064,0.28,0.28,ENBW ENERGIE BADEN-WURTTEMBERG
+Waldeck II,PSPP,storage_energy_learning,476,440,0.87,0.92,0,1,3428,0.28,0.28,UNIPER
+Wehr,PSPP,storage_energy_learning,1000,992,0.86,0.9,0,1,6076,0.28,0.28,RWE POWER AG
+Markersbach,PSPP,storage_energy_learning,1140,1050,0.84,0.89,0,1,4018,0.28,0.28,VATTENFALL EUROPE AG
+Goldisthal,PSPP,storage_energy_learning,1140,1060,0.88,0.92,0,1,8480,0.28,0.28,VATTENFALL EUROPE AG
\ No newline at end of file
From 1432278384b0e6b32c73e0ad53fe454fddf623e3 Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Wed, 10 Dec 2025 18:03:46 +0100
Subject: [PATCH 03/24] adjusted outputs docu and release notes
---
docs/source/outputs.rst | 2 +-
docs/source/release_notes.rst | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/source/outputs.rst b/docs/source/outputs.rst
index 72a389c1a..9fa24eed4 100644
--- a/docs/source/outputs.rst
+++ b/docs/source/outputs.rst
@@ -86,7 +86,7 @@ When the outputs are stored in CSV files, the data is organized in a similar str
* - storage_meta
- Storage unit metadata.
- - simulation, unit_id, unit_type ("storage"), max_soc, min_soc, max_power_charge, max_power_discharge, min_power_charge, min_power_discharge, efficiency_charge, efficiency_discharge
+ - simulation, unit_id, unit_type ("storage"), capacity, max_soc, min_soc, max_power_charge, max_power_discharge, min_power_charge, min_power_discharge, efficiency_charge, efficiency_discharge
* - rl_params
- Reinforcement learning parameters.
diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst
index 48e8958eb..6e9a82179 100644
--- a/docs/source/release_notes.rst
+++ b/docs/source/release_notes.rst
@@ -23,6 +23,7 @@ Upcoming Release
**Improvements:**
- **Application of new naming convention for bidding strategies**: [unit]_[market]_[method]_[comment] for bidding strategy keys (in snake_case) and [Unit][Market][Method][Comment]Strategy for bidding strategy classes (in PascalCase for classes)
+- **Changed SoC Definition**: The state of charge (SoC) for storage units is now defined to take values between 0 and 1, instead of absolute energy content (MWh). This change ensures consistency with other models and standard definition. The absolute energy content can still be calculated by multiplying SoC with the unit's capacity. The previous 'max_soc' is renamed to 'capacity'.
- **Restructured learning_role tasks**: Major learning changes that make learning application more generalizable across the framework.
- **Simplified learning data flow:** Removed the special ``learning_unit_operator`` that previously aggregated unit data and forwarded it to the learning role. Eliminates the single-sender dependency and avoids double bookkeeping across units and operators.
From 5329fc3ca55c0a59d85d139187d4f7a6a453603b Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Wed, 10 Dec 2025 18:04:21 +0100
Subject: [PATCH 04/24] adjusted docker config
---
docker_configs/dashboard-definitions/ASSUME.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docker_configs/dashboard-definitions/ASSUME.json b/docker_configs/dashboard-definitions/ASSUME.json
index f99cb2fb4..7d9278a69 100644
--- a/docker_configs/dashboard-definitions/ASSUME.json
+++ b/docker_configs/dashboard-definitions/ASSUME.json
@@ -4342,7 +4342,7 @@
"id": 47,
"options": {
"afterRender": "",
- "content": "### General Information\n\nName: {{index}}
\nTechnology: {{technology}}
\nUnit Operator: {{unit_operator}}\n\n### Technical Specifications\nMaximum Capacity: {{max_soc}} MWh
\nMinimum Capacity: {{min_soc}} MWh
\n\nMaximum Power Discharge: {{max_power_discharge}} MW
\nMinimum Power Discharge: {{min_power_discharge}} MW
\nEfficiency Discharge: {{efficiency_discharge}}
\n\nMaximum Power Charge: {{max_power_charge}} MW
\nMinimum Power Charge: {{min_power_charge}} MW
\nEfficiency Charge: {{efficiency_charge}} %
",
+ "content": "### General Information\n\nName: {{index}}
\nTechnology: {{technology}}
\nUnit Operator: {{unit_operator}}\n\n### Technical Specifications\nCapacity: {{capacity}} MWh
\nMaximum State of Charge: {{max_soc}}
\nMinimum State of Charge: {{min_soc}}
\n\nMaximum Power Discharge: {{max_power_discharge}} MW
\nMinimum Power Discharge: {{min_power_discharge}} MW
\nEfficiency Discharge: {{efficiency_discharge}}
\n\nMaximum Power Charge: {{max_power_charge}} MW
\nMinimum Power Charge: {{min_power_charge}} MW
\nEfficiency Charge: {{efficiency_charge}} %
",
"contentPartials": [],
"defaultContent": "The query didn't return any results.",
"editor": {
From 8e291382709606bee986ac7aaca4e756f144f506 Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Wed, 10 Dec 2025 18:05:37 +0100
Subject: [PATCH 05/24] corrected loader_pypsa to have correct capacity,
technology and emission factor
---
assume/scenario/loader_pypsa.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/assume/scenario/loader_pypsa.py b/assume/scenario/loader_pypsa.py
index 31e2790a9..106228e8b 100644
--- a/assume/scenario/loader_pypsa.py
+++ b/assume/scenario/loader_pypsa.py
@@ -159,10 +159,10 @@ def load_pypsa(
"efficiency_charge": storage.efficiency_store,
"efficiency_discharge": storage.efficiency_dispatch,
"initial_soc": storage.state_of_charge_initial,
- "max_soc": storage.p_nom,
+ "capacity": storage.p_nom * storage.max_hours,
"bidding_strategies": bidding_strategies[unit_type][storage.name],
- "technology": "hydro",
- "emission_factor": 0,
+ "technology": storage.carrier,
+ "emission_factor": storage.emission_factor or 0,
"node": storage.bus,
},
UnitForecaster(index),
From 34ee278912c9689c043dcbbc4144a6e01d34979d Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Wed, 10 Dec 2025 18:06:23 +0100
Subject: [PATCH 06/24] adjust initial_soc calculation in loader_amiris
---
assume/scenario/loader_amiris.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/assume/scenario/loader_amiris.py b/assume/scenario/loader_amiris.py
index 90d184cf4..dd7dad3b9 100644
--- a/assume/scenario/loader_amiris.py
+++ b/assume/scenario/loader_amiris.py
@@ -285,8 +285,8 @@ def add_agent_to_world(
market_prices={"eom": forecast_price},
)
- max_soc = device["EnergyToPowerRatio"] * device["InstalledPowerInMW"]
- initial_soc = device["InitialEnergyLevelInMWH"]
+ capacity = device["EnergyToPowerRatio"] * device["InstalledPowerInMW"]
+ initial_soc = device["InitialEnergyLevelInMWH"] / capacity
# TODO device["SelfDischargeRatePerHour"]
world.add_unit(
f"StorageTrader_{agent['Id']}",
@@ -298,7 +298,7 @@ def add_agent_to_world(
"efficiency_charge": device["ChargingEfficiency"],
"efficiency_discharge": device["DischargingEfficiency"],
"initial_soc": initial_soc,
- "max_soc": max_soc,
+ "capacity": capacity,
"bidding_strategies": storage_strategies,
"technology": "hydro", # PSPP? Pump-Storage Power Plant
"emission_factor": 0,
From aa63ca146c5f4e4cb82c319e1172384a2a80ec0a Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Wed, 10 Dec 2025 18:07:19 +0100
Subject: [PATCH 07/24] adjusted loader_oeds and infrastructure, kept v0 as
initial storage volume
---
assume/scenario/loader_oeds.py | 5 +++--
assume/scenario/oeds/infrastructure.py | 8 +++++---
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/assume/scenario/loader_oeds.py b/assume/scenario/loader_oeds.py
index eaaa04330..d565ab8ac 100644
--- a/assume/scenario/loader_oeds.py
+++ b/assume/scenario/loader_oeds.py
@@ -302,8 +302,9 @@ def load_oeds(
{
"max_power_charge": storage["max_power_charge"] / 1e3,
"max_power_discharge": storage["max_power_discharge"] / 1e3,
- "max_soc": storage["max_soc"] / 1e3,
- "min_soc": storage["min_soc"] / 1e3,
+ "capacity": storage["capacity"] / 1e3,
+ "max_soc": storage["max_soc"],
+ "min_soc": storage["min_soc"],
"efficiency_charge": storage["efficiency_charge"],
"efficiency_discharge": storage["efficiency_discharge"],
"bidding_strategies": bidding_strategies["storage"],
diff --git a/assume/scenario/oeds/infrastructure.py b/assume/scenario/oeds/infrastructure.py
index 371f77758..c827051db 100644
--- a/assume/scenario/oeds/infrastructure.py
+++ b/assume/scenario/oeds/infrastructure.py
@@ -676,16 +676,18 @@ def get_water_storage_systems(
"startDate": pd.to_datetime(data["startDate"].to_numpy()[0]),
"max_power_discharge": data["PMinus_max"].sum(),
"max_power_charge": -data["PPlus_max"].sum(),
- "max_soc": data["VMax"].to_numpy()[0],
+ "capacity": data["VMax"].to_numpy()[0],
+ "max_soc": 1,
"min_soc": 0,
- "V0": data["VMax"].to_numpy()[0] / 2,
+ "initial_soc": 0.5,
+ "V0": 0.5 * data["VMax"].to_numpy()[0],
"lat": data["lat"].to_numpy()[0],
"lon": data["lon"].to_numpy()[0],
"efficiency_charge": 0.88,
"efficiency_discharge": 0.92,
}
# https://energie.ch/pumpspeicherkraftwerk/
- if storage["max_soc"] > 0:
+ if storage["capacity"] > 0:
storages.append(storage)
return storages
From d89aba9e8b323a77966fe9678671c2eb02e722e2 Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Wed, 10 Dec 2025 18:08:20 +0100
Subject: [PATCH 08/24] adjustment in dmas_storage (also kept v0)
---
assume/strategies/dmas_storage.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/assume/strategies/dmas_storage.py b/assume/strategies/dmas_storage.py
index e10ebc447..7d8a97e8b 100644
--- a/assume/strategies/dmas_storage.py
+++ b/assume/strategies/dmas_storage.py
@@ -139,7 +139,7 @@ def build_model(
time_range, within=pyo.Reals, bounds=(0, unit.max_power_discharge)
)
self.model.volume = pyo.Var(
- time_range, within=pyo.NonNegativeReals, bounds=(0, unit.max_soc)
+ time_range, within=pyo.NonNegativeReals, bounds=(0, unit.capacity)
)
self.power = np.array(
@@ -151,7 +151,7 @@ def build_model(
)
self.model.vol_con = pyo.ConstraintList()
- v0 = unit.outputs["soc"].at[start]
+ v0 = unit.outputs["soc"].at[start] * unit.capacity
for t in time_range:
if t == 0:
@@ -162,7 +162,7 @@ def build_model(
)
# always end with half full SoC
- self.model.vol_con.add(self.model.volume[hour_count - 1] == unit.max_soc / 2)
+ self.model.vol_con.add(self.model.volume[hour_count - 1] == unit.capacity / 2)
return self.power
def optimize(
From 339b0531774628470476ff75f539de5e40195f4a Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Wed, 10 Dec 2025 18:16:49 +0100
Subject: [PATCH 09/24] adjusted tests for flexible units having min_capacity
and max_capacity
---
tests/test_building.py | 14 ++++++-----
tests/test_ev.py | 8 +++----
tests/test_generic_storage.py | 32 ++++++++++++-------------
tests/test_hydrogen_plant.py | 10 ++++----
tests/test_seasonal_hydrogen_storage.py | 18 ++++++--------
tests/test_steam_plant.py | 4 ++--
tests/test_thermal_storage.py | 18 ++++++--------
7 files changed, 49 insertions(+), 55 deletions(-)
diff --git a/tests/test_building.py b/tests/test_building.py
index 7746cbc17..3cd66c26e 100644
--- a/tests/test_building.py
+++ b/tests/test_building.py
@@ -17,13 +17,14 @@
@pytest.fixture
def generic_storage_config():
return {
- "max_capacity": 100, # Maximum energy capacity in MWh
- "min_capacity": 0, # Minimum SOC in MWh
+ "capacity": 100, # Maximum energy capacity in MWh
+ "min_soc": 0, # Minimum SOC
+ "max_soc":1, # Maximum SOC
"max_power_charge": 100, # Maximum charging power in MW
"max_power_discharge": 100, # Maximum discharging power in MW
"efficiency_charge": 0.9, # Charging efficiency
"efficiency_discharge": 0.9, # Discharging efficiency
- "initial_soc": 0, # Initial SOC in MWh
+ "initial_soc": 0, # Initial SOC
"ramp_up": 10, # Maximum ramp-up rate in MW
"ramp_down": 10, # Maximum ramp-down rate in MW
"storage_loss_rate": 0.01, # 1% storage loss per time step
@@ -38,13 +39,14 @@ def thermal_storage_config(generic_storage_config):
@pytest.fixture
def ev_config():
return {
- "max_capacity": 10.0,
- "min_capacity": 0,
+ "capacity": 10.0, # EV battery capacity in MWh
+ "min_soc": 0,
+ "max_soc": 1,
"max_power_charge": 3, # Charge values will reflect a fraction of the capacity
"max_power_discharge": 2, # Discharge values will also be a fraction of the capacity
"efficiency_charge": 0.95,
"efficiency_discharge": 0.9,
- "initial_soc": 0, # SOC initialized to 50% of capacity
+ "initial_soc": 0, # initial SOC
}
diff --git a/tests/test_ev.py b/tests/test_ev.py
index 52f64ebfe..3ad65385f 100644
--- a/tests/test_ev.py
+++ b/tests/test_ev.py
@@ -15,13 +15,13 @@
@pytest.fixture
def ev_config():
return {
- "max_capacity": 10.0,
- "min_capacity": 0,
+ "capacity": 10.0,
+ "min_soc": 0,
"max_power_charge": 3, # Charge values will reflect a fraction of the capacity
"max_power_discharge": 2, # Discharge values will also be a fraction of the capacity
"efficiency_charge": 0.95,
"efficiency_discharge": 0.9,
- "initial_soc": 0, # SOC initialized to 50% of capacity
+ "initial_soc": 0, # initial SOC
}
@@ -138,7 +138,7 @@ def test_ev_charging_profile(ev_model_with_charging_profile, ev_config):
# Check if SOC stays within 0 and 1
for t in model.time_steps:
soc = pyo.value(model.ev.soc[t])
- assert ev_config["min_capacity"] <= soc <= ev_config["max_capacity"]
+ assert ev_config["min_soc"] <= soc <= ev_config["max_soc"]
if __name__ == "__main__":
diff --git a/tests/test_generic_storage.py b/tests/test_generic_storage.py
index a1154bc25..2c6ad8a7e 100644
--- a/tests/test_generic_storage.py
+++ b/tests/test_generic_storage.py
@@ -22,13 +22,13 @@ def price_profile():
@pytest.fixture
def generic_storage_config():
return {
- "max_capacity": 100, # Maximum energy capacity in MWh
- "min_capacity": 0, # Minimum SOC in MWh
+ "capacity": 100, # energy capacity in MWh
+ "min_soc": 0, # Minimum SOC
"max_power_charge": 0, # Maximum charging power in MW
"max_power_discharge": 0, # Maximum discharging power in MW
"efficiency_charge": 0.9, # Charging efficiency
"efficiency_discharge": 0.9, # Discharging efficiency
- "initial_soc": 0, # Initial SOC in MWh
+ "initial_soc": 0, # Initial SOC
"ramp_up": 50, # Maximum ramp-up rate in MW
"ramp_down": 50, # Maximum ramp-down rate in MW
"storage_loss_rate": 0.01, # 1% storage loss per time step
@@ -93,15 +93,15 @@ def test_state_of_charge_constraints(generic_storage_model, generic_storage_conf
and evolves correctly based on charging, discharging, and storage losses.
"""
model, _ = generic_storage_model
- min_soc = generic_storage_config["min_capacity"]
- max_soc = generic_storage_config["max_capacity"]
+ min_soc = generic_storage_config["min_soc"]
+ max_soc = generic_storage_config["max_soc"]
efficiency_charge = generic_storage_config["efficiency_charge"]
efficiency_discharge = generic_storage_config["efficiency_discharge"]
storage_loss_rate = generic_storage_config["storage_loss_rate"]
initial_soc = generic_storage_config["initial_soc"]
time_steps = sorted(model.time_steps)
- previous_soc = initial_soc * generic_storage_config["max_capacity"]
+ previous_soc = initial_soc
for i, t in enumerate(time_steps):
current_soc = pyo.value(model.storage.soc[t])
@@ -228,31 +228,31 @@ def test_storage_loss_rate(generic_storage_model, generic_storage_config):
previous_soc = actual_soc
-def test_min_capacity_enforcement(generic_storage_model, generic_storage_config):
+def test_min_soc_enforcement(generic_storage_model, generic_storage_config):
"""
- Test that the state of charge does not go below the minimum capacity.
+ Test that the state of charge does not go below the minimum soc.
"""
model, _ = generic_storage_model
- min_capacity = generic_storage_config["min_capacity"]
+ min_soc = generic_storage_config["min_soc"]
for t in model.time_steps:
soc = pyo.value(model.storage.soc[t])
- assert soc >= min_capacity - 1e-5, (
- f"SOC at time {t} is {soc}, which is below the minimum capacity of {min_capacity}."
+ assert soc >= min_soc - 1e-5, (
+ f"SOC at time {t} is {soc}, which is below the minimum soc of {min_soc}."
)
-def test_max_capacity_enforcement(generic_storage_model, generic_storage_config):
+def test_max_soc_enforcement(generic_storage_model, generic_storage_config):
"""
- Test that the state of charge does not exceed the maximum capacity.
+ Test that the state of charge does not exceed the maximum soc.
"""
model, _ = generic_storage_model
- max_capacity = generic_storage_config["max_capacity"]
+ max_soc = generic_storage_config["max_soc"]
for t in model.time_steps:
soc = pyo.value(model.storage.soc[t])
- assert soc <= max_capacity + 1e-5, (
- f"SOC at time {t} is {soc}, which exceeds the maximum capacity of {max_capacity}."
+ assert soc <= max_soc + 1e-5, (
+ f"SOC at time {t} is {soc}, which exceeds the maximum soc of {max_soc}."
)
diff --git a/tests/test_hydrogen_plant.py b/tests/test_hydrogen_plant.py
index cae764901..f93a1e492 100644
--- a/tests/test_hydrogen_plant.py
+++ b/tests/test_hydrogen_plant.py
@@ -25,8 +25,8 @@ def hydrogen_components():
"min_down_time": 0,
},
"hydrogen_seasonal_storage": {
- "max_capacity": 200,
- "min_capacity": 20,
+ "capacity": 200,
+ "min_soc": 0.1,
"max_power_charge": 40,
"max_power_discharge": 40,
"efficiency_charge": 0.95,
@@ -139,10 +139,10 @@ def test_ramping_constraints_without_flex(hydrogen_plant):
)
-def test_initial_soc_greater_than_capacity(hydrogen_plant):
+def test_initial_soc_greater_than_max_soc(hydrogen_plant):
storage = hydrogen_plant.components["hydrogen_seasonal_storage"]
- assert storage.initial_soc <= storage.max_capacity, (
- f"Initial SOC should be capped at max_capacity. Got {storage.initial_soc} > {storage.max_capacity}"
+ assert storage.initial_soc <= storage.max_soc, (
+ f"Initial SOC should be capped at max_soc. Got {storage.initial_soc} > {storage.max_soc}"
)
diff --git a/tests/test_seasonal_hydrogen_storage.py b/tests/test_seasonal_hydrogen_storage.py
index 8085c2028..c5a1034f8 100644
--- a/tests/test_seasonal_hydrogen_storage.py
+++ b/tests/test_seasonal_hydrogen_storage.py
@@ -19,8 +19,8 @@ def price_profile():
@pytest.fixture
def storage_config():
return {
- "max_capacity": 200,
- "min_capacity": 0,
+ "capacity": 200,
+ "min_soc": 0,
"max_power_charge": 40,
"max_power_discharge": 40,
"efficiency_charge": 0.95,
@@ -145,21 +145,17 @@ def test_long_term_storage_follows_schedule(
def test_long_term_storage_soc_limits(long_term_storage_model, storage_config):
model, _ = long_term_storage_model
- max_capacity = storage_config["max_capacity"]
- min_capacity = storage_config["min_capacity"]
+ max_soc = storage_config["max_soc"]
+ min_soc = storage_config["min_soc"]
for t in model.time_steps:
soc = pyo.value(model.storage.soc[t])
- assert soc <= max_capacity + 1e-5
- assert soc >= min_capacity - 1e-5
+ assert soc <= max_soc + 1e-5
+ assert soc >= min_soc - 1e-5
def test_long_term_storage_initial_soc(long_term_storage_model, storage_config):
model, _ = long_term_storage_model
- initial_soc = (
- storage_config["initial_soc"] * storage_config["max_capacity"]
- if storage_config["initial_soc"] <= 1
- else storage_config["initial_soc"]
- )
+ initial_soc = storage_config["initial_soc"]
soc_0 = pyo.value(model.storage.soc[0])
assert soc_0 == initial_soc
diff --git a/tests/test_steam_plant.py b/tests/test_steam_plant.py
index 9fafa4831..3f3cab504 100644
--- a/tests/test_steam_plant.py
+++ b/tests/test_steam_plant.py
@@ -259,8 +259,8 @@ def steam_plant_components_with_hp_b_ts():
"ramp_down": 50,
},
"thermal_storage": {
- "max_capacity": 100,
- "min_capacity": 0,
+ "capacity": 100,
+ "min_soc": 0,
"max_power_charge": 50,
"max_power_discharge": 50,
"efficiency_charge": 1,
diff --git a/tests/test_thermal_storage.py b/tests/test_thermal_storage.py
index beb054d41..671a1e9d1 100644
--- a/tests/test_thermal_storage.py
+++ b/tests/test_thermal_storage.py
@@ -19,8 +19,8 @@ def price_profile():
@pytest.fixture
def storage_config():
return {
- "max_capacity": 200,
- "min_capacity": 0,
+ "capacity": 200,
+ "min_soc": 0,
"max_power_charge": 40,
"max_power_discharge": 40,
"efficiency_charge": 0.95,
@@ -145,21 +145,17 @@ def test_long_term_storage_follows_schedule(
def test_long_term_storage_soc_limits(long_term_storage_model, storage_config):
model, _ = long_term_storage_model
- max_capacity = storage_config["max_capacity"]
- min_capacity = storage_config["min_capacity"]
+ max_soc = storage_config["max_soc"]
+ min_soc = storage_config["min_soc"]
for t in model.time_steps:
soc = pyo.value(model.storage.soc[t])
- assert soc <= max_capacity + 1e-5
- assert soc >= min_capacity - 1e-5
+ assert soc <= max_soc + 1e-5
+ assert soc >= min_soc - 1e-5
def test_long_term_storage_initial_soc(long_term_storage_model, storage_config):
model, _ = long_term_storage_model
- initial_soc = (
- storage_config["initial_soc"] * storage_config["max_capacity"]
- if storage_config["initial_soc"] <= 1
- else storage_config["initial_soc"]
- )
+ initial_soc = storage_config["initial_soc"]
soc_0 = pyo.value(model.storage.soc[0])
assert soc_0 == initial_soc
From 1919ae9d47f09d18566217696a24ff07eb36dab2 Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Wed, 10 Dec 2025 18:20:05 +0100
Subject: [PATCH 10/24] adjusted tests for storages
---
tests/test_dmas_storage.py | 8 ++---
tests/test_drl_storage_strategy.py | 4 +--
tests/test_flexable_storage_strategies.py | 4 +--
tests/test_storage.py | 41 ++++++++++++-----------
4 files changed, 29 insertions(+), 28 deletions(-)
diff --git a/tests/test_dmas_storage.py b/tests/test_dmas_storage.py
index fd2db091e..9452ae618 100644
--- a/tests/test_dmas_storage.py
+++ b/tests/test_dmas_storage.py
@@ -30,8 +30,8 @@ def storage_unit() -> Storage:
forecaster=forecaster,
max_power_charge=-100,
max_power_discharge=100,
- max_soc=1000,
- initial_soc=500,
+ capacity=1000,
+ initial_soc=0.5,
efficiency_charge=0.9,
efficiency_discharge=0.95,
ramp_down_charge=-50,
@@ -57,8 +57,8 @@ def storage_day() -> Storage:
bidding_strategies={"EOM": StorageEnergyOptimizationDmasStrategy()},
max_power_charge=-100,
max_power_discharge=100,
- max_soc=1000,
- initial_soc=500,
+ capacity=1000,
+ initial_soc=0.5,
efficiency_charge=0.9,
efficiency_discharge=0.95,
ramp_down_charge=-50,
diff --git a/tests/test_drl_storage_strategy.py b/tests/test_drl_storage_strategy.py
index b3dd91267..b0446da9c 100644
--- a/tests/test_drl_storage_strategy.py
+++ b/tests/test_drl_storage_strategy.py
@@ -56,9 +56,9 @@ def storage_unit() -> Storage:
},
max_power_charge=-500, # Negative for charging
max_power_discharge=500,
- max_soc=1000,
+ capacity=1000,
min_soc=0,
- initial_soc=500,
+ initial_soc=0.5,
efficiency_charge=0.9,
efficiency_discharge=0.9,
additional_cost_charge=5,
diff --git a/tests/test_flexable_storage_strategies.py b/tests/test_flexable_storage_strategies.py
index f2cb77e89..65d6eec5c 100644
--- a/tests/test_flexable_storage_strategies.py
+++ b/tests/test_flexable_storage_strategies.py
@@ -31,8 +31,8 @@ def storage() -> Storage:
forecaster=forecaster,
max_power_charge=-100,
max_power_discharge=100,
- max_soc=1000,
- initial_soc=500,
+ capacity=1000,
+ initial_soc=0.5,
efficiency_charge=0.9,
efficiency_discharge=0.95,
ramp_down_charge=-50,
diff --git a/tests/test_storage.py b/tests/test_storage.py
index f82239e3e..d84857234 100644
--- a/tests/test_storage.py
+++ b/tests/test_storage.py
@@ -25,7 +25,7 @@ def storage_unit() -> Storage:
forecaster=forecaster,
max_power_charge=-100,
max_power_discharge=100,
- max_soc=1000,
+ capacity=1000,
efficiency_charge=0.9,
efficiency_discharge=0.95,
ramp_down_charge=-50,
@@ -51,7 +51,7 @@ def test_init_function(storage_unit):
assert storage_unit.ramp_down_discharge == 50
assert storage_unit.ramp_up_charge == -60
assert storage_unit.ramp_up_discharge == 60
- assert storage_unit.initial_soc == 500
+ assert storage_unit.initial_soc == 0.5
def test_reset_function(storage_unit):
@@ -74,7 +74,7 @@ def test_reset_function(storage_unit):
# check if state of charge (soc) is reset correctly
assert (
storage_unit.outputs["soc"]
- == pd.Series(500.0, index=pd.date_range("2022-01-01", periods=4, freq="h"))
+ == pd.Series(0.5, index=pd.date_range("2022-01-01", periods=4, freq="h"))
).all()
@@ -130,11 +130,11 @@ def test_soc_constraint(storage_unit):
storage_unit.outputs["capacity_pos"][start] = 30
storage_unit.outputs["soc"][start - timedelta(hours=1)] = (
- 0.05 * storage_unit.max_soc
+ 0.05 * storage_unit.capacity
)
assert (
storage_unit.outputs["soc"][start - storage_unit.index.freq]
- == 0.05 * storage_unit.max_soc
+ == 0.05 * storage_unit.capacity
)
min_power_discharge, max_power_discharge = storage_unit.calculate_min_max_discharge(
start, end
@@ -142,7 +142,7 @@ def test_soc_constraint(storage_unit):
assert min_power_discharge[0] == 40
assert max_power_discharge[0] == 60
- storage_unit.outputs["soc"][start] = 0.95 * storage_unit.max_soc
+ storage_unit.outputs["soc"][start] = 0.95
min_power_charge, max_power_charge = storage_unit.calculate_min_max_charge(
start, end
)
@@ -270,35 +270,35 @@ def test_execute_dispatch(storage_unit):
end = datetime(2022, 1, 1, 2)
storage_unit.outputs["energy"][start] = 100
- storage_unit.outputs["soc"][start] = 0.5 * storage_unit.max_soc
+ storage_unit.outputs["soc"][start] = 0.5
# dispatch full discharge
dispatched_energy = storage_unit.execute_current_dispatch(start, end)
assert dispatched_energy[0] == 100
assert math.isclose(
storage_unit.outputs["soc"][end],
- 500 - 100 / storage_unit.efficiency_discharge,
+ 500 - 100 / storage_unit.efficiency_discharge / storage_unit.capacity,
)
# dispatch full charging
storage_unit.outputs["energy"][start] = -100
- storage_unit.outputs["soc"][start] = 0.5 * storage_unit.max_soc
+ storage_unit.outputs["soc"][start] = 0.5
dispatched_energy = storage_unit.execute_current_dispatch(start, end)
assert dispatched_energy[0] == -100
assert math.isclose(
storage_unit.outputs["soc"][end],
- 500 + 100 * storage_unit.efficiency_charge,
+ 500 + 100 * storage_unit.efficiency_charge / storage_unit.capacity,
)
# adjust dispatch to soc limit for discharge
storage_unit.outputs["energy"][start] = 100
- storage_unit.outputs["soc"][start] = 0.05 * storage_unit.max_soc
+ storage_unit.outputs["soc"][start] = 0.05
dispatched_energy = storage_unit.execute_current_dispatch(start, end)
assert math.isclose(
dispatched_energy[0], 50 * storage_unit.efficiency_discharge, abs_tol=0.1
)
# adjust dispatch to soc limit for charging
storage_unit.outputs["energy"][start] = -100
- storage_unit.outputs["soc"][start] = 0.95 * storage_unit.max_soc
+ storage_unit.outputs["soc"][start] = 0.95
dispatched_energy = storage_unit.execute_current_dispatch(start, end)
assert math.isclose(
dispatched_energy[0], -50 / storage_unit.efficiency_charge, abs_tol=0.1
@@ -328,7 +328,7 @@ def test_set_dispatch_plan(mock_market_config, storage_unit):
product_tuples = [(start, end, None)]
storage_unit.outputs["energy"][start] = 100
- storage_unit.outputs["soc"][start] = 0.5 * storage_unit.max_soc
+ storage_unit.outputs["soc"][start] = 0.5
bids = strategy.calculate_bids(storage_unit, mc, product_tuples=product_tuples)
assert len(bids) == 0
@@ -340,11 +340,11 @@ def test_set_dispatch_plan(mock_market_config, storage_unit):
assert storage_unit.outputs["energy"][start] == 100
assert math.isclose(
storage_unit.outputs["soc"][end],
- 500 - 100 / storage_unit.efficiency_discharge,
+ 500 - 100 / storage_unit.efficiency_discharge / storage_unit.capacity,
)
# dispatch full charging
storage_unit.outputs["energy"][start] = -100
- storage_unit.outputs["soc"][start] = 0.5 * storage_unit.max_soc
+ storage_unit.outputs["soc"][start] = 0.5
storage_unit.set_dispatch_plan(mc, bids)
storage_unit.execute_current_dispatch(start, end)
@@ -352,23 +352,23 @@ def test_set_dispatch_plan(mock_market_config, storage_unit):
assert storage_unit.outputs["energy"][start] == -100
assert math.isclose(
storage_unit.outputs["soc"][end],
- 500 + 100 * storage_unit.efficiency_charge,
+ 500 + 100 * storage_unit.efficiency_charge / storage_unit.capacity,
)
# adjust dispatch to soc limit for discharge
storage_unit.outputs["energy"][start] = 100
- storage_unit.outputs["soc"][start] = 0.05 * storage_unit.max_soc
+ storage_unit.outputs["soc"][start] = 0.05
storage_unit.set_dispatch_plan(mc, bids)
storage_unit.execute_current_dispatch(start, end)
assert math.isclose(
storage_unit.outputs["energy"][start],
- 50 * storage_unit.efficiency_discharge,
+ 50 * storage_unit.efficiency_discharge / storage_unit.capacity,
abs_tol=0.1,
)
# adjust dispatch to soc limit for charging
storage_unit.outputs["energy"][start] = -100
- storage_unit.outputs["soc"][start] = 0.95 * storage_unit.max_soc
+ storage_unit.outputs["soc"][start] = 0.95
storage_unit.set_dispatch_plan(mc, bids)
storage_unit.execute_current_dispatch(start, end)
@@ -407,7 +407,7 @@ def test_set_dispatch_plan_multi_hours(mock_market_config, storage_unit):
strategy = StorageEnergyHeuristicFlexableStrategy()
storage_unit.outputs["energy"][start] = 100
- storage_unit.outputs["soc"][start] = 0.5 * storage_unit.max_soc
+ storage_unit.outputs["soc"][start] = 0.5
bids = strategy.calculate_bids(storage_unit, mc, product_tuples=product_tuples)
assert len(bids) == 2
@@ -477,6 +477,7 @@ def test_initialising_invalid_storages():
"max_power_charge": 0.0,
"max_power_discharge": 0.0,
"max_soc": 0.0,
+ "capacity": 0.0,
}
with pytest.raises(
ValueError, match="max_power_charge=10 must be <= 0 for unit id"
From fc8b726585529a6ad6088404cba928dd6c5409a3 Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Wed, 10 Dec 2025 20:57:18 +0100
Subject: [PATCH 11/24] small fixes in flexible units
---
assume/units/dst_components.py | 99 ++++++++++++++-----------
tests/test_ev.py | 1 +
tests/test_generic_storage.py | 1 +
tests/test_seasonal_hydrogen_storage.py | 1 +
tests/test_steam_plant.py | 6 +-
tests/test_thermal_storage.py | 1 +
6 files changed, 63 insertions(+), 46 deletions(-)
diff --git a/assume/units/dst_components.py b/assume/units/dst_components.py
index cac188e96..f745d7c08 100644
--- a/assume/units/dst_components.py
+++ b/assume/units/dst_components.py
@@ -417,13 +417,14 @@ class GenericStorage:
ramp rates, and storage losses.
Args:
- max_capacity (float): Maximum energy storage capacity of the storage unit.
- min_capacity (float, optional): Minimum allowable state of charge (SOC). Defaults to 0.0.
- max_power_charge (float, optional): Maximum charging power of the storage unit. Defaults to `max_capacity` if not provided.
- max_power_discharge (float, optional): Maximum discharging power of the storage unit. Defaults to `max_capacity` if not provided.
+ capacity (float): Energy storage capacity of the storage unit.
+ min_soc (float, optional): Minimum allowable state of charge (SOC). Defaults to 0.0.
+ max_soc (float, optional): Maximum allowable state of charge (SOC). Defaults to 1.0.
+ max_power_charge (float, optional): Maximum charging power of the storage unit. Defaults to `capacity` if not provided.
+ max_power_discharge (float, optional): Maximum discharging power of the storage unit. Defaults to `capacity` if not provided.
efficiency_charge (float, optional): Efficiency of the charging process. Defaults to 1.0.
efficiency_discharge (float, optional): Efficiency of the discharging process. Defaults to 1.0.
- initial_soc (float, optional): Initial state of charge as a fraction of `max_capacity`. Defaults to 1.0.
+ initial_soc (float, optional): Initial state of charge as a fraction of `capacity`. Defaults to 1.0.
ramp_up (float, optional): Maximum allowed increase in charging/discharging power per time step. Defaults to None (no ramp constraint).
ramp_down (float, optional): Maximum allowed decrease in charging/discharging power per time step. Defaults to None (no ramp constraint).
storage_loss_rate (float, optional): Fraction of energy lost per time step due to storage inefficiencies. Defaults to 0.0.
@@ -431,9 +432,10 @@ class GenericStorage:
def __init__(
self,
- max_capacity: float,
+ capacity: float,
time_steps: list[int],
- min_capacity: float = 0.0,
+ min_soc: float = 0.0,
+ max_soc: float = 1.0,
max_power_charge: float | None = None,
max_power_discharge: float | None = None,
efficiency_charge: float = 1.0,
@@ -448,23 +450,24 @@ def __init__(
# check if initial_soc is within the bounds [0, 1] and fix it if not
if initial_soc > 1:
- initial_soc /= max_capacity
+ initial_soc = 1.0
logger.warning(
f"Initial SOC is greater than 1.0. Setting it to {initial_soc}."
)
- self.max_capacity = max_capacity
- self.min_capacity = min_capacity
+ self.capacity = capacity
+ self.min_soc = min_soc
+ self.max_soc = max_soc
self.time_steps = time_steps
self.max_power_charge = (
- max_capacity if max_power_charge is None else max_power_charge
+ capacity if max_power_charge is None else max_power_charge
)
self.max_power_discharge = (
- max_capacity if max_power_discharge is None else max_power_discharge
+ capacity if max_power_discharge is None else max_power_discharge
)
self.efficiency_charge = efficiency_charge
self.efficiency_discharge = efficiency_discharge
- self.initial_soc = initial_soc * max_capacity
+ self.initial_soc = initial_soc
self.ramp_up = max_power_charge if ramp_up is None else ramp_up
self.ramp_down = max_power_charge if ramp_down is None else ramp_down
self.storage_loss_rate = storage_loss_rate
@@ -478,8 +481,9 @@ def add_to_model(
Pyomo Components:
- **Parameters**:
- - `max_capacity`: Maximum capacity of the storage unit.
- - `min_capacity`: Minimum state of charge (SOC).
+ - `capacity`: Capacity of the storage unit.
+ - `min_soc`: Minimum state of charge (SOC, between 0 and 1).
+ - `max_soc`: Maximum state of charge (SOC, between 0 and 1).
- `max_power_charge`: Maximum charging power.
- `max_power_discharge`: Maximum discharging power.
- `efficiency_charge`: Charging efficiency.
@@ -510,26 +514,23 @@ def add_to_model(
"""
# Define parameters
- model_block.max_capacity = pyo.Param(initialize=self.max_capacity)
- model_block.min_capacity = pyo.Param(initialize=self.min_capacity)
+ model_block.capacity = pyo.Param(initialize=self.capacity)
+ model_block.min_soc = pyo.Param(initialize=self.min_soc)
+ model_block.max_soc = pyo.Param(initialize=self.max_soc)
model_block.max_power_charge = pyo.Param(initialize=self.max_power_charge)
model_block.max_power_discharge = pyo.Param(initialize=self.max_power_discharge)
model_block.efficiency_charge = pyo.Param(initialize=self.efficiency_charge)
- model_block.efficiency_discharge = pyo.Param(
- initialize=self.efficiency_discharge
- )
+ model_block.efficiency_discharge = pyo.Param(initialize=self.efficiency_discharge)
model_block.ramp_up = pyo.Param(initialize=self.ramp_up)
model_block.ramp_down = pyo.Param(initialize=self.ramp_down)
- model_block.initial_soc = pyo.Param(
- initialize=self.initial_soc * self.max_capacity
- )
+ model_block.initial_soc = pyo.Param(initialize=self.initial_soc)
model_block.storage_loss_rate = pyo.Param(initialize=self.storage_loss_rate)
# Define variables
model_block.soc = pyo.Var(
self.time_steps,
within=pyo.NonNegativeReals,
- bounds=(model_block.min_capacity, model_block.max_capacity),
+ bounds=(model_block.min_soc, model_block.max_soc),
doc="State of Charge at each time step",
)
model_block.charge = pyo.Var(
@@ -554,9 +555,11 @@ def soc_balance_rule(b, t):
prev_soc = b.soc[t - 1]
return b.soc[t] == (
prev_soc
- + b.efficiency_charge * b.charge[t]
+ + (
+ b.efficiency_charge * b.charge[t]
- (1 / b.efficiency_discharge) * b.discharge[t]
- - b.storage_loss_rate * prev_soc
+ - b.storage_loss_rate * prev_soc * b.capacity
+ ) / b.capacity
)
# Apply ramp-up constraints if ramp_up is specified
@@ -1394,15 +1397,16 @@ class ElectricVehicle(GenericStorage):
and predefined charging profiles.
Args:
- max_capacity (float): Maximum capacity of the EV battery.
- min_capacity (float): Minimum capacity of the EV battery.
+ capacity (float): Energy capacity of the EV battery in MWh.
+ min_soc (float): Minimum soc of the EV battery (between 0 and 1).
+ max_soc (float): Maximum soc of the EV battery (between 0 and 1).
max_power_charge (float): Maximum allowable charging power.
max_power_discharge (float): Maximum allowable discharging power. Defaults to 0 (no discharging allowed).
availability_profile (pd.Series): A pandas Series indicating the EV's availability, where 1 means available and 0 means unavailable.
time_steps (list[int]): A list of time steps over which the EV operates.
efficiency_charge (float, optional): Charging efficiency of the EV. Defaults to 1.0.
efficiency_discharge (float, optional): Discharging efficiency of the EV. Defaults to 1.0.
- initial_soc (float, optional): Initial state of charge (SOC) of the EV, represented as a fraction of `max_capacity`. Defaults to 1.0.
+ initial_soc (float, optional): Initial state of charge (SOC) of the EV, represented as a fraction of `capacity`. Defaults to 1.0.
ramp_up (float, optional): Maximum allowed increase in charging power per time step. Defaults to None (no ramp constraint).
ramp_down (float, optional): Maximum allowed decrease in charging power per time step. Defaults to None (no ramp constraint).
charging_profile (pd.Series | None, optional): A predefined charging profile. If provided, the EV follows this profile instead of optimizing the charge. Defaults to None.
@@ -1410,11 +1414,12 @@ class ElectricVehicle(GenericStorage):
def __init__(
self,
- max_capacity: float,
+ capacity: float,
time_steps: list[int],
availability_profile: pd.Series,
max_power_charge: float,
- min_capacity: float = 0.0,
+ min_soc: float = 0.0,
+ max_soc: float = 1.0,
max_power_discharge: float = 0,
efficiency_charge: float = 1.0,
efficiency_discharge: float = 1.0,
@@ -1427,9 +1432,10 @@ def __init__(
):
# Call the parent class (GenericStorage) __init__ method
super().__init__(
- max_capacity=max_capacity,
+ capacity=capacity,
time_steps=time_steps,
- min_capacity=min_capacity,
+ min_soc=min_soc,
+ max_soc=max_soc,
max_power_charge=max_power_charge,
max_power_discharge=max_power_discharge,
efficiency_charge=efficiency_charge,
@@ -1453,8 +1459,9 @@ def add_to_model(
Pyomo Components:
- **Parameters**:
- - `max_capacity`: Maximum battery capacity of the EV.
- - `min_capacity`: Minimum allowable battery capacity.
+ - `capacity`: Energy capacity of the EV battery in MWh.
+ - `min_soc`: Minimum allowable state of charge (SOC) of the EV battery (between 0 and 1).
+ - `max_soc`: Maximum allowable state of charge (SOC) of the EV battery (between 0 and 1).
- `max_power_charge`: Maximum charging power.
- `max_power_discharge`: Maximum discharging power.
- `efficiency_charge`: Charging efficiency.
@@ -1468,7 +1475,7 @@ def add_to_model(
- **Constraints**:
- `availability_constraints`: Ensures charging and discharging occur only during available periods.
- `charging_profile_constraints`: Enforces predefined charging profiles if provided.
- - `soc_constraints`: Keeps SOC between `min_capacity` and `max_capacity`.
+ - `soc_constraints`: Keeps SOC between `min_soc` and `max_soc`.
- `ramp_constraints`: Limits ramp-up and ramp-down rates for charging.
Args:
@@ -1526,9 +1533,10 @@ class HydrogenBufferStorage(GenericStorage):
def __init__(
self,
- max_capacity: float,
+ capacity: float,
time_steps: list[int],
- min_capacity: float = 0.0,
+ min_soc: float = 0.0,
+ max_soc: float = 1.0,
max_power_charge: float | None = None,
max_power_discharge: float | None = None,
efficiency_charge: float = 1.0,
@@ -1540,9 +1548,10 @@ def __init__(
**kwargs,
):
super().__init__(
- max_capacity=max_capacity,
+ capacity=capacity,
time_steps=time_steps,
- min_capacity=min_capacity,
+ min_soc=min_soc,
+ max_soc=max_soc,
max_power_charge=max_power_charge,
max_power_discharge=max_power_discharge,
efficiency_charge=efficiency_charge,
@@ -1681,9 +1690,10 @@ class DRIStorage(GenericStorage):
def __init__(
self,
- max_capacity: float,
+ capacity: float,
time_steps: list[int],
- min_capacity: float = 0.0,
+ min_soc: float = 0.0,
+ max_soc: float = 1.0,
max_power_charge: float | None = None,
max_power_discharge: float | None = None,
efficiency_charge: float = 1.0,
@@ -1695,9 +1705,10 @@ def __init__(
**kwargs,
):
super().__init__(
- max_capacity=max_capacity,
+ capacity=capacity,
time_steps=time_steps,
- min_capacity=min_capacity,
+ min_soc=min_soc,
+ max_soc=max_soc,
max_power_charge=max_power_charge,
max_power_discharge=max_power_discharge,
efficiency_charge=efficiency_charge,
diff --git a/tests/test_ev.py b/tests/test_ev.py
index 3ad65385f..696ac48d9 100644
--- a/tests/test_ev.py
+++ b/tests/test_ev.py
@@ -17,6 +17,7 @@ def ev_config():
return {
"capacity": 10.0,
"min_soc": 0,
+ "max_soc": 1,
"max_power_charge": 3, # Charge values will reflect a fraction of the capacity
"max_power_discharge": 2, # Discharge values will also be a fraction of the capacity
"efficiency_charge": 0.95,
diff --git a/tests/test_generic_storage.py b/tests/test_generic_storage.py
index 2c6ad8a7e..b01a738bf 100644
--- a/tests/test_generic_storage.py
+++ b/tests/test_generic_storage.py
@@ -24,6 +24,7 @@ def generic_storage_config():
return {
"capacity": 100, # energy capacity in MWh
"min_soc": 0, # Minimum SOC
+ "max_soc": 1, # Maximum SOC
"max_power_charge": 0, # Maximum charging power in MW
"max_power_discharge": 0, # Maximum discharging power in MW
"efficiency_charge": 0.9, # Charging efficiency
diff --git a/tests/test_seasonal_hydrogen_storage.py b/tests/test_seasonal_hydrogen_storage.py
index c5a1034f8..acf7f377e 100644
--- a/tests/test_seasonal_hydrogen_storage.py
+++ b/tests/test_seasonal_hydrogen_storage.py
@@ -21,6 +21,7 @@ def storage_config():
return {
"capacity": 200,
"min_soc": 0,
+ "max_soc": 1,
"max_power_charge": 40,
"max_power_discharge": 40,
"efficiency_charge": 0.95,
diff --git a/tests/test_steam_plant.py b/tests/test_steam_plant.py
index 3f3cab504..71047628c 100644
--- a/tests/test_steam_plant.py
+++ b/tests/test_steam_plant.py
@@ -261,6 +261,7 @@ def steam_plant_components_with_hp_b_ts():
"thermal_storage": {
"capacity": 100,
"min_soc": 0,
+ "max_soc": 1,
"max_power_charge": 50,
"max_power_discharge": 50,
"efficiency_charge": 1,
@@ -396,8 +397,9 @@ def steam_plant_components_with_hp_b_longterm_ts():
"ramp_down": 50,
},
"thermal_storage": {
- "max_capacity": 200,
- "min_capacity": 0,
+ "capacity": 200,
+ "min_soc": 0,
+ "max_soc": 1,
"max_power_charge": 40,
"max_power_discharge": 50,
"efficiency_charge": 1,
diff --git a/tests/test_thermal_storage.py b/tests/test_thermal_storage.py
index 671a1e9d1..302cfca33 100644
--- a/tests/test_thermal_storage.py
+++ b/tests/test_thermal_storage.py
@@ -21,6 +21,7 @@ def storage_config():
return {
"capacity": 200,
"min_soc": 0,
+ "max_soc": 1,
"max_power_charge": 40,
"max_power_discharge": 40,
"efficiency_charge": 0.95,
From 37e2893827f976a46c13b17e763cd1e1ccdc91af Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Tue, 16 Dec 2025 12:40:29 +0100
Subject: [PATCH 12/24] change soc_max to capacity in storage.py, base.py and
learning_strategy
---
assume/common/base.py | 4 ++--
assume/strategies/learning_strategies.py | 10 +++++-----
assume/units/storage.py | 4 ++--
.../04c_reinforcement_learning_storage_example.ipynb | 4 ++--
4 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/assume/common/base.py b/assume/common/base.py
index 02c8b3ac5..cc3371f5c 100644
--- a/assume/common/base.py
+++ b/assume/common/base.py
@@ -661,7 +661,7 @@ def set_dispatch_plan(
if current_power > max_soc_discharge:
current_power = max_soc_discharge
- delta_soc = -current_power * time_delta / self.efficiency_discharge
+ delta_soc = (-current_power * time_delta / self.efficiency_discharge) / self.capacity
# charging
elif current_power < 0:
@@ -670,7 +670,7 @@ def set_dispatch_plan(
if current_power < max_soc_charge:
current_power = max_soc_charge
- delta_soc = -current_power * time_delta * self.efficiency_charge
+ delta_soc = (-current_power * time_delta * self.efficiency_charge) / self.capacity
# update the values of the state of charge and the energy
self.outputs["soc"].at[next_t] = soc + delta_soc
diff --git a/assume/strategies/learning_strategies.py b/assume/strategies/learning_strategies.py
index 11464a7a4..bcd9ff1cd 100644
--- a/assume/strategies/learning_strategies.py
+++ b/assume/strategies/learning_strategies.py
@@ -914,12 +914,12 @@ def get_individual_observations(
the agent's action selection.
"""
# get the current soc and energy cost value
- soc_scaled = unit.outputs["soc"].at[start] / unit.max_soc
+ soc = unit.outputs["soc"].at[start]
cost_stored_energy_scaled = (
unit.outputs["cost_stored_energy"].at[start] / self.max_bid_price
)
- individual_observations = np.array([soc_scaled, cost_stored_energy_scaled])
+ individual_observations = np.array([soc, cost_stored_energy_scaled])
return individual_observations
@@ -1077,15 +1077,15 @@ def calculate_reward(
# Calculate and clip the energy cost for the start time
# cost_stored_energy = average volume-weighted procurement costs of the currently stored energy
- if next_soc < 1:
+ if next_soc * unit.capacity < 1:
unit.outputs["cost_stored_energy"].at[next_time] = 0
elif accepted_volume < 0:
# increase costs of current SoC by price for buying energy
# not fully representing the true cost per MWh (e.g. omitting discharge efficiency losses), but serving as a proxy for it
unit.outputs["cost_stored_energy"].at[next_time] = (
- unit.outputs["cost_stored_energy"].at[start] * current_soc
+ unit.outputs["cost_stored_energy"].at[start] * current_soc * unit.capacity
- (accepted_price + marginal_cost) * accepted_volume * duration_hours
- ) / next_soc
+ ) / (next_soc * unit.capacity)
else:
unit.outputs["cost_stored_energy"].at[next_time] = unit.outputs[
"cost_stored_energy"
diff --git a/assume/units/storage.py b/assume/units/storage.py
index 4b7eac654..95e3c5c5d 100644
--- a/assume/units/storage.py
+++ b/assume/units/storage.py
@@ -237,7 +237,7 @@ def execute_current_dispatch(self, start: datetime, end: datetime) -> np.ndarray
if current_power > max_soc_discharge:
current_power = max_soc_discharge
- delta_soc = -current_power * time_delta / self.efficiency_discharge
+ delta_soc = (-current_power * time_delta / self.efficiency_discharge) / self.capacity
# charging
elif current_power < 0:
@@ -246,7 +246,7 @@ def execute_current_dispatch(self, start: datetime, end: datetime) -> np.ndarray
if current_power < max_soc_charge:
current_power = max_soc_charge
- delta_soc = -current_power * time_delta * self.efficiency_charge
+ delta_soc = (-current_power * time_delta * self.efficiency_charge) / self.capacity
# update the values of the state of charge and the energy
next_freq = t + self.index.freq
diff --git a/examples/notebooks/04c_reinforcement_learning_storage_example.ipynb b/examples/notebooks/04c_reinforcement_learning_storage_example.ipynb
index bcdcd4a15..217dee135 100644
--- a/examples/notebooks/04c_reinforcement_learning_storage_example.ipynb
+++ b/examples/notebooks/04c_reinforcement_learning_storage_example.ipynb
@@ -590,10 +590,10 @@
" \"\"\"\n",
"\n",
" # get the current soc and energy cost value\n",
- " soc_scaled = unit.outputs[\"soc\"].at[start] / unit.max_soc\n",
+ " soc = unit.outputs[\"soc\"].at[start]\n",
" energy_cost_scaled = unit.outputs[\"energy_cost\"].at[start] / self.max_bid_price\n",
"\n",
- " individual_observations = np.array([soc_scaled, energy_cost_scaled])\n",
+ " individual_observations = np.array([soc, energy_cost_scaled])\n",
"\n",
" return individual_observations"
]
From d6c2605079b136e07aafb50a6bed594dcacb25e6 Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Tue, 16 Dec 2025 12:40:48 +0100
Subject: [PATCH 13/24] add soc test in cost_stored_energy test
---
tests/test_drl_storage_strategy.py | 26 +++++++++++++++++++++++---
1 file changed, 23 insertions(+), 3 deletions(-)
diff --git a/tests/test_drl_storage_strategy.py b/tests/test_drl_storage_strategy.py
index b0446da9c..174b0580d 100644
--- a/tests/test_drl_storage_strategy.py
+++ b/tests/test_drl_storage_strategy.py
@@ -58,6 +58,7 @@ def storage_unit() -> Storage:
max_power_discharge=500,
capacity=1000,
min_soc=0,
+ max_soc=1,
initial_soc=0.5,
efficiency_charge=0.9,
efficiency_discharge=0.9,
@@ -288,9 +289,8 @@ def test_storage_rl_strategy_buy_bid(mock_market_config, storage_unit):
f"Expected costs {expected_costs}, got {costs[0]}"
)
-
@pytest.mark.require_learning
-def test_storage_rl_strategy_cost_stored_energy(mock_market_config, storage_unit):
+def test_storage_rl_strategy_soc_and_cost_stored_energy(mock_market_config, storage_unit):
"""
Test the StorageEnergyLearningStrategy if unique observations are created as expected.
"""
@@ -350,18 +350,38 @@ def test_storage_rl_strategy_cost_stored_energy(mock_market_config, storage_unit
product_index.union([product_index[-1] + product_index.freq])
]
+ soc = storage_unit.outputs["soc"].loc[
+ product_index.union([product_index[-1] + product_index.freq])
+ ]
+ expected_soc_t0 = 0.5 # initial soc
+ assert math.isclose(soc[0], expected_soc_t0, rel_tol=1e-3), (
+ f"Expected SoC at t=0 to be {expected_soc_t0}, got {soc[0]}"
+ )
# Initial state: 500 MWh at default energy costs of 0 €/MWh
- # 1. Charge 500 MWh at 30 €/MWh): cost_stored_energy_t1 = (0 €/MWh * 500 MWh - ((30 €/MWh + 5 €/MWh) * - 500 MW * 1h)) / 950 MWh = 18.41 €/MWh
+ # 1. Charge 500 MWh at 30 €/MWh: cost_stored_energy_t1 = (0 €/MWh * 500 MWh - ((30 €/MWh + 5 €/MWh) * - 500 MW * 1h)) / 950 MWh = 18.41 €/MWh
+ expected_soc_t1 = 0.5 + (500 * 0.9 / 1000) # 0.95
+ assert math.isclose(soc[1], expected_soc_t1, rel_tol=1e-3), (
+ f"Expected SoC at t=1 to be {expected_soc_t1}, got {soc[1]}"
+ )
expected_cost_t1 = (500 * 35) / 950
assert math.isclose(cost_stored_energy[1], expected_cost_t1, rel_tol=1e-3), (
f"Expected energy cost at t=1 to be {expected_cost_t1}, got {cost_stored_energy[1]}"
)
# 2. Discharge 500 MWh at 60 €/MWh: cost_stored_energy_t2 = 18.41 €/MWh unchanged
+ expected_soc_t2 = 0.95 - (500 / 0.9 / 1000) # 0.3944
+ assert math.isclose(soc[2], expected_soc_t2, rel_tol=1e-3), (
+ f"Expected SoC at t=2 to be {expected_soc_t2}, got {soc[2]}"
+ )
expected_cost_t2 = expected_cost_t1
assert math.isclose(cost_stored_energy[2], expected_cost_t2, rel_tol=1e-3), (
f"Expected energy cost at t=2 to be {expected_cost_t2}, got {cost_stored_energy[2]}"
)
# 3. Discharge remaining 355 MWh at 80 €/Mwh: SoC < 1 --> cost_stored_energy_t3 = 0 €/MWh
+ expected_soc_t3 = 0.3944 - (355 / 0.9 / 1000) # 0
+ # use abs_tol here as values are close to zero
+ assert math.isclose(soc[3], expected_soc_t3, abs_tol=0.1), (
+ f"Expected SoC at t=3 to be {expected_soc_t3}, got {soc[3]}"
+ )
expected_cost_t3 = 0
assert math.isclose(cost_stored_energy[3], expected_cost_t3, rel_tol=1e-3), (
f"Expected energy cost at t=3 to be {expected_cost_t3}, got {cost_stored_energy[3]}"
From f79e3b62763ecf9484811665865bcf5ddd831878 Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Tue, 16 Dec 2025 15:28:13 +0100
Subject: [PATCH 14/24] found some mistakes and corrected
---
tests/test_storage.py | 32 +++++++++++++++++---------------
1 file changed, 17 insertions(+), 15 deletions(-)
diff --git a/tests/test_storage.py b/tests/test_storage.py
index d84857234..ea5b4fb19 100644
--- a/tests/test_storage.py
+++ b/tests/test_storage.py
@@ -129,12 +129,10 @@ def test_soc_constraint(storage_unit):
storage_unit.outputs["capacity_neg"][start] = -50
storage_unit.outputs["capacity_pos"][start] = 30
- storage_unit.outputs["soc"][start - timedelta(hours=1)] = (
- 0.05 * storage_unit.capacity
- )
+ storage_unit.outputs["soc"][start - timedelta(hours=1)] = 0.05
assert (
storage_unit.outputs["soc"][start - storage_unit.index.freq]
- == 0.05 * storage_unit.capacity
+ == 0.05
)
min_power_discharge, max_power_discharge = storage_unit.calculate_min_max_discharge(
start, end
@@ -227,9 +225,9 @@ def test_storage_ramping(storage_unit):
assert max_power_discharge[0] == 100
max_ramp_discharge = storage_unit.calculate_ramp_discharge(
- 500, 0, max_power_discharge[0]
+ 0.5, 0, max_power_discharge[0]
)
- max_ramp_charge = storage_unit.calculate_ramp_charge(500, 0, max_power_charge[0])
+ max_ramp_charge = storage_unit.calculate_ramp_charge(0.5, 0, max_power_charge[0])
assert max_ramp_discharge == 60
assert max_ramp_charge == -60
@@ -242,9 +240,11 @@ def test_storage_ramping(storage_unit):
end = datetime(2022, 1, 1, 2)
max_ramp_discharge = storage_unit.calculate_ramp_discharge(
- 500, 60, max_power_discharge[0]
+ 0.5, 60, max_power_discharge[0]
+ )
+ max_ramp_charge = storage_unit.calculate_ramp_charge(
+ 0.5, 60, max_power_charge[0]
)
- max_ramp_charge = storage_unit.calculate_ramp_charge(500, 60, max_power_charge[0])
assert max_ramp_discharge == 100
assert max_ramp_charge == -60
@@ -257,9 +257,11 @@ def test_storage_ramping(storage_unit):
end = datetime(2022, 1, 1, 3)
max_ramp_discharge = storage_unit.calculate_ramp_discharge(
- 500, -60, max_power_discharge[0]
+ 0.5, -60, max_power_discharge[0]
+ )
+ max_ramp_charge = storage_unit.calculate_ramp_charge(
+ 0.5, -60, max_power_charge[0]
)
- max_ramp_charge = storage_unit.calculate_ramp_charge(500, -60, max_power_charge[0])
assert max_ramp_discharge == 60
assert max_ramp_charge == -100
@@ -277,7 +279,7 @@ def test_execute_dispatch(storage_unit):
assert dispatched_energy[0] == 100
assert math.isclose(
storage_unit.outputs["soc"][end],
- 500 - 100 / storage_unit.efficiency_discharge / storage_unit.capacity,
+ (500 - 100 / storage_unit.efficiency_discharge) / storage_unit.capacity,
)
# dispatch full charging
@@ -287,7 +289,7 @@ def test_execute_dispatch(storage_unit):
assert dispatched_energy[0] == -100
assert math.isclose(
storage_unit.outputs["soc"][end],
- 500 + 100 * storage_unit.efficiency_charge / storage_unit.capacity,
+ (500 + 100 * storage_unit.efficiency_charge) / storage_unit.capacity,
)
# adjust dispatch to soc limit for discharge
storage_unit.outputs["energy"][start] = 100
@@ -340,7 +342,7 @@ def test_set_dispatch_plan(mock_market_config, storage_unit):
assert storage_unit.outputs["energy"][start] == 100
assert math.isclose(
storage_unit.outputs["soc"][end],
- 500 - 100 / storage_unit.efficiency_discharge / storage_unit.capacity,
+ (500 - 100 / storage_unit.efficiency_discharge) / storage_unit.capacity,
)
# dispatch full charging
storage_unit.outputs["energy"][start] = -100
@@ -352,7 +354,7 @@ def test_set_dispatch_plan(mock_market_config, storage_unit):
assert storage_unit.outputs["energy"][start] == -100
assert math.isclose(
storage_unit.outputs["soc"][end],
- 500 + 100 * storage_unit.efficiency_charge / storage_unit.capacity,
+ (500 + 100 * storage_unit.efficiency_charge) / storage_unit.capacity,
)
# adjust dispatch to soc limit for discharge
storage_unit.outputs["energy"][start] = 100
@@ -363,7 +365,7 @@ def test_set_dispatch_plan(mock_market_config, storage_unit):
assert math.isclose(
storage_unit.outputs["energy"][start],
- 50 * storage_unit.efficiency_discharge / storage_unit.capacity,
+ 50 * storage_unit.efficiency_discharge,
abs_tol=0.1,
)
# adjust dispatch to soc limit for charging
From 7b73baa6ad81cc642fe4352f00f25bea854709f5 Mon Sep 17 00:00:00 2001
From: mthede
Date: Mon, 22 Dec 2025 14:38:10 +0100
Subject: [PATCH 15/24] fix tests by consistent calculation of (relative) SoC
within test and FlexableStrategy
---
assume/strategies/flexable_storage.py | 33 ++++++++++++++++++---------
tests/test_storage.py | 19 ++++++---------
2 files changed, 29 insertions(+), 23 deletions(-)
diff --git a/assume/strategies/flexable_storage.py b/assume/strategies/flexable_storage.py
index 07bd21248..8e8344bc1 100644
--- a/assume/strategies/flexable_storage.py
+++ b/assume/strategies/flexable_storage.py
@@ -152,14 +152,22 @@ def calculate_bids(
# calculate theoretic SOC
time_delta = (end - start) / timedelta(hours=1)
if bid_quantity + current_power > 0:
- delta_soc = -(
- (bid_quantity + current_power)
- * time_delta
- / unit.efficiency_discharge
+ delta_soc = (
+ -(
+ (bid_quantity + current_power)
+ * time_delta
+ / unit.efficiency_discharge
+ )
+ / unit.capacity
)
elif bid_quantity + current_power < 0:
- delta_soc = -(
- (bid_quantity + current_power) * time_delta * unit.efficiency_charge
+ delta_soc = (
+ -(
+ (bid_quantity + current_power)
+ * time_delta
+ * unit.efficiency_charge
+ )
+ / unit.capacity
)
else:
delta_soc = 0
@@ -329,10 +337,13 @@ def calculate_bids(
)
# calculate theoretic SOC
time_delta = (end - start) / timedelta(hours=1)
- delta_soc = -(
- (bid_quantity + current_power)
- * time_delta
- / unit.efficiency_discharge
+ delta_soc = (
+ -(
+ (bid_quantity + current_power)
+ * time_delta
+ / unit.efficiency_discharge
+ )
+ / unit.capacity
)
theoretic_SOC += delta_soc
previous_power = bid_quantity + current_power
@@ -440,7 +451,7 @@ def calculate_bids(
time_delta = (end - start) / timedelta(hours=1)
delta_soc = (
(bid_quantity + current_power) * time_delta * unit.efficiency_charge
- )
+ ) / unit.capacity
theoretic_SOC += delta_soc
previous_power = bid_quantity + current_power
else:
diff --git a/tests/test_storage.py b/tests/test_storage.py
index ea5b4fb19..0d721e078 100644
--- a/tests/test_storage.py
+++ b/tests/test_storage.py
@@ -130,10 +130,7 @@ def test_soc_constraint(storage_unit):
storage_unit.outputs["capacity_pos"][start] = 30
storage_unit.outputs["soc"][start - timedelta(hours=1)] = 0.05
- assert (
- storage_unit.outputs["soc"][start - storage_unit.index.freq]
- == 0.05
- )
+ assert storage_unit.outputs["soc"][start - storage_unit.index.freq] == 0.05
min_power_discharge, max_power_discharge = storage_unit.calculate_min_max_discharge(
start, end
)
@@ -242,9 +239,7 @@ def test_storage_ramping(storage_unit):
max_ramp_discharge = storage_unit.calculate_ramp_discharge(
0.5, 60, max_power_discharge[0]
)
- max_ramp_charge = storage_unit.calculate_ramp_charge(
- 0.5, 60, max_power_charge[0]
- )
+ max_ramp_charge = storage_unit.calculate_ramp_charge(0.5, 60, max_power_charge[0])
assert max_ramp_discharge == 100
assert max_ramp_charge == -60
@@ -259,9 +254,7 @@ def test_storage_ramping(storage_unit):
max_ramp_discharge = storage_unit.calculate_ramp_discharge(
0.5, -60, max_power_discharge[0]
)
- max_ramp_charge = storage_unit.calculate_ramp_charge(
- 0.5, -60, max_power_charge[0]
- )
+ max_ramp_charge = storage_unit.calculate_ramp_charge(0.5, -60, max_power_charge[0])
assert max_ramp_discharge == 60
assert max_ramp_charge == -100
@@ -442,7 +435,9 @@ def test_set_dispatch_plan_multi_hours(mock_market_config, storage_unit):
delta_set_dispatch = (
storage_unit.outputs["energy"][s] / storage_unit.efficiency_discharge
)
- assert math.isclose(delta_set_dispatch, delta_soc_set_dispatch)
+ assert math.isclose(
+ delta_set_dispatch / storage_unit.capacity, delta_soc_set_dispatch
+ )
# test if it is executed correctly, which should be the same with the mock market config only covering one market
storage_unit.execute_current_dispatch(start, end)
@@ -458,7 +453,7 @@ def test_set_dispatch_plan_multi_hours(mock_market_config, storage_unit):
delta = (
storage_unit.outputs["energy"][s] / storage_unit.efficiency_discharge
)
- assert math.isclose(delta, delta_soc)
+ assert math.isclose(delta / storage_unit.capacity, delta_soc)
# check that deltas are the same, which again must be due to only one considered market
assert math.isclose(delta_soc_set_dispatch, delta_soc)
From f9e3e2e20c527962dd05f397b8712a6b09acd11f Mon Sep 17 00:00:00 2001
From: mthede
Date: Mon, 22 Dec 2025 14:43:34 +0100
Subject: [PATCH 16/24] run pre-commit
---
assume/common/base.py | 8 ++++++--
assume/strategies/learning_strategies.py | 4 +++-
assume/units/dst_components.py | 13 ++++++++-----
assume/units/storage.py | 19 +++++++++++++++----
examples/inputs/example_01g/storage_units.csv | 2 +-
examples/inputs/example_03c/storage_units.csv | 2 +-
tests/test_building.py | 4 ++--
tests/test_drl_storage_strategy.py | 5 ++++-
8 files changed, 40 insertions(+), 17 deletions(-)
diff --git a/assume/common/base.py b/assume/common/base.py
index cc3371f5c..aba6a05bd 100644
--- a/assume/common/base.py
+++ b/assume/common/base.py
@@ -661,7 +661,9 @@ def set_dispatch_plan(
if current_power > max_soc_discharge:
current_power = max_soc_discharge
- delta_soc = (-current_power * time_delta / self.efficiency_discharge) / self.capacity
+ delta_soc = (
+ -current_power * time_delta / self.efficiency_discharge
+ ) / self.capacity
# charging
elif current_power < 0:
@@ -670,7 +672,9 @@ def set_dispatch_plan(
if current_power < max_soc_charge:
current_power = max_soc_charge
- delta_soc = (-current_power * time_delta * self.efficiency_charge) / self.capacity
+ delta_soc = (
+ -current_power * time_delta * self.efficiency_charge
+ ) / self.capacity
# update the values of the state of charge and the energy
self.outputs["soc"].at[next_t] = soc + delta_soc
diff --git a/assume/strategies/learning_strategies.py b/assume/strategies/learning_strategies.py
index bcd9ff1cd..02f2c7415 100644
--- a/assume/strategies/learning_strategies.py
+++ b/assume/strategies/learning_strategies.py
@@ -1083,7 +1083,9 @@ def calculate_reward(
# increase costs of current SoC by price for buying energy
# not fully representing the true cost per MWh (e.g. omitting discharge efficiency losses), but serving as a proxy for it
unit.outputs["cost_stored_energy"].at[next_time] = (
- unit.outputs["cost_stored_energy"].at[start] * current_soc * unit.capacity
+ unit.outputs["cost_stored_energy"].at[start]
+ * current_soc
+ * unit.capacity
- (accepted_price + marginal_cost) * accepted_volume * duration_hours
) / (next_soc * unit.capacity)
else:
diff --git a/assume/units/dst_components.py b/assume/units/dst_components.py
index f745d7c08..1fe9a1f78 100644
--- a/assume/units/dst_components.py
+++ b/assume/units/dst_components.py
@@ -520,7 +520,9 @@ def add_to_model(
model_block.max_power_charge = pyo.Param(initialize=self.max_power_charge)
model_block.max_power_discharge = pyo.Param(initialize=self.max_power_discharge)
model_block.efficiency_charge = pyo.Param(initialize=self.efficiency_charge)
- model_block.efficiency_discharge = pyo.Param(initialize=self.efficiency_discharge)
+ model_block.efficiency_discharge = pyo.Param(
+ initialize=self.efficiency_discharge
+ )
model_block.ramp_up = pyo.Param(initialize=self.ramp_up)
model_block.ramp_down = pyo.Param(initialize=self.ramp_down)
model_block.initial_soc = pyo.Param(initialize=self.initial_soc)
@@ -556,10 +558,11 @@ def soc_balance_rule(b, t):
return b.soc[t] == (
prev_soc
+ (
- b.efficiency_charge * b.charge[t]
- - (1 / b.efficiency_discharge) * b.discharge[t]
- - b.storage_loss_rate * prev_soc * b.capacity
- ) / b.capacity
+ b.efficiency_charge * b.charge[t]
+ - (1 / b.efficiency_discharge) * b.discharge[t]
+ - b.storage_loss_rate * prev_soc * b.capacity
+ )
+ / b.capacity
)
# Apply ramp-up constraints if ramp_up is specified
diff --git a/assume/units/storage.py b/assume/units/storage.py
index 95e3c5c5d..5b3f73b33 100644
--- a/assume/units/storage.py
+++ b/assume/units/storage.py
@@ -111,7 +111,9 @@ def __init__(
if initial_soc is None:
initial_soc = 0.5
if not 0 <= initial_soc <= 1:
- raise ValueError(f"{initial_soc=} must be between 0 and 1 for unit {self.id}")
+ raise ValueError(
+ f"{initial_soc=} must be between 0 and 1 for unit {self.id}"
+ )
self.initial_soc = initial_soc
if max_power_charge > 0:
@@ -237,7 +239,9 @@ def execute_current_dispatch(self, start: datetime, end: datetime) -> np.ndarray
if current_power > max_soc_discharge:
current_power = max_soc_discharge
- delta_soc = (-current_power * time_delta / self.efficiency_discharge) / self.capacity
+ delta_soc = (
+ -current_power * time_delta / self.efficiency_discharge
+ ) / self.capacity
# charging
elif current_power < 0:
@@ -246,7 +250,9 @@ def execute_current_dispatch(self, start: datetime, end: datetime) -> np.ndarray
if current_power < max_soc_charge:
current_power = max_soc_charge
- delta_soc = (-current_power * time_delta * self.efficiency_charge) / self.capacity
+ delta_soc = (
+ -current_power * time_delta * self.efficiency_charge
+ ) / self.capacity
# update the values of the state of charge and the energy
next_freq = t + self.index.freq
@@ -296,7 +302,12 @@ def calculate_soc_max_discharge(self, soc) -> float:
duration = self.index.freq / timedelta(hours=1)
power = max(
0,
- ((soc - self.min_soc) * self.capacity * self.efficiency_discharge / duration),
+ (
+ (soc - self.min_soc)
+ * self.capacity
+ * self.efficiency_discharge
+ / duration
+ ),
)
return power
diff --git a/examples/inputs/example_01g/storage_units.csv b/examples/inputs/example_01g/storage_units.csv
index 735a9adbb..b329ab731 100644
--- a/examples/inputs/example_01g/storage_units.csv
+++ b/examples/inputs/example_01g/storage_units.csv
@@ -1,3 +1,3 @@
name,technology,bidding_energy,bidding_capacity_pos,bidding_capacity_neg,max_power_charge,max_power_discharge,efficiency_charge,efficiency_discharge,min_soc,max_soc,capacity,variable_cost_charge,variable_cost_discharge,natural_inflow,unit_operator
storage_unit,PSPP,storage_energy_heuristic_flexable,-,-,490,452.9,0.87,0.92,0.0,1.0,3528.8,0.28,0.28,0,storage_operator
-battery_unit,li-ion_battery,storage_energy_heuristic_flexable,-,-,100,90,0.95,0.95,0.0,1.0,190,30,30,0,battery_operator
\ No newline at end of file
+battery_unit,li-ion_battery,storage_energy_heuristic_flexable,-,-,100,90,0.95,0.95,0.0,1.0,190,30,30,0,battery_operator
diff --git a/examples/inputs/example_03c/storage_units.csv b/examples/inputs/example_03c/storage_units.csv
index 7afb4dee6..97d5ca3fa 100644
--- a/examples/inputs/example_03c/storage_units.csv
+++ b/examples/inputs/example_03c/storage_units.csv
@@ -23,4 +23,4 @@ Säckingen,PSPP,storage_energy_learning,301,353,0.86,0.91,0,1,2064,0.28,0.28,ENB
Waldeck II,PSPP,storage_energy_learning,476,440,0.87,0.92,0,1,3428,0.28,0.28,UNIPER
Wehr,PSPP,storage_energy_learning,1000,992,0.86,0.9,0,1,6076,0.28,0.28,RWE POWER AG
Markersbach,PSPP,storage_energy_learning,1140,1050,0.84,0.89,0,1,4018,0.28,0.28,VATTENFALL EUROPE AG
-Goldisthal,PSPP,storage_energy_learning,1140,1060,0.88,0.92,0,1,8480,0.28,0.28,VATTENFALL EUROPE AG
\ No newline at end of file
+Goldisthal,PSPP,storage_energy_learning,1140,1060,0.88,0.92,0,1,8480,0.28,0.28,VATTENFALL EUROPE AG
diff --git a/tests/test_building.py b/tests/test_building.py
index 3cd66c26e..28300a701 100644
--- a/tests/test_building.py
+++ b/tests/test_building.py
@@ -19,7 +19,7 @@ def generic_storage_config():
return {
"capacity": 100, # Maximum energy capacity in MWh
"min_soc": 0, # Minimum SOC
- "max_soc":1, # Maximum SOC
+ "max_soc": 1, # Maximum SOC
"max_power_charge": 100, # Maximum charging power in MW
"max_power_discharge": 100, # Maximum discharging power in MW
"efficiency_charge": 0.9, # Charging efficiency
@@ -39,7 +39,7 @@ def thermal_storage_config(generic_storage_config):
@pytest.fixture
def ev_config():
return {
- "capacity": 10.0, # EV battery capacity in MWh
+ "capacity": 10.0, # EV battery capacity in MWh
"min_soc": 0,
"max_soc": 1,
"max_power_charge": 3, # Charge values will reflect a fraction of the capacity
diff --git a/tests/test_drl_storage_strategy.py b/tests/test_drl_storage_strategy.py
index 174b0580d..f4aeb80b5 100644
--- a/tests/test_drl_storage_strategy.py
+++ b/tests/test_drl_storage_strategy.py
@@ -289,8 +289,11 @@ def test_storage_rl_strategy_buy_bid(mock_market_config, storage_unit):
f"Expected costs {expected_costs}, got {costs[0]}"
)
+
@pytest.mark.require_learning
-def test_storage_rl_strategy_soc_and_cost_stored_energy(mock_market_config, storage_unit):
+def test_storage_rl_strategy_soc_and_cost_stored_energy(
+ mock_market_config, storage_unit
+):
"""
Test the StorageEnergyLearningStrategy if unique observations are created as expected.
"""
From 684e4663586b2bfbdf7a58382f59525370f50bba Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Tue, 23 Dec 2025 10:05:27 +0100
Subject: [PATCH 17/24] added hint on use of max_soc and min_soc in release
notes
---
docs/source/release_notes.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst
index 6e9a82179..e0f81a1c0 100644
--- a/docs/source/release_notes.rst
+++ b/docs/source/release_notes.rst
@@ -23,7 +23,7 @@ Upcoming Release
**Improvements:**
- **Application of new naming convention for bidding strategies**: [unit]_[market]_[method]_[comment] for bidding strategy keys (in snake_case) and [Unit][Market][Method][Comment]Strategy for bidding strategy classes (in PascalCase for classes)
-- **Changed SoC Definition**: The state of charge (SoC) for storage units is now defined to take values between 0 and 1, instead of absolute energy content (MWh). This change ensures consistency with other models and standard definition. The absolute energy content can still be calculated by multiplying SoC with the unit's capacity. The previous 'max_soc' is renamed to 'capacity'.
+- **Changed SoC Definition**: The state of charge (SoC) for storage units is now defined to take values between 0 and 1, instead of absolute energy content (MWh). This change ensures consistency with other models and standard definition. The absolute energy content can still be calculated by multiplying SoC with the unit's capacity. The previous 'max_soc' is renamed to 'capacity'. 'max_soc' and 'min_soc' can still be used to model allowed SoC ranges, but are now defined between 0 and 1 as well.
- **Restructured learning_role tasks**: Major learning changes that make learning application more generalizable across the framework.
- **Simplified learning data flow:** Removed the special ``learning_unit_operator`` that previously aggregated unit data and forwarded it to the learning role. Eliminates the single-sender dependency and avoids double bookkeeping across units and operators.
From 436eed2209a26a08f34b19881be144c10fe166dc Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Tue, 23 Dec 2025 10:05:56 +0100
Subject: [PATCH 18/24] added check to loader_csv to have 'capacity' as column
in storage_units.csv
---
assume/scenario/loader_csv.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/assume/scenario/loader_csv.py b/assume/scenario/loader_csv.py
index c58f86fb9..5a2f296a5 100644
--- a/assume/scenario/loader_csv.py
+++ b/assume/scenario/loader_csv.py
@@ -515,6 +515,8 @@ def load_config_and_create_forecaster(
storage_units["max_power_charge"] = -abs(storage_units["max_power_charge"])
if "min_power_charge" in storage_units.columns:
storage_units["min_power_charge"] = -abs(storage_units["min_power_charge"])
+ if not "capacity" in storage_units.columns:
+ raise ValueError("No capacity column provided for storage units!")
# Initialize an empty dictionary to combine the DSM units
dsm_units = {}
From 3402d9b5d6def2120ac76e22475cc2bc5dbbfe72 Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Tue, 23 Dec 2025 10:15:02 +0100
Subject: [PATCH 19/24] pre-commit
---
assume/scenario/loader_csv.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assume/scenario/loader_csv.py b/assume/scenario/loader_csv.py
index 5a2f296a5..078386fa8 100644
--- a/assume/scenario/loader_csv.py
+++ b/assume/scenario/loader_csv.py
@@ -515,7 +515,7 @@ def load_config_and_create_forecaster(
storage_units["max_power_charge"] = -abs(storage_units["max_power_charge"])
if "min_power_charge" in storage_units.columns:
storage_units["min_power_charge"] = -abs(storage_units["min_power_charge"])
- if not "capacity" in storage_units.columns:
+ if "capacity" not in storage_units.columns:
raise ValueError("No capacity column provided for storage units!")
# Initialize an empty dictionary to combine the DSM units
From bbf187f80d293cde51afa641b234e7ce8e973860 Mon Sep 17 00:00:00 2001
From: kim-mskw
Date: Tue, 23 Dec 2025 11:40:58 +0100
Subject: [PATCH 20/24] - adjust SoC plot in dashboard and use % and capacity
---
.../ASSUME Comparison.json | 566 +++++++++---------
.../dashboard-definitions/ASSUME.json | 20 +-
2 files changed, 300 insertions(+), 286 deletions(-)
diff --git a/docker_configs/dashboard-definitions/ASSUME Comparison.json b/docker_configs/dashboard-definitions/ASSUME Comparison.json
index a3b843f68..a25ff7765 100644
--- a/docker_configs/dashboard-definitions/ASSUME Comparison.json
+++ b/docker_configs/dashboard-definitions/ASSUME Comparison.json
@@ -29,10 +29,6 @@
"panels": [
{
"description": "",
- "fieldConfig": {
- "defaults": {},
- "overrides": []
- },
"gridPos": {
"h": 6,
"w": 24,
@@ -49,7 +45,7 @@
"content": "# Welcome to our ASSUME Simulation Comparison Demo\n\nThis is the Grafana Dashboard helps you to compare the results of two simulation. Please note, this dashboard is still under development. Here you can visualize the differences between two simulations. So if some/most of the plots are 0 it just means that the results of your simulation are the same. \n\nYou are currently displaying the difference between:\n##### ${simulation_comp} - ${simulation}",
"mode": "markdown"
},
- "pluginVersion": "11.3.1",
+ "pluginVersion": "11.0.1",
"title": "Overview Dashboard",
"type": "text"
},
@@ -67,10 +63,6 @@
"type": "row"
},
{
- "fieldConfig": {
- "defaults": {},
- "overrides": []
- },
"gridPos": {
"h": 3,
"w": 24,
@@ -87,8 +79,7 @@
"content": "# Market-specific Data\n\nData specific for the market depending on the choice made at te top of the panel\n\n",
"mode": "markdown"
},
- "pluginVersion": "11.3.1",
- "title": "",
+ "pluginVersion": "11.0.1",
"type": "text"
},
{
@@ -108,7 +99,6 @@
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
- "barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
@@ -166,6 +156,7 @@
"showLegend": true
},
"tooltip": {
+ "maxHeight": 600,
"mode": "multi",
"sort": "none"
}
@@ -238,6 +229,7 @@
"mode": "palette-classic"
},
"custom": {
+ "axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
@@ -251,6 +243,7 @@
"tooltip": false,
"viz": false
},
+ "insertNulls": false,
"lineInterpolation": "stepAfter",
"lineStyle": {
"fill": "solid"
@@ -344,6 +337,7 @@
""
],
"tooltip": {
+ "maxHeight": 600,
"mode": "multi",
"sort": "none"
}
@@ -437,7 +431,6 @@
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
- "barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
@@ -496,6 +489,7 @@
"showLegend": true
},
"tooltip": {
+ "maxHeight": 600,
"mode": "single",
"sort": "none"
}
@@ -616,7 +610,6 @@
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
- "barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
@@ -677,6 +670,7 @@
"showLegend": true
},
"tooltip": {
+ "maxHeight": 600,
"mode": "multi",
"sort": "desc"
}
@@ -896,6 +890,7 @@
"showValue": "never",
"stacking": "none",
"tooltip": {
+ "maxHeight": 600,
"mode": "multi",
"sort": "none"
},
@@ -1024,7 +1019,7 @@
}
]
},
- "pluginVersion": "11.3.1",
+ "pluginVersion": "11.0.1",
"targets": [
{
"datasource": {
@@ -1112,6 +1107,7 @@
"values": true
},
"tooltip": {
+ "maxHeight": 600,
"mode": "single",
"sort": "none"
}
@@ -1189,10 +1185,6 @@
},
{
"description": "",
- "fieldConfig": {
- "defaults": {},
- "overrides": []
- },
"gridPos": {
"h": 3,
"w": 24,
@@ -1209,8 +1201,7 @@
"content": "# Unit Specific Data\n\nFor the chosen market and the chosen unit here the dispatch is displayed.",
"mode": "markdown"
},
- "pluginVersion": "11.3.1",
- "title": "",
+ "pluginVersion": "11.0.1",
"type": "text"
},
{
@@ -1230,7 +1221,6 @@
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
- "barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
@@ -1292,6 +1282,7 @@
"showLegend": true
},
"tooltip": {
+ "maxHeight": 600,
"mode": "multi",
"sort": "desc"
}
@@ -1452,7 +1443,7 @@
"styles": "",
"wrap": true
},
- "pluginVersion": "5.4.0",
+ "pluginVersion": "6.2.0",
"targets": [
{
"datasource": {
@@ -1508,7 +1499,6 @@
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
- "barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
@@ -1580,6 +1570,7 @@
"showLegend": true
},
"tooltip": {
+ "maxHeight": 600,
"mode": "multi",
"sort": "desc"
}
@@ -1697,10 +1688,6 @@
},
{
"description": "",
- "fieldConfig": {
- "defaults": {},
- "overrides": []
- },
"gridPos": {
"h": 3,
"w": 24,
@@ -1717,8 +1704,7 @@
"content": "# Unit Specific Data\n\nFor the chosen market and the chosen unit here the dispatch is displayed.",
"mode": "markdown"
},
- "pluginVersion": "11.3.1",
- "title": "",
+ "pluginVersion": "11.0.1",
"type": "text"
},
{
@@ -1738,7 +1724,6 @@
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
- "barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
@@ -1797,6 +1782,7 @@
"showLegend": true
},
"tooltip": {
+ "maxHeight": 600,
"mode": "multi",
"sort": "desc"
}
@@ -1938,7 +1924,7 @@
"styles": "",
"wrap": true
},
- "pluginVersion": "5.4.0",
+ "pluginVersion": "6.2.0",
"targets": [
{
"datasource": {
@@ -1977,7 +1963,7 @@
"type": "marcusolsson-dynamictext-panel"
},
{
- "collapsed": true,
+ "collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
@@ -1985,276 +1971,279 @@
"y": 83
},
"id": 44,
- "panels": [
- {
- "description": "",
- "fieldConfig": {
- "defaults": {},
- "overrides": []
- },
- "gridPos": {
- "h": 3,
- "w": 24,
- "x": 0,
- "y": 82
+ "panels": [],
+ "repeat": "Storage_Units",
+ "title": "Storage units data $Storage_Units",
+ "type": "row"
+ },
+ {
+ "description": "",
+ "gridPos": {
+ "h": 3,
+ "w": 24,
+ "x": 0,
+ "y": 84
+ },
+ "id": 45,
+ "options": {
+ "code": {
+ "language": "plaintext",
+ "showLineNumbers": false,
+ "showMiniMap": false
+ },
+ "content": "# Unit Specific Data\n\nFor the chosen market and the chosen storage unit here the dispatch is displayed.",
+ "mode": "markdown"
+ },
+ "pluginVersion": "11.0.1",
+ "type": "text"
+ },
+ {
+ "datasource": {
+ "type": "postgres",
+ "uid": "P7B13B9DF907EC40C"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
},
- "id": 45,
- "options": {
- "code": {
- "language": "plaintext",
- "showLineNumbers": false,
- "showMiniMap": false
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "smooth",
+ "lineWidth": 2,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "always",
+ "spanNulls": true,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
},
- "content": "# Unit Specific Data\n\nFor the chosen market and the chosen storage unit here the dispatch is displayed.",
- "mode": "markdown"
+ "thresholdsStyle": {
+ "mode": "off"
+ }
},
- "pluginVersion": "9.2.15",
- "title": "",
- "type": "text"
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "megwatt"
},
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 18,
+ "x": 0,
+ "y": 87
+ },
+ "id": 65,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "maxHeight": 600,
+ "mode": "multi",
+ "sort": "desc"
+ }
+ },
+ "targets": [
{
"datasource": {
"type": "postgres",
"uid": "P7B13B9DF907EC40C"
},
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisCenteredZero": false,
- "axisColorMode": "text",
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 0,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "viz": false
- },
- "lineInterpolation": "smooth",
- "lineWidth": 2,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "always",
- "spanNulls": true,
- "stacking": {
- "group": "A",
- "mode": "none"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green"
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "megwatt"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 9,
- "w": 18,
- "x": 0,
- "y": 85
- },
- "id": 65,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom",
- "showLegend": true
- },
- "tooltip": {
- "mode": "multi",
- "sort": "desc"
+ "format": "time_series",
+ "group": [],
+ "metricColumn": "none",
+ "rawQuery": true,
+ "rawSql": "select sim1.\"time\", sim2.\"power\" - sim1.\"power\" as \"power\", sim1.unit_id, sim1.market_id from (\nSELECT\n $__timeGroupAlias(datetime,$__interval),\n power AS \"power\",\n unit_id,\n market_id\nFROM market_dispatch\nWHERE\n $__timeFilter(datetime) AND\n simulation = '$simulation' AND\n unit_id in ($Storage_Units)\nGROUP BY 1, unit_id, power, market_id\nORDER BY 1\n) sim1 join\n(SELECT\n $__timeGroupAlias(datetime,$__interval),\n power AS \"power\",\n unit_id,\n market_id\nFROM market_dispatch\nWHERE\n $__timeFilter(datetime) AND\n simulation = '$simulation_comp' AND\n unit_id in ($Storage_Units)\nGROUP BY 1, unit_id, power, market_id\nORDER BY 1) sim2\non sim1.unit_id = sim2.unit_id and sim1.market_id = sim2.market_id and sim1.time=sim2.time",
+ "refId": "A",
+ "select": [
+ [
+ {
+ "params": [
+ "volume"
+ ],
+ "type": "column"
+ }
+ ]
+ ],
+ "table": "demand_meta",
+ "timeColumn": "\"Timestamp\"",
+ "timeColumnType": "timestamp",
+ "where": [
+ {
+ "name": "$__timeFilter",
+ "params": [],
+ "type": "macro"
}
+ ]
+ },
+ {
+ "datasource": {
+ "type": "postgres",
+ "uid": "P7B13B9DF907EC40C"
},
- "targets": [
- {
- "datasource": {
- "type": "postgres",
- "uid": "P7B13B9DF907EC40C"
- },
- "format": "time_series",
- "group": [],
- "metricColumn": "none",
- "rawQuery": true,
- "rawSql": "select sim1.\"time\", sim2.\"power\" - sim1.\"power\" as \"power\", sim1.unit_id, sim1.market_id from (\nSELECT\n $__timeGroupAlias(datetime,$__interval),\n power AS \"power\",\n unit_id,\n market_id\nFROM market_dispatch\nWHERE\n $__timeFilter(datetime) AND\n simulation = '$simulation' AND\n unit_id in ($Storage_Units)\nGROUP BY 1, unit_id, power, market_id\nORDER BY 1\n) sim1 join\n(SELECT\n $__timeGroupAlias(datetime,$__interval),\n power AS \"power\",\n unit_id,\n market_id\nFROM market_dispatch\nWHERE\n $__timeFilter(datetime) AND\n simulation = '$simulation_comp' AND\n unit_id in ($Storage_Units)\nGROUP BY 1, unit_id, power, market_id\nORDER BY 1) sim2\non sim1.unit_id = sim2.unit_id and sim1.market_id = sim2.market_id and sim1.time=sim2.time",
- "refId": "A",
- "select": [
- [
- {
- "params": [
- "volume"
- ],
- "type": "column"
- }
- ]
- ],
- "table": "demand_meta",
- "timeColumn": "\"Timestamp\"",
- "timeColumnType": "timestamp",
- "where": [
- {
- "name": "$__timeFilter",
- "params": [],
- "type": "macro"
- }
- ]
- },
+ "format": "time_series",
+ "group": [],
+ "hide": false,
+ "metricColumn": "none",
+ "rawQuery": true,
+ "rawSql": "select sim1.\"time\", sim2.\"dispatch\" - sim1.\"dispatch\" as \"dispatch\", sim1.unit from (\nSELECT\n $__timeGroupAlias(time,$__interval),\n power AS \"dispatch\",\n unit\nFROM unit_dispatch\nWHERE\n $__timeFilter(index) AND\n simulation = '$simulation' AND\n unit in ($Storage_Units)\nGROUP BY 1, unit, power\nORDER BY 1\n) sim1\njoin (\nSELECT\n $__timeGroupAlias(time,$__interval),\n power AS \"dispatch\",\n unit\nFROM unit_dispatch\nWHERE\n $__timeFilter(index) AND\n simulation = '$simulation_comp' AND\n unit in ($Storage_Units)\nGROUP BY 1, unit, power\nORDER BY 1\n) sim2\non sim1.unit = sim2.unit and sim1.time=sim2.time",
+ "refId": "B",
+ "select": [
+ [
+ {
+ "params": [
+ "power"
+ ],
+ "type": "column"
+ }
+ ]
+ ],
+ "table": "market_dispatch",
+ "timeColumn": "datetime",
+ "timeColumnType": "timestamp",
+ "where": [
{
- "datasource": {
- "type": "postgres",
- "uid": "P7B13B9DF907EC40C"
- },
- "format": "time_series",
- "group": [],
- "hide": false,
- "metricColumn": "none",
- "rawQuery": true,
- "rawSql": "select sim1.\"time\", sim2.\"dispatch\" - sim1.\"dispatch\" as \"dispatch\", sim1.unit from (\nSELECT\n $__timeGroupAlias(time,$__interval),\n power AS \"dispatch\",\n unit\nFROM unit_dispatch\nWHERE\n $__timeFilter(index) AND\n simulation = '$simulation' AND\n unit in ($Storage_Units)\nGROUP BY 1, unit, power\nORDER BY 1\n) sim1\njoin (\nSELECT\n $__timeGroupAlias(time,$__interval),\n power AS \"dispatch\",\n unit\nFROM unit_dispatch\nWHERE\n $__timeFilter(index) AND\n simulation = '$simulation_comp' AND\n unit in ($Storage_Units)\nGROUP BY 1, unit, power\nORDER BY 1\n) sim2\non sim1.unit = sim2.unit and sim1.time=sim2.time",
- "refId": "B",
- "select": [
- [
- {
- "params": [
- "power"
- ],
- "type": "column"
- }
- ]
- ],
- "table": "market_dispatch",
- "timeColumn": "datetime",
- "timeColumnType": "timestamp",
- "where": [
- {
- "name": "$__timeFilter",
- "params": [],
- "type": "macro"
- }
- ]
+ "name": "$__timeFilter",
+ "params": [],
+ "type": "macro"
}
- ],
- "title": "Storage Dispatch",
- "type": "timeseries"
+ ]
+ }
+ ],
+ "title": "Storage Dispatch",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "postgres",
+ "uid": "P7B13B9DF907EC40C"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
},
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 6,
+ "x": 18,
+ "y": 87
+ },
+ "id": 47,
+ "options": {
+ "afterRender": "",
+ "content": "### General Information\n\nName: {{index}}
\nTechnology: {{technology}}
\n\n### Technical Specifications\nEmissions: {{emission_factor}} t/MWh
\nCapacity: {{capacity}} MWh
\nMaximum Power Disharge: {{max_power_discharge}} MW
\nMinimum Power Discharge: {{min_power_discharge}} MW
\nEfficiency Discharge: {{efficiency_discharge}} %
\n\nMaximum Power Charge: {{max_power_charge}} MW
\nMinimum Power Charge: {{min_power_charge}} MW
\nEfficiency Charge: {{efficiency_charge}} %
\n\n##### Unit Operator: {{unit_operator}}\n ",
+ "contentPartials": [],
+ "defaultContent": "The query didn't return any results.",
+ "editor": {
+ "format": "auto",
+ "height": 200,
+ "language": "markdown"
+ },
+ "editors": [],
+ "externalScripts": [],
+ "externalStyles": [],
+ "helpers": "",
+ "renderMode": "everyRow",
+ "styles": "",
+ "wrap": true
+ },
+ "pluginVersion": "6.2.0",
+ "targets": [
{
"datasource": {
"type": "postgres",
"uid": "P7B13B9DF907EC40C"
},
- "fieldConfig": {
- "defaults": {
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green"
- },
- {
- "color": "red",
- "value": 80
- }
- ]
+ "format": "table",
+ "group": [],
+ "metricColumn": "none",
+ "rawQuery": true,
+ "rawSql": "SELECT * FROM storage_meta\nWHERE index in ($Storage_Units) and simulation = '$simulation'\n",
+ "refId": "A",
+ "select": [
+ [
+ {
+ "params": [
+ "volume"
+ ],
+ "type": "column"
}
- },
- "overrides": []
- },
- "gridPos": {
- "h": 9,
- "w": 6,
- "x": 18,
- "y": 85
- },
- "id": 47,
- "options": {
- "content": "### General Information\n\nName: {{index}}
\nTechnology: {{technology}}
\n\n### Technical Specifications\nEmissions: {{emission_factor}} t/MWh
\nMaximum Power Disharge: {{max_power_discharge}} MW
\nMinimum Power Discharge: {{min_power_discharge}} MW
\nEfficiency Discharge: {{efficiency_discharge}}
\n\nMaximum Power Charge: {{max_power_charge}} MW
\nMinimum Power Charge: {{min_power_charge}} MW
\nEfficiency Charge: {{efficiency_charge}}
\n\n##### Unit Operator: {{unit_operator}}\n ",
- "defaultContent": "The query didn't return any results.",
- "editor": {
- "format": "auto",
- "height": 200,
- "language": "markdown"
- },
- "editors": [],
- "everyRow": true,
- "externalScripts": [],
- "externalStyles": [],
- "helpers": "",
- "styles": ""
- },
- "targets": [
+ ]
+ ],
+ "table": "demand_meta",
+ "timeColumn": "\"Timestamp\"",
+ "timeColumnType": "timestamp",
+ "where": [
{
- "datasource": {
- "type": "postgres",
- "uid": "P7B13B9DF907EC40C"
- },
- "format": "table",
- "group": [],
- "metricColumn": "none",
- "rawQuery": true,
- "rawSql": "SELECT * FROM storage_meta\nWHERE index in ($Storage_Units) and simulation = '$simulation'\n",
- "refId": "A",
- "select": [
- [
- {
- "params": [
- "volume"
- ],
- "type": "column"
- }
- ]
- ],
- "table": "demand_meta",
- "timeColumn": "\"Timestamp\"",
- "timeColumnType": "timestamp",
- "where": [
- {
- "name": "$__timeFilter",
- "params": [],
- "type": "macro"
- }
- ]
+ "name": "$__timeFilter",
+ "params": [],
+ "type": "macro"
}
- ],
- "title": "Chosen Unit Specifications",
- "type": "marcusolsson-dynamictext-panel"
+ ]
}
],
- "repeat": "Storage_Units",
- "title": "Storage units data $Storage_Units",
- "type": "row"
+ "title": "Chosen Unit Specifications",
+ "type": "marcusolsson-dynamictext-panel"
}
],
- "preload": false,
"refresh": "",
- "schemaVersion": 40,
+ "schemaVersion": 39,
"tags": [],
"templating": {
"list": [
{
"current": {
- "text": "example_01a_base",
- "value": "example_01a_base"
+ "selected": false,
+ "text": "example_01c_eom_only",
+ "value": "example_01c_eom_only"
},
"datasource": {
"type": "postgres",
@@ -2262,19 +2251,23 @@
},
"definition": "SELECT \n simulation\nFROM power_plant_meta\ngroup by simulation;",
"description": "Can choose which simulation we want to show ",
+ "hide": 0,
"includeAll": false,
+ "multi": false,
"name": "simulation",
"options": [],
"query": "SELECT \n simulation\nFROM power_plant_meta\ngroup by simulation;",
"refresh": 1,
"regex": "",
+ "skipUrlSync": false,
"sort": 1,
"type": "query"
},
{
"current": {
- "text": "example_01a_base",
- "value": "example_01a_base"
+ "selected": true,
+ "text": "example_01d_base",
+ "value": "example_01d_base"
},
"datasource": {
"type": "postgres",
@@ -2282,17 +2275,21 @@
},
"definition": "SELECT \n simulation\nFROM power_plant_meta\ngroup by simulation;",
"description": "Simulation to which a comparison is made",
+ "hide": 0,
"includeAll": false,
+ "multi": false,
"name": "simulation_comp",
"options": [],
"query": "SELECT \n simulation\nFROM power_plant_meta\ngroup by simulation;",
"refresh": 1,
"regex": "",
+ "skipUrlSync": false,
"sort": 1,
"type": "query"
},
{
"current": {
+ "selected": false,
"text": "EOM",
"value": "EOM"
},
@@ -2302,17 +2299,21 @@
},
"definition": "SELECT \n market_id\nFROM market_meta\nwhere simulation='$simulation'\ngroup by market_id ;",
"description": "Choose for which market the data is displayed",
+ "hide": 0,
"includeAll": false,
+ "multi": false,
"name": "market",
"options": [],
"query": "SELECT \n market_id\nFROM market_meta\nwhere simulation='$simulation'\ngroup by market_id ;",
"refresh": 2,
"regex": "",
+ "skipUrlSync": false,
"sort": 1,
"type": "query"
},
{
"current": {
+ "selected": true,
"text": [
"Unit 1"
],
@@ -2326,6 +2327,7 @@
},
"definition": "SELECT index\nFROM power_plant_meta\nwhere simulation = '$simulation';",
"description": "Can choose which units we want to display ",
+ "hide": 0,
"includeAll": false,
"multi": true,
"name": "Gen_Units",
@@ -2333,11 +2335,13 @@
"query": "SELECT index\nFROM power_plant_meta\nwhere simulation = '$simulation';",
"refresh": 2,
"regex": "",
+ "skipUrlSync": false,
"sort": 1,
"type": "query"
},
{
"current": {
+ "selected": true,
"text": [
"demand_EOM"
],
@@ -2351,6 +2355,7 @@
},
"definition": "SELECT index\nFROM demand_meta\nwhere simulation = '$simulation';",
"description": "Can choose which units we want to display ",
+ "hide": 0,
"includeAll": false,
"multi": true,
"name": "Demand_Units",
@@ -2358,13 +2363,19 @@
"query": "SELECT index\nFROM demand_meta\nwhere simulation = '$simulation';",
"refresh": 2,
"regex": "",
+ "skipUrlSync": false,
"sort": 1,
"type": "query"
},
{
"current": {
- "text": [],
- "value": []
+ "selected": true,
+ "text": [
+ "Storage 1"
+ ],
+ "value": [
+ "Storage 1"
+ ]
},
"datasource": {
"type": "postgres",
@@ -2372,6 +2383,7 @@
},
"definition": "SELECT index\nFROM storage_meta\nwhere simulation = '$simulation';",
"description": "Can choose which storage units we want to display ",
+ "hide": 0,
"includeAll": false,
"multi": true,
"name": "Storage_Units",
@@ -2379,6 +2391,7 @@
"query": "SELECT index\nFROM storage_meta\nwhere simulation = '$simulation';",
"refresh": 2,
"regex": "",
+ "skipUrlSync": false,
"sort": 1,
"type": "query"
}
@@ -2388,10 +2401,11 @@
"from": "2019-02-28T23:00:00.000Z",
"to": "2019-04-01T21:59:59.000Z"
},
+ "timeRangeUpdatedDuringEditOrView": false,
"timepicker": {},
"timezone": "",
"title": "ASSUME: Compare scenarios",
"uid": "vP8U8-q4k",
- "version": 5,
+ "version": 2,
"weekStart": ""
}
diff --git a/docker_configs/dashboard-definitions/ASSUME.json b/docker_configs/dashboard-definitions/ASSUME.json
index 7d9278a69..6c124ef7b 100644
--- a/docker_configs/dashboard-definitions/ASSUME.json
+++ b/docker_configs/dashboard-definitions/ASSUME.json
@@ -24,7 +24,7 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
- "id": 6,
+ "id": 2,
"links": [],
"panels": [
{
@@ -2041,7 +2041,7 @@
"styles": "",
"wrap": true
},
- "pluginVersion": "5.7.0",
+ "pluginVersion": "6.2.0",
"targets": [
{
"datasource": {
@@ -3125,7 +3125,7 @@
"styles": "",
"wrap": true
},
- "pluginVersion": "5.7.0",
+ "pluginVersion": "6.2.0",
"targets": [
{
"datasource": {
@@ -4358,7 +4358,7 @@
"styles": "",
"wrap": true
},
- "pluginVersion": "5.7.0",
+ "pluginVersion": "6.2.0",
"targets": [
{
"datasource": {
@@ -4453,7 +4453,7 @@
}
]
},
- "unit": "mwatth"
+ "unit": "percentunit"
},
"overrides": []
},
@@ -5420,8 +5420,8 @@
{
"current": {
"selected": false,
- "text": "2019-01-01 01:00:00",
- "value": "2019-01-01 01:00:00"
+ "text": "2019-01-01 02:00:00",
+ "value": "2019-01-01 02:00:00"
},
"datasource": {
"type": "grafana-postgresql-datasource",
@@ -5444,8 +5444,8 @@
]
},
"time": {
- "from": "2019-01-01T00:00:00.000Z",
- "to": "2019-12-01T23:59:59.000Z"
+ "from": "2019-01-01T01:11:53.495Z",
+ "to": "2019-01-31T18:05:53.940Z"
},
"timeRangeUpdatedDuringEditOrView": false,
"timepicker": {
@@ -5461,6 +5461,6 @@
"timezone": "utc",
"title": "ASSUME: Main overview",
"uid": "aemnxld4ukgsga",
- "version": 2,
+ "version": 5,
"weekStart": ""
}
From fbfded07b8446136288fce7455328a9f69374a93 Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Tue, 23 Dec 2025 12:06:11 +0100
Subject: [PATCH 21/24] raise ValueError if SOC > 1
---
assume/units/dst_components.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/assume/units/dst_components.py b/assume/units/dst_components.py
index 1fe9a1f78..e63ac27ba 100644
--- a/assume/units/dst_components.py
+++ b/assume/units/dst_components.py
@@ -450,10 +450,10 @@ def __init__(
# check if initial_soc is within the bounds [0, 1] and fix it if not
if initial_soc > 1:
- initial_soc = 1.0
logger.warning(
- f"Initial SOC is greater than 1.0. Setting it to {initial_soc}."
+ f"Initial SOC is greater than 1.0 but SOC must be between 0 and 1."
)
+ raise ValueError("Initial SOC must be between 0 and 1.")
self.capacity = capacity
self.min_soc = min_soc
From 670cb5b19502f27cbbaa9ebae56b41e9c7a9d44d Mon Sep 17 00:00:00 2001
From: kim-mskw
Date: Tue, 23 Dec 2025 12:29:42 +0100
Subject: [PATCH 22/24] - fix fleaxble startegy definition for DSM example with
min_down_time of zero
---
assume/strategies/flexable.py | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/assume/strategies/flexable.py b/assume/strategies/flexable.py
index 9901445a8..b533a5b0b 100644
--- a/assume/strategies/flexable.py
+++ b/assume/strategies/flexable.py
@@ -503,7 +503,12 @@ def calculate_EOM_price_if_off(
# if we split starting_cost across av_operating_time
# we are never adding the other parts of the cost to the following hours
- markup = starting_cost / avg_operating_time / bid_quantity_inflex
+ # if unit never operated before and min_operating_time is 0, set avg_operating_time is considered to be 1 and hence neglected to avoid division by zero
+ # this lets the power plant only start if it can recover the starting costs in the first hour, which is quite restrictive
+ if avg_operating_time == 0:
+ markup = starting_cost / bid_quantity_inflex
+ else:
+ markup = starting_cost / avg_operating_time / bid_quantity_inflex
bid_price_inflex = min(marginal_cost_inflex + markup, 3000.0)
@@ -546,7 +551,13 @@ def calculate_EOM_price_if_on(
# check the starting cost if the unit were turned off for min_down_time
starting_cost = unit.get_starting_costs(-unit.min_down_time)
- price_reduction_restart = starting_cost / unit.min_down_time / bid_quantity_inflex
+ # disregard unit.min_down_time of 0 to avoid division by zero
+ if unit.min_down_time == 0:
+ price_reduction_restart = starting_cost / bid_quantity_inflex
+ else:
+ price_reduction_restart = (
+ starting_cost / unit.min_down_time / bid_quantity_inflex
+ )
if unit.outputs["heat"].at[start] > 0:
heat_gen_cost = (
From 63edd5b0631bcef54b5ad3ad4691c7227506ea13 Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Tue, 23 Dec 2025 12:35:06 +0100
Subject: [PATCH 23/24] fix ruff test with ValueError string
---
assume/units/dst_components.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assume/units/dst_components.py b/assume/units/dst_components.py
index e63ac27ba..5bf95a53d 100644
--- a/assume/units/dst_components.py
+++ b/assume/units/dst_components.py
@@ -451,7 +451,7 @@ def __init__(
# check if initial_soc is within the bounds [0, 1] and fix it if not
if initial_soc > 1:
logger.warning(
- f"Initial SOC is greater than 1.0 but SOC must be between 0 and 1."
+ "Initial SOC is greater than 1.0 but SOC must be between 0 and 1."
)
raise ValueError("Initial SOC must be between 0 and 1.")
From 708deb2461b9f2817a02ac323b82f663d120155a Mon Sep 17 00:00:00 2001
From: gugrimm <155527339+gugrimm@users.noreply.github.com>
Date: Tue, 23 Dec 2025 12:35:28 +0100
Subject: [PATCH 24/24] minor renaming in 10a
---
examples/inputs/example_01h/residential_dsm_units.csv | 4 ++--
examples/notebooks/10a_DSU_and_flexibility.ipynb | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/examples/inputs/example_01h/residential_dsm_units.csv b/examples/inputs/example_01h/residential_dsm_units.csv
index 10cd805a0..b52b74705 100644
--- a/examples/inputs/example_01h/residential_dsm_units.csv
+++ b/examples/inputs/example_01h/residential_dsm_units.csv
@@ -1,3 +1,3 @@
-name,unit_type,technology,node,bidding_EOM,fuel_type,bidding_redispatch,unit_operator,objective,flexibility_measure,cost_tolerance,charging_profile,uses_power_profile,availability_periods,demand,max_power,min_power,ramp_up,ramp_down,min_operating_time,min_down_time,efficiency,cop,max_capacity,min_capacity,initial_soc,storage_loss_rate,efficiency_charge,efficiency_discharge,max_charging_rate,max_discharging_rate,is_prosumer
+name,unit_type,technology,node,bidding_EOM,fuel_type,bidding_redispatch,unit_operator,objective,flexibility_measure,cost_tolerance,charging_profile,uses_power_profile,availability_periods,demand,max_power,min_power,ramp_up,ramp_down,min_operating_time,min_down_time,efficiency,cop,capacity,min_soc,initial_soc,storage_loss_rate,efficiency_charge,efficiency_discharge,max_charging_rate,max_discharging_rate,is_prosumer
A360,building,heat_pump,north,household_energy_optimization,,,dsm_operator_1,min_variable_cost,cost_based_load_shift,10,,No,,,5,0,5,5,0,0,,2,,,,,,,,,No
-A360,building,generic_storage,north,,,,,,,,,,,,,,0.002,0.002,,,,,0.0362,0.0015,0.0015,,0.9731,0.9731,0.002,0.002,
+A360,building,generic_storage,north,,,,,,,,,,,,,,0.002,0.002,,,,,0.0362,0.0414364,0.0414364,,0.9731,0.9731,0.002,0.002,
diff --git a/examples/notebooks/10a_DSU_and_flexibility.ipynb b/examples/notebooks/10a_DSU_and_flexibility.ipynb
index 34ef00e1d..6eb5d4bb5 100644
--- a/examples/notebooks/10a_DSU_and_flexibility.ipynb
+++ b/examples/notebooks/10a_DSU_and_flexibility.ipynb
@@ -935,7 +935,7 @@
"\n",
"components = {\n",
" \"pv_plant\": {\"max_power\": 10, \"efficiency\": 0.98},\n",
- " \"battery_storage\": {\"max_capacity\": 40, \"max_power_charge\": 5, \"max_power_discharge\": 5, \"efficiency\": 0.9},\n",
+ " \"battery_storage\": {\"capacity\": 40, \"max_power_charge\": 5, \"max_power_discharge\": 5, \"efficiency\": 0.9},\n",
" \"heat_pump\": {\"max_power\": 8, \"min_power\": 2, \"efficiency\": 3.5},\n",
" \"electric_vehicle\": {\"max_power_charge\": 7, \"max_power_discharge\": 7, \"battery_capacity\": 50},\n",
"}\n",
@@ -2477,8 +2477,8 @@
" \"min_down_time\": [0, None],\n",
" \"efficiency\": [0.8, None],\n",
" \"start_price\": [5, None],\n",
- " \"max_capacity\": [None, 200],\n",
- " \"min_capacity\": [None, 0],\n",
+ " \"capacity\": [None, 200],\n",
+ " \"min_soc\": [None, 0],\n",
" \"initial_soc\": [None, 0],\n",
" \"storage_loss_rate\": [None, 0],\n",
" \"charge_loss_rate\": [None, 0],\n",