Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/greensteel-eco-sync' into gree…
Browse files Browse the repository at this point in the history
…nheart-layout
  • Loading branch information
kbrunik committed Jun 5, 2024
2 parents c8191e8 + 7fe8a0f commit 4d55862
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import math
import numpy as np


def hydrogen_storage_capacity(H2_Results, electrolyzer_size_mw, hydrogen_demand_kgphr):
"""Calculate storage capacity based on hydrogen demand and production.
Args:
H2_Results (dict): Dictionary including electrolyzer physics results.
electrolyzer_size_mw (float): Electrolyzer size in MW.
hydrogen_demand_kgphr (float): Hydrogen demand in kg/hr.
Returns:
hydrogen_storage_capacity_kg (float): Hydrogen storage capacity in kilograms.
hydrogen_storage_duration_hr (float): Hydrogen storage duration in hours using HHV/LHV.
hydrogen_storage_soc (list): Timeseries of the hydrogen storage state of charge.
"""

hydrogen_production_kgphr = H2_Results["Hydrogen Hourly Production [kg/hr]"]

hydrogen_demand_kgphr = max(
hydrogen_demand_kgphr, np.mean(hydrogen_production_kgphr)
) # TODO: potentially add buffer No buffer needed since we are already oversizing

# TODO: SOC is just an absolute value and is not a percentage. Ideally would calculate as shortfall in future.
hydrogen_storage_soc = []
for j in range(len(hydrogen_production_kgphr)):
if j == 0:
hydrogen_storage_soc.append(
hydrogen_production_kgphr[j] - hydrogen_demand_kgphr
)
else:
hydrogen_storage_soc.append(
hydrogen_storage_soc[j - 1]
+ hydrogen_production_kgphr[j]
- hydrogen_demand_kgphr
)

minimum_soc = np.min(hydrogen_storage_soc)

#adjust soc so it's not negative.
if minimum_soc < 0:
hydrogen_storage_soc = [x + np.abs(minimum_soc) for x in hydrogen_storage_soc]

hydrogen_storage_capacity_kg = np.max(hydrogen_storage_soc) - np.min(
hydrogen_storage_soc
)
h2_LHV = 119.96 # MJ/kg
h2_HHV = 141.88 # MJ/kg
hydrogen_storage_capacity_MWh_LHV = hydrogen_storage_capacity_kg * h2_LHV / 3600
hydrogen_storage_capacity_MWh_HHV = hydrogen_storage_capacity_kg * h2_HHV / 3600

# # Get max injection/withdrawal rate
# hydrogen_injection_withdrawal_rate = []
# for j in range(len(hydrogen_production_kgphr)):
# hydrogen_injection_withdrawal_rate.append(
# hydrogen_production_kgphr[j] - hydrogen_demand_kgphr
# )
# max_h2_injection_rate_kgphr = max(hydrogen_injection_withdrawal_rate)

# # Get storage compressor capacity. TODO: sync compressor calculation here with GreenHEART compressor model
# compressor_total_capacity_kW = (
# max_h2_injection_rate_kgphr / 3600 / 2.0158 * 8641.678424
# )

# compressor_max_capacity_kw = 16000
# n_comps = math.ceil(compressor_total_capacity_kW / compressor_max_capacity_kw)

# small_positive = 1e-6
# compressor_avg_capacity_kw = compressor_total_capacity_kW / (
# n_comps + small_positive
# )

# Get average electrolyzer efficiency
electrolyzer_average_efficiency_HHV = H2_Results['Sim: Average Efficiency [%-HHV]']

# Calculate storage durationhyd
hydrogen_storage_duration_hr = (
hydrogen_storage_capacity_MWh_LHV
/ electrolyzer_size_mw
/ electrolyzer_average_efficiency_HHV
)

return hydrogen_storage_capacity_kg, hydrogen_storage_duration_hr, hydrogen_storage_soc
14 changes: 12 additions & 2 deletions greenheart/tools/eco/hydrogen_mgmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
)
from greenheart.simulation.technologies.offshore.all_platforms import calc_platform_opex

from greenheart.simulation.technologies.hydrogen.h2_storage.storage_sizing import hydrogen_storage_capacity


def run_h2_pipe_array(
greenheart_config,
Expand Down Expand Up @@ -289,8 +291,16 @@ def run_h2_storage(
greenheart_config["h2_capacity"] = 0.0
h2_storage_results["h2_storage_kg"] = 0.0
else:
greenheart_config["h2_capacity"] = h2_capacity
h2_storage_results["h2_storage_kg"] = h2_capacity
if greenheart_config['h2_storage']['size_capacity_from_demand']['flag']:
hydrogen_storage_demand = np.mean(electrolyzer_physics_results["H2_Results"][
"Hydrogen Hourly Production [kg/hr]"
]) # TODO: update demand based on end-use needs
hydrogen_storage_capacity_kg, hydrogen_storage_duration_hr, hydrogen_storage_soc = hydrogen_storage_capacity(electrolyzer_physics_results['H2_Results'], greenheart_config['electrolyzer']['rating'], hydrogen_storage_demand)
greenheart_config["h2_capacity"] = hydrogen_storage_capacity_kg
h2_storage_results["h2_storage_kg"] = hydrogen_storage_capacity_kg
else:
greenheart_config["h2_capacity"] = h2_capacity
h2_storage_results["h2_storage_kg"] = h2_capacity

# if storage_hours == 0:
if (
Expand Down
18 changes: 15 additions & 3 deletions greenheart/tools/optimization/gc_PoseOptimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,23 @@ def set_driver(self, opt_prob):
opt_options = self.config.greenheart_config["opt_options"]["driver"]["optimization"]
step_size = self._get_step_size()

if opt_options["step_calc"] == "None":
if "step_calc" in opt_options.keys():
if opt_options["step_calc"] == "None":
step_calc = None
else:
step_calc = opt_options["step_calc"]
else:
step_calc = None

if "form" in opt_options.keys():
if opt_options["form"] == "None":
form = None
else:
form = opt_options["form"]
else:
step_calc = opt_options["step_calc"]
opt_prob.model.approx_totals(method="fd", step=step_size, form=opt_options["form"], step_calc=step_calc)
form = None

opt_prob.model.approx_totals(method="fd", step=step_size, form=form, step_calc=step_calc)

# Set optimization solver and options. First, Scipy's SLSQP and COBYLA
if opt_options["solver"] in self.scipy_methods:
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ openpyxl
CoolProp
attrs
utm
orbit-nrel @ git+https://github.com/WISDEM/ORBIT.git@SemiTaut_Mooring_Update
orbit-nrel @ git+https://github.com/WISDEM/ORBIT.git@3baed36052d7503de6ea6f2db0b40945d637ea35
pyyaml-include <= 1.4.1
electrolyzer @ git+https://github.com/jaredthomas68/electrolyzer.git@smoothing
ProFAST @ git+https://github.com/NREL/ProFAST.git
Expand Down
5 changes: 3 additions & 2 deletions tests/greenheart/input_files/plant/greenheart_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,11 @@ h2_storage_compressor:
h2_transport_pipe:
outlet_pressure: 10 # bar - from example in code from Jamie #TODO check this value
h2_storage:
# capacity_kg: 18750 # kg
size_capacity_from_demand:
flag: False # If True, then storage is sized to provide steady-state storage
capacity_from_max_on_turbine_storage: False # if True, then days of storage is ignored and storage capacity is based on how much h2 storage fits on the turbines in the plant using Kottenstete 2003.
type: "none" # can be one of ["none", "pipe", "turbine", "pressure_vessel", "salt_cavern", "lined_rock_cavern"]
days: 3 # [days] how many days worth of production we should be able to store (this is ignored if `capacity_from_max_on_turbine_storage` is set to True)
days: 3 # [days] how many days worth of production we should be able to store (this is ignored if `capacity_from_max_on_turbine_storage` or `size_capacity_from_demand` is set to True)

platform:
opex_rate: 0.0111 # % of capex to determine opex (see table 5 in https://www.acm.nl/sites/default/files/documents/study-on-estimation-method-for-additional-efficient-offshore-grid-opex.pdf)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ h2_storage_compressor:
h2_transport_pipe:
outlet_pressure: 10 # bar - from example in code from Jamie #TODO check this value
h2_storage:
# capacity_kg: 18750 # kg
size_capacity_from_demand:
flag: False # If True, then storage is sized to provide steady-state storage
capacity_from_max_on_turbine_storage: False # if True, then days of storage is ignored and storage capacity is based on how much h2 storage fits on the turbines in the plant using Kottenstete 2003.
type: "none" # can be one of ["none", "pipe", "turbine", "pressure_vessel", "salt_cavern", "lined_rock_cavern"]
days: 3 # [days] how many days worth of production we should be able to store (this is ignored if `capacity_from_max_on_turbine_storage` is set to True)
Expand Down
52 changes: 51 additions & 1 deletion tests/greenheart/test_greenheart_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,4 +363,54 @@ def test_simulation_wind_battery_pv_onshore_steel_ammonia(subtests):
expected_length = 8760

for key in greenheart_output.hourly_energy_breakdown.keys():
assert len(greenheart_output.hourly_energy_breakdown[key]) == expected_length
assert len(greenheart_output.hourly_energy_breakdown[key]) == expected_length

def test_simulation_wind_onshore_steel_ammonia_ss_h2storage(subtests):

config = GreenHeartSimulationConfig(
filename_hopp_config=filename_hopp_config,
filename_greenheart_config=filename_greenheart_config_onshore,
filename_turbine_config=filename_turbine_config,
filename_floris_config=filename_floris_config,
verbose=False,
show_plots=False,
save_plots=True,
output_dir=os.path.abspath(pathlib.Path(__file__).parent.resolve()) + "/output/",
use_profast=True,
post_processing=True,
incentive_option=1,
plant_design_scenario=9,
output_level=7,
)

config.greenheart_config['h2_storage']['size_capacity_from_demand']['flag'] = True
config.greenheart_config['h2_storage']['type'] = 'pipe'

# based on 2023 ATB moderate case for onshore wind
config.hopp_config["config"]["cost_info"]["wind_installed_cost_mw"] = 1434000.0
# based on 2023 ATB moderate case for onshore wind
config.hopp_config["config"]["cost_info"]["wind_om_per_kw"] = 29.567
config.hopp_config["technologies"]["wind"]["fin_model"]["system_costs"]["om_fixed"][0] = config.hopp_config["config"]["cost_info"]["wind_om_per_kw"]
# set skip_financial to false for onshore wind
config.hopp_config["config"]["simulation_options"]["wind"]["skip_financial"] = False
lcoe, lcoh, steel_finance, ammonia_finance = run_simulation(config)

# TODO base this test value on something
with subtests.test("lcoh"):
assert lcoh == approx(4.023687007795485, rel=rtol)

# TODO base this test value on something
with subtests.test("lcoe"):
assert lcoe == approx(0.03486192934806013, rel=rtol)

# TODO base this test value on something
with subtests.test("steel_finance"):
lcos_expected = 1414.0330270955506

assert steel_finance.sol.get("price") == approx(lcos_expected, rel=rtol)

# TODO base this test value on something
with subtests.test("ammonia_finance"):
lcoa_expected = 1.0419096226034346

assert ammonia_finance.sol.get("price") == approx(lcoa_expected, rel=rtol)

0 comments on commit 4d55862

Please sign in to comment.