Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
259b895
Adding in compressed gas storage capex (not complete)
jmartin4u Apr 2, 2026
302aef0
Capex finished
jmartin4u Apr 8, 2026
70d5496
Opex added, compressor model crashing
jmartin4u Apr 8, 2026
b159329
Temporarily using pysam wind for speed
jmartin4u Apr 14, 2026
ea3ac03
Calculating storage cost in example, need to fix tests
jmartin4u Apr 15, 2026
6483f76
First test passing
jmartin4u Apr 15, 2026
e3d5d59
Changed compressor types, capex_per_kg test failing
jmartin4u Apr 15, 2026
b27efa3
Merge branch 'develop' into comp_h2_storage
jmartin4u Apr 15, 2026
04aa292
Storage capex per keg test fixed
jmartin4u Apr 15, 2026
0463477
Reverting example tech_config
jmartin4u Apr 15, 2026
e3bc32a
Docs + changing h2 storage constants to uninstalled (installed is ca…
jmartin4u Apr 16, 2026
18bc662
Fixing test values
jmartin4u Apr 16, 2026
595c3a5
Merge remote-tracking branch 'origin/develop' into comp_h2_storage
jmartin4u Apr 16, 2026
81db005
Moving images
jmartin4u Apr 16, 2026
ee7701e
Changelog update
jmartin4u Apr 16, 2026
70ba6a1
Merge branch 'develop' into comp_h2_storage
johnjasa Apr 16, 2026
d56c333
Updating test_hydrogen_storage
jmartin4u Apr 17, 2026
c971250
Fixing h2_storage_cost
jmartin4u Apr 17, 2026
93d23d0
Fixing double CEPCI inflation in tank cost
jmartin4u Apr 17, 2026
86d0a4b
Merge remote-tracking branch 'origin/develop' into comp_h2_storage
jmartin4u Apr 17, 2026
2e14c63
removed commodity_name from h2_storage cost config and updated format…
elenya-grant Apr 22, 2026
4f8be20
Merge branch 'develop' into comp_h2_storage
johnjasa Apr 23, 2026
d66659a
Addressing Jasa comments
jmartin4u Apr 23, 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
2 changes: 2 additions & 0 deletions h2integrate/core/supported_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
from h2integrate.storage.hydrogen.h2_storage_cost import (
PipeStorageCostModel,
SaltCavernStorageCostModel,
CompressedGasStorageCostModel,
LinedRockCavernStorageCostModel,
)
from h2integrate.transporters.gas_stream_combiner import GasStreamCombinerPerformanceModel
Expand Down Expand Up @@ -270,6 +271,7 @@
"StoragePerformanceModel": StoragePerformanceModel,
"StorageAutoSizingModel": StorageAutoSizingModel,
"LinedRockCavernStorageCostModel": LinedRockCavernStorageCostModel,
"CompressedGasStorageCostModel": CompressedGasStorageCostModel,
"SaltCavernStorageCostModel": SaltCavernStorageCostModel,
"MCHTOLStorageCostModel": MCHTOLStorageCostModel,
"PipeStorageCostModel": PipeStorageCostModel,
Expand Down
210 changes: 204 additions & 6 deletions h2integrate/storage/hydrogen/h2_storage_cost.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,21 @@
class HydrogenStorageBaseCostModelConfig(BaseConfig):
"""Base config class for HydrogenStorageBaseCostModel.

Fields include `max_capacity`, `max_charge_rate`, `sizing_mode`, `commodity_name`,
`commodity_units`, `cost_year`, `labor_rate`, `insurance`, `property_taxes`,
`licensing_permits`, `compressor_om`, and `facility_om`.
Fields:
Comment thread
jmartin4u marked this conversation as resolved.
Outdated
- `max_capacity`
- `max_charge_rate`
- `sizing_mode`
- `commodity_name`,
- `commodity_units`
- `cost_year`
- `labor_rate`
- `insurance`
- `property_taxes`,
Comment thread
jmartin4u marked this conversation as resolved.
Outdated
- `licensing_permits`
- `compressor_om`
- `facility_om`
- `inlet_pressure_bar` # Update in other modesl besides comp gas?
- `storage_pressure_bar` - max 700 # Update in other modesl besides comp gas?
"""

max_capacity: float | None = field(default=None)
Expand All @@ -33,6 +45,8 @@ class HydrogenStorageBaseCostModelConfig(BaseConfig):
licensing_permits: float = field(default=0.001, validator=range_val(0, 1))
compressor_om: float = field(default=0.04, validator=range_val(0, 1))
facility_om: float = field(default=0.01, validator=range_val(0, 1))
inlet_pressure_bar: float = field(default=20, validator=gte_zero)
storage_pressure_bar: float = field(default=200, validator=range_val(0, 700))

def __attrs_post_init__(self):
undefined_capacities = self.max_capacity is None or self.max_charge_rate is None
Expand Down Expand Up @@ -227,8 +241,12 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
# ============================================================================
outlet_pressure = 200 # Max outlet pressure of lined rock cavern in [1] [bar]
n_compressors = 2
comp_type = "pipeline"
storage_compressor = Compressor(
outlet_pressure, system_flow_rate, n_compressors=n_compressors
outlet_pressure,
system_flow_rate,
n_compressors=n_compressors,
compressor_type=comp_type,
)
storage_compressor.compressor_power()
motor_rating, power = storage_compressor.compressor_system_power()
Expand Down Expand Up @@ -357,8 +375,12 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
# ============================================================================
outlet_pressure = 120 # Max outlet pressure of salt cavern in [1] [bar]
n_compressors = 2
comp_type = "pipeline"
storage_compressor = Compressor(
outlet_pressure, system_flow_rate, n_compressors=n_compressors
outlet_pressure,
system_flow_rate,
n_compressors=n_compressors,
compressor_type=comp_type,
)
storage_compressor.compressor_power()
motor_rating, power = storage_compressor.compressor_system_power()
Expand Down Expand Up @@ -501,8 +523,12 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
compressor_output_pressure # Max outlet pressure of underground pipe storage [1] [bar]
)
n_compressors = 2
comp_type = "pipeline"
storage_compressor = Compressor(
outlet_pressure, system_flow_rate, n_compressors=n_compressors
outlet_pressure,
system_flow_rate,
n_compressors=n_compressors,
compressor_type=comp_type,
)
storage_compressor.compressor_power()
motor_rating, power = storage_compressor.compressor_system_power()
Expand Down Expand Up @@ -546,3 +572,175 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):

outputs["CapEx"] = installed_capex
outputs["OpEx"] = total_om


class CompressedGasStorageCostModel(HydrogenStorageBaseCostModel):
"""Capital and operational cost model for compressed gas hydrogen storage.

This model is based on HDSAM's compressed gas hydrogen storage terminal cost model, which is
designed for loading of trucks with compressed H2. In this model, we isolate just the parts of
the HDSAM that relate to filling and storage, and ignoring the costs related to truck loading.

Costs have been converted to 2018 costs to match the models above using CEPCI values in HDSAM.

References:
[1] HDSAM V5.5 Compressed Gas H2 Terminal: https://hdsam.es.anl.gov/index.php?content=hdsam
"""

def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
"""Calculate installed capital and O&M costs for lined rock cavern hydrogen storage.

Args:
inputs: OpenMDAO inputs containing ``max_capacity`` (total capacity [kg]),
``max_charge_rate`` (charge rate [kg/h]), and ``hydrogen_in``
(timeseries [kg/h]).
outputs: OpenMDAO outputs dict.
discrete_inputs: OpenMDAO discrete inputs dict.
discrete_outputs: OpenMDAO discrete outputs dict.

Sets:
outputs["CapEx"]: Installed capital cost in 2018 USD (including compressor).
outputs["OpEx"]: Annual fixed O&M in 2018 USD/yr (excluding electricity).
"""

# ============================================================================
# Design inputs
# ============================================================================
# Relevant design parameters (mostly rows 32-74 of "Compressed Gas H2 Terminal" in [1])

h2_in_kg_d = units.convert_units(
inputs["hydrogen_in"], f"({self.config.commodity_units})", "kg/d"
Comment thread
jmartin4u marked this conversation as resolved.
Outdated
)
terminal_capacity_kg_d = np.max(h2_in_kg_d)
storage_capacity_kg = units.convert_units(
inputs["storage_capacity"][0], f"({self.config.commodity_units})*h", "kg"
)
n_compressors = np.ceil(terminal_capacity_kg_d / 24 / 50) # Cell B59
# Not sure where the 50 comes from in HDSAM - using rule of thumb of 1 unit per 50 kg/hr?
storage_compressor = Compressor(
compressor_type="storage",
p_inlet=self.config.inlet_pressure_bar,
p_outlet=self.config.storage_pressure_bar,
flow_rate_kg_d=terminal_capacity_kg_d,
n_compressors=n_compressors,
)

# ============================================================================
# Calculate CAPEX
# ============================================================================
# Installed capital cost per kg from rows 158-180 of "Compressed Gas H2 Terminal" in [1]
# Capex for compressor and storage scales with size
# Capex for piping, plumbing, electrical, instrumentation, and buildings is constant
# CEPCI data from HDSAM used to convert most costs to 2018, for those without a CEPCI index
# the BLS CPI calcualtor was used instead: https://data.bls.gov/cgi-bin/cpicalc.pl
# "Truck Loading Compressor" and "Truck Scale" from HDSAM are not included

# Storage Compressor
storage_compressor.compressor_power()
unit_power_kw, system_power_kw = storage_compressor.compressor_system_power()
comp_capex_2016, _ = storage_compressor.compressor_costs()
comp_capex = comp_capex_2016 * 1.36013289036545 / 1.2890365448505
# Values taken from CEPCI table in "Feedstock & Utility Prices"

# Compressed Gas H2 Storage
# Currently using a linear fit between 350 and 700 bar (the two discrete HDSAM levels)
capex_per_kg_350_bar_2013 = 1560 # "Cost Data" row 89
capex_per_kg_700_bar_2013 = 2340 # "Cost Data" row 96
tank_capex_per_kg_2013 = (
capex_per_kg_350_bar_2013
+ (capex_per_kg_700_bar_2013 - capex_per_kg_350_bar_2013)
* (self.config.storage_pressure_bar - 350)
/ 350
)
tank_installation_factor = 1.3
capex_2013 = tank_capex_per_kg_2013 * storage_capacity_kg * tank_installation_factor
tank_capex = capex_2013 * 1.36013289036545 / 1.22431893687708

# Piping - simplifying a bit from HDSAM since this is a "drop in the bucket"
kg_d_per_pipe_m = 300 # Estimated by dividing B34 by B104 for many different values
pipe_length_m = terminal_capacity_kg_d / kg_d_per_pipe_m # Simplifying calc of B104
pipe_capex_per_m_2005 = 300 # Using H2A "Estimate based on engineering judgement"
pipe_capex_2005 = pipe_length_m * pipe_capex_per_m_2005
pipe_capex = pipe_capex_2005 * 1.53471220137887 / 1.0

# Plumbing, electrical, instrumentation capex = "pei" - simplifying a bit from HDSAM
kg_d_per_bay = 1600 # Estimated by dividing B34 by B103 for many different values
num_bays = terminal_capacity_kg_d / kg_d_per_bay # Simplifying calc of B103
pipe_capex_per_bay_2005 = 10000 # Using H2A "Estimate based on engineering judgement"
pei_capex_2005 = num_bays * pipe_capex_per_bay_2005
pei_capex = pei_capex_2005 * 2.0454178984144 / 1.0

# Buildings and structures
buildings_capex_2022 = 370029
buildings_capex = buildings_capex_2022 * 1.33340822287126 / 1.85014603459897

# Land - simplifying
kg_d_per_land_m2 = 4 # Estimated by dividing B34 by B192 for many different values
land_required_m2 = terminal_capacity_kg_d / kg_d_per_land_m2
land_capex_per_m2_2022 = 12.35
land_capex_2022 = land_required_m2 * land_capex_per_m2_2022
land_capex = land_capex_2022 * 0.88 # Using CPI to convert to 2018 no CEPCI for land)

# Other
depreciable_capex = comp_capex + tank_capex + pipe_capex + pei_capex + buildings_capex
site_preparation_pct = 0.05
engineering_design_pct = 0.1
project_contingency_pct = 0.1
licensing_pct = 0.0
Comment thread
jmartin4u marked this conversation as resolved.
permitting_pct = 0.03
owner_cost_pct = 0.12
total_other_capex_pct = (
site_preparation_pct
+ engineering_design_pct
+ project_contingency_pct
+ licensing_pct
+ permitting_pct
+ owner_cost_pct
)
other_capex_2022 = depreciable_capex * total_other_capex_pct
other_capex = other_capex_2022 * 0.88 # Using CPI to convert to 2018

# Final, total installed cost:
installed_capex = depreciable_capex + land_capex + other_capex

# ============================================================================
# Calculate OPEX - NEEDS TO BE UPDATED
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.

Is this "NEEDS TO BE UPDATED" note still relevant? part of this PR or follow-on work?

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.

Nope - I put that there in an intermediate commit where I had just copy-pasted code from the geologic storage. Now it is updated, I'll remove the note

# ============================================================================
# Operations and Maintenance costs [1]

# Labor
# Base case is 2 operators, 24 hours a day, 7 days a week for a 100,000 kg/day
# average capacity facility. Scaling factor of 0.25 is used for other sized facilities
# See equation on HDSAM "Cost Data" tab, row 12
# Cost corrected to 2018 using HDSAM "Feedstock & Utility Prices" tab, Table B3
system_flow_rate = terminal_capacity_kg_d
annual_hours = 2 * 8760 * (system_flow_rate / 100000) ** 0.25
labor_rate_2013 = 27.51
labor_rate = labor_rate_2013 * 1.29 / 1.09
overhead = 0.5
labor_om = annual_hours * labor_rate * overhead

# Other O&M
insurance_pct = 0.01 # "Compressed Gas H2 Terminal" tab, cell B229
property_taxes_pct = 0.01 # "Compressed Gas H2 Terminal" tab, cell B229
licensing_permits_pct = 0.001
comp_om_pct = 0.04
facility_om_pct = 0.01
insurance_om = insurance_pct * installed_capex
Comment thread
jmartin4u marked this conversation as resolved.
property_taxes_om = property_taxes_pct * installed_capex
licensing_permits_om = licensing_permits_pct * installed_capex
comp_om = comp_om_pct * comp_capex
facility_om = facility_om_pct * (installed_capex - comp_capex)

# O&M excludes electricity requirements
total_om = (
labor_om
+ insurance_om
+ licensing_permits_om
+ property_taxes_om
+ comp_om
+ facility_om
)

outputs["CapEx"] = installed_capex
outputs["OpEx"] = total_om
22 changes: 17 additions & 5 deletions h2integrate/storage/hydrogen/h2_transport/h2_compression.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@ def __init__(
self,
p_outlet,
flow_rate_kg_d,
compressor_type="pipeline",
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.

The compressor cost model works different for pipeline compressors and storage compressors - see HDSAM, "Cost Data" tab, "Compressor Costs" section (lines 120-131)

p_inlet=20,
n_compressors=2,
sizing_safety_factor=1.1,
):
"""
Parameters:
---------------
compressor_type: "pipeline" or "storage"
p_inlet: inlet pressure (bar)
p_outlet: outlet pressure (bar)
flow_rate_kg_d: mass flow rate in kg/day
"""
self.compressor_type = compressor_type
self.p_inlet = p_inlet # bar
self.p_outlet = p_outlet # bar
self.flow_rate_kg_d = flow_rate_kg_d # kg/day
Expand Down Expand Up @@ -121,15 +125,23 @@ def compressor_costs(self):
n_comp_total = (
self.n_compressors + self.n_comp_back_up
) # 2 compressors + 1 backup for reliability
production_volume_factor = 0.55 # Assume high production volume
CEPCI = 1.29 / 1.1 # Convert from 2007 to 2016$
production_volume_factor = 0.55 # Assume high production volume - HDSAM "Scenario" tab Q7

# From HDSAM "Cost Data" tab, rows 120-131
if self.compressor_type == "pipeline":
cost_per_unit_2007 = 1962.2 * self.motor_rating**0.8225 * production_volume_factor
CEPCI_2007_to_2016 = 1.29 / 1.1 # Convert from 2007 to 2016$
cost_per_unit = cost_per_unit_2007 * CEPCI_2007_to_2016
install_cost_factor = 2
elif self.compressor_type == "storage":
cost_per_unit_2013 = (3758.2 * self.motor_rating + 107562) * production_volume_factor
CEPCI_2013_to_2016 = 1.29 / 1.22 # Convert from 2013 to 2016$
cost_per_unit = cost_per_unit_2013 * CEPCI_2013_to_2016
install_cost_factor = 1.3

cost_per_unit = 1962.2 * self.motor_rating**0.8225 * production_volume_factor * CEPCI
if self.stages > 2:
cost_per_unit = cost_per_unit * (1 + 0.2 * (self.stages - 2))

install_cost_factor = 2

direct_capex = cost_per_unit * n_comp_total * install_cost_factor

land_required = 10000 # m^2 This doesn't change at all in HDSAM...?
Expand Down
Loading
Loading