Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
a862e69
added draft of performance model baseclass to set outputs
elenya-grant Jan 20, 2026
bc6a798
updated wind models for standardized outputs
elenya-grant Jan 20, 2026
73edcd0
minor cleanup to wind pysam
elenya-grant Jan 20, 2026
d3d5616
minor cleanup to floris outputs
elenya-grant Jan 20, 2026
3a8ad35
updated outputs of hydroplant
elenya-grant Jan 20, 2026
682959e
updated solar pv models for standardized outputs
elenya-grant Jan 20, 2026
0aef02a
updated solar resource integration tests with updated solar model out…
elenya-grant Jan 20, 2026
8f993d4
added fraction_of_year_simulated attribute to performance baseclass
elenya-grant Jan 20, 2026
a660e21
updated tests for turbine preprocessing tools
elenya-grant Jan 21, 2026
1f9788a
fixed variable naming in test_all_examples for wind and solar
elenya-grant Jan 21, 2026
d1f2387
started updating electrolyzer model outputs
elenya-grant Jan 21, 2026
5db62ae
Merge branch 'develop' into converter_baseclass
johnjasa Jan 21, 2026
f515f5a
Merge branch 'converter_baseclass' of github.com:elenya-grant/GreenHE…
elenya-grant Jan 21, 2026
4227c13
minor bugfix to test_sql_timeseries_to_csv for example 2
elenya-grant Jan 21, 2026
ff3de30
fixed save_case_timeseries_as_csv
elenya-grant Jan 21, 2026
d84c10a
update to sql_to_csv function just in case
elenya-grant Jan 21, 2026
1169376
merged in develop
elenya-grant Jan 21, 2026
40ae090
added attribute check in PerformanceModelBaseClass
elenya-grant Jan 26, 2026
82116bc
typo bugfix in refurb period calc in electrolyzer model
elenya-grant Jan 26, 2026
08f94ef
added test for solar performance baseclass
elenya-grant Jan 27, 2026
7da471c
merged in develop
elenya-grant Jan 27, 2026
8df11d0
added solar test to check that all outputs are set in parent class
elenya-grant Jan 27, 2026
5a38e8f
commented out unused variables in new solar test
elenya-grant Jan 27, 2026
1a51e7b
generalized solar unit test so it can be easily used for other compon…
elenya-grant Jan 27, 2026
3ea3568
updated natural gas plant
elenya-grant Jan 27, 2026
d02ec1b
started updating co2 models
elenya-grant Jan 27, 2026
e868d82
updated grid model
elenya-grant Jan 27, 2026
16a44c5
updated asu model
elenya-grant Jan 27, 2026
d8fda02
updated grid tests
elenya-grant Jan 27, 2026
c32e752
updated desal model
elenya-grant Jan 27, 2026
910a374
updated co2 models and tests
elenya-grant Jan 27, 2026
cca9e4b
updated newest steel models
elenya-grant Jan 27, 2026
b899fae
started updating methanol models but not tested
elenya-grant Jan 27, 2026
e570263
made it so ResizablePerformanceModelBaseClass inherits PerformanceMod…
elenya-grant Jan 27, 2026
ee220e5
updated electrolyzer model and ammonia synloop model
elenya-grant Jan 27, 2026
1b64406
updated simple ammonia model
elenya-grant Jan 27, 2026
496fba0
updated hopp wrapper
elenya-grant Jan 27, 2026
8117034
updated steel.py
elenya-grant Jan 27, 2026
6c7790c
updated iron mine and dri models
elenya-grant Jan 27, 2026
1d0b1ea
updated geoh2 models
elenya-grant Jan 28, 2026
f582c4c
updated battery
elenya-grant Jan 28, 2026
71a1b85
added todo comments to storage models
elenya-grant Jan 28, 2026
be535a3
added unit tests to check that outputs are populated
elenya-grant Jan 28, 2026
b54f32f
working on updating combiners and h2imodel
elenya-grant Jan 28, 2026
8349163
updated electrolyzer so test values dont change and other bugfixes so…
elenya-grant Jan 28, 2026
199c6fd
updated how_to_set_up_an_analysis.md
elenya-grant Jan 28, 2026
d0ac7d8
removed init file from new hydro power test folder
elenya-grant Jan 29, 2026
95fe559
updated remaining failing tests
elenya-grant Jan 29, 2026
9503a1b
updated example 28 and iron_wrapper
elenya-grant Jan 29, 2026
d98ed88
updated capacity factor strings in run_size_modes files
elenya-grant Jan 29, 2026
022f6ca
Merge remote-tracking branch 'h2i_upstream/develop' into converter_ba…
elenya-grant Jan 29, 2026
11d45b2
removed commented out outputs
elenya-grant Jan 29, 2026
01f4124
updated changelog
elenya-grant Jan 29, 2026
9c8dd0e
removed duplicate inheritance of PerformanceModelBaseClass in electro…
elenya-grant Jan 29, 2026
1011686
updated pysam battery outputs
elenya-grant Jan 29, 2026
36c0772
updated annual outputs to properly account for fraction of year simul…
elenya-grant Jan 30, 2026
632d028
moved commodity defn to initialize
elenya-grant Jan 30, 2026
6c9e6ba
updates based on reviewer feedback
elenya-grant Jan 30, 2026
61dddc7
Merge branch 'develop' into converter_baseclass
johnjasa Feb 2, 2026
65b1450
Merge branch 'converter_baseclass_temp' into converter_baseclass
elenya-grant Feb 2, 2026
f36cb4d
merged in develop
elenya-grant Feb 2, 2026
9a770fd
merged in develop
elenya-grant Feb 2, 2026
ed4988c
updated generic storage models and simple controllers
elenya-grant Feb 2, 2026
0cca96d
bugfix in simple_storage_autosizing
elenya-grant Feb 2, 2026
5d0832a
updated test for post-processing timeseries
elenya-grant Feb 3, 2026
0a83daf
added subtest for ex 12
elenya-grant Feb 3, 2026
35ab37d
updated ex 12 test values
elenya-grant Feb 3, 2026
16b54d8
Merge remote-tracking branch 'h2i_upstream/develop' into converter_ba…
elenya-grant Feb 3, 2026
6bfdb07
removed commented out code
elenya-grant Feb 3, 2026
9976fcc
updated changelog
elenya-grant Feb 3, 2026
2ac03d2
added some comments to generic storage performance models
elenya-grant Feb 3, 2026
6221d3e
added more inline and docstring comments
elenya-grant Feb 3, 2026
fe991ae
Merge branch 'develop' into converter_baseclass_storage
johnjasa Feb 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
enforced by a newly added test to ensure adherence.
- Remove `pytest-subtests` as it's incorporated into pytest as of v9, and is an archived project.
- Added `PerformanceModelBaseClass` and standardized outputs of converter performance models
- Updated storage control strategies to output `commodity_set_point` and generic storage performance models to output `commodity_out` and input `commodity_set_point`.

## 0.5.1 [December 18, 2025]

Expand Down
14 changes: 11 additions & 3 deletions examples/test/test_all_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,15 @@ def test_ammonia_synloop_example(subtests):
assert pytest.approx(model.prob.get_val("ammonia.CapEx"), rel=1e-6) == 1.15173753e09

with subtests.test("Check ammonia OpEx"):
assert pytest.approx(model.prob.get_val("ammonia.OpEx"), rel=1e-4) == 25737370.661763854
assert pytest.approx(model.prob.get_val("ammonia.OpEx")[0], rel=1e-4) == 25414748.989416014

with subtests.test("Check ammonia production"):
assert (
pytest.approx(
model.prob.get_val("ammonia.annual_ammonia_produced", units="t/yr").mean(), rel=1e-4
)
== 406333.161
)

with subtests.test("Check total adjusted CapEx"):
assert (
Expand All @@ -273,7 +281,7 @@ def test_ammonia_synloop_example(subtests):
pytest.approx(
model.prob.get_val("finance_subgroup_nh3.total_opex_adjusted")[0], rel=1e-6
)
== 79744581.00552343
== 79421959.33317558
)

with subtests.test("Check LCOH"):
Expand All @@ -285,7 +293,7 @@ def test_ammonia_synloop_example(subtests):
with subtests.test("Check LCOA"):
assert (
pytest.approx(model.prob.get_val("finance_subgroup_nh3.LCOA")[0], rel=1e-6)
== 1.2310335361130984
== 1.1022714567388747
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,6 @@ def compute(self, inputs, outputs):
)

# Calculate actual output based on demand met and curtailment
outputs[f"{commodity}_out"] = (
outputs[f"{commodity}_set_point"] = (
inputs[f"{commodity}_in"] - outputs[f"{commodity}_unused_commodity"]
)
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,6 @@ def compute(self, inputs, outputs):
)

# Calculate actual output based on demand met and curtailment
outputs[f"{commodity}_out"] = (
outputs[f"{commodity}_set_point"] = (
inputs[f"{commodity}_in"] - outputs[f"{commodity}_unused_commodity"]
)
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def setup(self):
)

self.add_output(
f"{commodity}_out",
f"{commodity}_set_point",
val=0.0,
shape=(n_timesteps),
units=self.config.commodity_units,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def setup(self):
)

self.add_output(
f"{self.config.commodity_name}_out",
f"{self.config.commodity_name}_set_point",
copy_shape=f"{self.config.commodity_name}_in",
units=self.config.commodity_units,
desc=f"{self.config.commodity_name} output timeseries from plant after storage",
Expand All @@ -53,7 +53,9 @@ def compute(self, inputs, outputs):
"""

# Assign the input to the output
outputs[f"{self.config.commodity_name}_out"] = inputs[f"{self.config.commodity_name}_in"]
outputs[f"{self.config.commodity_name}_set_point"] = inputs[
f"{self.config.commodity_name}_in"
]

def setup_partials(self):
"""
Expand All @@ -74,7 +76,7 @@ def setup_partials(self):
# Declare partials sparsely for all elements as an identity matrix
# (diagonal elements are 1.0, others are 0.0)
self.declare_partials(
of=f"{self.config.commodity_name}_out",
of=f"{self.config.commodity_name}_set_point",
wrt=f"{self.config.commodity_name}_in",
rows=np.arange(size),
cols=np.arange(size),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ def compute(self, inputs, outputs):
# initialize outputs
soc_array = outputs[f"{commodity}_soc"]
unused_commodity_array = outputs[f"{commodity}_unused_commodity"]
output_array = outputs[f"{commodity}_out"]
output_array = outputs[f"{commodity}_set_point"]
unmet_demand_array = outputs[f"{commodity}_unmet_demand"]

# Loop through each time step
Expand Down Expand Up @@ -338,7 +338,7 @@ def compute(self, inputs, outputs):
# Record the missed load at the current time step
unmet_demand_array[t] = max(0.0, (demand_t - output_array[t]))

outputs[f"{commodity}_out"] = output_array
outputs[f"{commodity}_set_point"] = output_array

# Return the SOC
outputs[f"{commodity}_soc"] = soc_array
Expand Down
14 changes: 8 additions & 6 deletions h2integrate/control/test/test_openloop_controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,15 @@ def test_pass_through_controller(subtests):

# Run the test
with subtests.test("Check output"):
assert pytest.approx(prob.get_val("hydrogen_out"), rel=1e-3) == np.arange(10)
assert pytest.approx(prob.get_val("hydrogen_set_point"), rel=1e-3) == np.arange(10)

# Run the test
with subtests.test("Check derivatives"):
# check total derivatives using OpenMDAO's check_totals and assert tools
assert_check_totals(
prob.check_totals(
of=[
"hydrogen_out",
"hydrogen_set_point",
],
wrt=[
"hydrogen_in",
Expand Down Expand Up @@ -149,7 +149,7 @@ def test_storage_demand_controller(subtests):
# Run the test
with subtests.test("Check output"):
assert pytest.approx([0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]) == prob.get_val(
"hydrogen_out"
"hydrogen_set_point"
)

with subtests.test("Check curtailment"):
Expand Down Expand Up @@ -241,7 +241,9 @@ def set_up_and_run_problem(config):

# Run the test
with subtests.test("Check output"):
assert pytest.approx(prob_ioe.get_val("hydrogen_out")) == prob_rte.get_val("hydrogen_out")
assert pytest.approx(prob_ioe.get_val("hydrogen_set_point")) == prob_rte.get_val(
"hydrogen_set_point"
)

with subtests.test("Check curtailment"):
assert pytest.approx(prob_ioe.get_val("hydrogen_unused_commodity")) == prob_rte.get_val(
Expand Down Expand Up @@ -324,7 +326,7 @@ def test_generic_storage_demand_controller(subtests):
# # Run the test
with subtests.test("Check output"):
assert pytest.approx([0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]) == prob.get_val(
"hydrogen_out"
"hydrogen_set_point"
)

with subtests.test("Check curtailment"):
Expand Down Expand Up @@ -396,7 +398,7 @@ def test_demand_converter_controller(subtests):
# # Run the test
with subtests.test("Check output"):
assert pytest.approx([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 5.0, 5.0, 5.0]) == prob.get_val(
"hydrogen_out"
"hydrogen_set_point"
)

with subtests.test("Check curtailment"):
Expand Down
2 changes: 1 addition & 1 deletion h2integrate/postprocess/test/test_sql_timeseries_to_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def test_save_csv_all_results(subtests, run_example_02_sql_fpath):
res = save_case_timeseries_as_csv(run_example_02_sql_fpath, save_to_file=True)

with subtests.test("Check number of columns"):
assert len(res.columns.to_list()) == 35
assert len(res.columns.to_list()) == 36

with subtests.test("Check number of rows"):
assert len(res) == 8760
Expand Down
48 changes: 35 additions & 13 deletions h2integrate/storage/simple_generic_storage.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import openmdao.api as om
from attrs import field, define

from h2integrate.core.utilities import BaseConfig, merge_shared_inputs
from h2integrate.core.model_baseclasses import PerformanceModelBaseClass


@define(kw_only=True)
Expand All @@ -10,26 +10,48 @@ class SimpleGenericStorageConfig(BaseConfig):
commodity_units: str = field() # TODO: update to commodity_rate_units


class SimpleGenericStorage(om.ExplicitComponent):
"""
Simple generic storage model.
class SimpleGenericStorage(PerformanceModelBaseClass):
"""
Simple generic storage model that acts as a pass-through component.

Note: this storage performance model is intended to be used with the
`DemandOpenLoopStorageController` controller.

def initialize(self):
self.options.declare("tech_config", types=dict)
self.options.declare("plant_config", types=dict)
self.options.declare("driver_config", types=dict)
"""

def setup(self):
n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]
self.config = SimpleGenericStorageConfig.from_dict(
merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"),
strict=False,
additional_cls_name=self.__class__.__name__,
)
commodity_name = self.config.commodity_name
commodity_units = self.config.commodity_units
self.add_input(f"{commodity_name}_in", val=0.0, shape=n_timesteps, units=commodity_units)
self.commodity = self.config.commodity_name
self.commodity_rate_units = self.config.commodity_units
self.commodity_amount_units = f"({self.commodity_rate_units})*h"
super().setup()
self.add_input(
f"{self.commodity}_set_point",
val=0.0,
shape=self.n_timesteps,
units=self.commodity_rate_units,
)

def compute(self, inputs, outputs):
pass
# Pass the commodity_out as the commodity_set_point
outputs[f"{self.commodity}_out"] = inputs[f"{self.commodity}_set_point"]

# Estimate the rated commodity production as the maximum value from the commodity_set_point
outputs[f"rated_{self.commodity}_production"] = inputs[f"{self.commodity}_set_point"].max()

# Calculate the total and annual commodity produced
outputs[f"total_{self.commodity}_produced"] = outputs[f"{self.commodity}_out"].sum()
outputs[f"annual_{self.commodity}_produced"] = outputs[
f"total_{self.commodity}_produced"
] * (1 / self.fraction_of_year_simulated)

# Calculate the maximum commodity production over the simulation
max_production = (
inputs[f"{self.commodity}_set_point"].max() * self.n_timesteps * (self.dt / 3600)
)

outputs["capacity_factor"] = outputs[f"total_{self.commodity}_produced"] / max_production
Loading