diff --git a/.gitignore b/.gitignore index 03acac668..9526b1e27 100644 --- a/.gitignore +++ b/.gitignore @@ -161,3 +161,6 @@ resource_files/solar/* # Ignore library files library/* + +# Don't ignore docs images +!docs/*/images/*.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 0206c6865..2b022bf22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ - Modified CI setup so Windows is temporarily disabled and also so unit, regression, and integration tests are run in separate jobs to speed up testing and provide more information on test failures. [PR 668](https://github.com/NatLabRockies/H2Integrate/pull/668) - Added infrastructure for running models with non-hourly time steps via a class attribute `_time_step_bounds` and sets new time step bounds of 5-minutes to 1-hour for the grid components. [PR 653](https://github.com/NatLabRockies/H2Integrate/pull/653) and [PR 671](https://github.com/NatLabRockies/H2Integrate/pull/671) - Remove demand-related outputs from storage performance models and replace usage with demand components [PR 666](https://github.com/NatLabRockies/H2Integrate/pull/666) +- Added a compressed gas hydrogen storage model [PR 680](https://github.com/NatLabRockies/H2Integrate/pull/680) ## 0.7.2 [April 9, 2026] diff --git a/docs/storage/hydrogen_storage.md b/docs/storage/hydrogen_storage.md index 258e4c00a..31514ad15 100644 --- a/docs/storage/hydrogen_storage.md +++ b/docs/storage/hydrogen_storage.md @@ -1,25 +1,34 @@ (h2-storage-cost)= # Bulk Hydrogen Storage Cost Model +This models the cost of bulk storage of hydrogen based on the total kg capacity and kg/h charge/discharge requirements. +The model can be found at `h2integrate\storage\hydrogen\h2_storage_cost.py`. +This file contains a `HydrogenStorageBaseCostModel` which is then used to define models for different types of storage. + ## Storage Types -H2Integrate models at least three types of bulk hydrogen storage technologies: +H2Integrate models four types of bulk hydrogen storage technologies: -- **Underground Pipe Storage**: Hydrogen stored in underground pipeline networks -- **Lined Rock Caverns (LRC)**: Hydrogen stored in rock caverns with engineered linings -- **Salt Caverns**: Hydrogen stored in solution-mined salt caverns +1. **Underground Pipe Storage (`PipeStorageCostModel`)**: Hydrogen stored in underground pipeline networks +2. **Lined Rock Caverns (LRC) (`LinedRockCavernStorageCostModel`)**: Hydrogen stored in rock caverns with engineered linings +3. **Salt Caverns (`SaltCavernStorageCostModel`)**: Hydrogen stored in solution-mined salt caverns +4. **Compressed Gas Terminals (`CompressedGasStorageCostModel`)**: Hydrogen stored in compressed gas tanks up to 700 bar These storage options provide different cost-capacity relationships suitable for various scales of hydrogen production and distribution. -## Cost Correlations +## Cost Correlations - Options 1-3 +For the first three options (underground pipe, lined rock caverns, and salt caverns), cost correlations from [Papadias and Ahluwalia](https://doi.org/10.1016/j.ijhydene.2021.08.028) are used for capital cost calculation. The bulk hydrogen storage costs are modeled as functions of storage capacity using exponential correlations: $$Cost = \exp(a(\ln(m))^2 - b\ln(m) + c)$$ where $m$ is the useable amount of H₂ stored in tonnes. -## Installed Capital Cost and Lifetime Storage Cost +Operational costs are calculated using the [HDSAM model](https://hdsam.es.anl.gov/index.php?content=hdsam) from Argonne National Laboratory, which is also used to model all costs for Option 4. +This method is shown further down this page in the "Cost Correlations - Options 4". + +### Installed Capital Cost and Lifetime Storage Cost The figures below show how storage costs scale with capacity for different storage technologies: @@ -31,8 +40,6 @@ The figures below show how storage costs scale with capacity for different stora *Figure 1b: Lifetime storage cost (\$/kg-H₂-stored) as a function of usable hydrogen storage capacity* -## Cost Correlation Coefficients - ### Capital Cost Coefficients (Figure 1a) | Storage | a | b | c | @@ -48,3 +55,66 @@ The figures below show how storage costs scale with capacity for different stora | Underground pipe storage | 0.001559 | 0.03531 | 4.5183 | | Underground lined rock caverns | 0.092286 | 1.5565 | 8.4658 | | Underground salt caverns | 0.085863 | 1.5574 | 8.1606 | + +## Cost Correlations - Option 4 + +The compressed gas terminals (CGTs) are modeled using a simplified version of the [HDSAM model](https://hdsam.es.anl.gov/index.php?content=hdsam) from Argonne National Laboratory. +HDSAM was developed as hydrogen storage *and* transport model; we have only included the costs that are relevant to *storage* for CGT. + +The model uses the calculation in the HDSAM "Compressed Gas H2 Terminal" sheet. +Although HDSAM as a whole calculates the "Terminal capacity (kg/day)", and "Design terminal storage capacity (kg)" values from other sheets, our simplified CGT model instead takes these as inputs. +- "Terminal capacity (kg/day)" from HDSAM is, in our model, set by the maximum value of the `hydrogen_in` input across its entire timeseries +- "Design terminal storage capacity (kg)" from HDSAM is, in our model, set by the `storage_capacity` input + +For all of the capital and operating costs, CEPCI indexes from with in HDSAM were used to adjust costs to the 2018 cost year that is used by the other models. +These indices can be found in the "Feedstock & Utility Prices" tab of HDSAM. + +### Installed Capital Cost + +The figure below shows an example of the main CGT capital costs as calculated by HDSAM: + +![HDSAM CGT Capex](images/hdsam_cgt_capex.png) + +Since we are not considering transport costs in our model, the "Truck Loading Compressor" and "Truck Scale" components of capital cost are ignored by this model. + +The storage compressor size and kW power consumption are calculated using HDSAM's "H2 Compressor", which is implemented in `h2integrate\storage\hydrogen\h2_transport\h2_compression.py`. +Costs are then calculated using the equations shown here in the "Cost Data" tab of HDSAM: + +![HDSAM Compressor Costs](images/hdsam_compressor_costs.png) + +The tank cost is calculated using HDSAM's cost figures for two discrete storage pressure levels: 350 bar and 700 bar. + +![HDSAM Tank Costs](images/hdsam_tank_costs.png) + +The "Buildings and structures" cost is a constant in HDSAM. +The calculations of "Piping, Supply, Discharge, and Headers", and "Plumbing, electrical, and instrumentation at individual bays" are simplified relative to the HDSAM calculations, since these tend to be very minor components of the overall cost and the full calculation would be unnecessarily complex to implement in H2I. + +For piping etc., HDSAM performs a detailed calculation of the total length of pipe needed for a certain terminal capacity, then multiplies this by an estimate of pipe cost per unit length. +In our simplified model, we ran HDSAM for multiple terminal capacities and calculated the average ratio of terminal capacity to pipe length. +This ratio is the `kg_d_per_pipe_m` constant of 300 kg/day per meter of pipe. + +Similarly, for plumping etc., HDSAM performs a detailed calculation of the total number of storage bays needed for a certain terminal capacity, then multiplies this by an estimate of plumbing cost per bay. +In our simplified model, we ran HDSAM for multiple terminal capacities and calculated the average ratio of terminal capacity to number of bays. +This ratio is the `kg_d_per_bay` constant of 1600 kg/day per storage bay. + +Besides the main capital costs above, other minor capital costs are shown below: + +![HDSAM CGT Other Capex](images/hdsam_cgt_other_capex.png) + +Besides land, these costs are all calculated as a precentage of the main capital costs, in the main cost table as ("Total Capital Investment (Depreciable)"). +For land, we used a similar simplification based on capacity as those used for piping/plumbing. +In our simplified model, we ran HDSAM for multiple terminal capacities and calculated the average ratio of terminal capacity to "GH2 Terminal Land Required (m2)". +This ratio is the `kg_d_per_land_m2` constant of 4 kg/day per square meter of land. + +### Operating Cost + +The O&M costs of the CGT as calculated by HDSAM are shown below: + +![HDSAM CGT Opex](images/hdsam_cgt_opex.png) + +Of these costs, electricity cost is ignored, since this is a feedstock. +To consider electricity cost, a separate electricity feedstock or generator should be connected to the hydrogen storage model via the `plant_config`. +Most of the other costs are percentages of the "Total Capital Investment", except labor. +Labor is calculated on a scaled basis using the equations in the "Cost Data" tab of HDSAM: + +![HDSAM Labor](images/hdsam_labor.png) diff --git a/docs/storage/images/hdsam_cgt_capex.png b/docs/storage/images/hdsam_cgt_capex.png new file mode 100644 index 000000000..bd2d92a04 Binary files /dev/null and b/docs/storage/images/hdsam_cgt_capex.png differ diff --git a/docs/storage/images/hdsam_cgt_opex.png b/docs/storage/images/hdsam_cgt_opex.png new file mode 100644 index 000000000..d816e549a Binary files /dev/null and b/docs/storage/images/hdsam_cgt_opex.png differ diff --git a/docs/storage/images/hdsam_cgt_other_capex.png b/docs/storage/images/hdsam_cgt_other_capex.png new file mode 100644 index 000000000..84cda1bf3 Binary files /dev/null and b/docs/storage/images/hdsam_cgt_other_capex.png differ diff --git a/docs/storage/images/hdsam_compressor_costs.png b/docs/storage/images/hdsam_compressor_costs.png new file mode 100644 index 000000000..a915a5c07 Binary files /dev/null and b/docs/storage/images/hdsam_compressor_costs.png differ diff --git a/docs/storage/images/hdsam_labor.png b/docs/storage/images/hdsam_labor.png new file mode 100644 index 000000000..cf6bdc6a1 Binary files /dev/null and b/docs/storage/images/hdsam_labor.png differ diff --git a/docs/storage/images/hdsam_tank_costs.png b/docs/storage/images/hdsam_tank_costs.png new file mode 100644 index 000000000..2da9619a2 Binary files /dev/null and b/docs/storage/images/hdsam_tank_costs.png differ diff --git a/docs/technology_models/images/installed_capital_cost_h2.png b/docs/storage/images/installed_capital_cost_h2.png similarity index 100% rename from docs/technology_models/images/installed_capital_cost_h2.png rename to docs/storage/images/installed_capital_cost_h2.png diff --git a/docs/technology_models/images/lifetime_storage_cost_h2.png b/docs/storage/images/lifetime_storage_cost_h2.png similarity index 100% rename from docs/technology_models/images/lifetime_storage_cost_h2.png rename to docs/storage/images/lifetime_storage_cost_h2.png diff --git a/h2integrate/core/supported_models.py b/h2integrate/core/supported_models.py index 9fa656517..62e197de9 100644 --- a/h2integrate/core/supported_models.py +++ b/h2integrate/core/supported_models.py @@ -55,6 +55,7 @@ from h2integrate.storage.hydrogen.h2_storage_cost import ( PipeStorageCostModel, SaltCavernStorageCostModel, + CompressedGasStorageCostModel, LinedRockCavernStorageCostModel, ) from h2integrate.transporters.gas_stream_combiner import GasStreamCombinerPerformanceModel @@ -285,6 +286,7 @@ "StoragePerformanceModel": StoragePerformanceModel, "StorageAutoSizingModel": StorageAutoSizingModel, "LinedRockCavernStorageCostModel": LinedRockCavernStorageCostModel, + "CompressedGasStorageCostModel": CompressedGasStorageCostModel, "SaltCavernStorageCostModel": SaltCavernStorageCostModel, "MCHTOLStorageCostModel": MCHTOLStorageCostModel, "PipeStorageCostModel": PipeStorageCostModel, diff --git a/h2integrate/storage/hydrogen/h2_storage_cost.py b/h2integrate/storage/hydrogen/h2_storage_cost.py index ab392c5b2..bbc29c796 100644 --- a/h2integrate/storage/hydrogen/h2_storage_cost.py +++ b/h2integrate/storage/hydrogen/h2_storage_cost.py @@ -12,9 +12,24 @@ 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`. + Attributes: + max_capacity (float): Maximum storage capacity (kg) + max_charge_rate (float): Maximum charging rate (kg/h) + sizing_mode (str): Mode for sizing storage (auto or set) + commodity_rate_units (str): Units of the commodity + cost_year (int): Year for cost calculations + labor_rate (float): Labor rate for cost calculations + insurance (float): Insurance cost as a fraction of total cost + property_taxes (float): Property taxes as a fraction of total cost + licensing_permits (float): Licensing and permits cost as a fraction of total cost + compressor_om (float): Compressor operation and maintenance cost as a fraction of total cost + facility_om (float): Facility operation and maintenance cost as a fraction of total cost + inlet_pressure_bar (float): Inlet pressure for compressed gas storage (bar) + storage_pressure_bar (float): Storage pressure for compressed gas storage (bar) - max 700 + cg_capex_per_kg_350_bar (float): Capital cost per kg for compressed gas storage at 350 bar + Default is $1200 (2013 dollars) from HDSAM, converted to 2018 dollars using CEPCI. + cg_capex_per_kg_700_bar (float): Capital cost per kg for compressed gas storage at 700 bar + Default is $1800 (2013 dollars) from HDSAM, converted to 2018 dollars using CEPCI. """ max_capacity: float | None = field(default=None) @@ -23,8 +38,7 @@ class HydrogenStorageBaseCostModelConfig(BaseConfig): default="set", converter=(str.strip, str.lower), validator=contains(["auto", "set"]) ) - commodity_name: str = field(default="hydrogen") - commodity_units: str = field(default="kg/h", validator=contains(["kg/h", "g/h", "t/h"])) + commodity_rate_units: str = field(default="kg/h", validator=contains(["kg/h", "g/h", "t/h"])) cost_year: int = field(default=2018, converter=int, validator=contains([2018])) labor_rate: float = field(default=37.39817, validator=gte_zero) @@ -33,6 +47,10 @@ 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)) + cg_capex_per_kg_350_bar: float = field(default=1333.11625, validator=gte_zero) + cg_capex_per_kg_700_bar: float = field(default=1999.67437, validator=gte_zero) def __attrs_post_init__(self): undefined_capacities = self.max_capacity is None or self.max_charge_rate is None @@ -65,8 +83,7 @@ def make_model_dict(self): h2i_params = [ "max_capacity", "max_charge_rate", - "commodity_name", - "commodity_units", + "commodity_rate_units", "cost_year", ] lrc_dict = {k: v for k, v in params.items() if k not in h2i_params} @@ -96,14 +113,14 @@ def setup(self): self.add_input( "max_charge_rate", val=self.config.max_charge_rate, - units=f"{self.config.commodity_units}", + units=f"{self.config.commodity_rate_units}", desc="Hydrogen storage charge rate", ) self.add_input( "storage_capacity", val=self.config.max_capacity, - units=f"{self.config.commodity_units}*h", + units=f"{self.config.commodity_rate_units}*h", desc="Hydrogen storage capacity", ) @@ -111,7 +128,7 @@ def setup(self): "hydrogen_in", val=0.0, shape=n_timesteps, - units=f"{self.config.commodity_units}", + units=f"{self.config.commodity_rate_units}", desc="Hydrogen input timeseries for average flow rate calculation", ) @@ -122,7 +139,7 @@ def make_storage_input_dict(self, inputs): # convert capacity to kg max_capacity_kg = units.convert_units( - inputs["storage_capacity"], f"({self.config.commodity_units})*h", "kg" + inputs["storage_capacity"], f"({self.config.commodity_rate_units})*h", "kg" ) storage_input["h2_storage_kg"] = max_capacity_kg[0] @@ -132,7 +149,7 @@ def make_storage_input_dict(self, inputs): # not the maximum fill rate. avg_hydrogen_in = np.mean(inputs["hydrogen_in"]) system_flow_rate = units.convert_units( - avg_hydrogen_in, f"{self.config.commodity_units}", "kg/d" + avg_hydrogen_in, f"{self.config.commodity_rate_units}", "kg/d" ) storage_input["system_flow_rate"] = system_flow_rate # kg/day @@ -227,8 +244,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() @@ -357,8 +378,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() @@ -501,8 +526,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() @@ -546,3 +575,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_rate_units})", "kg/d" + ) + terminal_capacity_kg_d = np.max(h2_in_kg_d) + storage_capacity_kg = units.convert_units( + inputs["storage_capacity"][0], f"({self.config.commodity_rate_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) + # Fit is coming from config values which are already in 2018 dollars + capex_per_kg_350_bar = self.config.cg_capex_per_kg_350_bar # "Cost Data" row 89 + capex_per_kg_700_bar = self.config.cg_capex_per_kg_700_bar # "Cost Data" row 96 + tank_capex_per_kg = ( + capex_per_kg_350_bar + + (capex_per_kg_700_bar - capex_per_kg_350_bar) + * (self.config.storage_pressure_bar - 350) + / 350 + ) + tank_installation_factor = 1.3 + tank_capex = tank_capex_per_kg * storage_capacity_kg * tank_installation_factor + + # 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 + 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 + # ============================================================================ + # 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 = self.config.insurance + property_taxes_pct = self.config.property_taxes + licensing_permits_pct = self.config.licensing_permits + comp_om_pct = self.config.compressor_om + facility_om_pct = self.config.facility_om + insurance_om = insurance_pct * installed_capex + 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 diff --git a/h2integrate/storage/hydrogen/h2_transport/h2_compression.py b/h2integrate/storage/hydrogen/h2_transport/h2_compression.py index fd534555e..2333f8585 100644 --- a/h2integrate/storage/hydrogen/h2_transport/h2_compression.py +++ b/h2integrate/storage/hydrogen/h2_transport/h2_compression.py @@ -15,6 +15,7 @@ def __init__( self, p_outlet, flow_rate_kg_d, + compressor_type="pipeline", p_inlet=20, n_compressors=2, sizing_safety_factor=1.1, @@ -22,9 +23,12 @@ def __init__( """ 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 @@ -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...? diff --git a/h2integrate/storage/hydrogen/test/test_hydrogen_storage.py b/h2integrate/storage/hydrogen/test/test_hydrogen_storage.py index 2f736b214..c1bb21363 100644 --- a/h2integrate/storage/hydrogen/test/test_hydrogen_storage.py +++ b/h2integrate/storage/hydrogen/test/test_hydrogen_storage.py @@ -12,7 +12,8 @@ def tech_config(max_capacity, max_charge_rate): "shared_parameters": { "max_capacity": max_capacity, "max_charge_rate": max_charge_rate, - } + }, + "cost_parameters": {"storage_pressure_bar": 500}, } } return config @@ -31,6 +32,7 @@ def tech_config(max_capacity, max_charge_rate): ("LinedRockCavernStorageCostModel", 8760, 1000000, 100000 / 24, 51136144, 2359700.44640052, 0, 2018), # noqa: E501 ("SaltCavernStorageCostModel", 8760, 1000000, 100000 / 24, 24992482.4198, 1461663.9089168755, 0, 2018), # noqa: E501 ("PipeStorageCostModel", 8760, 1000000, 100000 / 24, 508745483.851, 16439748.432128396, 0, 2018), # noqa: E501 + ("CompressedGasStorageCostModel", 8760, 1000000, 100000 / 24, 2943465745.086474, 93656670.9723113, 0, 2018), # noqa: E501 ], ids=[ "SaltCavernStorageCostModel-ex2", @@ -41,6 +43,7 @@ def tech_config(max_capacity, max_charge_rate): "LinedRockCavernStorageCostModel-1M-kg", "SaltCavernStorageCostModel-1M-kg", "PipeStorageCostModel-1M-kg", + "CompressedGasStorageCostModel-1M-kg", ] ) # fmt: on @@ -89,16 +92,22 @@ def test_h2_storage_capex_opex( # fmt: off @pytest.mark.regression @pytest.mark.parametrize( - "model,n_timesteps,max_capacity,max_charge_rate,a,b,c", + "model,n_timesteps,max_capacity,max_charge_rate,expected_storage_capex,a,b,c", [ - ("LinedRockCavernStorageCostModel", 8760, 1000000, 100000 / 24, 0.095803, 1.5868, 10.332), - ("SaltCavernStorageCostModel", 8760, 1000000, 100000 / 24, 0.092548, 1.6432, 10.161), - ("PipeStorageCostModel", 8760, 1000000, 100000 / 24, 0.0041617, 0.060369, 6.4581), + ("LinedRockCavernStorageCostModel", + 8760, 1000000, 100000 / 24, 51136144, 0.095803, 1.5868, 10.332), + ("SaltCavernStorageCostModel", + 8760, 1000000, 100000 / 24, 24992482.4198, 0.092548, 1.6432, 10.161), + ("PipeStorageCostModel", + 8760, 1000000, 100000 / 24, 508745483.851, 0.0041617, 0.060369, 6.4581), + ("CompressedGasStorageCostModel", + 8760, 1000000, 100000 / 24, 1761617900.172114, 500, 1200, 1800), ], ids=[ "LinedRockCavernStorageCostModel-1M-kg", "SaltCavernStorageCostModel-1M-kg", "PipeStorageCostModel-1M-kg", + "CompressedGasStorageCostModel-1M-kg", ] ) # fmt: on @@ -109,11 +118,20 @@ def test_h2_storage_capex_per_kg( n_timesteps, max_capacity, max_charge_rate, + expected_storage_capex, a, b, c, ): - """Test based on original test_lined_rock_storage.py with 1M kg storage capacity.""" + """ + Tests calculation of H2 storage costs based on storage capacity. + + For cavern/pipe methods polynomial coefficients `a`, `b` and `c` are used to calculate capex, + and `expected_storage_capex` is the *total* capex, which scales with capacity. + + For compressed gas, `a` is storage pressure, `b` and `c` are capex/kg at 350 and 700 bar, + and `exptected_storage_capex` is the *tank* component of capex, which scales with capacity. + """ prob = om.Problem() comp = supported_models[model]( plant_config=plant_config, @@ -130,13 +148,20 @@ def test_h2_storage_capex_per_kg( # Calculate expected capex per kg h2_storage_kg = max_capacity - capex_per_kg = np.exp( - a * (np.log(h2_storage_kg / 1000)) ** 2 - b * np.log(h2_storage_kg / 1000) + c - ) - cepci_overall = 1.29 / 1.30 - expected_capex = cepci_overall * capex_per_kg * h2_storage_kg + if model != "CompressedGasStorageCostModel": + capex_per_kg = np.exp( + a * (np.log(h2_storage_kg / 1000)) ** 2 - b * np.log(h2_storage_kg / 1000) + c + ) + cepci_overall = 1.29 / 1.30 + expected_capex = cepci_overall * capex_per_kg * h2_storage_kg + else: + h2_storage_pressure_bar = a + capex_per_kg = b + (h2_storage_pressure_bar - 350) / 700 * c + cepci_overall = 1.36013289036545 / 1.22431893687708 + expected_capex = cepci_overall * capex_per_kg * h2_storage_kg + - assert pytest.approx(prob.get_val("sys.CapEx", units="USD")[0], rel=1e-6) == expected_capex + assert pytest.approx(expected_storage_capex, rel=1e-6) == expected_capex @pytest.mark.regression