diff --git a/assume/common/base.py b/assume/common/base.py
index 0b41b7d28..33fb9d28a 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.
@@ -660,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
+ delta_soc = (
+ -current_power * time_delta / self.efficiency_discharge
+ ) / self.capacity
# charging
elif current_power < 0:
@@ -669,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
+ 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/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,
diff --git a/assume/scenario/loader_csv.py b/assume/scenario/loader_csv.py
index c58f86fb9..078386fa8 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 "capacity" not in storage_units.columns:
+ raise ValueError("No capacity column provided for storage units!")
# Initialize an empty dictionary to combine the DSM units
dsm_units = {}
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/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),
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
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(
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 = (
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/assume/strategies/learning_strategies.py b/assume/strategies/learning_strategies.py
index b6a5e10db..a55db0cb0 100644
--- a/assume/strategies/learning_strategies.py
+++ b/assume/strategies/learning_strategies.py
@@ -906,12 +906,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
@@ -1069,15 +1069,17 @@ 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/dst_components.py b/assume/units/dst_components.py
index cac188e96..5bf95a53d 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
logger.warning(
- f"Initial SOC is greater than 1.0. Setting it to {initial_soc}."
+ "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.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,8 +514,9 @@ 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)
@@ -520,16 +525,14 @@ def add_to_model(
)
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 +557,12 @@ def soc_balance_rule(b, t):
prev_soc = b.soc[t - 1]
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.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
@@ -1394,15 +1400,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 +1417,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 +1435,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 +1462,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 +1478,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 +1536,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 +1551,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 +1693,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 +1708,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/assume/units/storage.py b/assume/units/storage.py
index 9e81804ef..5b3f73b33 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,23 @@ 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:
@@ -232,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
+ delta_soc = (
+ -current_power * time_delta / self.efficiency_discharge
+ ) / self.capacity
# charging
elif current_power < 0:
@@ -241,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
+ 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
@@ -283,15 +294,20 @@ 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 +319,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 +342,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 +384,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 +431,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 +466,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 +520,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,
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 f99cb2fb4..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": {
@@ -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": {
@@ -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": ""
}
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 7128cde42..9e9fcf1d7 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'. '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.
- **Direct write access:** All learning-capable entities (units, unit operators, market agents) now write learning data directly to the learning role.
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..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,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
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/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..97d5ca3fa 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
diff --git a/examples/notebooks/04c_reinforcement_learning_storage_example.ipynb b/examples/notebooks/04c_reinforcement_learning_storage_example.ipynb
index 097a3ac40..02b14e8e0 100644
--- a/examples/notebooks/04c_reinforcement_learning_storage_example.ipynb
+++ b/examples/notebooks/04c_reinforcement_learning_storage_example.ipynb
@@ -588,10 +588,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"
]
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",
diff --git a/tests/test_building.py b/tests/test_building.py
index 7746cbc17..28300a701 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_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..f4aeb80b5 100644
--- a/tests/test_drl_storage_strategy.py
+++ b/tests/test_drl_storage_strategy.py
@@ -56,9 +56,10 @@ 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,
+ max_soc=1,
+ initial_soc=0.5,
efficiency_charge=0.9,
efficiency_discharge=0.9,
additional_cost_charge=5,
@@ -290,7 +291,9 @@ def test_storage_rl_strategy_buy_bid(mock_market_config, storage_unit):
@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 +353,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]}"
diff --git a/tests/test_ev.py b/tests/test_ev.py
index 52f64ebfe..696ac48d9 100644
--- a/tests/test_ev.py
+++ b/tests/test_ev.py
@@ -15,13 +15,14 @@
@pytest.fixture
def ev_config():
return {
- "max_capacity": 10.0,
- "min_capacity": 0,
+ "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,
"efficiency_discharge": 0.9,
- "initial_soc": 0, # SOC initialized to 50% of capacity
+ "initial_soc": 0, # initial SOC
}
@@ -138,7 +139,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_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_generic_storage.py b/tests/test_generic_storage.py
index a1154bc25..b01a738bf 100644
--- a/tests/test_generic_storage.py
+++ b/tests/test_generic_storage.py
@@ -22,13 +22,14 @@ 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_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
"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 +94,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 +229,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..acf7f377e 100644
--- a/tests/test_seasonal_hydrogen_storage.py
+++ b/tests/test_seasonal_hydrogen_storage.py
@@ -19,8 +19,9 @@ def price_profile():
@pytest.fixture
def storage_config():
return {
- "max_capacity": 200,
- "min_capacity": 0,
+ "capacity": 200,
+ "min_soc": 0,
+ "max_soc": 1,
"max_power_charge": 40,
"max_power_discharge": 40,
"efficiency_charge": 0.95,
@@ -145,21 +146,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..71047628c 100644
--- a/tests/test_steam_plant.py
+++ b/tests/test_steam_plant.py
@@ -259,8 +259,9 @@ 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_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_storage.py b/tests/test_storage.py
index f82239e3e..0d721e078 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()
@@ -129,20 +129,15 @@ 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.max_soc
- )
- assert (
- storage_unit.outputs["soc"][start - storage_unit.index.freq]
- == 0.05 * storage_unit.max_soc
- )
+ storage_unit.outputs["soc"][start - timedelta(hours=1)] = 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
)
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
)
@@ -227,9 +222,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 +237,9 @@ 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(500, 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
@@ -257,9 +252,9 @@ 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(500, -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
@@ -270,35 +265,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 +323,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 +335,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,11 +347,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_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)
@@ -368,7 +363,7 @@ def test_set_dispatch_plan(mock_market_config, storage_unit):
)
# 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 +402,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
@@ -440,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)
@@ -456,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)
@@ -477,6 +474,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"
diff --git a/tests/test_thermal_storage.py b/tests/test_thermal_storage.py
index beb054d41..302cfca33 100644
--- a/tests/test_thermal_storage.py
+++ b/tests/test_thermal_storage.py
@@ -19,8 +19,9 @@ def price_profile():
@pytest.fixture
def storage_config():
return {
- "max_capacity": 200,
- "min_capacity": 0,
+ "capacity": 200,
+ "min_soc": 0,
+ "max_soc": 1,
"max_power_charge": 40,
"max_power_discharge": 40,
"efficiency_charge": 0.95,
@@ -145,21 +146,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