Skip to content

Commit 4576870

Browse files
authored
CO2 Model Fixes (#617)
* units and connecting variables * fix doc page * catch if annual_input_energy is 0 * remove base class * fix oae test * failing doc test * fix value call * final touchs
1 parent 6fb47db commit 4576870

10 files changed

Lines changed: 200 additions & 266 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
- Bugfix in tests of pyomo control strategies with `StoragePerformanceModel` so that the pathname attribute is correct
2222
- Added `demand_profile` as an input to `StoragePerformanceModel` and `PySAMBatteryPerformanceModel`
2323
- Renamed `xx_charge_fraction` to `xx_soc_fraction`
24+
- Bugfix input energy to OAE financial model [PR 617](https://github.com/NatLabRockies/H2Integrate/pull/617)
25+
- Remove `MarineCarbonCapture` base classes
2426

2527
## 0.7.1 [March 13, 2026]
2628

docs/technology_models/co2.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# Marine Carbon Dioxide Capture Models
22
Marine carbon dioxide (CO₂) capture technologies aim to remove CO₂ from the ocean or enhance the ocean’s natural capacity to store atmospheric CO₂. These approaches provide additional pathways for managing carbon in the marine environment and can complement land-based strategies for resource management and ocean health.
33

4-
This section provides an overview of the marine carbon dioxide capture models integrated into H2Integrate. The models are adapted from and maintained in NREL's [MarineCarbonManagement Repository](https://github.com/Nrel/MarineCarbonManagement) and have been integrated here for ease of scenario analysis and system-level optimization.
4+
This section provides an overview of the marine carbon dioxide capture models integrated into H2Integrate. The models are adapted from and maintained in NLR's [MarineCarbonManagement Repository](https://github.com/NatLabRockies/MarineCarbonManagement) and have been integrated here for ease of scenario analysis and system-level optimization.
55

66

77
## Direct Ocean Capture (DOC) Model
88

99
Direct Ocean Capture extracts dissolved CO₂ directly from seawater using engineered processes. By reducing the concentration of dissolved inorganic carbon, the ocean naturally reabsorbs an equivalent amount of atmospheric CO₂. The resultant CO₂ can then be used for downstream processes and conversion or storage.
1010

11-
The DOC model is built on electrodialysis-based separation and includes both performance and cost components, allowing users to explore a wide range of system configurations, operational scenarios, and infrastructure options. It is designed for process design, optimization, and cost evaluation of marine carbon capture systems and is integrated from NREL's [MarineCarbonManagement Repository](https://github.com/Nrel/MarineCarbonManagement). Additional information about this specific model can be found in [Niffenegger et al.](https://doi.org/10.3390/cleantechnol7030052)
11+
The DOC models, `DOCPerformanceModel` and `DOCCostModel`, are built on electrodialysis-based separation and includes both performance and cost components, allowing users to explore a wide range of system configurations, operational scenarios, and infrastructure options. It is designed for process design, optimization, and cost evaluation of marine carbon capture systems and is integrated from NLR's [MarineCarbonManagement Repository](https://github.com/NatLabRockies/MarineCarbonManagement). Additional information about this specific model can be found in [Niffenegger et al.](https://doi.org/10.3390/cleantechnol7030052)
1212

1313
### Why Use This Model
1414
- Evaluate technology performance — quantify system throughput, process efficiencies, and resource usage under different operating conditions.
@@ -55,7 +55,7 @@ The Ocean Alkalinity Enhancement (OAE) Model simulates the process of increasing
5555
This model estimates the CO₂ absorption potential of the ocean after the OAE process. CO₂ is not a usable downstream product.
5656
```
5757

58-
The OAE model is adapted from NREL’s MarineCarbonManagement Repository and includes both performance and cost components, as well as a financial model enabling users to explore a variety of system designs, operational strategies, and infrastructure setups. Additional information about this specific model can be found in [Niffenegger et al.](https://doi.org/10.3390/cleantechnol8010012)
58+
The OAE models, `OAEPerformanceModel` and `OAECostModel`, are adapted from NLR’s MarineCarbonManagement Repository and includes both performance and cost components, as well as a financial model enabling users to explore a variety of system designs, operational strategies, and infrastructure setups. Additional information about this specific model can be found in [Niffenegger et al.](https://doi.org/10.3390/cleantechnol8010012)
5959

6060
### Why Use This Model
6161
- Assess system performance — determine processing capacity, flow characteristics, and operational profiles.
@@ -95,7 +95,7 @@ Computes capital (CapEx) and operational (OpEx) costs based on plant design and
9595
- Annual operating cost (USD/year)
9696

9797
#### OAE Cost and Financial Model
98-
Extends the cost model to include financial metrics. Allows calculation of net present value (NPV) and determination of credit values required for financial viability.
98+
The `OAECostAndFinancialModel` extends the cost model to include financial metrics. Allows calculation of net present value (NPV) and determination of credit values required for financial viability.
9999

100100
**Additional inputs include:**
101101
- Levelized cost of electricity (LCOE)

docs/user_guide/connecting_technologies.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,17 @@ There are two connection formats:
4949

5050
##### Different shared parameter names
5151
```yaml
52-
["source_tech", "destination_tech", ("source_parameter", "destination_parameter")]
52+
["source_tech", "destination_tech", ["source_parameter", "destination_parameter"]]
5353
```
5454

5555
- **source_tech**: Name of the technology providing the output
5656
- **destination_tech**: Name of the technology receiving the input
5757
- **source_parameter**: The name of the parameter within ``"source_tech"``
5858
- **destination_parameter**: The name of the parameter within ``"destination_tech"``
5959

60+
```{note}
61+
The `source_parameter` and `destination_parameter` should be input into the array as another array. If it's input as a tuple the model will raise an error.
62+
```
6063

6164
### Internal connection logic
6265

examples/09_co2/ocean_alkalinity_enhancement_financials/plant_config_financials.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ plant:
1818
technology_interconnections:
1919
- [wind, oae, electricity, cable]
2020
- [finance_subgroup_electricity, oae, LCOE]
21+
- [wind, oae, [annual_electricity_produced, annual_input_electricity]]
2122
# etc
2223
resource_to_tech_connections:
2324
# connect the wind resource to the wind technology

examples/test/test_all_examples.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -850,8 +850,6 @@ def test_wind_wave_oae_example(subtests, temp_copy_of_example):
850850
model.post_process()
851851

852852
# Subtests for checking specific values
853-
# Note: These are placeholder values. Update with actual values after running the test
854-
# when MCM package is properly installed and configured
855853
with subtests.test("Check LCOC"):
856854
assert (
857855
pytest.approx(
@@ -887,8 +885,6 @@ def test_wind_wave_oae_example_with_finance(subtests, temp_copy_of_example):
887885
model.post_process()
888886

889887
# Subtests for checking specific values
890-
# Note: These are placeholder values. Update with actual values after running the test
891-
# when MCM package is properly installed and configured
892888
with subtests.test("Check LCOE"):
893889
assert (
894890
pytest.approx(
@@ -901,7 +897,7 @@ def test_wind_wave_oae_example_with_finance(subtests, temp_copy_of_example):
901897
with subtests.test("Check Carbon Credit"):
902898
assert (
903899
pytest.approx(model.prob.get_val("oae.carbon_credit_value", units="USD/t")[0], rel=1e-3)
904-
== 574.37466
900+
== 1026.4684117
905901
)
906902

907903

h2integrate/converters/co2/marine/direct_ocean_capture.py

Lines changed: 58 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
from attrs import field, define
22
from mcm.capture import echem_mcc
33

4-
from h2integrate.core.utilities import merge_shared_inputs
5-
from h2integrate.core.validators import must_equal
6-
from h2integrate.converters.co2.marine.marine_carbon_capture_baseclass import (
7-
MarineCarbonCaptureCostBaseClass,
8-
MarineCarbonCapturePerformanceConfig,
9-
MarineCarbonCapturePerformanceBaseClass,
10-
)
4+
from h2integrate.core.utilities import BaseConfig, merge_shared_inputs
5+
from h2integrate.core.validators import gt_zero, contains, gte_zero, range_val, must_equal
6+
from h2integrate.core.model_baseclasses import CostModelBaseClass, PerformanceModelBaseClass
117

128

139
def setup_electrodialysis_inputs(config):
@@ -29,10 +25,14 @@ def setup_electrodialysis_inputs(config):
2925

3026

3127
@define(kw_only=True)
32-
class DOCPerformanceConfig(MarineCarbonCapturePerformanceConfig):
28+
class DOCPerformanceConfig(BaseConfig):
3329
"""Extended configuration for Direct Ocean Capture (DOC) performance model.
3430
3531
Attributes:
32+
number_ed_min (int): Minimum number of ED units to operate.
33+
number_ed_max (int): Maximum number of ED units available.
34+
use_storage_tanks (bool): Flag indicating whether to use storage tanks.
35+
store_hours (float): Number of hours of CO₂ storage capacity (hours).
3636
power_single_ed_w (float): Power requirement of a single electrodialysis (ED) unit (watts).
3737
flow_rate_single_ed_m3s (float): Flow rate of a single ED unit (cubic meters per second).
3838
E_HCl (float): Energy required per mole of HCl produced (kWh/mol).
@@ -50,54 +50,55 @@ class DOCPerformanceConfig(MarineCarbonCapturePerformanceConfig):
5050
save_plots (bool, optional): If true, save plots of results. Defaults to False.
5151
"""
5252

53-
power_single_ed_w: float = field()
54-
flow_rate_single_ed_m3s: float = field()
55-
E_HCl: float = field()
56-
E_NaOH: float = field()
57-
y_ext: float = field()
58-
y_pur: float = field()
59-
y_vac: float = field()
60-
frac_ed_flow: float = field()
61-
temp_C: float = field()
62-
sal: float = field()
63-
dic_i: float = field()
64-
pH_i: float = field()
65-
initial_tank_volume_m3: float = field()
53+
number_ed_min: int = field(validator=gt_zero)
54+
number_ed_max: int = field(validator=gt_zero)
55+
use_storage_tanks: bool = field()
56+
store_hours: float = field(validator=gte_zero)
57+
power_single_ed_w: float = field(validator=gte_zero)
58+
flow_rate_single_ed_m3s: float = field(validator=gt_zero)
59+
E_HCl: float = field(validator=gte_zero)
60+
E_NaOH: float = field(validator=gte_zero)
61+
y_ext: float = field(validator=range_val(0, 1))
62+
y_pur: float = field(validator=range_val(0, 1))
63+
y_vac: float = field(validator=range_val(0, 1))
64+
frac_ed_flow: float = field(validator=range_val(0, 1))
65+
temp_C: float = field(validator=gte_zero)
66+
sal: float = field(validator=gte_zero)
67+
dic_i: float = field(validator=gte_zero)
68+
pH_i: float = field(validator=gte_zero)
69+
initial_tank_volume_m3: float = field(validator=gte_zero)
6670
save_outputs: bool = field(default=False)
6771
save_plots: bool = field(default=False)
6872

6973

70-
class DOCPerformanceModel(MarineCarbonCapturePerformanceBaseClass):
74+
class DOCPerformanceModel(PerformanceModelBaseClass):
7175
"""
7276
An OpenMDAO component for modeling the performance of a Direct Ocean Capture (DOC) plant.
73-
74-
Extends:
75-
MarineCarbonCapturePerformanceBaseClass
76-
77-
Computes:
78-
- co2_out: Hourly CO2 capture rate (kg/h)
79-
- co2_capture_mtpy: Annual CO2 capture (t/year)
80-
- total_tank_volume_m3: Total tank volume (m^3)
81-
- plant_mCC_capacity_mtph: Plant carbon capture capacity (t/h)
8277
"""
8378

8479
def initialize(self):
8580
super().initialize()
81+
self.commodity = "co2"
82+
self.commodity_rate_units = "kg/h"
83+
self.commodity_amount_units = "kg"
8684

8785
def setup(self):
8886
self.config = DOCPerformanceConfig.from_dict(
8987
merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"),
9088
additional_cls_name=self.__class__.__name__,
9189
)
9290
super().setup()
93-
self.add_output(
94-
"plant_mCC_capacity_mtph",
91+
92+
self.add_input(
93+
"electricity_in",
9594
val=0.0,
96-
units="t/h",
97-
desc="Theoretical maximum CO₂ capture (t/h)",
95+
shape=self.n_timesteps,
96+
units="W",
97+
desc="Hourly input electricity (W)",
9898
)
99+
99100
self.add_output(
100-
"total_tank_volume_m3",
101+
"total_tank_volume",
101102
val=0.0,
102103
units="m**3",
103104
)
@@ -123,9 +124,7 @@ def compute(self, inputs, outputs):
123124
)
124125

125126
outputs["co2_out"] = ed_outputs.ED_outputs["mCC"] * 1000 # kg/h
126-
outputs["co2_capture_mtpy"] = max(ed_outputs.mCC_yr, 1e-6) # Must be >0 #TODO: remove
127-
outputs["total_tank_volume_m3"] = range_outputs.V_aT_max + range_outputs.V_bT_max
128-
outputs["plant_mCC_capacity_mtph"] = max(range_outputs.S1["mCC"]) # TODO: remove
127+
outputs["total_tank_volume"] = range_outputs.V_aT_max + range_outputs.V_bT_max
129128

130129
outputs["rated_co2_production"] = (ed_outputs.mCC_yr_MaxPwr / 8760) * 1e3
131130
outputs["total_co2_produced"] = outputs["co2_out"].sum()
@@ -141,21 +140,17 @@ class DOCCostModelConfig(DOCPerformanceConfig):
141140
"""Configuration for the DOC cost model.
142141
143142
Attributes:
144-
infrastructure_type (str): Type of infrastructure (e.g., "desal", "swCool", "new"").
143+
infrastructure_type (str): Type of infrastructure (e.g., "desal", "swCool", "new").
145144
cost_year (int): dollar year corresponding to cost values
146145
"""
147146

148-
infrastructure_type: str = field()
147+
infrastructure_type: str = field(validator=contains(["desal", "swCool", "new"]))
149148
cost_year: int = field(default=2023, converter=int, validator=must_equal(2023))
150149

151150

152-
class DOCCostModel(MarineCarbonCaptureCostBaseClass):
151+
class DOCCostModel(CostModelBaseClass):
153152
"""OpenMDAO component for computing capital (CapEx) and operational (OpEx) costs of a
154-
direct ocean capture (DOC) system.
155-
156-
Computes:
157-
- CapEx (USD)
158-
- OpEx (USD/year)
153+
direct ocean capture (DOC) system.
159154
"""
160155

161156
def initialize(self):
@@ -168,18 +163,27 @@ def setup(self):
168163
)
169164

170165
super().setup()
166+
plant_life = int(self.options["plant_config"]["plant"]["plant_life"])
171167

172168
self.add_input(
173-
"total_tank_volume_m3",
169+
"total_tank_volume",
174170
val=0.0,
175171
units="m**3",
176172
)
177173

178174
self.add_input(
179-
"plant_mCC_capacity_mtph", # TODO: replace with rated_co2_production
175+
"annual_co2_produced",
176+
val=0.0,
177+
shape=plant_life,
178+
units="t/year",
179+
desc="Annual co2 captured",
180+
)
181+
182+
self.add_input(
183+
"rated_co2_production",
180184
val=0.0,
181185
units="t/h",
182-
desc="Theoretical plant maximum CO₂ capture (t/h)",
186+
desc="Theoretical plant maximum CO₂ capture",
183187
)
184188

185189
def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
@@ -189,15 +193,13 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
189193
res = echem_mcc.electrodialysis_cost_model(
190194
echem_mcc.ElectrodialysisCostInputs(
191195
electrodialysis_inputs=ED_inputs,
192-
mCC_yr=inputs["co2_capture_mtpy"], # TODO: replace with annual_co2_produced
193-
total_tank_volume=inputs["total_tank_volume_m3"],
196+
mCC_yr=inputs["annual_co2_produced"],
197+
total_tank_volume=inputs["total_tank_volume"],
194198
infrastructure_type=self.config.infrastructure_type,
195-
max_theoretical_mCC=inputs[
196-
"plant_mCC_capacity_mtph"
197-
], # TODO: replaced with rated_co2_production
199+
max_theoretical_mCC=inputs["rated_co2_production"],
198200
)
199201
)
200202

201203
# Calculate CapEx
202204
outputs["CapEx"] = res.initial_capital_cost
203-
outputs["OpEx"] = res.yearly_operational_cost
205+
outputs["OpEx"] = res.yearly_operational_cost[0]

h2integrate/converters/co2/marine/marine_carbon_capture_baseclass.py

Lines changed: 0 additions & 78 deletions
This file was deleted.

0 commit comments

Comments
 (0)