Skip to content
38 changes: 24 additions & 14 deletions h2integrate/storage/storage_baseclass.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import numpy as np
from attrs import field, define
from openmdao.utils import units as om_units

from h2integrate.core.utilities import BaseConfig
from h2integrate.core.validators import range_val
Expand Down Expand Up @@ -46,8 +47,8 @@ class StoragePerformanceBase(PerformanceModelBaseClass):
"""

_time_step_bounds = (
3600,
3600,
1,
36000,
) # (min, max) time step lengths (in seconds) compatible with this model

def setup(self):
Expand Down Expand Up @@ -185,10 +186,16 @@ def setup(self):
)

self.using_feedback_control = using_feedback_control
# convert from seconds to hours
self.dt_hr = int(self.options["plant_config"]["plant"]["simulation"]["dt"]) / (
3600
) # convert from seconds to hours
# convert from seconds to hours (kept for PySAM and legacy callers)
self.dt_hr = self.dt / 3600.0

# dt expressed in (commodity_amount_units / commodity_rate_units), i.e. the
# timestep width in whatever time unit makes rate * dt_amount = amount.
self.dt_amount = om_units.convert_units(
self.dt,
"s",
f"({self.commodity_amount_units})/({self.commodity_rate_units})",
)
Comment on lines +194 to +198
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great use of the built-in units conversion here!


def compute(self, inputs, outputs, discrete_inputs=[], discrete_outputs=[]):
"""Run the storage model.
Expand Down Expand Up @@ -296,7 +303,8 @@ def run_storage(

# Performance model outputs
outputs[f"rated_{self.commodity}_production"] = discharge_rate
outputs[f"total_{self.commodity}_produced"] = np.sum(storage_commodity_out)
# rate * dt_amount = commodity_amount_units (works for any commodity_rate_units)
outputs[f"total_{self.commodity}_produced"] = np.sum(storage_commodity_out) * self.dt_amount
outputs[f"annual_{self.commodity}_produced"] = outputs[
f"total_{self.commodity}_produced"
] * (1 / self.fraction_of_year_simulated)
Expand All @@ -306,12 +314,14 @@ def run_storage(
outputs["standard_capacity_factor"] = 0.0
else:
outputs["capacity_factor"] = outputs[f"total_{self.commodity}_produced"] / (
outputs[f"rated_{self.commodity}_production"] * self.n_timesteps
outputs[f"rated_{self.commodity}_production"] * self.n_timesteps * self.dt_amount
)
# standard_capacity_factor is the ratio of commodity discharged to the discharge rate
total_commodity_discharged = outputs[f"storage_{self.commodity}_discharge"].sum()
total_commodity_discharged = (
outputs[f"storage_{self.commodity}_discharge"].sum() * self.dt_amount
)
outputs["standard_capacity_factor"] = total_commodity_discharged / (
outputs[f"rated_{self.commodity}_production"] * self.n_timesteps
outputs[f"rated_{self.commodity}_production"] * self.n_timesteps * self.dt_amount
)
return outputs

Expand Down Expand Up @@ -402,7 +412,7 @@ def simulate(
# --- Charging ---
# headroom: how much more commodity the storage can accept,
# expressed as a rate (commodity_rate_units).
headroom = (soc_max - soc) * storage_capacity / self.dt_hr
headroom = (soc_max - soc) * storage_capacity / self.dt_amount

# charge available based on the available input commodity
charge_available = commodity_available[sim_start_index + t]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking here - we expect that the headroom, charge_available, the input storage_dispatch_commands, etc are in commodity_rate_units?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct!

Expand All @@ -418,7 +428,7 @@ def simulate(
)

# Update SOC (actual_charge is in post-efficiency units)
soc += actual_charge / storage_capacity
soc += actual_charge * self.dt_amount / storage_capacity

# Update the amount of commodity used to charge from the input stream
# If charge_eff<1, more commodity is pulled from the input stream than
Expand All @@ -428,7 +438,7 @@ def simulate(
# --- Discharging ---
# headroom: how much commodity can still be drawn before
# hitting the minimum SOC, expressed as a rate.
headroom = (soc - soc_min) * storage_capacity / self.dt_hr
headroom = (soc - soc_min) * storage_capacity / self.dt_amount

# Clip to the most restrictive limit without applied efficiency.
# Efficiency losses occur as energy leaves storage.
Expand All @@ -437,7 +447,7 @@ def simulate(
)

# Update SOC (actual_discharge is before efficiency losses are applied.)
soc -= actual_discharge / storage_capacity
soc -= actual_discharge * self.dt_amount / storage_capacity

# If discharge_eff<1, then less commodity is output from the storage
# than the commodity discharged from storage
Expand Down
2 changes: 1 addition & 1 deletion h2integrate/storage/storage_performance_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class StoragePerformanceModel(StoragePerformanceBase):
"""OpenMDAO component for a storage component."""

_time_step_bounds = (
3600,
1,
3600,
) # (min, max) time step lengths (in seconds) compatible with this model

Expand Down
Loading
Loading