From 4ba397ba130ff5107253c041f18bf298ee4c9368 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 15 Jan 2026 14:10:45 -0800 Subject: [PATCH 01/22] replace docs notebooks with executable markdown for simple diffs --- ...turbine_models_library_preprocessing.ipynb | 1155 ----------------- .../turbine_models_library_preprocessing.md | 263 ++++ .../how_to_set_up_an_analysis.ipynb | 390 ------ docs/user_guide/how_to_set_up_an_analysis.md | 303 +++++ 4 files changed, 566 insertions(+), 1545 deletions(-) delete mode 100644 docs/misc_resources/turbine_models_library_preprocessing.ipynb create mode 100644 docs/misc_resources/turbine_models_library_preprocessing.md delete mode 100644 docs/user_guide/how_to_set_up_an_analysis.ipynb create mode 100644 docs/user_guide/how_to_set_up_an_analysis.md diff --git a/docs/misc_resources/turbine_models_library_preprocessing.ipynb b/docs/misc_resources/turbine_models_library_preprocessing.ipynb deleted file mode 100644 index b8819ee7f..000000000 --- a/docs/misc_resources/turbine_models_library_preprocessing.ipynb +++ /dev/null @@ -1,1155 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Turbine Models Library Pre-Processing Tools\n", - "\n", - "The [turbine-models package](https://github.com/NREL/turbine-models/tree/main) hosts wind turbine data for a variety of wind turbines and has tools that can streamline the process to run new turbines with the [PySAM Windpower model](https://nrel-pysam.readthedocs.io/en/main/modules/Windpower.html) or [FLORIS](https://github.com/NREL/floris/tree/main).\n", - "\n", - "The full list of turbine models available in the turbine-models library can be found [here](https://github.com/NREL/turbine-models/blob/main/turbine_models/supported_turbines.py)\n", - "\n", - "H2Integrate has preprocessing tools that leverage the functionality available in the turbine-models library. The function `export_turbine_to_pysam_format()` will save turbine model specifications formatted for the PySAM Windpower model. The PySAM Windpower model is wrapped in H2I and can be utilized with the \"pysam_wind_plant_performance\" model. Example usage of the `export_turbine_to_pysam_format()` function is demonstrated in the following section using Example 8.\n", - "\n", - "\n", - "## Turbine Model Pre-Processing with PySAM Windpower Model\n", - "Example 8 (`08_wind_electrolyzer`) currently uses an 8.3 MW turbine. In the following sections we will demonstrate how to:\n", - "\n", - "1. Save turbine model specifications for the NREL 5 MW turbine in the PySAM Windpower format using `export_turbine_to_pysam_format()`\n", - "2. Load the turbine model specifications for the NREL 5 MW turbine and update performance parameters for the wind technology in the `tech_config` dictionary for the NREL 5 MW turbine.\n", - "3. Run H2I with the updated tech_config dictionary, showcasing two different methods to run H2I with the NREL 5 MW turbine:\n", - " - initializing H2I with a dictionary input\n", - " - saving the updated tech_config dictionary to a new file and initializing H2I by specifying the filepath to the top-level config file.\n", - "\n", - "\n", - "We'll start off by importing the required modules and packages:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/egrant/opt/anaconda3/envs/h2i_env/lib/python3.11/site-packages/polars/_cpu_check.py:250: RuntimeWarning: Missing required CPU features.\n", - "\n", - "The following required CPU features were not detected:\n", - " avx, avx2, fma, bmi1, bmi2, lzcnt, movbe\n", - "Continuing to use this version of Polars on this processor will likely result in a crash.\n", - "Install `polars[rtcompat]` instead of `polars` to run Polars with better compatibility.\n", - "\n", - "Hint: If you are on an Apple ARM machine (e.g. M1) this is likely due to running Python under Rosetta.\n", - "It is recommended to install a native version of Python that does not run under Rosetta x86-64 emulation.\n", - "\n", - "If you believe this warning to be a false positive, you can set the `POLARS_SKIP_CPU_CHECK` environment variable to bypass this check.\n", - "\n", - " warnings.warn(\n", - "/Users/egrant/opt/anaconda3/envs/h2i_env/lib/python3.11/site-packages/fastkml/__init__.py:28: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.\n", - " from pkg_resources import DistributionNotFound\n" - ] - } - ], - "source": [ - "import os\n", - "import numpy as np\n", - "\n", - "\n", - "from h2integrate import EXAMPLE_DIR\n", - "from h2integrate.core.utilities import load_yaml\n", - "from h2integrate.core.inputs.validation import load_tech_yaml\n", - "from h2integrate.preprocess.wind_turbine_file_tools import export_turbine_to_pysam_format\n", - "from h2integrate.core.h2integrate_model import H2IntegrateModel" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Load the tech config file that we want to update the turbine model for:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "# Load the tech config file\n", - "tech_config_path = EXAMPLE_DIR / \"08_wind_electrolyzer\" / \"tech_config.yaml\"\n", - "tech_config = load_tech_yaml(tech_config_path)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This example uses the \"pysam_wind_plant_performance\" performance model for the wind plant. Currently, the performance model is using an 8.3MW wind turbine with a rotor diameter of 196 meters and a hub-height of 130 meters. This information is defined in the `tech_config` file:\n", - "\n", - "```yaml\n", - "technologies:\n", - " wind:\n", - " performance_model:\n", - " model: \"pysam_wind_plant_performance\"\n", - " cost_model:\n", - " model: \"atb_wind_cost\"\n", - " model_inputs:\n", - " performance_parameters:\n", - " num_turbines: 100\n", - " turbine_rating_kw: 8300\n", - " rotor_diameter: 196.\n", - " hub_height: 130.\n", - " create_model_from: \"default\"\n", - " config_name: \"WindPowerSingleOwner\"\n", - " pysam_options: !include \"pysam_options_8.3MW.yaml\"\n", - " run_recalculate_power_curve: False\n", - " layout:\n", - " layout_mode: \"basicgrid\"\n", - " layout_options:\n", - " row_D_spacing: 10.0\n", - " turbine_D_spacing: 10.0\n", - " rotation_angle_deg: 0.0\n", - " row_phase_offset: 0.0\n", - " layout_shape: \"square\"\n", - " cost_parameters:\n", - " capex_per_kW: 1500.0\n", - " opex_per_kW_per_year: 45\n", - " cost_year: 2019\n", - "```\n", - "\n", - "\n", - "If we want to replace the 8.3 MW turbine with the NREL 5 MW turbine, we can do so using the `export_turbine_to_pysam_format()` function:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/Users/egrant/Documents/projects/GreenHEART/library/pysam_options_NREL_5MW.yaml\n" - ] - } - ], - "source": [ - "turbine_name = \"NREL_5MW\"\n", - "\n", - "turbine_model_fpath = export_turbine_to_pysam_format(turbine_name)\n", - "\n", - "print(turbine_model_fpath)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'Turbine': {'wind_turbine_max_cp': 0.481305875,\n", - " 'wind_turbine_ct_curve': [0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 1.132034888,\n", - " 0.999470963,\n", - " 0.917697381,\n", - " 0.860849503,\n", - " 0.815371198,\n", - " 0.811614904,\n", - " 0.807939328,\n", - " 0.80443352,\n", - " 0.800993851,\n", - " 0.79768116,\n", - " 0.794529244,\n", - " 0.791495834,\n", - " 0.788560434,\n", - " 0.787217182,\n", - " 0.787127977,\n", - " 0.785839257,\n", - " 0.783812219,\n", - " 0.783568108,\n", - " 0.783328285,\n", - " 0.781194418,\n", - " 0.777292539,\n", - " 0.773464375,\n", - " 0.769690236,\n", - " 0.766001924,\n", - " 0.762348072,\n", - " 0.758760824,\n", - " 0.755242872,\n", - " 0.751792927,\n", - " 0.748434131,\n", - " 0.745113997,\n", - " 0.717806682,\n", - " 0.672204789,\n", - " 0.63831272,\n", - " 0.610176496,\n", - " 0.585456847,\n", - " 0.563222111,\n", - " 0.542912273,\n", - " 0.399312061,\n", - " 0.310517829,\n", - " 0.248633226,\n", - " 0.203543725,\n", - " 0.169616419,\n", - " 0.143478955,\n", - " 0.122938861,\n", - " 0.106515296,\n", - " 0.093026095,\n", - " 0.081648606,\n", - " 0.072197368,\n", - " 0.064388275,\n", - " 0.057782745,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0],\n", - " 'wind_turbine_powercurve_windspeeds': [0.0,\n", - " 1.0,\n", - " 2.0,\n", - " 3.0,\n", - " 4.0,\n", - " 5.0,\n", - " 6.0,\n", - " 7.0,\n", - " 7.1,\n", - " 7.2,\n", - " 7.3,\n", - " 7.4,\n", - " 7.5,\n", - " 7.6,\n", - " 7.7,\n", - " 7.8,\n", - " 7.9,\n", - " 8.0,\n", - " 9.0,\n", - " 10.0,\n", - " 10.1,\n", - " 10.2,\n", - " 10.3,\n", - " 10.4,\n", - " 10.5,\n", - " 10.6,\n", - " 10.7,\n", - " 10.8,\n", - " 10.9,\n", - " 11.0,\n", - " 11.1,\n", - " 11.2,\n", - " 11.3,\n", - " 11.4,\n", - " 11.5,\n", - " 11.6,\n", - " 11.7,\n", - " 11.8,\n", - " 11.9,\n", - " 12.0,\n", - " 13.0,\n", - " 14.0,\n", - " 15.0,\n", - " 16.0,\n", - " 17.0,\n", - " 18.0,\n", - " 19.0,\n", - " 20.0,\n", - " 21.0,\n", - " 22.0,\n", - " 23.0,\n", - " 24.0,\n", - " 25.0,\n", - " 26.0,\n", - " 27.0,\n", - " 28.0,\n", - " 29.0,\n", - " 30.0,\n", - " 31.0,\n", - " 32.0,\n", - " 33.0,\n", - " 34.0,\n", - " 35.0,\n", - " 36.0,\n", - " 37.0,\n", - " 38.0,\n", - " 39.0,\n", - " 40.0,\n", - " 41.0,\n", - " 42.0,\n", - " 43.0,\n", - " 44.0,\n", - " 45.0,\n", - " 46.0,\n", - " 47.0,\n", - " 48.0,\n", - " 49.0],\n", - " 'wind_turbine_powercurve_powerout': [0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 40.52,\n", - " 177.67,\n", - " 403.9,\n", - " 737.59,\n", - " 1187.18,\n", - " 1239.25,\n", - " 1292.52,\n", - " 1347.32,\n", - " 1403.26,\n", - " 1460.7,\n", - " 1519.64,\n", - " 1580.17,\n", - " 1642.11,\n", - " 1705.76,\n", - " 1771.17,\n", - " 2518.55,\n", - " 3448.38,\n", - " 3552.14,\n", - " 3657.95,\n", - " 3765.12,\n", - " 3873.93,\n", - " 3984.48,\n", - " 4096.58,\n", - " 4210.72,\n", - " 4326.15,\n", - " 4443.4,\n", - " 4562.5,\n", - " 4683.42,\n", - " 4806.16,\n", - " 4929.93,\n", - " 5000.0,\n", - " 5000.0,\n", - " 4999.98,\n", - " 4999.96,\n", - " 4999.98,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0],\n", - " 'wind_turbine_rotor_diameter': 126,\n", - " 'wind_turbine_hub_ht': 90}}" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Load the turbine model file formatted for the PySAM Windpower module\n", - "pysam_options = load_yaml(turbine_model_fpath)\n", - "pysam_options" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'num_turbines': 100,\n", - " 'turbine_rating_kw': np.float64(5000.0),\n", - " 'rotor_diameter': 126,\n", - " 'hub_height': 90,\n", - " 'create_model_from': 'default',\n", - " 'config_name': 'WindPowerSingleOwner',\n", - " 'pysam_options': {'Turbine': {'wind_turbine_max_cp': 0.481305875,\n", - " 'wind_turbine_ct_curve': [0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 1.132034888,\n", - " 0.999470963,\n", - " 0.917697381,\n", - " 0.860849503,\n", - " 0.815371198,\n", - " 0.811614904,\n", - " 0.807939328,\n", - " 0.80443352,\n", - " 0.800993851,\n", - " 0.79768116,\n", - " 0.794529244,\n", - " 0.791495834,\n", - " 0.788560434,\n", - " 0.787217182,\n", - " 0.787127977,\n", - " 0.785839257,\n", - " 0.783812219,\n", - " 0.783568108,\n", - " 0.783328285,\n", - " 0.781194418,\n", - " 0.777292539,\n", - " 0.773464375,\n", - " 0.769690236,\n", - " 0.766001924,\n", - " 0.762348072,\n", - " 0.758760824,\n", - " 0.755242872,\n", - " 0.751792927,\n", - " 0.748434131,\n", - " 0.745113997,\n", - " 0.717806682,\n", - " 0.672204789,\n", - " 0.63831272,\n", - " 0.610176496,\n", - " 0.585456847,\n", - " 0.563222111,\n", - " 0.542912273,\n", - " 0.399312061,\n", - " 0.310517829,\n", - " 0.248633226,\n", - " 0.203543725,\n", - " 0.169616419,\n", - " 0.143478955,\n", - " 0.122938861,\n", - " 0.106515296,\n", - " 0.093026095,\n", - " 0.081648606,\n", - " 0.072197368,\n", - " 0.064388275,\n", - " 0.057782745,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0],\n", - " 'wind_turbine_powercurve_windspeeds': [0.0,\n", - " 1.0,\n", - " 2.0,\n", - " 3.0,\n", - " 4.0,\n", - " 5.0,\n", - " 6.0,\n", - " 7.0,\n", - " 7.1,\n", - " 7.2,\n", - " 7.3,\n", - " 7.4,\n", - " 7.5,\n", - " 7.6,\n", - " 7.7,\n", - " 7.8,\n", - " 7.9,\n", - " 8.0,\n", - " 9.0,\n", - " 10.0,\n", - " 10.1,\n", - " 10.2,\n", - " 10.3,\n", - " 10.4,\n", - " 10.5,\n", - " 10.6,\n", - " 10.7,\n", - " 10.8,\n", - " 10.9,\n", - " 11.0,\n", - " 11.1,\n", - " 11.2,\n", - " 11.3,\n", - " 11.4,\n", - " 11.5,\n", - " 11.6,\n", - " 11.7,\n", - " 11.8,\n", - " 11.9,\n", - " 12.0,\n", - " 13.0,\n", - " 14.0,\n", - " 15.0,\n", - " 16.0,\n", - " 17.0,\n", - " 18.0,\n", - " 19.0,\n", - " 20.0,\n", - " 21.0,\n", - " 22.0,\n", - " 23.0,\n", - " 24.0,\n", - " 25.0,\n", - " 26.0,\n", - " 27.0,\n", - " 28.0,\n", - " 29.0,\n", - " 30.0,\n", - " 31.0,\n", - " 32.0,\n", - " 33.0,\n", - " 34.0,\n", - " 35.0,\n", - " 36.0,\n", - " 37.0,\n", - " 38.0,\n", - " 39.0,\n", - " 40.0,\n", - " 41.0,\n", - " 42.0,\n", - " 43.0,\n", - " 44.0,\n", - " 45.0,\n", - " 46.0,\n", - " 47.0,\n", - " 48.0,\n", - " 49.0],\n", - " 'wind_turbine_powercurve_powerout': [0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 40.52,\n", - " 177.67,\n", - " 403.9,\n", - " 737.59,\n", - " 1187.18,\n", - " 1239.25,\n", - " 1292.52,\n", - " 1347.32,\n", - " 1403.26,\n", - " 1460.7,\n", - " 1519.64,\n", - " 1580.17,\n", - " 1642.11,\n", - " 1705.76,\n", - " 1771.17,\n", - " 2518.55,\n", - " 3448.38,\n", - " 3552.14,\n", - " 3657.95,\n", - " 3765.12,\n", - " 3873.93,\n", - " 3984.48,\n", - " 4096.58,\n", - " 4210.72,\n", - " 4326.15,\n", - " 4443.4,\n", - " 4562.5,\n", - " 4683.42,\n", - " 4806.16,\n", - " 4929.93,\n", - " 5000.0,\n", - " 5000.0,\n", - " 4999.98,\n", - " 4999.96,\n", - " 4999.98,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 5000.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0]}},\n", - " 'run_recalculate_power_curve': False,\n", - " 'layout': {'layout_mode': 'basicgrid',\n", - " 'layout_options': {'row_D_spacing': 10.0,\n", - " 'turbine_D_spacing': 10.0,\n", - " 'rotation_angle_deg': 0.0,\n", - " 'row_phase_offset': 0.0,\n", - " 'layout_shape': 'square'}}}" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Create dictionary of updated inputs for the new turbine formatted for\n", - "# the \"pysam_wind_plant_performance\" model\n", - "updated_parameters = {\n", - " \"turbine_rating_kw\": np.max(pysam_options[\"Turbine\"].get(\"wind_turbine_powercurve_powerout\")),\n", - " \"rotor_diameter\": pysam_options[\"Turbine\"].pop(\"wind_turbine_rotor_diameter\"),\n", - " \"hub_height\": pysam_options[\"Turbine\"].pop(\"wind_turbine_hub_ht\"),\n", - " \"pysam_options\": pysam_options,\n", - "}\n", - "\n", - "# Update wind performance parameters with model from PySAM\n", - "tech_config[\"technologies\"][\"wind\"][\"model_inputs\"][\"performance_parameters\"].update(\n", - " updated_parameters\n", - ")\n", - "\n", - "# The technology input for the updated wind turbine model\n", - "tech_config[\"technologies\"][\"wind\"][\"model_inputs\"][\"performance_parameters\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Option 1: Run H2I with dictionary input" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Wind LCOE is $68.18/MWh\n", - "LCOH is $6.84/kg\n" - ] - } - ], - "source": [ - "# Create the top-level config input dictionary for H2I\n", - "h2i_config = {\n", - " # \"name\": \"H2Integrate Config\",\n", - " # \"system_summary\": f\"Updated hybrid plant using {turbine_name} turbine\",\n", - " \"driver_config\": EXAMPLE_DIR / \"08_wind_electrolyzer\" / \"driver_config.yaml\",\n", - " \"technology_config\": tech_config,\n", - " \"plant_config\": EXAMPLE_DIR / \"08_wind_electrolyzer\" / \"plant_config.yaml\",\n", - "}\n", - "\n", - "# Create a H2Integrate model with the updated tech config\n", - "h2i = H2IntegrateModel(h2i_config)\n", - "\n", - "# Run the model\n", - "h2i.run()\n", - "\n", - "# Get LCOE of wind plant\n", - "wind_lcoe = h2i.model.get_val(\"finance_subgroup_electricity_profast.LCOE\", units=\"USD/MW/h\")\n", - "print(f\"Wind LCOE is ${wind_lcoe[0]:.2f}/MWh\")\n", - "\n", - "# Get LCOH of wind/electrolyzer plant\n", - "lcoh = h2i.model.get_val(\"finance_subgroup_hydrogen.LCOH_produced_profast_model\", units=\"USD/kg\")\n", - "print(f\"LCOH is ${lcoh[0]:.2f}/kg\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Option 2: Save new tech_config to file and run H2I from file" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Wind LCOE is $68.18/MWh\n", - "LCOH is $6.84/kg\n" - ] - } - ], - "source": [ - "from h2integrate.core.utilities import write_readable_yaml\n", - "\n", - "# Define a new filepath for the updated tech config\n", - "tech_config_path_new = EXAMPLE_DIR / \"08_wind_electrolyzer\" / f\"tech_config_{turbine_name}.yaml\"\n", - "\n", - "# Save the updated tech config to the new filepath\n", - "write_readable_yaml(tech_config, tech_config_path_new)\n", - "\n", - "# Load in the top-level H2I config file\n", - "h2i_config_path = EXAMPLE_DIR / \"08_wind_electrolyzer\" / \"wind_plant_electrolyzer.yaml\"\n", - "h2i_config_dict = load_yaml(h2i_config_path)\n", - "\n", - "# Define a new filepath for the updated top-level config\n", - "h2i_config_path_new = (\n", - " EXAMPLE_DIR / \"08_wind_electrolyzer\" / f\"wind_plant_electrolyzer_{turbine_name}.yaml\"\n", - ")\n", - "\n", - "# Update the technology config filepath in the top-level config with the updated\n", - "# tech config filename\n", - "h2i_config_dict[\"technology_config\"] = tech_config_path_new.name\n", - "\n", - "# Save the updated top-level H2I config to the new filepath\n", - "write_readable_yaml(h2i_config_dict, h2i_config_path_new)\n", - "\n", - "# Change the CWD to the example folder since filepaths in h2i_config_dict are relative\n", - "# to the \"08_wind_electrolyzer\" folder\n", - "os.chdir(EXAMPLE_DIR / \"08_wind_electrolyzer\")\n", - "\n", - "# Create a H2Integrate model with the updated tech config\n", - "h2i = H2IntegrateModel(h2i_config_path_new.name)\n", - "\n", - "# Run the model\n", - "h2i.run()\n", - "\n", - "# Get LCOE of wind plant\n", - "wind_lcoe = h2i.model.get_val(\"finance_subgroup_electricity_profast.LCOE\", units=\"USD/MW/h\")\n", - "print(f\"Wind LCOE is ${wind_lcoe[0]:.2f}/MWh\")\n", - "\n", - "# Get LCOH of wind/electrolyzer plant\n", - "lcoh = h2i.model.get_val(\"finance_subgroup_hydrogen.LCOH_produced_profast_model\", units=\"USD/kg\")\n", - "print(f\"LCOH is ${lcoh[0]:.2f}/kg\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Turbine Model Pre-Processing with FLORIS\n", - "\n", - "Example 26 (`26_floris`) currently uses an 660 kW turbine. This example uses the \"floris_wind_plant_performance\" performance model for the wind plant. Currently, the performance model is using an 660 kW wind turbine with a rotor diameter of 47.0 meters and a hub-height of 65 meters. In the following sections we will demonstrate how to:\n", - "\n", - "1. Save turbine model specifications for the Vestas 1.65 MW turbine in the FLORIS format using `export_turbine_to_floris_format()`\n", - "2. Load the turbine model specifications for the Vestas 1.65 MW turbine and update performance parameters for the wind technology in the `tech_config` dictionary for the Vestas 1.65 MW turbine.\n", - "3. Run H2I with the updated tech_config dictionary for the Vestas 1.65 MW turbine\n", - "\n", - "\n", - "We'll start off with Step 1 and importing the function `export_turbine_to_floris_format()`, which will save turbine model specifications of the Vestas 1.65 MW turbine formatted for FLORIS. " - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/Users/egrant/Documents/projects/GreenHEART/library/floris_turbine_Vestas_1.65MW.yaml\n" - ] - } - ], - "source": [ - "from h2integrate.preprocess.wind_turbine_file_tools import export_turbine_to_floris_format\n", - "\n", - "turbine_name = \"Vestas_1.65MW\"\n", - "\n", - "turbine_model_fpath = export_turbine_to_floris_format(turbine_name)\n", - "\n", - "print(turbine_model_fpath)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Step 2: Load the turbine model specifications for the Vestas 1.65 MW turbine and update performance parameters for the wind technology in the `tech_config` dictionary for the Vestas 1.65 MW turbine." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'num_turbines': 100,\n", - " 'hub_height': -1,\n", - " 'operational_losses': 12.83,\n", - " 'floris_wake_config': {'name': 'Gauss',\n", - " 'description': 'Onshore template',\n", - " 'floris_version': 'v4.0.0',\n", - " 'logging': {'console': {'enable': False, 'level': 'WARNING'},\n", - " 'file': {'enable': False, 'level': 'WARNING'}},\n", - " 'solver': {'type': 'turbine_grid', 'turbine_grid_points': 1},\n", - " 'flow_field': {'air_density': 1.225,\n", - " 'reference_wind_height': -1,\n", - " 'wind_directions': [],\n", - " 'wind_shear': 0.33,\n", - " 'wind_speeds': [],\n", - " 'wind_veer': 0.0},\n", - " 'wake': {'model_strings': {'combination_model': 'sosfs',\n", - " 'deflection_model': 'gauss',\n", - " 'turbulence_model': 'crespo_hernandez',\n", - " 'velocity_model': 'gauss'},\n", - " 'enable_secondary_steering': False,\n", - " 'enable_yaw_added_recovery': False,\n", - " 'enable_transverse_velocities': False,\n", - " 'wake_deflection_parameters': {'gauss': {'ad': 0.0,\n", - " 'alpha': 0.58,\n", - " 'bd': 0.0,\n", - " 'beta': 0.077,\n", - " 'dm': 1.0,\n", - " 'ka': 0.38,\n", - " 'kb': 0.004},\n", - " 'jimenez': {'ad': 0.0, 'bd': 0.0, 'kd': 0.05}},\n", - " 'wake_velocity_parameters': {'cc': {'a_f': 3.11,\n", - " 'a_s': 0.179367259,\n", - " 'alpha_mod': 1.0,\n", - " 'b_f': -0.68,\n", - " 'b_s': 0.0118889215,\n", - " 'c_f': 2.41,\n", - " 'c_s1': 0.0563691592,\n", - " 'c_s2': 0.13290157},\n", - " 'gauss': {'alpha': 0.58, 'beta': 0.077, 'ka': 0.38, 'kb': 0.004},\n", - " 'jensen': {'we': 0.05}},\n", - " 'wake_turbulence_parameters': {'crespo_hernandez': {'initial': 0.1,\n", - " 'constant': 0.5,\n", - " 'ai': 0.8,\n", - " 'downstream': -0.32}},\n", - " 'enable_active_wake_mixing': False},\n", - " 'farm': {'layout_x': [], 'layout_y': []}},\n", - " 'floris_turbine_config': {'turbine_type': 'Vestas_1.65MW',\n", - " 'hub_height': 70.0,\n", - " 'TSR': 8.0,\n", - " 'rotor_diameter': 82,\n", - " 'power_thrust_table': {'wind_speed': [0.0,\n", - " 1.0,\n", - " 2.0,\n", - " 3.0,\n", - " 4.0,\n", - " 5.0,\n", - " 6.0,\n", - " 7.0,\n", - " 8.0,\n", - " 9.0,\n", - " 10.0,\n", - " 11.0,\n", - " 12.0,\n", - " 13.0,\n", - " 14.0,\n", - " 15.0,\n", - " 16.0,\n", - " 17.0,\n", - " 18.0,\n", - " 19.0,\n", - " 20.0,\n", - " 21.0,\n", - " 22.0,\n", - " 23.0,\n", - " 24.0,\n", - " 25.0,\n", - " 26.0,\n", - " 27.0,\n", - " 28.0,\n", - " 29.0,\n", - " 30.0,\n", - " 31.0,\n", - " 32.0,\n", - " 33.0,\n", - " 34.0,\n", - " 35.0,\n", - " 36.0,\n", - " 37.0,\n", - " 38.0,\n", - " 39.0,\n", - " 40.0,\n", - " 41.0,\n", - " 42.0,\n", - " 43.0,\n", - " 44.0,\n", - " 45.0,\n", - " 46.0,\n", - " 47.0,\n", - " 48.0,\n", - " 49.0],\n", - " 'power': [0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 28.0,\n", - " 144.0,\n", - " 309.0,\n", - " 511.0,\n", - " 758.0,\n", - " 1017.0,\n", - " 1285.0,\n", - " 1504.0,\n", - " 1637.0,\n", - " 1650.0,\n", - " 1650.0,\n", - " 1650.0,\n", - " 1650.0,\n", - " 1650.0,\n", - " 1650.0,\n", - " 1650.0,\n", - " 1650.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0],\n", - " 'thrust_coefficient': [0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.979,\n", - " 1.111,\n", - " 1.014,\n", - " 0.925,\n", - " 0.843,\n", - " 0.768,\n", - " 0.701,\n", - " 0.642,\n", - " 0.578,\n", - " 0.509,\n", - " 0.438,\n", - " 0.379,\n", - " 0.334,\n", - " 0.299,\n", - " 0.272,\n", - " 0.249,\n", - " 0.232,\n", - " 0.218,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0],\n", - " 'ref_air_density': 1.225,\n", - " 'ref_tilt': 5.0,\n", - " 'cosine_loss_exponent_yaw': 1.88,\n", - " 'cosine_loss_exponent_tilt': 1.88}},\n", - " 'resource_data_averaging_method': 'weighted_average',\n", - " 'floris_operation_model': 'cosine-loss',\n", - " 'default_turbulence_intensity': 0.06,\n", - " 'enable_caching': True,\n", - " 'cache_dir': 'cache',\n", - " 'layout': {'layout_mode': 'basicgrid',\n", - " 'layout_options': {'row_D_spacing': 5.0,\n", - " 'turbine_D_spacing': 5.0,\n", - " 'rotation_angle_deg': 0.0,\n", - " 'row_phase_offset': 0.0,\n", - " 'layout_shape': 'square'}}}" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Load the tech config file\n", - "tech_config_path = EXAMPLE_DIR / \"26_floris\" / \"tech_config.yaml\"\n", - "tech_config = load_tech_yaml(tech_config_path)\n", - "\n", - "# Load the turbine model file formatted for FLORIS\n", - "floris_options = load_yaml(turbine_model_fpath)\n", - "\n", - "# Create dictionary of updated inputs for the new turbine formatted for\n", - "# the \"floris_wind_plant_performance\" model\n", - "updated_parameters = {\n", - " \"hub_height\": -1, # -1 indicates to use the hub-height in the floris_turbine_config\n", - " \"floris_turbine_config\": floris_options,\n", - "}\n", - "\n", - "# Update wind performance parameters in the tech config\n", - "tech_config[\"technologies\"][\"wind\"][\"model_inputs\"][\"performance_parameters\"].update(\n", - " updated_parameters\n", - ")\n", - "\n", - "# The technology input for the updated wind turbine model\n", - "tech_config[\"technologies\"][\"wind\"][\"model_inputs\"][\"performance_parameters\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Step 3: Run H2I with the updated tech_config dictionary for the Vestas 1.65 MW turbine" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Wind LCOE is $120.87/MWh\n" - ] - } - ], - "source": [ - "# Create the top-level config input dictionary for H2I\n", - "h2i_config = {\n", - " \"driver_config\": EXAMPLE_DIR / \"26_floris\" / \"driver_config.yaml\",\n", - " \"technology_config\": tech_config,\n", - " \"plant_config\": EXAMPLE_DIR / \"26_floris\" / \"plant_config.yaml\",\n", - "}\n", - "\n", - "# Create a H2Integrate model with the updated tech config\n", - "h2i = H2IntegrateModel(h2i_config)\n", - "\n", - "# Run the model\n", - "h2i.run()\n", - "\n", - "# Get LCOE of wind plant\n", - "wind_lcoe = h2i.model.get_val(\"finance_subgroup_electricity.LCOE\", units=\"USD/MW/h\")\n", - "print(f\"Wind LCOE is ${wind_lcoe[0]:.2f}/MWh\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "h2integrate", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.13" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/misc_resources/turbine_models_library_preprocessing.md b/docs/misc_resources/turbine_models_library_preprocessing.md new file mode 100644 index 000000000..546d1bab5 --- /dev/null +++ b/docs/misc_resources/turbine_models_library_preprocessing.md @@ -0,0 +1,263 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.18.1 +kernelspec: + display_name: h2integrate + language: python + name: python3 +--- + +# Turbine Models Library Pre-Processing Tools + +The [turbine-models package](https://github.com/NREL/turbine-models/tree/main) hosts wind turbine data for a variety of wind turbines and has tools that can streamline the process to run new turbines with the [PySAM Windpower model](https://nrel-pysam.readthedocs.io/en/main/modules/Windpower.html) or [FLORIS](https://github.com/NREL/floris/tree/main). + +The full list of turbine models available in the turbine-models library can be found [here](https://github.com/NREL/turbine-models/blob/main/turbine_models/supported_turbines.py) + +H2Integrate has preprocessing tools that leverage the functionality available in the turbine-models library. The function `export_turbine_to_pysam_format()` will save turbine model specifications formatted for the PySAM Windpower model. The PySAM Windpower model is wrapped in H2I and can be utilized with the "pysam_wind_plant_performance" model. Example usage of the `export_turbine_to_pysam_format()` function is demonstrated in the following section using Example 8. + + +## Turbine Model Pre-Processing with PySAM Windpower Model +Example 8 (`08_wind_electrolyzer`) currently uses an 8.3 MW turbine. In the following sections we will demonstrate how to: + +1. Save turbine model specifications for the NREL 5 MW turbine in the PySAM Windpower format using `export_turbine_to_pysam_format()` +2. Load the turbine model specifications for the NREL 5 MW turbine and update performance parameters for the wind technology in the `tech_config` dictionary for the NREL 5 MW turbine. +3. Run H2I with the updated tech_config dictionary, showcasing two different methods to run H2I with the NREL 5 MW turbine: + - initializing H2I with a dictionary input + - saving the updated tech_config dictionary to a new file and initializing H2I by specifying the filepath to the top-level config file. + + +We'll start off by importing the required modules and packages: + +```{code-cell} ipython3 +import os +import numpy as np + + +from h2integrate import EXAMPLE_DIR +from h2integrate.core.utilities import load_yaml +from h2integrate.core.inputs.validation import load_tech_yaml +from h2integrate.preprocess.wind_turbine_file_tools import export_turbine_to_pysam_format +from h2integrate.core.h2integrate_model import H2IntegrateModel +``` + +Load the tech config file that we want to update the turbine model for: + +```{code-cell} ipython3 +# Load the tech config file +tech_config_path = EXAMPLE_DIR / "08_wind_electrolyzer" / "tech_config.yaml" +tech_config = load_tech_yaml(tech_config_path) +``` + +This example uses the "pysam_wind_plant_performance" performance model for the wind plant. Currently, the performance model is using an 8.3MW wind turbine with a rotor diameter of 196 meters and a hub-height of 130 meters. This information is defined in the `tech_config` file: + +```yaml +technologies: + wind: + performance_model: + model: "pysam_wind_plant_performance" + cost_model: + model: "atb_wind_cost" + model_inputs: + performance_parameters: + num_turbines: 100 + turbine_rating_kw: 8300 + rotor_diameter: 196. + hub_height: 130. + create_model_from: "default" + config_name: "WindPowerSingleOwner" + pysam_options: !include "pysam_options_8.3MW.yaml" + run_recalculate_power_curve: False + layout: + layout_mode: "basicgrid" + layout_options: + row_D_spacing: 10.0 + turbine_D_spacing: 10.0 + rotation_angle_deg: 0.0 + row_phase_offset: 0.0 + layout_shape: "square" + cost_parameters: + capex_per_kW: 1500.0 + opex_per_kW_per_year: 45 + cost_year: 2019 +``` + + +If we want to replace the 8.3 MW turbine with the NREL 5 MW turbine, we can do so using the `export_turbine_to_pysam_format()` function: + +```{code-cell} ipython3 +turbine_name = "NREL_5MW" + +turbine_model_fpath = export_turbine_to_pysam_format(turbine_name) + +print(turbine_model_fpath) +``` + +```{code-cell} ipython3 +# Load the turbine model file formatted for the PySAM Windpower module +pysam_options = load_yaml(turbine_model_fpath) +pysam_options +``` + +```{code-cell} ipython3 +# Create dictionary of updated inputs for the new turbine formatted for +# the "pysam_wind_plant_performance" model +updated_parameters = { + "turbine_rating_kw": np.max(pysam_options["Turbine"].get("wind_turbine_powercurve_powerout")), + "rotor_diameter": pysam_options["Turbine"].pop("wind_turbine_rotor_diameter"), + "hub_height": pysam_options["Turbine"].pop("wind_turbine_hub_ht"), + "pysam_options": pysam_options, +} + +# Update wind performance parameters with model from PySAM +tech_config["technologies"]["wind"]["model_inputs"]["performance_parameters"].update( + updated_parameters +) + +# The technology input for the updated wind turbine model +tech_config["technologies"]["wind"]["model_inputs"]["performance_parameters"] +``` + +### Option 1: Run H2I with dictionary input + +```{code-cell} ipython3 +# Create the top-level config input dictionary for H2I +h2i_config = { + # "name": "H2Integrate Config", + # "system_summary": f"Updated hybrid plant using {turbine_name} turbine", + "driver_config": EXAMPLE_DIR / "08_wind_electrolyzer" / "driver_config.yaml", + "technology_config": tech_config, + "plant_config": EXAMPLE_DIR / "08_wind_electrolyzer" / "plant_config.yaml", +} + +# Create a H2Integrate model with the updated tech config +h2i = H2IntegrateModel(h2i_config) + +# Run the model +h2i.run() + +# Get LCOE of wind plant +wind_lcoe = h2i.model.get_val("finance_subgroup_electricity_profast.LCOE", units="USD/MW/h") +print(f"Wind LCOE is ${wind_lcoe[0]:.2f}/MWh") + +# Get LCOH of wind/electrolyzer plant +lcoh = h2i.model.get_val("finance_subgroup_hydrogen.LCOH_produced_profast_model", units="USD/kg") +print(f"LCOH is ${lcoh[0]:.2f}/kg") +``` + +### Option 2: Save new tech_config to file and run H2I from file + +```{code-cell} ipython3 +from h2integrate.core.utilities import write_readable_yaml + +# Define a new filepath for the updated tech config +tech_config_path_new = EXAMPLE_DIR / "08_wind_electrolyzer" / f"tech_config_{turbine_name}.yaml" + +# Save the updated tech config to the new filepath +write_readable_yaml(tech_config, tech_config_path_new) + +# Load in the top-level H2I config file +h2i_config_path = EXAMPLE_DIR / "08_wind_electrolyzer" / "wind_plant_electrolyzer.yaml" +h2i_config_dict = load_yaml(h2i_config_path) + +# Define a new filepath for the updated top-level config +h2i_config_path_new = ( + EXAMPLE_DIR / "08_wind_electrolyzer" / f"wind_plant_electrolyzer_{turbine_name}.yaml" +) + +# Update the technology config filepath in the top-level config with the updated +# tech config filename +h2i_config_dict["technology_config"] = tech_config_path_new.name + +# Save the updated top-level H2I config to the new filepath +write_readable_yaml(h2i_config_dict, h2i_config_path_new) + +# Change the CWD to the example folder since filepaths in h2i_config_dict are relative +# to the "08_wind_electrolyzer" folder +os.chdir(EXAMPLE_DIR / "08_wind_electrolyzer") + +# Create a H2Integrate model with the updated tech config +h2i = H2IntegrateModel(h2i_config_path_new.name) + +# Run the model +h2i.run() + +# Get LCOE of wind plant +wind_lcoe = h2i.model.get_val("finance_subgroup_electricity_profast.LCOE", units="USD/MW/h") +print(f"Wind LCOE is ${wind_lcoe[0]:.2f}/MWh") + +# Get LCOH of wind/electrolyzer plant +lcoh = h2i.model.get_val("finance_subgroup_hydrogen.LCOH_produced_profast_model", units="USD/kg") +print(f"LCOH is ${lcoh[0]:.2f}/kg") +``` + +## Turbine Model Pre-Processing with FLORIS + +Example 26 (`26_floris`) currently uses an 660 kW turbine. This example uses the "floris_wind_plant_performance" performance model for the wind plant. Currently, the performance model is using an 660 kW wind turbine with a rotor diameter of 47.0 meters and a hub-height of 65 meters. In the following sections we will demonstrate how to: + +1. Save turbine model specifications for the Vestas 1.65 MW turbine in the FLORIS format using `export_turbine_to_floris_format()` +2. Load the turbine model specifications for the Vestas 1.65 MW turbine and update performance parameters for the wind technology in the `tech_config` dictionary for the Vestas 1.65 MW turbine. +3. Run H2I with the updated tech_config dictionary for the Vestas 1.65 MW turbine + + +We'll start off with Step 1 and importing the function `export_turbine_to_floris_format()`, which will save turbine model specifications of the Vestas 1.65 MW turbine formatted for FLORIS. + +```{code-cell} ipython3 +from h2integrate.preprocess.wind_turbine_file_tools import export_turbine_to_floris_format + +turbine_name = "Vestas_1.65MW" + +turbine_model_fpath = export_turbine_to_floris_format(turbine_name) + +print(turbine_model_fpath) +``` + +Step 2: Load the turbine model specifications for the Vestas 1.65 MW turbine and update performance parameters for the wind technology in the `tech_config` dictionary for the Vestas 1.65 MW turbine. + +```{code-cell} ipython3 +# Load the tech config file +tech_config_path = EXAMPLE_DIR / "26_floris" / "tech_config.yaml" +tech_config = load_tech_yaml(tech_config_path) + +# Load the turbine model file formatted for FLORIS +floris_options = load_yaml(turbine_model_fpath) + +# Create dictionary of updated inputs for the new turbine formatted for +# the "floris_wind_plant_performance" model +updated_parameters = { + "hub_height": -1, # -1 indicates to use the hub-height in the floris_turbine_config + "floris_turbine_config": floris_options, +} + +# Update wind performance parameters in the tech config +tech_config["technologies"]["wind"]["model_inputs"]["performance_parameters"].update( + updated_parameters +) + +# The technology input for the updated wind turbine model +tech_config["technologies"]["wind"]["model_inputs"]["performance_parameters"] +``` + +Step 3: Run H2I with the updated tech_config dictionary for the Vestas 1.65 MW turbine + +```{code-cell} ipython3 +# Create the top-level config input dictionary for H2I +h2i_config = { + "driver_config": EXAMPLE_DIR / "26_floris" / "driver_config.yaml", + "technology_config": tech_config, + "plant_config": EXAMPLE_DIR / "26_floris" / "plant_config.yaml", +} + +# Create a H2Integrate model with the updated tech config +h2i = H2IntegrateModel(h2i_config) + +# Run the model +h2i.run() + +# Get LCOE of wind plant +wind_lcoe = h2i.model.get_val("finance_subgroup_electricity.LCOE", units="USD/MW/h") +print(f"Wind LCOE is ${wind_lcoe[0]:.2f}/MWh") +``` diff --git a/docs/user_guide/how_to_set_up_an_analysis.ipynb b/docs/user_guide/how_to_set_up_an_analysis.ipynb deleted file mode 100644 index 4a7687e79..000000000 --- a/docs/user_guide/how_to_set_up_an_analysis.ipynb +++ /dev/null @@ -1,390 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0a2018cf", - "metadata": {}, - "source": [ - "# How to set up an analysis\n", - "\n", - "H2Integrate is designed so that you can run a basic analysis or design problem without extensive Python experience.\n", - "The key inputs for the analysis are the configuration files, which are in YAML format.\n", - "This doc page will walk you through the steps to set up a basic analysis, focusing on the different types of configuration files and how use them.\n", - "\n", - "## Top-level config file\n", - "\n", - "The top-level config file is the main entry point for H2Integrate.\n", - "Its main purpose is to define the analysis type and the configuration files for the different components of the analysis.\n", - "Here is an example of a top-level config file:\n", - "\n", - "```yaml\n", - "name: H2Integrate_config\n", - "\n", - "system_summary: This reference hybrid plant is located in Minnesota and contains wind, solar, and battery storage technologies. The system is designed to produce hydrogen using an electrolyzer and also produce steel using a grid-connected plant.\n", - "\n", - "driver_config: driver_config.yaml\n", - "technology_config: tech_config.yaml\n", - "plant_config: plant_config.yaml\n", - "```\n", - "\n", - "The top-level config file contains the following keys:\n", - "- `name`: (optional) A name for the analysis. This is used to identify the analysis in the output files.\n", - "- `system_summary`: (optional) A summary of the analysis. This helpful for quickly describing the analysis for documentation purposes.\n", - "\n", - "- `driver_config`: The path to the driver config file. This file defines the analysis type and the optimization settings.\n", - "- `technology_config`: The path to the technology config file. This file defines the technologies included in the analysis, their modeling parameters, and the performance, cost, and financial models used for each technology.\n", - "- `plant_config`: The path to the plant config file. This file defines the system configuration and how the technologies are connected together.\n", - "\n", - "The goal of the organization of the top-level config file is that it is easy to swap out different configurations for the analysis without having to change the code.\n", - "For example, if you had different optimization problems, you could have different driver config files for each optimization problem and just change the `driver_config` key in the top-level config file to point to the new file.\n", - "This allows you to quickly test different configurations and see how they affect the results.\n", - "\n", - "```{note}\n", - "The filepaths for the `plant_config`, `tech_config`, and `driver_config` files specified in the top-level config file can be specified as either:\n", - "1. Filepaths relative to the top-level config file; this is done in most examples\n", - "2. Filepaths relative to the current working directory; this is also done in most examples, which are intended to be run from the folder they're in.\n", - "3. Filepaths relative to the H2Integrate root directory; this works best for unique filenames.\n", - "4. Absolute filepaths.\n", - "\n", - "More information about file handling in H2I can be found [here](https://h2integrate.readthedocs.io/en/latest/misc_resources/defining_filepaths.html)\n", - "```\n", - "\n", - "## Driver config file\n", - "\n", - "The driver config file defines the analysis type and the optimization settings.\n", - "If you are running a basic analysis and not an optimization, the driver config file is quite straightforward and might look like this:\n", - "\n", - "```yaml\n", - "name: driver_config\n", - "description: This analysis runs a hybrid plant to match the first example in H2Integrate\n", - "\n", - "general:\n", - " folder_output: outputs\n", - "```\n", - "\n", - "If you are running an optimization, the driver config file will contain additional keys to define the optimization settings, including design variables, constraints, and objective functions.\n", - "Further details of more complex instances of the driver config file can be found in more advanced examples as they are developed.\n", - "\n", - "## Technology config file\n", - "\n", - "The technology config file defines the technologies included in the analysis, their modeling parameters, and the performance, cost, and financial models used for each technology.\n", - "The yaml file is organized into sections for each technology included in the analysis under the `technologies` heading.\n", - "Here is an example of part of a technology config that is defining an energy system with only one technology, an electrolyzer:\n", - "\n", - "```yaml\n", - "name: technology_config\n", - "description: This hybrid plant produces steel\n", - "\n", - "technologies:\n", - " electrolyzer:\n", - " performance_model:\n", - " model: eco_pem_electrolyzer_performance\n", - " cost_model:\n", - " model: eco_pem_electrolyzer_cost\n", - " model_inputs:\n", - " shared_parameters:\n", - " location: onshore\n", - " electrolyzer_capex: 2000 # $/kW overnight installed capital costs\n", - "\n", - " performance_parameters:\n", - " sizing:\n", - " resize_for_enduse: False\n", - " size_for: BOL #'BOL' (generous) or 'EOL' (conservative)\n", - " hydrogen_dmd:\n", - " n_clusters: 13\n", - " cluster_rating_MW: 40\n", - " eol_eff_percent_loss: 13 #eol defined as x% change in efficiency from bol\n", - " uptime_hours_until_eol: 77600 #number of 'on' hours until electrolyzer reaches eol\n", - " include_degradation_penalty: True #include degradation\n", - " turndown_ratio: 0.1 #turndown_ratio = minimum_cluster_power/cluster_rating_MW\n", - "\n", - " cost_parameters:\n", - " cost_model: singlitico2021\n", - "\n", - " financial_parameters:\n", - " capital_items:\n", - " depr_period: 7\n", - " replacement_cost_percent: 0.15 # percent of capex - H2A default case\n", - "```\n", - "\n", - "Here, we have defined a electrolyzer model that uses the built-in `eco_pem_electrolyzer_performance` and `eco_pem_electrolyzer_cost` models.\n", - "The `performance_model` and `cost_model` keys define the models used for the performance and cost calculations, respectively.\n", - "The `model_inputs` key contains the inputs for the models, which are organized into sections for shared parameters, performance parameters, cost parameters, and financial parameters.\n", - "Here, we do not define the `financial_model` key, so the default financial model from ProFAST is used.\n", - "\n", - "The `shared_parameters` section contains parameters that are common to all models, such as the rating and location of the technology.\n", - "These values are defined once in the `shared_parameters` section and are used by all models that reference them.\n", - "The `performance_parameters` section contains parameters that are specific to the performance model, such as the sizing and efficiency of the technology.\n", - "The `cost_parameters` section contains parameters that are specific to the cost model, such as the capital costs and replacement costs.\n", - "The `financial_parameters` section contains parameters that are specific to the financial model, such as the replacement costs and financing terms.\n", - "\n", - "```{note}\n", - "There are no default values for the parameters in the technology config file.\n", - "You must define all the parameters for the models you are using in the analysis.\n", - "```\n", - "\n", - "Based on which models you choose to use, the inputs will vary.\n", - "Each model has its own set of inputs, which are defined in the source code for the model.\n", - "Because there are no default values for the parameters, we suggest you look at an existing example that uses the model you are interested in to see what inputs are required or look at the source code for the model.\n", - "The different models are defined in the `supported_models.py` file in the `h2integrate` package.\n", - "\n", - "## Plant config file\n", - "\n", - "The plant config file defines the system configuration, any parameters that might be shared across technologies, and how the technologies are connected together.\n", - "\n", - "Here is a snippet of an example plant config file:\n", - "\n", - "```yaml\n", - "name: plant_config\n", - "description: This plant is located in MN, USA\n", - "\n", - "site:\n", - " latitude: 47.5233\n", - " longitude: -92.5366\n", - " elevation_m: 439.0\n", - "\n", - "# array of arrays containing left-to-right technology\n", - "# interconnections; can support bidirectional connections\n", - "# with the reverse definition.\n", - "# this will naturally grow as we mature the interconnected tech\n", - "technology_interconnections: [\n", - " [\"wind\", \"electrolyzer\", \"electricity\", \"cable\"],\n", - " [\"electrolyzer\", \"h2_storage\", \"efficiency\"],\n", - " [\"electrolyzer\", \"h2_storage\", \"hydrogen\", \"pipe\"],\n", - " [\"finance_subgroup_1\", \"steel\", \"LCOH\"],\n", - " [\"wind\", \"steel\", \"electricity\", \"cable\"],\n", - " # etc\n", - "]\n", - "\n", - "plant:\n", - " plant_life: 30\n", - " grid_connection: False # option, can be turned on or off\n", - " ppa_price: 0.025 # $/kWh\n", - " hybrid_electricity_estimated_cf: 0.492\n", - " \n", - "\n", - "finance_parameters:\n", - " finance_model: \"ProFastComp\"\n", - " model_inputs:\n", - " params:\n", - " analysis_start_year: 2032\n", - " installation_time: 36 # months\n", - " cost_adjustment_parameters:\n", - " target_dollar_year: 2022\n", - " cost_year_adjustment_inflation: 0.025 # used to adjust modeled costs to target_dollar_year\n", - " inflation_rate: 0.0 # 0 for nominal analysis\n", - "...\n", - "```\n", - "\n", - "The `site` section contains the site parameters, such as the latitude, longitude, elevation, and time zone.\n", - "The `plant` section contains the plant parameters, such as the plant life, grid connection, PPA price, and installation time.\n", - "The `finance_parameters` section contains the financial parameters used across the plant, such as the inflation rates, financing terms, and other financial parameters.\n", - "\n", - "The `technology_interconnections` section contains the interconnections between the technologies in the system and is the most complex part of the plant config file.\n", - "The interconnections are defined as an list of lists, where each sub-list defines a connection between two technologies.\n", - "The first entry in the list is the technology that is providing the input to the next technology in the list.\n", - "If the list is length 4, then the third entry in the list is what's passing passed via a transporter of the type defined in the fourth entry.\n", - "If the list is length 3, then the third entry in the list is what is connected directly between the technologies.\n", - "\n", - "```{note}\n", - "For more information on how to define and interpret technology interconnections, see the {ref}`connecting_technologies` page.\n", - "```\n", - "\n", - "## Running the analysis\n", - "\n", - "Once you have the config files defined, you can run the analysis using a simple Python script that inputs the top-level config yaml.\n", - "Here, we will show a script that runs one of the example analyses included in the H2Integrate package." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "a7a3b522", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "c:\\Users\\jjasa\\Documents\\git\\H2Integrate\\docs\\user_guide\\log\\hybrid_systems_2025-09-17T22.35.53.165779.log\n", - "XDSM diagram written to connections_xdsm.pdf\n", - "Total hydrogen produced by the electrolyzer: 51724444.41 kg/year\n" - ] - } - ], - "source": [ - "from h2integrate.core.h2integrate_model import H2IntegrateModel\n", - "import os\n", - "\n", - "\n", - "# Change the current working directory\n", - "os.chdir(\"../../examples/08_wind_electrolyzer/\")\n", - "\n", - "# Create a H2Integrate model\n", - "h2i_model = H2IntegrateModel(\"wind_plant_electrolyzer.yaml\")\n", - "\n", - "# Run the model\n", - "h2i_model.run()\n", - "\n", - "# h2i_model.post_process()\n", - "\n", - "# Print the total hydrogen produced by the electrolyzer in kg/year\n", - "total_hydrogen = h2i_model.model.get_val(\"electrolyzer.total_hydrogen_produced\", units=\"kg/year\")[0]\n", - "print(f\"Total hydrogen produced by the electrolyzer: {total_hydrogen:.2f} kg/year\")" - ] - }, - { - "cell_type": "markdown", - "id": "c4af9b7b", - "metadata": {}, - "source": [ - "This will run the analysis defined in the config files and generate the output files in the through the `post_process` method.\n", - "\n", - "## Modifying and rerunning the analysis\n", - "\n", - "Once the configs are loaded into H2I, they are stored in the `H2IntegrateModel` instance as dictionaries, so you can modify them and rerun the analysis without having to reload the config files.\n", - "Here is an example of how to modify the config files and rerun the analysis:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "a802a697", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Total hydrogen produced by the electrolyzer: 56631148.67 kg/year\n" - ] - } - ], - "source": [ - "# Access the configuration dictionaries\n", - "tech_config = h2i_model.technology_config\n", - "\n", - "# Modify a parameter in the technology config\n", - "tech_config[\"technologies\"][\"electrolyzer\"][\"model_inputs\"][\"performance_parameters\"][\n", - " \"n_clusters\"\n", - "] = 15\n", - "\n", - "# Rerun the model with the updated configurations\n", - "h2i_model.run()\n", - "\n", - "# Post-process the results\n", - "# h2i_model.post_process()\n", - "\n", - "# Print the total hydrogen produced by the electrolyzer in kg/year\n", - "total_hydrogen = h2i_model.model.get_val(\"electrolyzer.total_hydrogen_produced\", units=\"kg/year\")[0]\n", - "print(f\"Total hydrogen produced by the electrolyzer: {total_hydrogen:.2f} kg/year\")" - ] - }, - { - "cell_type": "markdown", - "id": "27835884", - "metadata": {}, - "source": [ - "This is especially useful when you want to run an H2I model as a script and modify parameters dynamically without changing the original YAML configuration file.\n", - "If you want to do a simple parameter sweep, you can wrap this in a loop and modify the parameters as needed.\n", - "\n", - "In the example below, we modify the electrolyzer end-of-life efficiency drop and plot the impact on the LCOH." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "2e22aa41", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAHHCAYAAABDUnkqAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAAVgtJREFUeJzt3XlcVOX+B/DPsM2AMCCboBcR9Yoiau7iGgZiV0nNFXDBLbdyKUtJC7impqVp9dOuLVoZ7uZSLpGJaWruJeGCCqI4SIpsIgjM8/vDy1zHARx04MDx83695vXqPOc5Z77ncXI+nuecMwohhAARERGRTJhJXQARERGRKTHcEBERkaww3BAREZGsMNwQERGRrDDcEBERkaww3BAREZGsMNwQERGRrDDcEBERkaww3BAREZGsMNwQUaUJDw9HgwYNpC7jiURFRUGhUEhdRrWnUCgQFRUldRlEehhuiACsWbMGCoUCJ06ceGzf7OxsREdHo1WrVrC1tYW1tTV8fX0xa9Ys3Lhxw6D/Dz/8gN69e8PJyQkqlQpNmjTBzJkzcfv2bYO+4eHhsLW1LfO9FQoFXn311YodXCVQKBRlviZOnFhldcTExGDZsmVV9n41QYMGDfT+PGrVqoUOHTrgm2++eeJ97tq1iwGGahQLqQsgqkmuXLmCgIAApKSkYPDgwXjllVdgZWWFP//8E19++SW+//57XLx4Udd/5syZWLJkCVq1aoVZs2bB0dERp06dwqeffor169dj37598Pb2lvCInlxgYCBGjhxp0N6kSZMqqyEmJgbx8fGYPn16lb1nTfDcc8/hjTfeAABoNBp88cUXGDVqFAoKCjB+/PgK72/Xrl34v//7v1IDzr1792Bhwa8Sql74iSQyUlFREV5++WXcvHkTcXFx6Nq1q976+fPnY9GiRbrldevWYcmSJRg6dCi+++47mJub69aFh4fD398fgwcPxqlTp2rkl0OTJk0wfPhwqcswWn5+PqysrGBmVrNPWBcVFUGr1cLKyqrMPvXq1dP7swkPD0fDhg3x0UcfPVG4KY9KpTLp/ohMoWb/X05UhbZs2YI//vgDc+bMMQg2AKBWqzF//nzdcnR0NGrXro1Vq1bpBRsA6NChA2bNmoWzZ89i8+bNJq3T19cX/v7+Bu1arRb16tXDoEGDdG3r169H27ZtYWdnB7VajRYtWmD58uUmrae0OpYtW4bmzZtDpVKhTp06mDBhAu7cuWPQd/fu3ejRo4euvvbt2yMmJgYA8Pzzz+PHH3/E1atXdVMwJdf3xMXFQaFQYP369Zg7dy7q1asHGxsbZGdnAwA2bdqEtm3bwtraGs7Ozhg+fDhSU1PLrbtHjx5o1apVqeu8vb0RFBSkq6usKbs1a9botsnMzMT06dPh4eEBpVKJxo0bY9GiRdBqtbo+ycnJUCgU+PDDD7Fs2TI0atQISqUSCQkJRo83ALi4uKBp06a4fPmyXvvBgwcxePBg1K9fH0qlEh4eHpgxYwbu3bun6xMeHo7/+7//A6A/HVni0WtuSq5VunTpEsLDw+Hg4AB7e3uMHj0aeXl5eu9/7949TJ06Fc7OzrCzs8NLL72E1NRUXsdDT63m/XORSCI7duwAAIwYMeKxfRMTE3HhwgWEh4dDrVaX2mfkyJGIjIzEDz/8gGHDhumtu3Xr1hPXOXToUERFRSEtLQ1ubm669kOHDuHGjRu694qNjUVISAheeOEF3Rmnc+fO4bfffsO0adMe+z75+fml1qlWq8s9qzBhwgSsWbMGo0ePxtSpU5GUlIRPP/0Up0+fxm+//QZLS0sAD66DGjNmDJo3b46IiAg4ODjg9OnT2LNnD0JDQzFnzhxkZWXh+vXr+OijjwDA4HqlefPmwcrKCjNnzkRBQQGsrKx0792+fXssXLgQN2/exPLly/Hbb7/h9OnTcHBwKLXuESNGYPz48YiPj4evr6+u/fjx47h48SLmzp0LAJgzZw7GjRunt+3atWuxd+9euLq6AgDy8vLQo0cPpKamYsKECahfvz4OHz6MiIgIaDQag+uIVq9ejfz8fLzyyitQKpVwdHQsc3xLU1RUhOvXr6N27dp67Zs2bUJeXh4mTZoEJycnHDt2DJ988gmuX7+OTZs2AXjw53Xjxg3Exsbi22+/Nfo9hwwZAi8vLyxcuBCnTp3CF198AVdXV72zm+Hh4di4cSNGjBiBTp064cCBA+jTp0+Fjo2oVIKIxOrVqwUAcfz48TL7tG7dWtjb2xu1v23btgkA4qOPPiq3n1qtFm3atNEtjxo1SgAo9zVlypRy93nhwgUBQHzyySd67ZMnTxa2trYiLy9PCCHEtGnThFqtFkVFRUYd08PKq2/dunV6x+Pp6albPnjwoAAgvvvuO7397dmzR689MzNT2NnZiY4dO4p79+7p9dVqtbr/7tOnj97+S+zfv18AEA0bNtQdrxBC3L9/X7i6ugpfX1+9/f7www8CgHj33Xd1bZGRkeLhvyIzMzOFSqUSs2bN0nuvqVOnilq1aonc3NxSx+q3334TlpaWYsyYMbq2efPmiVq1aomLFy/q9Z09e7YwNzcXKSkpQgghkpKSBAChVqtFenp6qft/lKenp+jVq5f4+++/xd9//y3Onj0rRowYUepn5+GxKbFw4UKhUCjE1atXdW1TpkwRZX1dABCRkZG65ZJxe/h4hRBiwIABwsnJSbd88uRJAUBMnz5dr194eLjBPokqitNSREbKzs6GnZ2dUX1zcnIA4LH97ezsdFMlJVQqFWJjY0t9GaNJkyZ47rnnsGHDBl1bcXExNm/ejODgYFhbWwMAHBwccPfuXaP3+6h+/fqVWmNpU2IlNm3aBHt7ewQGBuLWrVu6V9u2bWFra4v9+/cDeHBWKScnB7Nnzza4pqMit2ePGjVKd7wAcOLECaSnp2Py5Ml6++3Tpw+aNm2KH3/8scx92dvbo1+/fli3bh2EEAAejOuGDRvQv39/1KpVy2CbtLQ0DBo0CM899xxWrFihNw7dunVD7dq19cYhICAAxcXF+PXXX/X2M3DgQLi4uBh93D/99BNcXFzg4uKCFi1a4Ntvv8Xo0aPxwQcf6PV7eGzu3r2LW7duoXPnzhBC4PTp00a/X2kevWuuW7duuH37tu7zvmfPHgDA5MmT9fq99tprT/W+RACnpYiMplarceXKFaP6loSakpBTlpycHN1URQlzc3MEBAQ8WZH/NXToULz99ttITU1FvXr1EBcXh/T0dAwdOlTXZ/Lkydi4cSNefPFF1KtXD7169cKQIUPQu3dvo97jH//4R4XrTExMRFZWlsExl0hPTwcA3bUhD0//PAkvLy+95atXrwJAqXeoNW3aFIcOHSp3fyNHjsSGDRtw8OBBdO/eHT///DNu3rxZ6lRlUVERhgwZguLiYmzduhVKpVK3LjExEX/++WeZgaVkHMo6jsfp2LEj3nvvPRQXFyM+Ph7vvfce7ty5YzBdmJKSgnfffRc7duwwuOYpKyurQu/5qPr16+stl0yJ3blzB2q1GlevXoWZmZnBsTVu3Pip3pcIYLghMlrTpk1x+vRpXLt2DR4eHuX2bdasGQDgzz//LLPP1atXkZ2dDR8fH5PWCTwINxEREdi0aROmT5+OjRs3wt7eXi+4uLq64syZM9i7dy92796N3bt3Y/Xq1Rg5ciS+/vprk9cEPLiY2NXVFd99912p6ytydsIYD5+ZMIWgoCDUqVMHa9euRffu3bF27Vq4ubmVGvLefPNNHDlyBD///DP+8Y9/6K3TarUIDAzEW2+9Ver7PHo7fUWPw9nZWVdTUFAQmjZtir59+2L58uV4/fXXATw46xQYGIiMjAzMmjULTZs2Ra1atZCamorw8HC9C5ufxKMX0ZcoOetFVJk4LUVkpODgYAAPLg59nCZNmqBJkybYtm1bmWdvSh6q1rdvX9MV+V9eXl7o0KEDNmzYgKKiImzduhX9+/fXO3sAAFZWVggODsaKFStw+fJlTJgwAd988w0uXbpk8poAoFGjRrh9+za6dOmCgIAAg1fJ3UiNGjUCAMTHx5e7v4o+QdjT0xMAcOHCBYN1Fy5c0K0vi7m5OUJDQ7F582bcuXMH27ZtQ0hIiMEX+fr167Fs2TJ8+OGH6NGjh8F+GjVqhNzc3FLHICAgwOCsx9Pq06cPevTogQULFuDu3bsAgLNnz+LixYtYsmQJZs2ahX79+iEgIAB169Y12L4yntTs6ekJrVaLpKQkvfbK+uzRs4XhhshIgwYNQosWLTB//nwcOXLEYH1OTg7mzJmjW3733Xdx584dTJw4EcXFxXp9T548iUWLFsHX1xcDBw6slHqHDh2Ko0eP4quvvsKtW7f0pqQAGDwh2czMDC1btgQAFBQUVEpNJdM08+bNM1hXVFSEzMxMAECvXr1gZ2eHhQsXIj8/X6/fw//yr1WrVoWmT9q1awdXV1d89tlnese4e/dunDt3zqg7dUaMGIE7d+5gwoQJyM3NNXjWT3x8PMaNG4fhw4eXedfZkCFDcOTIEezdu9dgXWZmJoqKiow+JmPNmjULt2/fxueffw7gf2dWHh5PIUSpjwIouZ6o5M/HFEpunX/4WiQA+OSTT0z2HvTs4rQU0UO++uor3YWOD5s2bRrs7OywdetWBAQEoHv37hgyZAi6dOkCS0tL/PXXX4iJiUHt2rV1z7oJCwvD8ePHsXz5ciQkJCAsLAy1a9fGqVOn8NVXX8HJyQmbN2/W3fpsakOGDMHMmTMxc+ZMODo6GkydjBs3DhkZGejZsyf+8Y9/4OrVq/jkk0/w3HPP6abVynPx4sVSz2LVqVMHgYGBpW7To0cPTJgwAQsXLsSZM2fQq1cvWFpaIjExEZs2bcLy5csxaNAgqNVqfPTRRxg3bhzat2+P0NBQ1K5dG3/88Qfy8vJ002Zt27bFhg0b8Prrr6N9+/awtbXVnWErjaWlJRYtWoTRo0ejR48eCAkJ0d0K3qBBA8yYMeOxx926dWv4+vpi06ZNaNasGdq0aaO3fvTo0QCgm7Z6WOfOndGwYUO8+eab2LFjB/r27Yvw8HC0bdsWd+/e1T33KDk5Gc7Ozo+tpSJefPFF+Pr6YunSpZgyZQqaNm2KRo0aYebMmUhNTYVarcaWLVtKfd5Q27ZtAQBTp05FUFAQzM3NDR5fUFFt27bFwIEDsWzZMty+fVt3K3jJE775u170VKS8VYuouii5Fbys17Vr13R979y5I959913RokULYWNjI1QqlfD19RURERFCo9EY7Hvbtm0iMDBQ1K5dWyiVStG4cWPxxhtviL///tug76hRo0StWrXKrBNG3Ar+sC5duggAYty4cQbrNm/eLHr16iVcXV2FlZWVqF+/vpgwYUKpx1BaHWW9evTooXc8pd2qvWrVKtG2bVthbW0t7OzsRIsWLcRbb70lbty4oddvx44donPnzsLa2lqo1WrRoUMHvVvNc3NzRWhoqHBwcBAAdO9Vciv4pk2bSq1/w4YNonXr1kKpVApHR0cRFhYmrl+/rtfn0VvBH7Z48WIBQCxYsMBgnaenZ5ljs3r1al2/nJwcERERIRo3biysrKyEs7Oz6Ny5s/jwww/F/fv3hRD/uxX8gw8+KLWO0nh6eoo+ffqUum7NmjV6dSQkJIiAgABha2srnJ2dxfjx48Uff/xhUGtRUZF47bXXhIuLi1AoFHrjgjJuBX/0813y/1hSUpKu7e7du2LKlCnC0dFR2Nraiv79++seZfD+++8bfcxEj1IIwau7iIgqYvny5ZgxYwaSk5NNfn3Ms+7MmTNo3bo11q5di7CwMKnLoRqK19wQEVWAEAJffvklevTowWDzlB7+mYcSy5Ytg5mZGbp37y5BRSQXvOaGiMgId+/exY4dO7B//36cPXsW27dvl7qkGm/x4sU4efIk/P39YWFhoXskwSuvvPLYxy0QlYfTUkRERkhOToaXlxccHBwwefJkvR9JpScTGxuL6OhoJCQkIDc3F/Xr18eIESMwZ84cWFjw39705BhuiIiISFZ4zQ0RERHJCsMNERERycozN6mp1Wpx48YN2NnZ8SFRRERENYQQAjk5Oahbty7MzMo/N/PMhZsbN27wKnwiIqIa6tq1awY/RvuoZy7c2NnZAXgwOGq1GoWFhfjpp590j4GnqsFxlwbHXRocd2lw3KVRWeOenZ0NDw8P3fd4eZ65cFMyFaVWq3XhxsbGBmq1mh/+KsRxlwbHXRocd2lw3KVR2eNuzCUlvKCYiIiIZIXhhoiIiGSF4YaIiIhkheGGiIiIZIXhhoiIiGSF4YaIiIhkheGGiIiIZIXhhoiIiGSF4YaIiIhk5Zl7QnFlKdYKHEvKQHpOPlztVOjg5QhzM/4wJxERUVVjuDGBPfEaRO9MgCYrX9fmbq9CZLAPevu6S1gZERHRs4fTUk9pT7wGk9ae0gs2AJCWlY9Ja09hT7xGosqIiIieTQw3T6FYKxC9MwGilHUlbdE7E1CsLa0HERERVQaGm6dwLCnD4IzNwwQATVY+jiVlVF1RREREzziGm6eQnlN2sHmSfkRERPT0GG6egqudyqT9iIiI6Okx3DyFDl6OcLdXoawbvhV4cNdUBy/HqiyLiIjomcZw8xTMzRSIDPYBAIOAU7IcGezD590QERFVIYabp9Tb1x0rh7eBm73+1JObvQorh7fhc26IiIiqGB/iZwK9fd0R6OPGJxQTERFVAww3JmJupoBfIyepyyAiInrmcVqKiIiIZIXhhoiIiGSF4YaIiIhkheGGiIiIZIXhhoiIiGSF4YaIiIhkheGGiIiIZIXhhoiIiGSF4YaIiIhkRfJwk5qaiuHDh8PJyQnW1tZo0aIFTpw4YdS2v/32GywsLPDcc89VbpFERERUY0j68wt37txBly5d4O/vj927d8PFxQWJiYmoXbv2Y7fNzMzEyJEj8cILL+DmzZtVUC0RERHVBJKGm0WLFsHDwwOrV6/WtXl5eRm17cSJExEaGgpzc3Ns27atkiokIiKimkbScLNjxw4EBQVh8ODBOHDgAOrVq4fJkydj/Pjx5W63evVqXLlyBWvXrsV7771Xbt+CggIUFBTolrOzswEAhYWFulfJMlUdjrs0OO7S4LhLg+Mujcoa94rsTyGEECZ99wpQqVQAgNdffx2DBw/G8ePHMW3aNHz22WcYNWpUqdskJiaia9euOHjwIJo0aYKoqChs27YNZ86cKbV/VFQUoqOjDdpjYmJgY2NjsmMhIiKiypOXl4fQ0FBkZWVBrVaX21fScGNlZYV27drh8OHDurapU6fi+PHjOHLkiEH/4uJidOrUCWPHjsXEiRMB4LHhprQzNx4eHrh16xbUajUKCwsRGxuLwMBAWFpamvYAqUwcd2lw3KXBcZcGx10alTXu2dnZcHZ2NircSDot5e7uDh8fH722Zs2aYcuWLaX2z8nJwYkTJ3D69Gm8+uqrAACtVgshBCwsLPDTTz+hZ8+eetsolUoolUqDfVlaWuoN+qPLVDU47tLguEuD4y4Njrs0TD3uFdmXpOGmS5cuuHDhgl7bxYsX4enpWWp/tVqNs2fP6rWtWLECv/zyCzZv3mz0xchEREQkX5KGmxkzZqBz585YsGABhgwZgmPHjmHVqlVYtWqVrk9ERARSU1PxzTffwMzMDL6+vnr7cHV1hUqlMmgnIiKiZ5OkD/Fr3749vv/+e6xbtw6+vr6YN28eli1bhrCwMF0fjUaDlJQUCaskIiKimkTSMzcA0LdvX/Tt27fM9WvWrCl3+6ioKERFRZm2KCIiIqqxJP/5BSIiIiJTYrghIiIiWWG4ISIiIllhuCEiIiJZYbghIiIiWWG4ISIiIllhuCEiIiJZYbghIiIiWWG4ISIiIllhuCEiIiJZYbghIiIiWWG4ISIiIllhuCEiIiJZYbghIiIiWWG4ISIiIllhuCEiIiJZYbghIiIiWWG4ISIiIllhuCEiIiJZYbghIiIiWWG4ISIiIllhuCEiIiJZYbghIiIiWWG4ISIiIllhuCEiIiJZYbghIiIiWWG4ISIiIllhuCEiIiJZYbghIiIiWWG4ISIiIllhuCEiIiJZYbghIiIiWWG4ISIiIlmRPNykpqZi+PDhcHJygrW1NVq0aIETJ06U2X/r1q0IDAyEi4sL1Go1/Pz8sHfv3iqsmIiIiKozScPNnTt30KVLF1haWmL37t1ISEjAkiVLULt27TK3+fXXXxEYGIhdu3bh5MmT8Pf3R3BwME6fPl2FlRMREVF1ZSHlmy9atAgeHh5YvXq1rs3Ly6vcbZYtW6a3vGDBAmzfvh07d+5E69atK6NMIiIiqkEkDTc7duxAUFAQBg8ejAMHDqBevXqYPHkyxo8fb/Q+tFotcnJy4OjoWOr6goICFBQU6Jazs7MBAIWFhbpXyTJVHY67NDju0uC4S4PjLo3KGveK7E8hhBAmffcKUKlUAIDXX38dgwcPxvHjxzFt2jR89tlnGDVqlFH7WLx4Md5//32cP38erq6uBuujoqIQHR1t0B4TEwMbG5unOwAiIiKqEnl5eQgNDUVWVhbUanW5fSUNN1ZWVmjXrh0OHz6sa5s6dSqOHz+OI0eOPHb7mJgYjB8/Htu3b0dAQECpfUo7c+Ph4YFbt25BrVajsLAQsbGxCAwMhKWl5dMfFBmF4y4Njrs0OO7S4LhLo7LGPTs7G87OzkaFG0mnpdzd3eHj46PX1qxZM2zZsuWx265fvx7jxo3Dpk2bygw2AKBUKqFUKg3aLS0t9Qb90WWqGhx3aXDcpcFxlwbHXRqmHveK7EvSu6W6dOmCCxcu6LVdvHgRnp6e5W63bt06jB49GuvWrUOfPn0qs0QiIiKqYSQNNzNmzMDRo0exYMECXLp0CTExMVi1ahWmTJmi6xMREYGRI0fqlmNiYjBy5EgsWbIEHTt2RFpaGtLS0pCVlSXFIRAREVE1I2m4ad++Pb7//nusW7cOvr6+mDdvHpYtW4awsDBdH41Gg5SUFN3yqlWrUFRUhClTpsDd3V33mjZtmhSHQERERNWMpNfcAEDfvn3Rt2/fMtevWbNGbzkuLq5yCyIiIqIaTfKfXyAiIiIyJYYbIiIikhWGGyIiIpIVhhsiIiKSFYYbIiIikhWGGyIiIpIVhhsiIiKSFYYbIiIikhWGGyIiIpIVhhsiIiKSFYYbIiIikhXJf1uKKk+xVuBYUgbSc/LhaqdCBy9HmJsppC6LiIioUjHcyNSeeA2idyZAk5Wva3O3VyEy2Ae9fd0lrIyIiKhycVpKhvbEazBp7Sm9YAMAaVn5mLT2FPbEaySqjIiIqPIx3MhMsVYgemcCRCnrStqidyagWFtaDyIiopqP4UZmjiVlGJyxeZgAoMnKx7GkjKorioiIqAox3MhMek7ZweZJ+hEREdU0DDcy42qnMmk/IiKimobhRmY6eDnC3V6Fsm74VuDBXVMdvByrsiwiIqIqw3AjM+ZmCkQG+wCAQcApWY4M9uHzboiISLYYbmSot687Vg5vAzd7/aknN3sVVg5vw+fcEBGRrPEhfjLV29cdgT5ufEIxERE9cxhuZMzcTAG/Rk5Sl0FERFSlOC1FREREssJwQ0RERLLCcENERESywnBDREREssJwQ0RERLLCcENERESywnBDREREssJwQ0RERLLCcENERESywnBDREREsiJ5uElNTcXw4cPh5OQEa2trtGjRAidOnCh3m7i4OLRp0wZKpRKNGzfGmjVrqqZYIiIiqvYkDTd37txBly5dYGlpid27dyMhIQFLlixB7dq1y9wmKSkJffr0gb+/P86cOYPp06dj3Lhx2Lt3bxVWTkRERNWVpD+cuWjRInh4eGD16tW6Ni8vr3K3+eyzz+Dl5YUlS5YAAJo1a4ZDhw7ho48+QlBQUKXWS0RERNWfpOFmx44dCAoKwuDBg3HgwAHUq1cPkydPxvjx48vc5siRIwgICNBrCwoKwvTp00vtX1BQgIKCAt1ydnY2AKCwsFD3KlmmqsNxlwbHXRocd2lw3KVRWeNekf0phBDCpO9eASqVCgDw+uuvY/DgwTh+/DimTZuGzz77DKNGjSp1myZNmmD06NGIiIjQte3atQt9+vRBXl4erK2t9fpHRUUhOjraYD8xMTGwsbEx4dEQERFRZcnLy0NoaCiysrKgVqvL7SvpmRutVot27dphwYIFAIDWrVsjPj6+3HBTUREREXj99dd1y9nZ2fDw8ECvXr2gVqtRWFiI2NhYBAYGwtLS0iTvSY/HcZcGx10aHHdpcNylUVnjXjLzYgxJw427uzt8fHz02po1a4YtW7aUuY2bmxtu3ryp13bz5k2o1WqDszYAoFQqoVQqDdotLS31Bv3RZaoaHHdpcNylwXGXBsddGqYe94rsS9K7pbp06YILFy7otV28eBGenp5lbuPn54d9+/bptcXGxsLPz69SaiQiIqKaRdJwM2PGDBw9ehQLFizApUuXEBMTg1WrVmHKlCm6PhERERg5cqRueeLEibhy5QreeustnD9/HitWrMDGjRsxY8YMKQ6BiIiIqhlJw0379u3x/fffY926dfD19cW8efOwbNkyhIWF6fpoNBqkpKTolr28vPDjjz8iNjYWrVq1wpIlS/DFF1/wNnAiIiICIPE1NwDQt29f9O3bt8z1pT19+Pnnn8fp06crsSoiIiKqqST/+QUiIiIiU2K4ISIiIllhuCEiIiJZYbghIiIiWWG4ISIiIllhuCEiIiJZYbghIiIiWWG4ISIiIllhuCEiIiJZYbghIiIiWWG4ISIiIllhuCEiIiJZeaIfzkxJScHVq1eRl5cHFxcXNG/eHEql0tS1EREREVWY0eEmOTkZK1euxPr163H9+nUIIXTrrKys0K1bN7zyyisYOHAgzMx4QoiIiIikYVQKmTp1Klq1aoWkpCS89957SEhIQFZWFu7fv4+0tDTs2rULXbt2xbvvvouWLVvi+PHjlV03ERERUamMOnNTq1YtXLlyBU5OTgbrXF1d0bNnT/Ts2RORkZHYs2cPrl27hvbt25u8WCIiIqLHMSrcLFy40Ogd9u7d+4mLISIiInpavDiGiIiIZKXCd0u1bt0aCoXCoF2hUEClUqFx48YIDw+Hv7+/SQokIiIiqogKn7np3bs3rly5glq1asHf3x/+/v6wtbXF5cuX0b59e2g0GgQEBGD79u2VUS8RERFRuSp85ubWrVt444038M477+i1v/fee7h69Sp++uknREZGYt68eejXr5/JCiUiIiIyRoXP3GzcuBEhISEG7cOGDcPGjRsBACEhIbhw4cLTV0dERERUQRUONyqVCocPHzZoP3z4MFQqFQBAq9Xq/puIiIioKlV4Wuq1117DxIkTcfLkSd2zbI4fP44vvvgCb7/9NgBg7969eO6550xaKBEREZExKhxu5s6dCy8vL3z66af49ttvAQDe3t74/PPPERoaCgCYOHEiJk2aZNpKiYiIiIzwRD+cGRYWhrCwMIN2IQQUCgWsra2fujAiIiKiJ1Hha24++OCDUtuLi4t1Z26IiIiIpPJE4ebLL7/UaysuLsawYcNw5swZU9VFRERE9EQqPC31448/olevXrC3t8egQYNQVFSEIUOG4Pz589i/f39l1EhERERktAqHm/bt22PLli3o378/rKys8OWXX+LSpUvYv38/6tSpUxk1EhERERntiX44s2fPnvjmm28wcOBAJCUl4cCBAww2REREVC0Ydebm5ZdfLrXdxcUFDg4OeOWVV3RtW7duNU1lRERERE/AqHBjb29fantQUJBJiyEiIiJ6WkaFm9WrVyMvLw82NjYmffOoqChER0frtXl7e+P8+fNlbrNs2TKsXLkSKSkpcHZ2xqBBg7Bw4UL+3AMREREBqMAFxc7OzujZsydeeukl9OvXz2TX2DRv3hw///zz/wqyKLukmJgYzJ49G1999RU6d+6MixcvIjw8HAqFAkuXLjVJPURERFSzGX1B8blz5xAUFISNGzfC09MTHTt2xPz583H27NmnKsDCwgJu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Gc2aNcPYsWORn5+vm5oYNWoUli1bhhUrVqB58+bo27cvEhMTdftYsmQJYmNj4eHhgdatW5dZl0KhwPbt21G7dm10794dAQEBaNiwITZs2FDu8ZiZmSE8PBzFxcUYOXKk3rpff/0V8fHx8PT01Dvea9euQaFQYNeuXejevTtGjx6NJk2aYNiwYbh69arubqqn9eqrr+LcuXPYtGkTWrVqhaVLl2LRokXw9fXFd999h4ULF+r179y5MyZOnIihQ4fCxcUFixcvfqL39fLywubNm7F161a0bNkSK1eu1N0tpVQqy9wuLCwMf/31Fy5cuFDqeltbW+zevRtCCPTp0wd3794FAKhUKnTq1Ak5OTl4/vnndf2VSqWuvbRws379eowfP/6JjpGoOlAIIYTURRCRPI0dOxZ///23wbNe6H/mz5+Pzz77TG/arzRvvvkmsrOz8Z///KdS69m9ezfeeOMN/Pnnn7Cw4GWZVDPxk0tEJpeVlYWzZ88iJiaGweYRK1asQPv27eHk5ITffvsNH3zwge7hiuWZM2cOVqxYAa1W+1QXjD/O3bt3sXr1agYbqtF45oaITO7555/HsWPHMGHCBHz00UdSl1OtzJgxAxs2bEBGRgbq16+PESNGICIigmGCyIQYboiIiEhWeEExERERyQrDDREREckKww0RERHJCsMNERERyQrDDREREckKww0RERHJCsMNERERyQrDDREREckKww0RERHJyv8D9UTi5qlIyrUAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "\n", - "# Get the electrolyzer cluster rated capacity\n", - "cluster_size_mw = int(\n", - " tech_config[\"technologies\"][\"electrolyzer\"][\"model_inputs\"][\"performance_parameters\"][\n", - " \"cluster_rating_MW\"\n", - " ]\n", - ")\n", - "\n", - "# Define the range for electrolyzer rating\n", - "ratings = np.arange(320, 840, cluster_size_mw)\n", - "\n", - "# Initialize arrays to store results\n", - "lcoh_results = []\n", - "\n", - "for rating in ratings:\n", - " # Calculate the number of clusters from the rating\n", - " n_clusters = int(rating / cluster_size_mw)\n", - "\n", - " # Update the number of clusters\n", - " tech_config[\"technologies\"][\"electrolyzer\"][\"model_inputs\"][\"performance_parameters\"][\n", - " \"n_clusters\"\n", - " ] = n_clusters\n", - "\n", - " # Rerun the model with the updated configurations\n", - " h2i_model.run()\n", - "\n", - " # Get the LCOH value\n", - " lcoh = h2i_model.model.get_val(\"finance_subgroup_default.LCOH\", units=\"USD/kg\")[0]\n", - "\n", - " # Store the results\n", - " lcoh_results.append(lcoh)\n", - "\n", - "# Create a scatter plot\n", - "plt.scatter(ratings, lcoh_results)\n", - "plt.xlabel(\"Electrolyzer Rating (kW)\")\n", - "plt.ylabel(\"LCOH ($/kg)\")\n", - "plt.title(\"LCOH vs Electrolyzer Rating\")\n", - "plt.grid(True)\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dfcd72c8", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3.11.13 ('h2i_env')", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.13" - }, - "vscode": { - "interpreter": { - "hash": "e55566d5f9cb5003b92ad1d2254e8146f3d62519cfa868f35d73d51fb57327c6" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/user_guide/how_to_set_up_an_analysis.md b/docs/user_guide/how_to_set_up_an_analysis.md new file mode 100644 index 000000000..fbf28cab5 --- /dev/null +++ b/docs/user_guide/how_to_set_up_an_analysis.md @@ -0,0 +1,303 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.18.1 +kernelspec: + display_name: Python 3.11.13 ('h2i_env') + language: python + name: python3 +--- + +# How to set up an analysis + +H2Integrate is designed so that you can run a basic analysis or design problem without extensive Python experience. +The key inputs for the analysis are the configuration files, which are in YAML format. +This doc page will walk you through the steps to set up a basic analysis, focusing on the different types of configuration files and how use them. + +## Top-level config file + +The top-level config file is the main entry point for H2Integrate. +Its main purpose is to define the analysis type and the configuration files for the different components of the analysis. +Here is an example of a top-level config file: + +```yaml +name: H2Integrate_config + +system_summary: This reference hybrid plant is located in Minnesota and contains wind, solar, and battery storage technologies. The system is designed to produce hydrogen using an electrolyzer and also produce steel using a grid-connected plant. + +driver_config: driver_config.yaml +technology_config: tech_config.yaml +plant_config: plant_config.yaml +``` + +The top-level config file contains the following keys: +- `name`: (optional) A name for the analysis. This is used to identify the analysis in the output files. +- `system_summary`: (optional) A summary of the analysis. This helpful for quickly describing the analysis for documentation purposes. + +- `driver_config`: The path to the driver config file. This file defines the analysis type and the optimization settings. +- `technology_config`: The path to the technology config file. This file defines the technologies included in the analysis, their modeling parameters, and the performance, cost, and financial models used for each technology. +- `plant_config`: The path to the plant config file. This file defines the system configuration and how the technologies are connected together. + +The goal of the organization of the top-level config file is that it is easy to swap out different configurations for the analysis without having to change the code. +For example, if you had different optimization problems, you could have different driver config files for each optimization problem and just change the `driver_config` key in the top-level config file to point to the new file. +This allows you to quickly test different configurations and see how they affect the results. + +```{note} +The filepaths for the `plant_config`, `tech_config`, and `driver_config` files specified in the top-level config file can be specified as either: +1. Filepaths relative to the top-level config file; this is done in most examples +2. Filepaths relative to the current working directory; this is also done in most examples, which are intended to be run from the folder they're in. +3. Filepaths relative to the H2Integrate root directory; this works best for unique filenames. +4. Absolute filepaths. + +More information about file handling in H2I can be found [here](https://h2integrate.readthedocs.io/en/latest/misc_resources/defining_filepaths.html) +``` + +## Driver config file + +The driver config file defines the analysis type and the optimization settings. +If you are running a basic analysis and not an optimization, the driver config file is quite straightforward and might look like this: + +```yaml +name: driver_config +description: This analysis runs a hybrid plant to match the first example in H2Integrate + +general: + folder_output: outputs +``` + +If you are running an optimization, the driver config file will contain additional keys to define the optimization settings, including design variables, constraints, and objective functions. +Further details of more complex instances of the driver config file can be found in more advanced examples as they are developed. + +## Technology config file + +The technology config file defines the technologies included in the analysis, their modeling parameters, and the performance, cost, and financial models used for each technology. +The yaml file is organized into sections for each technology included in the analysis under the `technologies` heading. +Here is an example of part of a technology config that is defining an energy system with only one technology, an electrolyzer: + +```yaml +name: technology_config +description: This hybrid plant produces steel + +technologies: + electrolyzer: + performance_model: + model: eco_pem_electrolyzer_performance + cost_model: + model: eco_pem_electrolyzer_cost + model_inputs: + shared_parameters: + location: onshore + electrolyzer_capex: 2000 # $/kW overnight installed capital costs + + performance_parameters: + sizing: + resize_for_enduse: False + size_for: BOL #'BOL' (generous) or 'EOL' (conservative) + hydrogen_dmd: + n_clusters: 13 + cluster_rating_MW: 40 + eol_eff_percent_loss: 13 #eol defined as x% change in efficiency from bol + uptime_hours_until_eol: 77600 #number of 'on' hours until electrolyzer reaches eol + include_degradation_penalty: True #include degradation + turndown_ratio: 0.1 #turndown_ratio = minimum_cluster_power/cluster_rating_MW + + cost_parameters: + cost_model: singlitico2021 + + financial_parameters: + capital_items: + depr_period: 7 + replacement_cost_percent: 0.15 # percent of capex - H2A default case +``` + +Here, we have defined a electrolyzer model that uses the built-in `eco_pem_electrolyzer_performance` and `eco_pem_electrolyzer_cost` models. +The `performance_model` and `cost_model` keys define the models used for the performance and cost calculations, respectively. +The `model_inputs` key contains the inputs for the models, which are organized into sections for shared parameters, performance parameters, cost parameters, and financial parameters. +Here, we do not define the `financial_model` key, so the default financial model from ProFAST is used. + +The `shared_parameters` section contains parameters that are common to all models, such as the rating and location of the technology. +These values are defined once in the `shared_parameters` section and are used by all models that reference them. +The `performance_parameters` section contains parameters that are specific to the performance model, such as the sizing and efficiency of the technology. +The `cost_parameters` section contains parameters that are specific to the cost model, such as the capital costs and replacement costs. +The `financial_parameters` section contains parameters that are specific to the financial model, such as the replacement costs and financing terms. + +```{note} +There are no default values for the parameters in the technology config file. +You must define all the parameters for the models you are using in the analysis. +``` + +Based on which models you choose to use, the inputs will vary. +Each model has its own set of inputs, which are defined in the source code for the model. +Because there are no default values for the parameters, we suggest you look at an existing example that uses the model you are interested in to see what inputs are required or look at the source code for the model. +The different models are defined in the `supported_models.py` file in the `h2integrate` package. + +## Plant config file + +The plant config file defines the system configuration, any parameters that might be shared across technologies, and how the technologies are connected together. + +Here is a snippet of an example plant config file: + +```yaml +name: plant_config +description: This plant is located in MN, USA + +site: + latitude: 47.5233 + longitude: -92.5366 + elevation_m: 439.0 + +# array of arrays containing left-to-right technology +# interconnections; can support bidirectional connections +# with the reverse definition. +# this will naturally grow as we mature the interconnected tech +technology_interconnections: [ + ["wind", "electrolyzer", "electricity", "cable"], + ["electrolyzer", "h2_storage", "efficiency"], + ["electrolyzer", "h2_storage", "hydrogen", "pipe"], + ["finance_subgroup_1", "steel", "LCOH"], + ["wind", "steel", "electricity", "cable"], + # etc +] + +plant: + plant_life: 30 + grid_connection: False # option, can be turned on or off + ppa_price: 0.025 # $/kWh + hybrid_electricity_estimated_cf: 0.492 + + +finance_parameters: + finance_model: "ProFastComp" + model_inputs: + params: + analysis_start_year: 2032 + installation_time: 36 # months + cost_adjustment_parameters: + target_dollar_year: 2022 + cost_year_adjustment_inflation: 0.025 # used to adjust modeled costs to target_dollar_year + inflation_rate: 0.0 # 0 for nominal analysis +... +``` + +The `site` section contains the site parameters, such as the latitude, longitude, elevation, and time zone. +The `plant` section contains the plant parameters, such as the plant life, grid connection, PPA price, and installation time. +The `finance_parameters` section contains the financial parameters used across the plant, such as the inflation rates, financing terms, and other financial parameters. + +The `technology_interconnections` section contains the interconnections between the technologies in the system and is the most complex part of the plant config file. +The interconnections are defined as an list of lists, where each sub-list defines a connection between two technologies. +The first entry in the list is the technology that is providing the input to the next technology in the list. +If the list is length 4, then the third entry in the list is what's passing passed via a transporter of the type defined in the fourth entry. +If the list is length 3, then the third entry in the list is what is connected directly between the technologies. + +```{note} +For more information on how to define and interpret technology interconnections, see the {ref}`connecting_technologies` page. +``` + +## Running the analysis + +Once you have the config files defined, you can run the analysis using a simple Python script that inputs the top-level config yaml. +Here, we will show a script that runs one of the example analyses included in the H2Integrate package. + +```{code-cell} ipython3 +from h2integrate.core.h2integrate_model import H2IntegrateModel +import os + + +# Change the current working directory +os.chdir("../../examples/08_wind_electrolyzer/") + +# Create a H2Integrate model +h2i_model = H2IntegrateModel("wind_plant_electrolyzer.yaml") + +# Run the model +h2i_model.run() + +# h2i_model.post_process() + +# Print the total hydrogen produced by the electrolyzer in kg/year +total_hydrogen = h2i_model.model.get_val("electrolyzer.total_hydrogen_produced", units="kg/year")[0] +print(f"Total hydrogen produced by the electrolyzer: {total_hydrogen:.2f} kg/year") +``` + +This will run the analysis defined in the config files and generate the output files in the through the `post_process` method. + +## Modifying and rerunning the analysis + +Once the configs are loaded into H2I, they are stored in the `H2IntegrateModel` instance as dictionaries, so you can modify them and rerun the analysis without having to reload the config files. +Here is an example of how to modify the config files and rerun the analysis: + +```{code-cell} ipython3 +# Access the configuration dictionaries +tech_config = h2i_model.technology_config + +# Modify a parameter in the technology config +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "n_clusters" +] = 15 + +# Rerun the model with the updated configurations +h2i_model.run() + +# Post-process the results +# h2i_model.post_process() + +# Print the total hydrogen produced by the electrolyzer in kg/year +total_hydrogen = h2i_model.model.get_val("electrolyzer.total_hydrogen_produced", units="kg/year")[0] +print(f"Total hydrogen produced by the electrolyzer: {total_hydrogen:.2f} kg/year") +``` + +This is especially useful when you want to run an H2I model as a script and modify parameters dynamically without changing the original YAML configuration file. +If you want to do a simple parameter sweep, you can wrap this in a loop and modify the parameters as needed. + +In the example below, we modify the electrolyzer end-of-life efficiency drop and plot the impact on the LCOH. + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt + +# Get the electrolyzer cluster rated capacity +cluster_size_mw = int( + tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "cluster_rating_MW" + ] +) + +# Define the range for electrolyzer rating +ratings = np.arange(320, 840, cluster_size_mw) + +# Initialize arrays to store results +lcoh_results = [] + +for rating in ratings: + # Calculate the number of clusters from the rating + n_clusters = int(rating / cluster_size_mw) + + # Update the number of clusters + tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "n_clusters" + ] = n_clusters + + # Rerun the model with the updated configurations + h2i_model.run() + + # Get the LCOH value + lcoh = h2i_model.model.get_val("finance_subgroup_hydrogen.LCOH_produced_profast_model", units="USD/kg")[0] + + # Store the results + lcoh_results.append(lcoh) + +# Create a scatter plot +plt.scatter(ratings, lcoh_results) +plt.xlabel("Electrolyzer Rating (kW)") +plt.ylabel("LCOH ($/kg)") +plt.title("LCOH vs Electrolyzer Rating") +plt.grid(True) +plt.show() +``` + +```{code-cell} ipython3 + +``` From e635d7765236baaff2a726977bb810b0da239c3c Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:09:31 -0800 Subject: [PATCH 02/22] add notebook conversion commentary --- docs/CONTRIBUTING.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 8aab93b63..83fab4380 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -46,6 +46,25 @@ If the browser appears to be out of date from what you expected to be built, ple 3. Delete the `_build` folder and rebuild the docs ``` +### Writing Executable Content + +All executable content, such as Jupyter notebooks, should be converted to the executable markdown +format used by Jupyter Book. For users that prefer to develop examples in Jupyter notebooks, then +Jupytext (separate installation required) can be used to convert their work using the following +command. For more details, please see their documentation: +https://jupytext.readthedocs.io/en/latest/using-cli.html. + +```bash +jupytext notebook.ipynb --to myst +``` + +Similarly, any documentation example that users wish to interact with can be converted to a notebook +using the following command. + +```bash +jupytext notebook.md --to .ipynb +``` + ## Tests The test suite can be run using `pytest tests/h2integrate`. Individual test files can be run by specifying them: From 63bd3b3aeb44f10f4ac65d721c6809923256aff5 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:11:36 -0800 Subject: [PATCH 03/22] update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7f54bf5d..1e28bf1f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog + ## 0.5.x [TBD] + - Updates models for NumPy version 2.4.0 - Update test values for WOMBAT update to 0.13.0 - Added standalone iron DRI and steel EAF performance and cost models @@ -7,6 +9,9 @@ - Add geologic hydrogen surface processing converter - Add baseclass for caching functionality - Minor reorg for profast tools +- Converted the documentation Jupyter notebooks to markdown files to simplify output diffs +- Updated the contributing documentation to clarify what developers should expect for including + executable content in the documentation. ## 0.5.1 [December 18, 2025] From f0e7c89d88c595bdc335ed650f65bc35fd277d5e Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:32:13 -0800 Subject: [PATCH 04/22] update gitignore for notebook output --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9096bc44e..5d447af42 100644 --- a/.gitignore +++ b/.gitignore @@ -117,7 +117,7 @@ ENV/ *.db-wal *n2.html -# H2Integrate structure specific +# H2Integrate test, documentation, and examples outputs tests/h2integrate/data/* tests/h2integrate/output.txt tests/h2integrate/test_hydrogen/data/* @@ -130,6 +130,7 @@ tests/h2integrate/test_hydrogen/output/ **/run_*_out/ *_out/ **/test_*_out/ +docs/misc_resources/wind_electrolyzer/ output* snopt_history.txt From 7520031fc59555d2137b4a4a6d58f8e97da863d7 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:36:41 -0800 Subject: [PATCH 05/22] add _autosummary to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5d447af42..2ca604144 100644 --- a/.gitignore +++ b/.gitignore @@ -131,6 +131,7 @@ tests/h2integrate/test_hydrogen/output/ *_out/ **/test_*_out/ docs/misc_resources/wind_electrolyzer/ +docs/_autosummary output* snopt_history.txt From 2400d58befdc0f08e15a6ccd49f37ece88f67894 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:23:07 -0800 Subject: [PATCH 06/22] replace the example notebook with basic script and move the demo to the docs --- docs/_toc.yml | 1 + docs/control/controller_demonstrations.md | 115 +++++ .../hydrogen_dispatch.ipynb | 409 ------------------ .../hydrogen_dispatch.py | 13 + 4 files changed, 129 insertions(+), 409 deletions(-) create mode 100644 docs/control/controller_demonstrations.md delete mode 100644 examples/14_wind_hydrogen_dispatch/hydrogen_dispatch.ipynb create mode 100644 examples/14_wind_hydrogen_dispatch/hydrogen_dispatch.py diff --git a/docs/_toc.yml b/docs/_toc.yml index b4baf5171..8fdc03d6f 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -59,6 +59,7 @@ parts: - file: control/control_overview - file: control/open-loop_controllers - file: control/pyomo_controllers + - file: control/controller_demonstrations - caption: Finance Models chapters: diff --git a/docs/control/controller_demonstrations.md b/docs/control/controller_demonstrations.md new file mode 100644 index 000000000..1bc727bec --- /dev/null +++ b/docs/control/controller_demonstrations.md @@ -0,0 +1,115 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.18.1 +kernelspec: + display_name: native-ard-h2i + language: python + name: python3 +--- + +# Open-Loop Storage Controllers Demonstrations + +```{code-cell} ipython3 +from pathlib import Path +from matplotlib import pyplot as plt +from h2integrate.core.h2integrate_model import H2IntegrateModel +``` + +## Hydrogen Dispatch + +The following example is an expanded form of `examples/14_wind_hydrogen_dispatch`. + +Here, we're highlighting the dispatch controller setup from +`examples/14_wind_hydrogen_dispatch/inputs/tech_config.yaml`. Please note some sections are removed +simply to highlight the controller sections + +```{literalinclude} ../../examples/14_wind_hydrogen_dispatch/inputs/tech_config.yaml +:language: yaml +:lineno-start: 54 +:linenos: true +:lines: 54,59-61,67-74 +``` + +Using the primary configuration, we can create, run, and postprocess an H2Integrate model. + +```{code-cell} ipython3 +# Create an H2Integrate model +model = H2IntegrateModel(Path("../../examples/14_wind_hydrogen_dispatch/inputs/h2i_wind_to_h2_storage.yaml")) + +# Run the model +model.run() +model.post_process() +``` + +Now, we can visualize the demand profiles over time. + +```{code-cell} ipython3 +fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(8, 6), layout="tight") + +start_hour = 0 +end_hour = 200 +total_time_steps = model.prob.get_val("h2_storage.hydrogen_soc").size +demand_profile = [ + model.technology_config["technologies"]["h2_storage"]["model_inputs"]["control_parameters"][ + "demand_profile" + ] + * 1e-3 +] * total_time_steps +xvals = list(range(start_hour, end_hour)) + +ax1.plot( + xvals, + model.prob.get_val("h2_storage.hydrogen_soc", units="percent")[start_hour:end_hour], + label="SOC", +) +ax2.plot( + xvals, + model.prob.get_val("h2_storage.hydrogen_in", units="t/h")[start_hour:end_hour], + linestyle="-", + label="H$_2$ Produced (kg)", +) +ax2.plot( + xvals, + model.prob.get_val("h2_storage.hydrogen_unused_commodity", units="t/h")[start_hour:end_hour], + linestyle=":", + label="H$_2$ Unused (kg)", +) +ax2.plot( + xvals, + model.prob.get_val("h2_storage.hydrogen_unmet_demand", units="t/h")[start_hour:end_hour], + linestyle=":", + label="H$_2$ Unmet Demand (kg)", +) +ax2.plot( + xvals, + model.prob.get_val("h2_storage.hydrogen_out", units="t/h")[start_hour:end_hour], + linestyle="-", + label="H$_2$ Delivered (kg)", +) +ax2.plot( + xvals, + demand_profile[start_hour:end_hour], + linestyle="--", + label="H$_2$ Demand (kg)", +) + +ax1.set_ylabel("SOC (%)") +ax1.grid() +ax1.set_axisbelow(True) +ax1.set_xlim(0, 200) +ax1.set_ylim(0, 50) + +ax2.set_ylabel("H$_2$ Hourly (t)") +ax2.set_xlabel("Timestep (hr)") +ax2.grid() +ax2.set_axisbelow(True) +ax2.set_ylim(0, 20) +ax2.set_yticks(range(0, 21, 2)) + +plt.legend(ncol=3) +fig.show() +``` diff --git a/examples/14_wind_hydrogen_dispatch/hydrogen_dispatch.ipynb b/examples/14_wind_hydrogen_dispatch/hydrogen_dispatch.ipynb deleted file mode 100644 index dab8bbd9e..000000000 --- a/examples/14_wind_hydrogen_dispatch/hydrogen_dispatch.ipynb +++ /dev/null @@ -1,409 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "4ff0af64", - "metadata": {}, - "source": [ - "# Hydrogen Dispatch Example" - ] - }, - { - "cell_type": "markdown", - "id": "fe61165b", - "metadata": {}, - "source": [ - "This example demonstrates how to use the dispatch controller capability in H2Integrate. In the H2Integrate input file (`./inputs/h2i_wind_to_h2_storage.yaml`), the `control_method` and the `control_parameters` are set as follows:\n", - "\n", - "```yaml\n", - "technologies:\n", - " h2_storage:\n", - " control_strategy:\n", - " method: \"demand_open_loop_storage_controller\"\n", - " control_parameters:\n", - " commodity_units: \"kg/h\"\n", - " max_capacity: 60000.0 # kg\n", - " max_charge_percent: 1.0 # percent as decimal\n", - " min_charge_percent: 0.1 # percent as decimal\n", - " init_charge_percent: 0.25 # percent as decimal\n", - " max_charge_rate: 10000.0 # kg/time step\n", - " max_discharge_rate: 10000.0 # kg/time step\n", - " charge_efficiency: 1.0 # percent as decimal\n", - " discharge_efficiency: 1.0 # percent as decimal\n", - " demand_profile: 5000 # constant demand of 5000 kg per hour (see commodity_units\n", - "\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "91c90e5c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "logging to stdout\n" - ] - } - ], - "source": [ - "from pathlib import Path\n", - "from matplotlib import pyplot as plt\n", - "from h2integrate.core.h2integrate_model import H2IntegrateModel" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "bcabc113", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "This is pdfTeX, Version 3.141592653-2.6-1.40.27 (TeX Live 2025) (preloaded format=pdflatex)\n", - " restricted \\write18 enabled.\n", - "entering extended mode\n", - "XDSM diagram written to connections_xdsm.pdf\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "HybridSim : INFO Set up SiteInfo with wind resource file: None\n", - "HybridSim : INFO Wind Layout set with 100 turbines for 830000.0 kw system capacity\n", - "HybridSim : INFO Wind Layout set with 100 turbines for 830000.0 kw system capacity\n", - "HybridSim : INFO WindPlant simulation executed with AEP 3125443108.9530354\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "53 Input(s) in 'model'\n", - "\n", - "varname val units prom_name mean \n", - "----------------------------------------- -------------------- --------- ------------------------------------------------------------------------ ------------------\n", - "plant\n", - " wind_to_electrolyzer_cable\n", - " electricity_in |40580346.11525534| kW wind_to_electrolyzer_cable.electricity_in 356785.74303116 \n", - " electrolyzer\n", - " eco_pem_electrolyzer_performance\n", - " electricity_in |40580346.11525534| kW electrolyzer.electricity_in 356785.74303116 \n", - " n_clusters [18.] unitless electrolyzer.n_clusters 18.0 \n", - " singlitico_electrolyzer_cost\n", - " total_hydrogen_produced [58145418.05837836] kg/year electrolyzer.total_hydrogen_produced \n", - " electricity_in |40393548.72275606| kW electrolyzer.electricity_in \n", - " electrolyzer_size_mw [720.] MW electrolyzer.electrolyzer_size_mw \n", - " electrolyzer_to_h2_storage_pipe\n", - " hydrogen_in |219.95801082| kg/s electrolyzer_to_h2_storage_pipe.hydrogen_in \n", - " h2_storage\n", - " h2_storage\n", - " hydrogen_in |791848.83895374| kg/h h2_storage.hydrogen_in \n", - " rated_h2_production_kg_pr_hr [0.] kg/h h2_storage.rated_h2_production_kg_pr_hr \n", - " efficiency [0.77728804] None h2_storage.efficiency \n", - " demand_open_loop_storage_controller\n", - " hydrogen_in |791848.83895374| kg/h h2_storage.hydrogen_in \n", - " hydrogen_demand |467974.3582719| kg/h**2 h2_storage.hydrogen_demand \n", - " finance_subgroup_default\n", - " electricity_sum\n", - " electricity_wind |40580346.11525534| kW finance_subgroup_elec.electricity_sum.electricity_wind 356785.74303116 \n", - " adjusted_capex_opex_comp\n", - " capex_wind [1.245e+09] USD finance_subgroup_elec.capex_wind 1245000000.0 \n", - " opex_wind [37350000.] USD/year finance_subgroup_elec.opex_wind 37350000.0 \n", - " varopex_wind |0.0| USD/year finance_subgroup_elec.varopex_wind 0.0 \n", - " capex_electrolyzer [6.75464089e+08] USD finance_subgroup_elec.capex_electrolyzer 675464089.1739205 \n", - " opex_electrolyzer [16541049.81608545] USD/year finance_subgroup_elec.opex_electrolyzer 16541049.81608545 \n", - " varopex_electrolyzer |0.0| USD/year finance_subgroup_elec.varopex_electrolyzer 0.0 \n", - " cost_year_wind 2019 n/a finance_subgroup_elec.cost_year_wind n/a \n", - " cost_year_electrolyzer 2021 n/a finance_subgroup_elec.cost_year_electrolyzer n/a \n", - " electricity_finance_default\n", - " total_electricity_produced [3.12544311e+09] kW*h/year finance_subgroup_elec.total_electricity_produced 3125443108.9529934\n", - " capex_adjusted_wind [1.34072883e+09] USD finance_subgroup_elec.capex_adjusted_wind 1340728828.1249995\n", - " opex_adjusted_wind [40221864.84374999] USD/year finance_subgroup_elec.opex_adjusted_wind 40221864.84374998 \n", - " varopex_adjusted_wind |0.0| USD/year finance_subgroup_elec.varopex_adjusted_wind 0.0 \n", - " capex_adjusted_electrolyzer [6.92350691e+08] USD finance_subgroup_elec.capex_adjusted_electrolyzer 692350691.4032685 \n", - " opex_adjusted_electrolyzer [16954576.06148758] USD/year finance_subgroup_elec.opex_adjusted_electrolyzer 16954576.06148758 \n", - " varopex_adjusted_electrolyzer |0.0| USD/year finance_subgroup_elec.varopex_adjusted_electrolyzer 0.0 \n", - " electrolyzer_time_until_replacement [32941.63777869] h finance_subgroup_elec.electrolyzer_time_until_replacement 32941.63777869 \n", - " electrolyzer_to_h2_storage_pipe\n", - " hydrogen_in |221.02084143| kg/s electrolyzer_to_h2_storage_pipe.hydrogen_in 1.95511557 \n", - " h2_storage\n", - " demand_open_loop_storage_controller\n", - " hydrogen_in |795675.02916366| kg/h h2_storage.hydrogen_in 7038.41603938 \n", - " hydrogen_demand_profile |467974.3582719| kg/h h2_storage.hydrogen_demand_profile 5000.0 \n", - " h2_storage\n", - " hydrogen_in |795675.02916366| kg/h h2_storage.hydrogen_in 7038.41603938 \n", - " rated_h2_production_kg_pr_hr [0.] kg/h h2_storage.rated_h2_production_kg_pr_hr 0.0 \n", - " efficiency [0.77744828] None h2_storage.efficiency 0.77744828 \n", - " finance_subgroup_dispatched_hydrogen\n", - " hydrogen_sum\n", - " hydrogen_in |440363.12307143| kg/h finance_subgroup_dispatched_hydrogen.hydrogen_sum.hydrogen_in 4507.31530924 \n", - " adjusted_capex_opex_comp\n", - " capex_wind [1.245e+09] USD finance_subgroup_dispatched_hydrogen.capex_wind 1245000000.0 \n", - " opex_wind [37350000.] USD/year finance_subgroup_dispatched_hydrogen.opex_wind 37350000.0 \n", - " varopex_wind |0.0| USD/year finance_subgroup_dispatched_hydrogen.varopex_wind 0.0 \n", - " capex_electrolyzer [6.75464089e+08] USD finance_subgroup_dispatched_hydrogen.capex_electrolyzer 675464089.1739205 \n", - " opex_electrolyzer [16541049.81608545] USD/year finance_subgroup_dispatched_hydrogen.opex_electrolyzer 16541049.81608545 \n", - " varopex_electrolyzer |0.0| USD/year finance_subgroup_dispatched_hydrogen.varopex_electrolyzer 0.0 \n", - " capex_h2_storage [1.28437699e+08] USD finance_subgroup_dispatched_hydrogen.capex_h2_storage 128437698.55614045\n", - " opex_h2_storage [4330693.49124951] USD/year finance_subgroup_dispatched_hydrogen.opex_h2_storage 4330693.49124951 \n", - " varopex_h2_storage |0.0| USD/year finance_subgroup_dispatched_hydrogen.varopex_h2_storage 0.0 \n", - " cost_year_wind 2019 n/a finance_subgroup_dispatched_hydrogen.cost_year_wind n/a \n", - " cost_year_electrolyzer 2021 n/a finance_subgroup_dispatched_hydrogen.cost_year_electrolyzer n/a \n", - " cost_year_h2_storage 2018 n/a finance_subgroup_dispatched_hydrogen.cost_year_h2_storage n/a \n", - " hydrogen_finance_default\n", - " total_hydrogen_produced [39484082.10891847] kg/year finance_subgroup_dispatched_hydrogen.total_hydrogen_produced 39484082.10891846 \n", - " capex_adjusted_wind [1.34072883e+09] USD finance_subgroup_dispatched_hydrogen.capex_adjusted_wind 1340728828.1249995\n", - " opex_adjusted_wind [40221864.84374999] USD/year finance_subgroup_dispatched_hydrogen.opex_adjusted_wind 40221864.84374998 \n", - " varopex_adjusted_wind |0.0| USD/year finance_subgroup_dispatched_hydrogen.varopex_adjusted_wind 0.0 \n", - " capex_adjusted_electrolyzer [6.92350691e+08] USD finance_subgroup_dispatched_hydrogen.capex_adjusted_electrolyzer 692350691.4032685 \n", - " opex_adjusted_electrolyzer [16954576.06148758] USD/year finance_subgroup_dispatched_hydrogen.opex_adjusted_electrolyzer 16954576.06148758 \n", - " varopex_adjusted_electrolyzer |0.0| USD/year finance_subgroup_dispatched_hydrogen.varopex_adjusted_electrolyzer 0.0 \n", - " capex_adjusted_h2_storage [1.41771187e+08] USD finance_subgroup_dispatched_hydrogen.capex_adjusted_h2_storage 141771187.30847573\n", - " opex_adjusted_h2_storage [4780275.30098699] USD/year finance_subgroup_dispatched_hydrogen.opex_adjusted_h2_storage 4780275.30098699 \n", - " varopex_adjusted_h2_storage |0.0| USD/year finance_subgroup_dispatched_hydrogen.varopex_adjusted_h2_storage 0.0 \n", - " electrolyzer_time_until_replacement [32941.63777869] h finance_subgroup_dispatched_hydrogen.electrolyzer_time_until_replacement 32941.63777869 \n", - "\n", - "\n", - "69 Explicit Output(s) in 'model'\n", - "\n", - "varname val units prom_name mean \n", - "----------------------------------------- -------------------- --------- ------------------------------------------------------------------------- ------------------\n", - "site\n", - " site_component\n", - " latitude [35.2018863] None latitude 35.2018863 \n", - " longitude [-101.945027] None longitude -101.945027 \n", - " elevation_m [0.] None elevation_m 0.0 \n", - " boundary_0_x |1414.21356237| None boundary_0_x 500.0 \n", - " boundary_0_y |1004.98756211| None boundary_0_y 275.0 \n", - " boundary_1_x |3774.91721764| None boundary_1_x 2166.66666667 \n", - " boundary_1_y |3774.91721764| None boundary_1_y 2166.66666667 \n", - "plant\n", - " wind\n", - " wind_plant_performance\n", - " electricity_out |40580346.11525534| kW wind.electricity_out 356785.74303116 \n", - " wind_plant_cost\n", - " CapEx [1.245e+09] USD wind.CapEx 1245000000.0 \n", - " OpEx [37350000.] USD/year wind.OpEx 37350000.0 \n", - " VarOpEx |0.0| USD/year wind.VarOpEx 0.0 \n", - " cost_year 2019 n/a wind.cost_year n/a \n", - " wind_to_electrolyzer_cable\n", - " electricity_out |40580346.11525534| kW wind_to_electrolyzer_cable.electricity_out 356785.74303116 \n", - " electrolyzer\n", - " eco_pem_electrolyzer_performance\n", - " hydrogen_out |795675.02916366| kg/h electrolyzer.hydrogen_out 7038.41603938 \n", - " time_until_replacement [32941.63777869] h electrolyzer.time_until_replacement 32941.63777869 \n", - " total_hydrogen_produced [58458963.85099926] kg/year electrolyzer.total_hydrogen_produced 58458963.85099926 \n", - " efficiency [0.77744828] None electrolyzer.efficiency 0.77744828 \n", - " rated_h2_production_kg_pr_hr [14118.38052473] kg/h electrolyzer.rated_h2_production_kg_pr_hr 14118.38052473 \n", - " electrolyzer_size_mw [720.] MW electrolyzer.electrolyzer_size_mw 720.0 \n", - " singlitico_electrolyzer_cost\n", - " CapEx [6.75464089e+08] USD electrolyzer.CapEx 675464089.1739205 \n", - " OpEx [16541049.81608545] USD/year electrolyzer.OpEx 16541049.81608545 \n", - " VarOpEx |0.0| USD/year electrolyzer.VarOpEx 0.0 \n", - " cost_year 2021 n/a electrolyzer.cost_year n/a \n", - " finance_subgroup_elec\n", - " electricity_sum\n", - " total_electricity_produced [3.12544311e+09] kW*h/year finance_subgroup_elec.electricity_sum.total_electricity_produced 3125443108.9529934\n", - " adjusted_capex_opex_comp\n", - " capex_adjusted_wind [1.34072883e+09] USD finance_subgroup_elec.capex_adjusted_wind 1340728828.1249995\n", - " opex_adjusted_wind [40221864.84374999] USD/year finance_subgroup_elec.opex_adjusted_wind 40221864.84374998 \n", - " varopex_adjusted_wind |0.0| USD/year finance_subgroup_elec.varopex_adjusted_wind 0.0 \n", - " capex_adjusted_electrolyzer [6.92350691e+08] USD finance_subgroup_elec.capex_adjusted_electrolyzer 692350691.4032685 \n", - " opex_adjusted_electrolyzer [16954576.06148758] USD/year finance_subgroup_elec.opex_adjusted_electrolyzer 16954576.06148758 \n", - " varopex_adjusted_electrolyzer |0.0| USD/year finance_subgroup_elec.varopex_adjusted_electrolyzer 0.0 \n", - " total_capex_adjusted [2.03307952e+09] USD finance_subgroup_elec.total_capex_adjusted 2033079519.5282679\n", - " total_opex_adjusted [57176440.90523757] USD/year finance_subgroup_elec.total_opex_adjusted 57176440.90523757 \n", - " total_varopex_adjusted |0.0| USD/year finance_subgroup_elec.total_varopex_adjusted 0.0 \n", - " electricity_finance_default\n", - " LCOE [0.09929732] USD/(kW*h) finance_subgroup_elec.LCOE 0.09929732 \n", - " wacc_electricity [0.06250448] percent finance_subgroup_elec.wacc_electricity 0.06250448 \n", - " crf_electricity [0.07227903] percent finance_subgroup_elec.crf_electricity 0.07227903 \n", - " irr_electricity [0.09] percent finance_subgroup_elec.irr_electricity 0.09 \n", - " profit_index_electricity [1.80745841] unitless finance_subgroup_elec.profit_index_electricity 1.80745841 \n", - " investor_payback_period_electricity [9.] year finance_subgroup_elec.investor_payback_period_electricity 9.0 \n", - " price_electricity [0.09929732] USD/(kW*h) finance_subgroup_elec.price_electricity 0.09929732 \n", - " electrolyzer_to_h2_storage_pipe\n", - " hydrogen_out |221.02084143| kg/s electrolyzer_to_h2_storage_pipe.hydrogen_out 1.95511557 \n", - " h2_storage\n", - " demand_open_loop_storage_controller\n", - " hydrogen_out |440363.12307143| kg/h h2_storage.hydrogen_out 4507.31530924 \n", - " hydrogen_soc |74.44964199| unitless h2_storage.hydrogen_soc 0.71454542 \n", - " hydrogen_unused_commodity |381367.37795319| kg/h h2_storage.hydrogen_unused_commodity 2531.22977391 \n", - " hydrogen_unmet_demand |134457.64787484| kg/h h2_storage.hydrogen_unmet_demand 492.68469076 \n", - " h2_storage\n", - " CapEx [1.28437699e+08] USD h2_storage.CapEx 128437698.55614045\n", - " OpEx [4330693.49124951] USD/year h2_storage.OpEx 4330693.49124951 \n", - " VarOpEx |0.0| USD/year h2_storage.VarOpEx 0.0 \n", - " cost_year 2018 n/a h2_storage.cost_year n/a \n", - " finance_subgroup_dispatched_hydrogen\n", - " hydrogen_sum\n", - " total_hydrogen_produced [39484082.10891847] kg/year finance_subgroup_dispatched_hydrogen.hydrogen_sum.total_hydrogen_produced 39484082.10891846 \n", - " adjusted_capex_opex_comp\n", - " capex_adjusted_wind [1.34072883e+09] USD finance_subgroup_dispatched_hydrogen.capex_adjusted_wind 1340728828.1249995\n", - " opex_adjusted_wind [40221864.84374999] USD/year finance_subgroup_dispatched_hydrogen.opex_adjusted_wind 40221864.84374998 \n", - " varopex_adjusted_wind |0.0| USD/year finance_subgroup_dispatched_hydrogen.varopex_adjusted_wind 0.0 \n", - " capex_adjusted_electrolyzer [6.92350691e+08] USD finance_subgroup_dispatched_hydrogen.capex_adjusted_electrolyzer 692350691.4032685 \n", - " opex_adjusted_electrolyzer [16954576.06148758] USD/year finance_subgroup_dispatched_hydrogen.opex_adjusted_electrolyzer 16954576.06148758 \n", - " varopex_adjusted_electrolyzer |0.0| USD/year finance_subgroup_dispatched_hydrogen.varopex_adjusted_electrolyzer 0.0 \n", - " capex_adjusted_h2_storage [1.41771187e+08] USD finance_subgroup_dispatched_hydrogen.capex_adjusted_h2_storage 141771187.30847573\n", - " opex_adjusted_h2_storage [4780275.30098699] USD/year finance_subgroup_dispatched_hydrogen.opex_adjusted_h2_storage 4780275.30098699 \n", - " varopex_adjusted_h2_storage |0.0| USD/year finance_subgroup_dispatched_hydrogen.varopex_adjusted_h2_storage 0.0 \n", - " total_capex_adjusted [2.17485071e+09] USD finance_subgroup_dispatched_hydrogen.total_capex_adjusted 2174850706.8367434\n", - " total_opex_adjusted [61956716.20622456] USD/year finance_subgroup_dispatched_hydrogen.total_opex_adjusted 61956716.20622456 \n", - " total_varopex_adjusted |0.0| USD/year finance_subgroup_dispatched_hydrogen.total_varopex_adjusted 0.0 \n", - " hydrogen_finance_default\n", - " LCOH [8.37119414] USD/kg finance_subgroup_dispatched_hydrogen.LCOH 8.37119414 \n", - " wacc_hydrogen [0.06250448] percent finance_subgroup_dispatched_hydrogen.wacc_hydrogen 0.06250448 \n", - " crf_hydrogen [0.07227903] percent finance_subgroup_dispatched_hydrogen.crf_hydrogen 0.07227903 \n", - " irr_hydrogen [0.09] percent finance_subgroup_dispatched_hydrogen.irr_hydrogen 0.09 \n", - " profit_index_hydrogen [1.80334107] unitless finance_subgroup_dispatched_hydrogen.profit_index_hydrogen 1.80334107 \n", - " investor_payback_period_hydrogen [9.] year finance_subgroup_dispatched_hydrogen.investor_payback_period_hydrogen 9.0 \n", - " price_hydrogen [8.37119414] USD/kg finance_subgroup_dispatched_hydrogen.price_hydrogen 8.37119414 \n", - "\n", - "\n", - "0 Implicit Output(s) in 'model'\n", - "\n", - "\n" - ] - } - ], - "source": [ - "# Create an H2Integrate model\n", - "model = H2IntegrateModel(Path(\"./inputs/h2i_wind_to_h2_storage.yaml\"))\n", - "\n", - "# Run the model\n", - "model.run()\n", - "model.post_process()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "8f08c94a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "8760\n" - ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fig, ax = plt.subplots(2, 1, sharex=True)\n", - "\n", - "start_hour = 0\n", - "end_hour = 200\n", - "total_time_steps = model.prob.get_val(\"h2_storage.hydrogen_soc\").size\n", - "print(total_time_steps)\n", - "demand_profile = [\n", - " model.technology_config[\"technologies\"][\"h2_storage\"][\"model_inputs\"][\"control_parameters\"][\n", - " \"demand_profile\"\n", - " ]\n", - " * 1e-3\n", - "] * total_time_steps\n", - "\n", - "ax[0].plot(\n", - " range(start_hour, end_hour),\n", - " model.prob.get_val(\"h2_storage.hydrogen_soc\", units=\"percent\")[start_hour:end_hour],\n", - " label=\"SOC\",\n", - ")\n", - "ax[0].set_ylabel(\"SOC (%)\")\n", - "ax[0].set_ylim([0, 110])\n", - "\n", - "ax[1].plot(\n", - " range(start_hour, end_hour),\n", - " model.prob.get_val(\"h2_storage.hydrogen_in\", units=\"t/h\")[start_hour:end_hour],\n", - " linestyle=\"-\",\n", - " label=\"H$_2$ Produced (kg)\",\n", - ")\n", - "ax[1].plot(\n", - " range(start_hour, end_hour),\n", - " model.prob.get_val(\"h2_storage.hydrogen_unused_commodity\", units=\"t/h\")[start_hour:end_hour],\n", - " linestyle=\":\",\n", - " label=\"H$_2$ Unused (kg)\",\n", - ")\n", - "ax[1].plot(\n", - " range(start_hour, end_hour),\n", - " model.prob.get_val(\"h2_storage.hydrogen_unmet_demand\", units=\"t/h\")[start_hour:end_hour],\n", - " linestyle=\":\",\n", - " label=\"H$_2$ Unmet Demand (kg)\",\n", - ")\n", - "ax[1].plot(\n", - " range(start_hour, end_hour),\n", - " model.prob.get_val(\"h2_storage.hydrogen_out\", units=\"t/h\")[start_hour:end_hour],\n", - " linestyle=\"-\",\n", - " label=\"H$_2$ Delivered (kg)\",\n", - ")\n", - "ax[1].plot(\n", - " range(start_hour, end_hour),\n", - " demand_profile[start_hour:end_hour],\n", - " linestyle=\"--\",\n", - " label=\"H$_2$ Demand (kg)\",\n", - ")\n", - "ax[1].set_ylim([0, 30])\n", - "ax[1].set_ylabel(\"H$_2$ Hourly (t)\")\n", - "ax[1].set_xlabel(\"Timestep (hr)\")\n", - "\n", - "plt.legend(ncol=2, frameon=False)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a64de673", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "native-ard-h2i", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/14_wind_hydrogen_dispatch/hydrogen_dispatch.py b/examples/14_wind_hydrogen_dispatch/hydrogen_dispatch.py new file mode 100644 index 000000000..1de81c73d --- /dev/null +++ b/examples/14_wind_hydrogen_dispatch/hydrogen_dispatch.py @@ -0,0 +1,13 @@ +"""Example script for running the base example in the hydrogren dispatch open-loop controller +example. +""" + +from pathlib import Path + +from h2integrate.core.h2integrate_model import H2IntegrateModel + + +config_file = Path("./inputs/h2i_wind_to_h2_storage.yaml").resolve() +model = H2IntegrateModel(config_file) +model.run() +model.post_process() From 8b9a46395dfbfc39c9f8e24163fdf4578c2bd380 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:25:59 -0800 Subject: [PATCH 07/22] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 349faec60..203454610 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ - Converted the documentation Jupyter notebooks to markdown files to simplify output diffs - Updated the contributing documentation to clarify what developers should expect for including executable content in the documentation. +- Converted the example notebooks to documentation examples, and maintain a basic working example + in the examples folder: + - `examples/14_wind_hydrogen_dispatch/hydrogren_dispatch.ipynb` -> `docs/control/controller_demonstrations.md` ## 0.5.1 [December 18, 2025] From ca8744ac8f37d2b5bd0dc73f15d98182cf1bbf82 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:49:32 -0800 Subject: [PATCH 08/22] update markdown formatting --- docs/user_guide/design_of_experiments_in_h2i.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/user_guide/design_of_experiments_in_h2i.md b/docs/user_guide/design_of_experiments_in_h2i.md index 487866f47..c1ab3c901 100644 --- a/docs/user_guide/design_of_experiments_in_h2i.md +++ b/docs/user_guide/design_of_experiments_in_h2i.md @@ -3,7 +3,8 @@ One of the key features of H2Integrate is the ability to perform a design of experiments (DOE) for hybrid energy systems. The design of experiments process uses the `driver_config.yaml` file to define the design sweep, including the design variables, constraints, and objective functions. -Detailed information on setting up the `driver_config.yaml` file can be found [here](https://h2integrate.readthedocs.io/en/latest/user_guide/design_optimization_in_h2i.html) +Detailed information on setting up the `driver_config.yaml` file can be found in the +[user guide](https://h2integrate.readthedocs.io/en/latest/user_guide/design_optimization_in_h2i.html) ## Driver config file @@ -53,7 +54,9 @@ recorder: ``` ## Types of Generators + H2Integrate currently supports the following types of generators: + - ["uniform"](#uniform): uses the `UniformGenerator` generator - ["fullfact"](#fullfactorial): uses the `FullFactorialGenerator` generator - ["plackettburman"](#plackettburman): uses the `PlackettBurmanGenerator` generator @@ -75,6 +78,7 @@ driver: ``` ### FullFactorial + ```yaml driver: design_of_experiments: @@ -86,13 +90,15 @@ driver: The **levels** input is the number of evenly spaced levels between each design variable lower and upper bound. You can check the values that will be used for a specific design variable by running: + ```python import numpy as np + design_variable_values = np.linspace(lower_bound,upper_bound,levels) ``` - ### PlackettBurman + ```yaml driver: design_of_experiments: @@ -101,6 +107,7 @@ driver: ``` ### BoxBehnken + ```yaml driver: design_of_experiments: @@ -109,6 +116,7 @@ driver: ``` ### LatinHypercube + ```yaml driver: design_of_experiments: @@ -119,8 +127,8 @@ driver: seed: #input is specific to this generator ``` - ### CSV + This method is useful if there are specific combinations of designs variables that you want to sweep. An example is shown here: ```yaml From 5e8fa9630fa27bf1776dc1efc1816fb97105c3bb Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:50:27 -0800 Subject: [PATCH 09/22] near complete revamp of run_csv_doe.ipynb --- .../design_of_experiments_in_h2i.md | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/docs/user_guide/design_of_experiments_in_h2i.md b/docs/user_guide/design_of_experiments_in_h2i.md index c1ab3c901..29bda6668 100644 --- a/docs/user_guide/design_of_experiments_in_h2i.md +++ b/docs/user_guide/design_of_experiments_in_h2i.md @@ -1,3 +1,16 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.18.1 +kernelspec: + display_name: h2i-fork + language: python + name: python3 +--- + # Design of experiments in H2I One of the key features of H2Integrate is the ability to perform a design of experiments (DOE) for hybrid energy systems. @@ -144,3 +157,115 @@ The **filename** input is the filepath to the csv file to read cases from. The f ```{note} You should check the csv file for potential formatting issues before running a simulation. This can be done using the `check_file_format_for_csv_generator` method in `h2integrate/core/utilities.py`. Usage of this method is shown in the `20_solar_electrolyzer_doe` example in the `examples` folder. ``` + +#### Demonstration Using Solar and Electrolyzer Capacities + +This `csvgen` generator example reflects the work to produce the `examples/20_solar_electrolyzer_doe` +example. + +We use the `examples/20_solar_electrolyzer_doe/driver_config.yaml` to run a design of experiments for +varying combinations of solar power and hydrogen electrolzer capacities. + +```{literalinclude} ../../examples/20_solar_electrolyzer_doe/driver_config.yaml +:language: yaml +:lineno-start: 4 +:linenos: true +:lines: 4-26 +``` + +The different combinations of solar and electrolzyer capacities are listed in the csv file `examples/20_solar_electrolyzer_doe/csv_doe_cases.csv`: + +```{literalinclude} ../../examples/20_solar_electrolyzer_doe/csv_doe_cases.csv +:language: csv +``` + +Next, we'll import the required models and functions to complete run a successful design of experiments. + +```{code-cell} ipython3 +# Import necessary methods and packages +from pathlib import Path + +from hopp.utilities.utilities import load_yaml +from h2integrate.core.utilities import check_file_format_for_csv_generator, load_yaml +from h2integrate.core.dict_utils import update_defaults +from h2integrate.core.h2integrate_model import H2IntegrateModel +from h2integrate.core.inputs.validation import load_driver_yaml, write_yaml +``` + +##### Setup and first attempt + +First, we need to update the relative file refrerences to ensure the demonstration works. + +```{code-cell} ipython3 +EXAMPLE_DIR = Path("../../examples/20_solar_electrolyzer_doe").resolve() + +config = load_yaml(EXAMPLE_DIR / "20_solar_electrolyzer_doe.yaml") + +driver_config = load_yaml(EXAMPLE_DIR / config["driver_config"]) +csv_config_fn = EXAMPLE_DIR / driver_config["driver"]["design_of_experiments"]["filename"] +config["driver_config"] = driver_config +config["driver_config"]["driver"]["design_of_experiments"]["filename"] = csv_config_fn + +config["technology_config"] = load_yaml(EXAMPLE_DIR / config["technology_config"]) +config["plant_config"] = load_yaml(EXAMPLE_DIR / config["plant_config"]) +``` + +As-is, the model produces a `UserWarning` that it will not successfully run with the existing +configuration, as shown below. + +```{code-cell} ipython3 +:tags: [raises-exception] + +model = H2IntegrateModel(config) +model.run() +``` + +##### Fixing the bug + +The UserWarning tells us that there may be an issue with our csv file. We will use the recommended method to create a new csv file that doesn't have formatting issues. + +We'll take the following steps to try and fix the bug: + +1. Run the `check_file_format_for_csv_generator` method mentioned in the UserWarning and create a new csv file that is hopefully free of errors +2. Make a new driver config file that has "filename" point to the new csv file created in Step 1. +3. Make a new top-level config file that points to the updated driver config file created in Step 2. + +```{code-cell} ipython3 +# Step 1 +new_csv_filename = check_file_format_for_csv_generator( + csv_config_fn, + driver_config, + check_only=False, + overwrite_file=False, +) + +new_csv_filename.name +``` + +Let's see the updates to combinations. + +```{literalinclude} ../../examples/20_solar_electrolyzer_doe/csv_doe_cases0.csv +:language: csv +``` + +```{code-cell} ipython3 +# Step 2 +updated_driver = update_defaults( + driver_config["driver"], + "filename", + EXAMPLE_DIR / new_csv_filename.name, +) +driver_config["driver"].update(updated_driver) + +# Step 3 +config["driver_config"] = driver_config +``` + +### Re-running + +Now that we've completed the debugging and fixing steps, lets try to run the simulation again but with our new files. + +```{code-cell} ipython3 +model = H2IntegrateModel(config) +model.run() +``` From 191151a6d9d84d95e0d4994510c51a064b139c1b Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 20 Jan 2026 08:53:29 -0800 Subject: [PATCH 10/22] get doe example working --- docs/user_guide/design_of_experiments_in_h2i.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user_guide/design_of_experiments_in_h2i.md b/docs/user_guide/design_of_experiments_in_h2i.md index 29bda6668..82791b7d1 100644 --- a/docs/user_guide/design_of_experiments_in_h2i.md +++ b/docs/user_guide/design_of_experiments_in_h2i.md @@ -253,7 +253,7 @@ Let's see the updates to combinations. updated_driver = update_defaults( driver_config["driver"], "filename", - EXAMPLE_DIR / new_csv_filename.name, + str(EXAMPLE_DIR / new_csv_filename.name), ) driver_config["driver"].update(updated_driver) From 10afdb1aa78b2c400ca116a939f94689adfa2856 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:53:02 -0800 Subject: [PATCH 11/22] replace notebook with Python script --- .../run_csv_doe.ipynb | 384 ------------------ .../20_solar_electrolyzer_doe/run_csv_doe.py | 53 +++ 2 files changed, 53 insertions(+), 384 deletions(-) delete mode 100644 examples/20_solar_electrolyzer_doe/run_csv_doe.ipynb create mode 100644 examples/20_solar_electrolyzer_doe/run_csv_doe.py diff --git a/examples/20_solar_electrolyzer_doe/run_csv_doe.ipynb b/examples/20_solar_electrolyzer_doe/run_csv_doe.ipynb deleted file mode 100644 index 38de1576f..000000000 --- a/examples/20_solar_electrolyzer_doe/run_csv_doe.ipynb +++ /dev/null @@ -1,384 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Running Design of Experiments (DOE) using CSVGenerator\n", - "\n", - "In this example, the `driver_config.yaml` file is set-up to run a design of experiments for different combinations of solar and electrolyzer capacities using the \"csvgen\" generator method.\n", - "\n", - "```yaml\n", - "general:\n", - " folder_output: ex_20_out\n", - " create_om_reports: true\n", - "driver:\n", - " design_of_experiments:\n", - " flag: true\n", - " debug_print: true\n", - " generator: \"csvgen\"\n", - " filename: \"csv_doe_cases.csv\"\n", - "design_variables:\n", - " solar:\n", - " capacity_kWdc:\n", - " flag: true\n", - " lower: 5000.0\n", - " upper: 5000000.0\n", - " units: \"kW\"\n", - " electrolyzer:\n", - " n_clusters:\n", - " flag: true\n", - " lower: 1\n", - " upper: 25\n", - " units: \"unitless\"\n", - "```\n", - "\n", - "The different combinations of solar and electrolzyer capacities are listed in the csv file \"csv_doe_cases.csv\":\n", - "\n", - "```text\n", - "solar.capacity_kWdc,electrolyzer.n_clusters\n", - "25000.0,5.0\n", - "50000.0,5.0\n", - "100000.0,5.0\n", - "200000.0,5.0\n", - "50000.0,10.0\n", - "100000.0,10.0\n", - "200000.0,10.0\n", - "250000.0,10.0\n", - "300000.0,10.0\n", - "500000.0,10.0\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/Users/egrant/Documents/projects/GreenHEART/examples/20_solar_electrolyzer_doe/log/hybrid_systems_2025-10-24T17.27.59.955461.log\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/egrant/opt/anaconda3/envs/h2i_env/lib/python3.11/site-packages/fastkml/__init__.py:28: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.\n", - " from pkg_resources import DistributionNotFound\n" - ] - } - ], - "source": [ - "from h2integrate.core.h2integrate_model import H2IntegrateModel" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Model set-up and attempted run\n", - "\n", - "We initialize the H2Integrate model and attempt to run the model. A UserWarning will be thrown saying that something may be wrong with our .csv file." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "ename": "UserWarning", - "evalue": "There may be issues with the csv file csv_doe_cases.csv, which may cause errors within OpenMDAO. To check this csv file or create a new one, run the function h2integrate.core.utilities.check_file_format_for_csv_generator().", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mUserWarning\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;66;03m# Create a H2Integrate model\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m model = \u001b[43mH2IntegrateModel\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m20_solar_electrolyzer_doe.yaml\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 3\u001b[39m \u001b[38;5;66;03m# Run the model\u001b[39;00m\n\u001b[32m 4\u001b[39m model.run()\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/projects/GreenHEART/h2integrate/core/h2integrate_model.py:61\u001b[39m, in \u001b[36mH2IntegrateModel.__init__\u001b[39m\u001b[34m(self, config_file)\u001b[39m\n\u001b[32m 57\u001b[39m \u001b[38;5;28mself\u001b[39m.connect_technologies()\n\u001b[32m 59\u001b[39m \u001b[38;5;66;03m# create driver model\u001b[39;00m\n\u001b[32m 60\u001b[39m \u001b[38;5;66;03m# might be an analysis or optimization\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m61\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mcreate_driver_model\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/projects/GreenHEART/h2integrate/core/h2integrate_model.py:964\u001b[39m, in \u001b[36mH2IntegrateModel.create_driver_model\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 962\u001b[39m myopt = PoseOptimization(\u001b[38;5;28mself\u001b[39m.driver_config)\n\u001b[32m 963\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33mdriver\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m.driver_config:\n\u001b[32m--> \u001b[39m\u001b[32m964\u001b[39m \u001b[43mmyopt\u001b[49m\u001b[43m.\u001b[49m\u001b[43mset_driver\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mprob\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 965\u001b[39m myopt.set_objective(\u001b[38;5;28mself\u001b[39m.prob)\n\u001b[32m 966\u001b[39m myopt.set_design_variables(\u001b[38;5;28mself\u001b[39m.prob)\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/projects/GreenHEART/h2integrate/core/pose_optimization.py:337\u001b[39m, in \u001b[36mPoseOptimization.set_driver\u001b[39m\u001b[34m(self, opt_prob)\u001b[39m\n\u001b[32m 333\u001b[39m valid_file = check_file_format_for_csv_generator(\n\u001b[32m 334\u001b[39m doe_options[\u001b[33m\"\u001b[39m\u001b[33mfilename\u001b[39m\u001b[33m\"\u001b[39m], \u001b[38;5;28mself\u001b[39m.config, check_only=\u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[32m 335\u001b[39m )\n\u001b[32m 336\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m valid_file:\n\u001b[32m--> \u001b[39m\u001b[32m337\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mUserWarning\u001b[39;00m(\n\u001b[32m 338\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mThere may be issues with the csv file \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mdoe_options[\u001b[33m'\u001b[39m\u001b[33mfilename\u001b[39m\u001b[33m'\u001b[39m]\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m, \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 339\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mwhich may cause errors within OpenMDAO. \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 340\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mTo check this csv file or create a new one, run the function \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 341\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mh2integrate.core.utilities.check_file_format_for_csv_generator().\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 342\u001b[39m )\n\u001b[32m 343\u001b[39m generator = om.CSVGenerator(\n\u001b[32m 344\u001b[39m filename=doe_options[\u001b[33m\"\u001b[39m\u001b[33mfilename\u001b[39m\u001b[33m\"\u001b[39m],\n\u001b[32m 345\u001b[39m )\n\u001b[32m 346\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n", - "\u001b[31mUserWarning\u001b[39m: There may be issues with the csv file csv_doe_cases.csv, which may cause errors within OpenMDAO. To check this csv file or create a new one, run the function h2integrate.core.utilities.check_file_format_for_csv_generator()." - ] - } - ], - "source": [ - "# Create a H2Integrate model\n", - "model = H2IntegrateModel(\"20_solar_electrolyzer_doe.yaml\")\n", - "# Run the model\n", - "model.run()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Fixing the bug\n", - "The UserWarning tells us that there may be an issue with our csv file. We will use the recommended method to create a new csv file that doesn't have formatting issues.\n", - "\n", - "We'll take the following steps to try and fix the bug:\n", - "1. Run the `check_file_format_for_csv_generator` method mentioned in the UserWarning and create a new csv file that is hopefully free of errors\n", - "2. Make a new driver config file that has \"filename\" point to the new csv file created in Step 1.\n", - "3. Make a new top-level config file that points to the updated driver config file created in Step 2." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'csv_doe_cases1.csv'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Import necessary methods and packages\n", - "from pathlib import Path\n", - "from h2integrate.core.utilities import check_file_format_for_csv_generator\n", - "from h2integrate.core.inputs.validation import load_driver_yaml, write_yaml\n", - "from hopp.utilities.utilities import load_yaml\n", - "from h2integrate.core.dict_utils import update_defaults\n", - "\n", - "# load the driver config file\n", - "driver_config = load_driver_yaml(\"driver_config.yaml\")\n", - "# specify the filepath to the csv file\n", - "csv_fpath = Path(driver_config[\"driver\"][\"design_of_experiments\"][\"filename\"]).absolute()\n", - "# run the csv checker method, we want it to write the csv file to a new filepath so\n", - "# set overwrite_file=False\n", - "new_csv_filename = check_file_format_for_csv_generator(\n", - " csv_fpath, driver_config, check_only=False, overwrite_file=False\n", - ")\n", - "# lets see what the filename of the new csv file is:\n", - "new_csv_filename.name" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "# update the csv filename in the driver config dictionary\n", - "updated_driver = update_defaults(driver_config[\"driver\"], \"filename\", new_csv_filename.name)\n", - "driver_config[\"driver\"].update(updated_driver)\n", - "\n", - "# save the updated driver to a new file\n", - "write_yaml(driver_config, \"driver_config_new.yaml\")\n", - "\n", - "# update the driver config filename in the top-level config\n", - "main_config = load_yaml(\"20_solar_electrolyzer_doe.yaml\")\n", - "main_config[\"driver_config\"] = \"driver_config_new.yaml\"\n", - "\n", - "# save the updated top-level config file to a new file\n", - "write_yaml(main_config, \"new_20_solar_electrolyzer_doe.yaml\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Re-running\n", - "\n", - "Now that we've completed the debugging and fixing steps, lets try to run the simulation again but with our new files." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Driver debug print for iter coord: rank0:DOEDriver_CSV|0\n", - "--------------------------------------------------------\n", - "Design Vars\n", - "{'electrolyzer.n_clusters': array([5.]), 'solar.capacity_kWdc': array([25000.])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_hydrogen.LCOH_optimistic': array([7.28340669])}\n", - "\n", - "Driver debug print for iter coord: rank0:DOEDriver_CSV|1\n", - "--------------------------------------------------------\n", - "Design Vars\n", - "{'electrolyzer.n_clusters': array([5.]), 'solar.capacity_kWdc': array([50000.])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_hydrogen.LCOH_optimistic': array([4.71306895])}\n", - "\n", - "Driver debug print for iter coord: rank0:DOEDriver_CSV|2\n", - "--------------------------------------------------------\n", - "Design Vars\n", - "{'electrolyzer.n_clusters': array([5.]),\n", - " 'solar.capacity_kWdc': array([100000.])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_hydrogen.LCOH_optimistic': array([4.46997463])}\n", - "\n", - "Driver debug print for iter coord: rank0:DOEDriver_CSV|3\n", - "--------------------------------------------------------\n", - "Design Vars\n", - "{'electrolyzer.n_clusters': array([5.]),\n", - " 'solar.capacity_kWdc': array([200000.])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_hydrogen.LCOH_optimistic': array([6.30803623])}\n", - "\n", - "Driver debug print for iter coord: rank0:DOEDriver_CSV|4\n", - "--------------------------------------------------------\n", - "Design Vars\n", - "{'electrolyzer.n_clusters': array([10.]),\n", - " 'solar.capacity_kWdc': array([50000.])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_hydrogen.LCOH_optimistic': array([7.26817165])}\n", - "\n", - "Driver debug print for iter coord: rank0:DOEDriver_CSV|5\n", - "--------------------------------------------------------\n", - "Design Vars\n", - "{'electrolyzer.n_clusters': array([10.]),\n", - " 'solar.capacity_kWdc': array([100000.])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_hydrogen.LCOH_optimistic': array([4.71007371])}\n", - "\n", - "Driver debug print for iter coord: rank0:DOEDriver_CSV|6\n", - "--------------------------------------------------------\n", - "Design Vars\n", - "{'electrolyzer.n_clusters': array([10.]),\n", - " 'solar.capacity_kWdc': array([200000.])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_hydrogen.LCOH_optimistic': array([4.46825885])}\n", - "\n", - "Driver debug print for iter coord: rank0:DOEDriver_CSV|7\n", - "--------------------------------------------------------\n", - "Design Vars\n", - "{'electrolyzer.n_clusters': array([10.]),\n", - " 'solar.capacity_kWdc': array([250000.])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_hydrogen.LCOH_optimistic': array([4.89582656])}\n", - "\n", - "Driver debug print for iter coord: rank0:DOEDriver_CSV|8\n", - "--------------------------------------------------------\n", - "Design Vars\n", - "{'electrolyzer.n_clusters': array([10.]),\n", - " 'solar.capacity_kWdc': array([300000.])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_hydrogen.LCOH_optimistic': array([5.35322338])}\n", - "\n", - "Driver debug print for iter coord: rank0:DOEDriver_CSV|9\n", - "--------------------------------------------------------\n", - "Design Vars\n", - "{'electrolyzer.n_clusters': array([10.]),\n", - " 'solar.capacity_kWdc': array([500000.])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_hydrogen.LCOH_optimistic': array([7.29072187])}\n", - "\n" - ] - } - ], - "source": [ - "# Create a H2Integrate model\n", - "model = H2IntegrateModel(\"new_20_solar_electrolyzer_doe.yaml\")\n", - "# Run the model\n", - "model.run()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "h2i-fork", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.13" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/20_solar_electrolyzer_doe/run_csv_doe.py b/examples/20_solar_electrolyzer_doe/run_csv_doe.py new file mode 100644 index 000000000..01a6b40e5 --- /dev/null +++ b/examples/20_solar_electrolyzer_doe/run_csv_doe.py @@ -0,0 +1,53 @@ +"""Minimal working example from the DOE user guide docs.""" + +from h2integrate.core.utilities import load_yaml, check_file_format_for_csv_generator +from h2integrate.core.dict_utils import update_defaults +from h2integrate.core.h2integrate_model import H2IntegrateModel + + +# Load the configurations and run the model +config = load_yaml("20_solar_electrolyzer_doe.yaml") + +driver_config = load_yaml(config["driver_config"]) +csv_config_fn = driver_config["driver"]["design_of_experiments"]["filename"] + +try: + model = H2IntegrateModel(config) + model.run() +except UserWarning as e: + print(f"Caught UserWarning: {e}") + +""" +To fix the issue with the UserWarning, we'll take the following steps to try and fix +the bug in our CSV file: +1. Run the `check_file_format_for_csv_generator` method mentioned in the UserWarning + and create a new csv file that is hopefully free of errors +2. Make a new driver config file that has "filename" point to the new csv file created + in Step 1. +3. Make a new top-level config file that points to the updated driver config file + created in Step 2. +""" + +# Step 1 +new_csv_filename = check_file_format_for_csv_generator( + csv_config_fn, + driver_config, + check_only=False, + overwrite_file=False, +) + +# Step 2 +updated_driver = update_defaults( + driver_config["driver"], + "filename", + new_csv_filename.name, +) +driver_config["driver"].update(updated_driver) +print(f"New DOE driver CSV file: {new_csv_filename}") + +# Step 3 +config["driver_config"] = driver_config + +# Rerun the model +model = H2IntegrateModel(config) +model.run() From a24cd4839870a057500852cc9937c6dbf1dc62ac Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:54:07 -0800 Subject: [PATCH 12/22] update bad import --- docs/user_guide/design_of_experiments_in_h2i.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/user_guide/design_of_experiments_in_h2i.md b/docs/user_guide/design_of_experiments_in_h2i.md index 82791b7d1..e11138ba8 100644 --- a/docs/user_guide/design_of_experiments_in_h2i.md +++ b/docs/user_guide/design_of_experiments_in_h2i.md @@ -185,7 +185,6 @@ Next, we'll import the required models and functions to complete run a successfu # Import necessary methods and packages from pathlib import Path -from hopp.utilities.utilities import load_yaml from h2integrate.core.utilities import check_file_format_for_csv_generator, load_yaml from h2integrate.core.dict_utils import update_defaults from h2integrate.core.h2integrate_model import H2IntegrateModel From acdb6eb6e1fa752c4d915d0893d5c006e5f2bd04 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:54:26 -0800 Subject: [PATCH 13/22] update gitignore for DOE driver configs --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 2ca604144..1e0cf19a7 100644 --- a/.gitignore +++ b/.gitignore @@ -125,6 +125,8 @@ tests/h2integrate/test_hydrogen/output.txt *speed_dir_data.csv examples/h2integrate/*/data examples/h2integrate/*/figures +examples/*/*_new.* +examples/*/*[0-9].* tests/h2integrate/reports/ tests/h2integrate/test_hydrogen/output/ **/run_*_out/ From 84c9035e5d6f00d87986e2080919a4b508fbdc17 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 20 Jan 2026 14:01:36 -0800 Subject: [PATCH 14/22] run example from top --- examples/25_sizing_modes/run_size_modes.ipynb | 361 +++++++++++++++--- 1 file changed, 309 insertions(+), 52 deletions(-) diff --git a/examples/25_sizing_modes/run_size_modes.ipynb b/examples/25_sizing_modes/run_size_modes.ipynb index 94897ed44..72a859400 100644 --- a/examples/25_sizing_modes/run_size_modes.ipynb +++ b/examples/25_sizing_modes/run_size_modes.ipynb @@ -74,8 +74,13 @@ "outputs": [], "source": [ "import numpy as np\n", - "from h2integrate.core.utilities import ResizeablePerformanceModelBaseConfig, merge_shared_inputs\n", - "from h2integrate.core.model_baseclasses import ResizeablePerformanceModelBaseClass\n", + "from h2integrate.core.utilities import merge_shared_inputs\n", + "from h2integrate.core.model_baseclasses import (\n", + " ResizeablePerformanceModelBaseClass,\n", + " ResizeablePerformanceModelBaseConfig,\n", + ")\n", + "from h2integrate.core.h2integrate_model import H2IntegrateModel\n", + "from h2integrate.core.inputs.validation import load_tech_yaml, load_driver_yaml, load_plant_yaml\n", "\n", "\n", "class TechPerformanceModelConfig(ResizeablePerformanceModelBaseConfig):\n", @@ -148,7 +153,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "a79029a8", "metadata": {}, "outputs": [ @@ -156,17 +161,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "XDSM diagram written to connections_xdsm.pdf\n", "electrolyzer: normal\n", "ammonia: normal\n" ] } ], "source": [ - "from h2integrate.core.h2integrate_model import H2IntegrateModel\n", - "from h2integrate.core.inputs.validation import load_tech_yaml, load_driver_yaml, load_plant_yaml\n", - "\n", - "\n", "# Create a H2Integrate model\n", "driver_config = load_driver_yaml(\"driver_config.yaml\")\n", "plant_config = load_plant_yaml(\"plant_config.yaml\")\n", @@ -193,6 +193,237 @@ " )" ] }, + { + "cell_type": "code", + "execution_count": 4, + "id": "97783113", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'site': {'data': {'lat': 32.31714,\n", + " 'lon': -100.18,\n", + " 'elev': 762,\n", + " 'year': 2013,\n", + " 'tz': -6,\n", + " 'site_boundaries': {'verts': [[0.0, 0.0],\n", + " [0.0, 20000.0],\n", + " [20000.0, 20000.0],\n", + " [20000.0, 0.0]],\n", + " 'verts_simple': [[0.0, 0.0],\n", + " [0.0, 20000.0],\n", + " [20000.0, 20000.0],\n", + " [20000.0, 0.0]]}},\n", + " 'solar_resource_file': '../02_texas_ammonia/tech_inputs/weather/solar/32.31714_-100.18_psmv3_60_2013.csv',\n", + " 'wind_resource_file': '../02_texas_ammonia/tech_inputs/weather/wind/32.31714_-100.18_windtoolkit_2013_60min_100m_120m.srw',\n", + " 'wave_resource_file': '',\n", + " 'grid_resource_file': '',\n", + " 'hub_height': 115.0,\n", + " 'capacity_hours': [],\n", + " 'solar': True,\n", + " 'wind': True,\n", + " 'wave': False,\n", + " 'wind_resource_origin': 'WTK'},\n", + " 'technologies': {'wind': {'num_turbines': 148,\n", + " 'turbine_rating_kw': 6000.0,\n", + " 'operational_losses': 10.49,\n", + " 'model_name': 'pysam',\n", + " 'timestep': [0, 8760],\n", + " 'fin_model': {'battery_system': {'batt_replacement_schedule_percent': [0],\n", + " 'batt_bank_replacement': [0],\n", + " 'batt_replacement_option': 0,\n", + " 'batt_computed_bank_capacity': 0,\n", + " 'batt_meter_position': 0},\n", + " 'system_costs': {'om_fixed': [0],\n", + " 'om_production': [0],\n", + " 'om_capacity': [0],\n", + " 'om_batt_fixed_cost': 0,\n", + " 'om_batt_variable_cost': [0],\n", + " 'om_batt_capacity_cost': 0,\n", + " 'om_batt_replacement_cost': 0,\n", + " 'om_replacement_cost_escal': 0},\n", + " 'system_use_lifetime_output': 0,\n", + " 'financial_parameters': {'inflation_rate': 0.0,\n", + " 'real_discount_rate': 6.3,\n", + " 'federal_tax_rate': 21.0,\n", + " 'state_tax_rate': 4.74,\n", + " 'property_tax_rate': 1.47,\n", + " 'insurance_rate': 1.0,\n", + " 'debt_percent': 72.4,\n", + " 'term_int_rate': 4.4,\n", + " 'months_working_reserve': 1,\n", + " 'analysis_start_year': 2030,\n", + " 'installation_months': 36,\n", + " 'sales_tax_rate_state': 0.0,\n", + " 'admin_expense_percent_of_sales': 0.0,\n", + " 'capital_gains_tax_rate': 15.0,\n", + " 'debt_type': 'Revolving debt',\n", + " 'depreciation_method': 'MACRS',\n", + " 'depreciation_period': 5},\n", + " 'cp_capacity_credit_percent': [0],\n", + " 'degradation': [0],\n", + " 'revenue': {'ppa_price_input': [0.0], 'ppa_escalation': 0}}},\n", + " 'pv': {'system_capacity_kw': 400000,\n", + " 'dc_degradation': [0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0],\n", + " 'fin_model': {'battery_system': {'batt_replacement_schedule_percent': [0],\n", + " 'batt_bank_replacement': [0],\n", + " 'batt_replacement_option': 0,\n", + " 'batt_computed_bank_capacity': 0,\n", + " 'batt_meter_position': 0},\n", + " 'system_costs': {'om_fixed': [0],\n", + " 'om_production': [0],\n", + " 'om_capacity': [0],\n", + " 'om_batt_fixed_cost': 0,\n", + " 'om_batt_variable_cost': [0],\n", + " 'om_batt_capacity_cost': 0,\n", + " 'om_batt_replacement_cost': 0,\n", + " 'om_replacement_cost_escal': 0},\n", + " 'system_use_lifetime_output': 0,\n", + " 'financial_parameters': {'inflation_rate': 0.0,\n", + " 'real_discount_rate': 5.9,\n", + " 'federal_tax_rate': 21.0,\n", + " 'state_tax_rate': 4.74,\n", + " 'property_tax_rate': 1.47,\n", + " 'insurance_rate': 1.0,\n", + " 'debt_percent': 75.3,\n", + " 'term_int_rate': 4.4,\n", + " 'months_working_reserve': 1,\n", + " 'analysis_start_year': 2030,\n", + " 'installation_months': 36,\n", + " 'sales_tax_rate_state': 0.0,\n", + " 'admin_expense_percent_of_sales': 0.0,\n", + " 'capital_gains_tax_rate': 15.0,\n", + " 'debt_type': 'Revolving debt',\n", + " 'depreciation_method': 'MACRS',\n", + " 'depreciation_period': 5},\n", + " 'cp_capacity_credit_percent': [0],\n", + " 'degradation': [0],\n", + " 'revenue': {'ppa_price_input': [0.0], 'ppa_escalation': 0}}},\n", + " 'battery': {'system_capacity_kwh': 96,\n", + " 'system_capacity_kw': 96,\n", + " 'minimum_SOC': 20.0,\n", + " 'maximum_SOC': 100.0,\n", + " 'initial_SOC': 90.0,\n", + " 'fin_model': {'battery_system': {'batt_replacement_schedule_percent': [0],\n", + " 'batt_bank_replacement': [0],\n", + " 'batt_replacement_option': 0,\n", + " 'batt_computed_bank_capacity': 0,\n", + " 'batt_meter_position': 0},\n", + " 'system_costs': {'om_fixed': [0],\n", + " 'om_production': [0],\n", + " 'om_capacity': [0],\n", + " 'om_batt_fixed_cost': 0,\n", + " 'om_batt_variable_cost': [0],\n", + " 'om_batt_capacity_cost': 0,\n", + " 'om_batt_replacement_cost': 0,\n", + " 'om_replacement_cost_escal': 0},\n", + " 'system_use_lifetime_output': 0,\n", + " 'financial_parameters': {'inflation_rate': 0.0,\n", + " 'real_discount_rate': 6.6,\n", + " 'federal_tax_rate': 21.0,\n", + " 'state_tax_rate': 4.74,\n", + " 'property_tax_rate': 1.47,\n", + " 'insurance_rate': 1.0,\n", + " 'debt_percent': 75.4,\n", + " 'term_int_rate': 4.4,\n", + " 'months_working_reserve': 1,\n", + " 'analysis_start_year': 2030,\n", + " 'installation_months': 36,\n", + " 'sales_tax_rate_state': 0.0,\n", + " 'admin_expense_percent_of_sales': 0.0,\n", + " 'capital_gains_tax_rate': 15.0,\n", + " 'debt_type': 'Revolving debt',\n", + " 'depreciation_method': 'MACRS',\n", + " 'depreciation_period': 5},\n", + " 'cp_capacity_credit_percent': [0],\n", + " 'degradation': [0],\n", + " 'revenue': {'ppa_price_input': [0.0], 'ppa_escalation': 0}}},\n", + " 'grid': {'interconnect_kw': 2000000,\n", + " 'fin_model': {'battery_system': {'batt_replacement_schedule_percent': [0],\n", + " 'batt_bank_replacement': [0],\n", + " 'batt_replacement_option': 0,\n", + " 'batt_computed_bank_capacity': 0,\n", + " 'batt_meter_position': 0},\n", + " 'system_costs': {'om_fixed': [0],\n", + " 'om_production': [0],\n", + " 'om_capacity': [0],\n", + " 'om_batt_fixed_cost': 0,\n", + " 'om_batt_variable_cost': [0],\n", + " 'om_batt_capacity_cost': 0,\n", + " 'om_batt_replacement_cost': 0,\n", + " 'om_replacement_cost_escal': 0},\n", + " 'system_use_lifetime_output': 0,\n", + " 'financial_parameters': {'inflation_rate': 0.0,\n", + " 'real_discount_rate': 6.3,\n", + " 'federal_tax_rate': 21.0,\n", + " 'state_tax_rate': 4.74,\n", + " 'property_tax_rate': 1.47,\n", + " 'insurance_rate': 1.0,\n", + " 'debt_percent': 72.4,\n", + " 'term_int_rate': 4.4,\n", + " 'months_working_reserve': 1,\n", + " 'analysis_start_year': 2030,\n", + " 'installation_months': 36,\n", + " 'sales_tax_rate_state': 0.0,\n", + " 'admin_expense_percent_of_sales': 0.0,\n", + " 'capital_gains_tax_rate': 15.0,\n", + " 'debt_type': 'Revolving debt',\n", + " 'depreciation_method': 'MACRS',\n", + " 'depreciation_period': 5},\n", + " 'cp_capacity_credit_percent': [0],\n", + " 'degradation': [0],\n", + " 'revenue': {'ppa_price_input': [0.0], 'ppa_escalation': 0}}}},\n", + " 'config': {'simulation_options': {'cache': True,\n", + " 'wind': {'skip_financial': False}},\n", + " 'dispatch_options': {'battery_dispatch': 'load_following_heuristic',\n", + " 'solver': 'cbc',\n", + " 'n_look_ahead_periods': 48,\n", + " 'grid_charging': False,\n", + " 'pv_charging_only': False,\n", + " 'include_lifecycle_count': False},\n", + " 'cost_info': {'wind_installed_cost_mw': 1380000,\n", + " 'solar_installed_cost_mw': 1323000,\n", + " 'storage_installed_cost_mwh': 310000,\n", + " 'storage_installed_cost_mw': 311000,\n", + " 'wind_om_per_kw': 29,\n", + " 'pv_om_per_kw': 18,\n", + " 'battery_om_per_kw': 15.525}}}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tech_config[\"technologies\"][\"hopp\"][\"model_inputs\"][\"performance_parameters\"][\"hopp_config\"]" + ] + }, { "cell_type": "markdown", "id": "5b88d3b1", @@ -205,7 +436,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "8ddb8b90", "metadata": {}, "outputs": [ @@ -234,28 +465,60 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "f22dd04e", "metadata": {}, "outputs": [ { - "ename": "AttributeError", - "evalue": "The class definition for ECOElectrolyzerPerformanceModelConfig is missing the following inputs: ['sizing']", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mAttributeError\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;66;03m# Run the model\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m model.run()\n\u001b[32m 4\u001b[39m \u001b[38;5;66;03m# Print selected output\u001b[39;00m\n\u001b[32m 5\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m value \u001b[38;5;129;01min\u001b[39;00m [\n\u001b[32m 6\u001b[39m \u001b[33m\"\u001b[39m\u001b[33melectrolyzer.electricity_in\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m 7\u001b[39m \u001b[33m\"\u001b[39m\u001b[33melectrolyzer.electrolyzer_size_mw\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m (...)\u001b[39m\u001b[32m 13\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mfinance_subgroup_nh3.LCOA\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m 14\u001b[39m ]:\n", - "\u001b[36mFile \u001b[39m\u001b[32m~\\Documents\\git\\H2Integrate\\h2integrate\\core\\h2integrate_model.py:1194\u001b[39m, in \u001b[36mH2IntegrateModel.run\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 1190\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mrun\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[32m 1191\u001b[39m \u001b[38;5;66;03m# do model setup based on the driver config\u001b[39;00m\n\u001b[32m 1192\u001b[39m \u001b[38;5;66;03m# might add a recorder, driver, set solver tolerances, etc\u001b[39;00m\n\u001b[32m 1193\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m.setup_has_been_called:\n\u001b[32m-> \u001b[39m\u001b[32m1194\u001b[39m \u001b[38;5;28mself\u001b[39m.prob.setup()\n\u001b[32m 1195\u001b[39m \u001b[38;5;28mself\u001b[39m.setup_has_been_called = \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[32m 1197\u001b[39m \u001b[38;5;28mself\u001b[39m.prob.run_driver()\n", - "\u001b[36mFile \u001b[39m\u001b[32m~\\Documents\\git\\OpenMDAO\\openmdao\\core\\problem.py:1163\u001b[39m, in \u001b[36mProblem.setup\u001b[39m\u001b[34m(self, check, logger, mode, force_alloc_complex, distributed_vector_class, local_vector_class, derivatives, parent)\u001b[39m\n\u001b[32m 1160\u001b[39m \u001b[38;5;28mself\u001b[39m._metadata[\u001b[33m'\u001b[39m\u001b[33mreports_dir\u001b[39m\u001b[33m'\u001b[39m] = \u001b[38;5;28mself\u001b[39m.get_reports_dir()\n\u001b[32m 1162\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m1163\u001b[39m model._setup(model_comm, \u001b[38;5;28mself\u001b[39m._metadata)\n\u001b[32m 1164\u001b[39m \u001b[38;5;28;01mfinally\u001b[39;00m:\n\u001b[32m 1165\u001b[39m \u001b[38;5;66;03m# whenever we're outside of model._setup, static mode should be True so that anything\u001b[39;00m\n\u001b[32m 1166\u001b[39m \u001b[38;5;66;03m# added outside of _setup will persist.\u001b[39;00m\n\u001b[32m 1167\u001b[39m \u001b[38;5;28mself\u001b[39m._metadata[\u001b[33m'\u001b[39m\u001b[33mstatic_mode\u001b[39m\u001b[33m'\u001b[39m] = \u001b[38;5;28;01mTrue\u001b[39;00m\n", - "\u001b[36mFile \u001b[39m\u001b[32m~\\Documents\\git\\OpenMDAO\\openmdao\\core\\group.py:830\u001b[39m, in \u001b[36mGroup._setup\u001b[39m\u001b[34m(self, comm, prob_meta)\u001b[39m\n\u001b[32m 827\u001b[39m \u001b[38;5;28mself\u001b[39m._post_components = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 829\u001b[39m \u001b[38;5;66;03m# Besides setting up the processors, this method also builds the model hierarchy.\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m830\u001b[39m \u001b[38;5;28mself\u001b[39m._setup_procs(\u001b[38;5;28mself\u001b[39m.pathname, comm, prob_meta)\n\u001b[32m 832\u001b[39m prob_meta[\u001b[33m'\u001b[39m\u001b[33mconfig_info\u001b[39m\u001b[33m'\u001b[39m] = _ConfigInfo()\n\u001b[32m 834\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 835\u001b[39m \u001b[38;5;66;03m# Recurse model from the bottom to the top for configuring.\u001b[39;00m\n", - "\u001b[36mFile \u001b[39m\u001b[32m~\\Documents\\git\\OpenMDAO\\openmdao\\core\\group.py:701\u001b[39m, in \u001b[36mGroup._setup_procs\u001b[39m\u001b[34m(self, pathname, comm, prob_meta)\u001b[39m\n\u001b[32m 699\u001b[39m \u001b[38;5;66;03m# Perform recursion\u001b[39;00m\n\u001b[32m 700\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m subsys \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m._subsystems_myproc:\n\u001b[32m--> \u001b[39m\u001b[32m701\u001b[39m subsys._setup_procs(subsys.pathname, sub_comm, prob_meta)\n\u001b[32m 703\u001b[39m \u001b[38;5;66;03m# build a list of local subgroups to speed up later loops\u001b[39;00m\n\u001b[32m 704\u001b[39m \u001b[38;5;28mself\u001b[39m._subgroups_myproc = [s \u001b[38;5;28;01mfor\u001b[39;00m s \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m._subsystems_myproc \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(s, Group)]\n", - "\u001b[36mFile \u001b[39m\u001b[32m~\\Documents\\git\\OpenMDAO\\openmdao\\core\\group.py:701\u001b[39m, in \u001b[36mGroup._setup_procs\u001b[39m\u001b[34m(self, pathname, comm, prob_meta)\u001b[39m\n\u001b[32m 699\u001b[39m \u001b[38;5;66;03m# Perform recursion\u001b[39;00m\n\u001b[32m 700\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m subsys \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m._subsystems_myproc:\n\u001b[32m--> \u001b[39m\u001b[32m701\u001b[39m subsys._setup_procs(subsys.pathname, sub_comm, prob_meta)\n\u001b[32m 703\u001b[39m \u001b[38;5;66;03m# build a list of local subgroups to speed up later loops\u001b[39;00m\n\u001b[32m 704\u001b[39m \u001b[38;5;28mself\u001b[39m._subgroups_myproc = [s \u001b[38;5;28;01mfor\u001b[39;00m s \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m._subsystems_myproc \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(s, Group)]\n", - "\u001b[36mFile \u001b[39m\u001b[32m~\\Documents\\git\\OpenMDAO\\openmdao\\core\\group.py:701\u001b[39m, in \u001b[36mGroup._setup_procs\u001b[39m\u001b[34m(self, pathname, comm, prob_meta)\u001b[39m\n\u001b[32m 699\u001b[39m \u001b[38;5;66;03m# Perform recursion\u001b[39;00m\n\u001b[32m 700\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m subsys \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m._subsystems_myproc:\n\u001b[32m--> \u001b[39m\u001b[32m701\u001b[39m subsys._setup_procs(subsys.pathname, sub_comm, prob_meta)\n\u001b[32m 703\u001b[39m \u001b[38;5;66;03m# build a list of local subgroups to speed up later loops\u001b[39;00m\n\u001b[32m 704\u001b[39m \u001b[38;5;28mself\u001b[39m._subgroups_myproc = [s \u001b[38;5;28;01mfor\u001b[39;00m s \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m._subsystems_myproc \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(s, Group)]\n", - "\u001b[36mFile \u001b[39m\u001b[32m~\\Documents\\git\\OpenMDAO\\openmdao\\core\\component.py:238\u001b[39m, in \u001b[36mComponent._setup_procs\u001b[39m\u001b[34m(self, pathname, comm, prob_meta)\u001b[39m\n\u001b[32m 235\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m io \u001b[38;5;129;01min\u001b[39;00m [\u001b[33m'\u001b[39m\u001b[33minput\u001b[39m\u001b[33m'\u001b[39m, \u001b[33m'\u001b[39m\u001b[33moutput\u001b[39m\u001b[33m'\u001b[39m]:\n\u001b[32m 236\u001b[39m \u001b[38;5;28mself\u001b[39m._var_rel_names[io].extend(\u001b[38;5;28mself\u001b[39m._static_var_rel_names[io])\n\u001b[32m--> \u001b[39m\u001b[32m238\u001b[39m \u001b[38;5;28mself\u001b[39m.setup()\n\u001b[32m 239\u001b[39m \u001b[38;5;28mself\u001b[39m._setup_check()\n\u001b[32m 241\u001b[39m \u001b[38;5;28mself\u001b[39m._set_vector_class()\n", - "\u001b[36mFile \u001b[39m\u001b[32m~\\Documents\\git\\H2Integrate\\h2integrate\\converters\\hydrogen\\pem_electrolyzer.py:53\u001b[39m, in \u001b[36mECOElectrolyzerPerformanceModel.setup\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 52\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34msetup\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[32m---> \u001b[39m\u001b[32m53\u001b[39m \u001b[38;5;28mself\u001b[39m.config = ECOElectrolyzerPerformanceModelConfig.from_dict(\n\u001b[32m 54\u001b[39m merge_shared_inputs(\u001b[38;5;28mself\u001b[39m.options[\u001b[33m\"\u001b[39m\u001b[33mtech_config\u001b[39m\u001b[33m\"\u001b[39m][\u001b[33m\"\u001b[39m\u001b[33mmodel_inputs\u001b[39m\u001b[33m\"\u001b[39m], \u001b[33m\"\u001b[39m\u001b[33mperformance\u001b[39m\u001b[33m\"\u001b[39m),\n\u001b[32m 55\u001b[39m strict=\u001b[38;5;28;01mFalse\u001b[39;00m,\n\u001b[32m 56\u001b[39m )\n\u001b[32m 57\u001b[39m \u001b[38;5;28msuper\u001b[39m().setup()\n\u001b[32m 58\u001b[39m \u001b[38;5;28mself\u001b[39m.add_output(\u001b[33m\"\u001b[39m\u001b[33mefficiency\u001b[39m\u001b[33m\"\u001b[39m, val=\u001b[32m0.0\u001b[39m, desc=\u001b[33m\"\u001b[39m\u001b[33mAverage efficiency of the electrolyzer\u001b[39m\u001b[33m\"\u001b[39m)\n", - "\u001b[36mFile \u001b[39m\u001b[32m~\\Documents\\git\\H2Integrate\\h2integrate\\core\\utilities.py:159\u001b[39m, in \u001b[36mBaseConfig.from_dict\u001b[39m\u001b[34m(cls, data, strict)\u001b[39m\n\u001b[32m 156\u001b[39m undefined = \u001b[38;5;28msorted\u001b[39m(\u001b[38;5;28mset\u001b[39m(required_inputs) - \u001b[38;5;28mset\u001b[39m(kwargs))\n\u001b[32m 158\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m undefined:\n\u001b[32m--> \u001b[39m\u001b[32m159\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mAttributeError\u001b[39;00m(\n\u001b[32m 160\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mThe class definition for \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mcls\u001b[39m.\u001b[34m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m is missing the following inputs: \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 161\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mundefined\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m 162\u001b[39m )\n\u001b[32m 163\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mcls\u001b[39m(**kwargs)\n", - "\u001b[31mAttributeError\u001b[39m: The class definition for ECOElectrolyzerPerformanceModelConfig is missing the following inputs: ['sizing']" + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", + "Initial SOC = 0.1999\n", + "Initial SOC was set to minimum value.\n", + "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", + "Initial SOC = 0.1998\n", + "Initial SOC was set to minimum value.\n", + "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", + "Initial SOC = 0.1997\n", + "Initial SOC was set to minimum value.\n", + "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", + "Initial SOC = 0.1997\n", + "Initial SOC was set to minimum value.\n", + "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", + "Initial SOC = 0.1999\n", + "Initial SOC was set to minimum value.\n", + "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", + "Initial SOC = 0.1999\n", + "Initial SOC was set to minimum value.\n", + "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", + "Initial SOC = 0.1999\n", + "Initial SOC was set to minimum value.\n", + "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", + "Initial SOC = 0.1999\n", + "Initial SOC was set to minimum value.\n", + "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", + "Initial SOC = 0.1999\n", + "Initial SOC was set to minimum value.\n", + "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", + "Initial SOC = 0.1999\n", + "Initial SOC was set to minimum value.\n", + "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", + "Initial SOC = 0.1999\n", + "Initial SOC was set to minimum value.\n", + "\n", + "HOPP Results\n", + "Hybrid Annual Energy: {\"pv\": 879054490.7804216, \"wind\": 3208874816.2121835, \"battery\": -1921.9792993162905, \"hybrid\": 4087927385.0134234}\n", + "Capacity factors: {\"pv\": 32.613323002698294, \"wind\": 41.25111605028209, \"battery\": 0, \"hybrid\": 39.02516643478516}\n", + "Real LCOE from HOPP: {\"pv\": 4.606676731023486, \"wind\": 3.2778528559987556, \"battery\": 18.22936747872604, \"hybrid\": 3.5877170301555252}\n", + "electrolyzer.electricity_in: 1048680.996768964\n", + "electrolyzer.electrolyzer_size_mw: 640.0\n", + "electrolyzer.hydrogen_capacity_factor: 0.6294601817868071\n", + "ammonia.hydrogen_in: 12543.682462076946\n", + "ammonia.max_hydrogen_capacity: 10589.360138101109\n", + "ammonia.ammonia_capacity_factor: 0.7113477543498785\n", + "finance_subgroup_h2.LCOH: 4.488239219857817\n", + "finance_subgroup_nh3.LCOA: 1.3520762296405033\n" ] } ], @@ -291,7 +554,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "e3468f03", "metadata": {}, "outputs": [ @@ -301,12 +564,12 @@ "text": [ "electrolyzer.electricity_in: 1048680.996768964\n", "electrolyzer.electrolyzer_size_mw: 1080.0\n", - "electrolyzer.hydrogen_capacity_factor: 0.43539128043319353\n", - "ammonia.hydrogen_in: 20499.002679502206\n", + "electrolyzer.hydrogen_capacity_factor: 0.43539128050604664\n", + "ammonia.hydrogen_in: 20499.002679368747\n", "ammonia.max_hydrogen_capacity: 10589.360138101109\n", - "ammonia.ammonia_capacity_factor: 0.7181332658817717\n", - "finance_subgroup_h2.LCOH: 4.797779223591998\n", - "finance_subgroup_nh3.LCOA: 1.5417851112436747\n" + "ammonia.ammonia_capacity_factor: 0.7181332660231103\n", + "finance_subgroup_h2.LCOH: 4.797799731110497\n", + "finance_subgroup_nh3.LCOA: 1.5417899754056714\n" ] } ], @@ -356,27 +619,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "2402eb78", "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "=====\n", - "plant\n", - "=====\n", - "NL: NLBGS Converged in 4 iterations\n", - "electrolyzer.electricity_in: 1048680.996768964\n", - "electrolyzer.electrolyzer_size_mw: 560.0\n", - "electrolyzer.hydrogen_capacity_factor: 0.6722260614534089\n", - "ammonia.hydrogen_in: 10701.566199695133\n", - "ammonia.max_hydrogen_capacity: 10589.360138101109\n", - "ammonia.ammonia_capacity_factor: 0.7076904219955559\n", - "finance_subgroup_h2.LCOH: 4.644401678804404\n", - "finance_subgroup_nh3.LCOA: 1.3047935193440283\n" + "ename": "KeyError", + "evalue": "'h2_pipe_array'", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mKeyError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[8]\u001b[39m\u001b[32m, line 19\u001b[39m\n\u001b[32m 12\u001b[39m plant_config[\u001b[33m\"\u001b[39m\u001b[33mtechnology_interconnections\u001b[39m\u001b[33m\"\u001b[39m] = [\n\u001b[32m 13\u001b[39m [\u001b[33m\"\u001b[39m\u001b[33mhopp\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33melectrolyzer\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33melectricity\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mcable\u001b[39m\u001b[33m\"\u001b[39m],\n\u001b[32m 14\u001b[39m [\u001b[33m\"\u001b[39m\u001b[33melectrolyzer\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mammonia\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mhydrogen\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mh2_pipe_array\u001b[39m\u001b[33m\"\u001b[39m],\n\u001b[32m 15\u001b[39m [\u001b[33m\"\u001b[39m\u001b[33mammonia\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33melectrolyzer\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mmax_hydrogen_capacity\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mdirect\u001b[39m\u001b[33m\"\u001b[39m],\n\u001b[32m 16\u001b[39m ]\n\u001b[32m 17\u001b[39m input_config[\u001b[33m\"\u001b[39m\u001b[33mplant_config\u001b[39m\u001b[33m\"\u001b[39m] = plant_config\n\u001b[32m---> \u001b[39m\u001b[32m19\u001b[39m model = \u001b[43mH2IntegrateModel\u001b[49m\u001b[43m(\u001b[49m\u001b[43minput_config\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 21\u001b[39m \u001b[38;5;66;03m# Run the model\u001b[39;00m\n\u001b[32m 22\u001b[39m model.run()\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/h2integrate/lib/python3.13/site-packages/h2integrate/core/h2integrate_model.py:70\u001b[39m, in \u001b[36mH2IntegrateModel.__init__\u001b[39m\u001b[34m(self, config_input)\u001b[39m\n\u001b[32m 65\u001b[39m \u001b[38;5;28mself\u001b[39m.create_finance_model()\n\u001b[32m 67\u001b[39m \u001b[38;5;66;03m# connect technologies\u001b[39;00m\n\u001b[32m 68\u001b[39m \u001b[38;5;66;03m# technologies are connected within the `technology_interconnections` section of the\u001b[39;00m\n\u001b[32m 69\u001b[39m \u001b[38;5;66;03m# plant config\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m70\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mconnect_technologies\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 72\u001b[39m \u001b[38;5;66;03m# create driver model\u001b[39;00m\n\u001b[32m 73\u001b[39m \u001b[38;5;66;03m# might be an analysis or optimization\u001b[39;00m\n\u001b[32m 74\u001b[39m \u001b[38;5;28mself\u001b[39m.create_driver_model()\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/h2integrate/lib/python3.13/site-packages/h2integrate/core/h2integrate_model.py:891\u001b[39m, in \u001b[36mH2IntegrateModel.connect_technologies\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 889\u001b[39m \u001b[38;5;28;01mpass\u001b[39;00m\n\u001b[32m 890\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m891\u001b[39m connection_component = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43msupported_models\u001b[49m\u001b[43m[\u001b[49m\u001b[43mtransport_type\u001b[49m\u001b[43m]\u001b[49m(\n\u001b[32m 892\u001b[39m transport_item=transport_item\n\u001b[32m 893\u001b[39m )\n\u001b[32m 895\u001b[39m \u001b[38;5;66;03m# Add the connection component to the model\u001b[39;00m\n\u001b[32m 896\u001b[39m \u001b[38;5;28mself\u001b[39m.plant.add_subsystem(connection_name, connection_component)\n", + "\u001b[31mKeyError\u001b[39m: 'h2_pipe_array'" ] } ], @@ -713,7 +970,7 @@ ], "metadata": { "kernelspec": { - "display_name": "hopp", + "display_name": "h2integrate", "language": "python", "name": "python3" }, @@ -727,7 +984,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.13" + "version": "3.13.3" } }, "nbformat": 4, From 9d80ba0ca720c654520b6e1ed0a18af89f32ffa5 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 20 Jan 2026 14:58:20 -0800 Subject: [PATCH 15/22] replace sizing notebook with executable markdown, and get it running --- docs/_toc.yml | 2 +- docs/user_guide/run_size_modes.md | 334 ++++++++++++++++++++++++++++++ 2 files changed, 335 insertions(+), 1 deletion(-) create mode 100644 docs/user_guide/run_size_modes.md diff --git a/docs/_toc.yml b/docs/_toc.yml index 8fdc03d6f..ea5e78c02 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -21,7 +21,7 @@ parts: - file: user_guide/recording_and_loading_data - file: user_guide/how_to_interface_with_user_defined_model - file: user_guide/how_to_run_several_cases_in_sequence - - file: ../examples/25_sizing_modes/run_size_modes + - file: user_guide/run_size_modes - caption: Technology Models chapters: diff --git a/docs/user_guide/run_size_modes.md b/docs/user_guide/run_size_modes.md new file mode 100644 index 000000000..0f6d86c92 --- /dev/null +++ b/docs/user_guide/run_size_modes.md @@ -0,0 +1,334 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.18.1 +kernelspec: + display_name: hopp + language: python + name: python3 +--- + +# Sizing Modes with Resizeable Converters + +When the size of one converter is changed, it may be desirable to have other converters in the plant resized to match. +This can be done manually by setting the sizes of each converter in the `tech_config`, but it can also be done automatically with resizeable converters. +Resizeable converters can execute their own built-in sizing methods based on how much of a feedstock can be produced upstream, or how much of a commodity can be offtaken downstream by other converters. +By connecting the capacities of converters to other converters, one can build a logical re-sizing scheme for a multi-technology plant that will resize all converters by changing just one config parameter. + +## Setting up a resizeable converter + +To set up a resizeable converter, use `ResizeablePerformanceModelBaseConfig` and `ResizeablePerformanceModelBaseClass`. +The `ResizeablePerformanceModelBaseConfig` will declare sizing performance parameters (size_mode, flow_used_for_sizing, max_feedstock_ratio, max_commodity_ratio) within the tech_config. +The `ResizeablePerformanceModelBaseClass` will automatically parse these parameters into the `inputs` and `discrete_inputs` that the performance model will need for resizing. +Here is the start of an example `tech_config` for such a converter: + +```{code-cell} ipython3 +tech_config = { + "model_inputs": { + "shared_parameters": { + "production_capacity": 1000.0, + }, + "performance_parameters": { + "size_mode": "normal", # Always required + "flow_used_for_sizing": "electricity", # Not required in "normal" mode + "max_feedstock_ratio": 1.6, # Only used in "resize_by_max_feedstock" + "max_commodity_ratio": 0.7, # Only used in "resize_by_max_commodity" + }, + } +} +``` + +Currently, there are three different modes defined for `size_mode`: + +- `normal`: In this mode, converters function as they always have previously: + - The size of the asset is fixed within `compute()`. +- `resize_by_max_feedstock`: In this mode, the size of the converter is adjusted to be able to utilize all of the available feedstock: + - The size of the asset should be calculated within `compute()` as a function of the maximum value of `_in` - with the `` specified by the `flow_used_for_sizing` parameter. + - This function will utilizes the `max_feedstock_ratio` parameter - e.g., if `max_feedstock_ratio` is 1.6, the converter will be resized so that its input capacity is 1.6 times the max of `_in`. + - The `set_val` method will over-write any previous sizing variables to reflect the adjusted size of the converter. +- `resize_by_max_commodity`: In this mode, the size of the asset is adjusted to be able to supply its product to the full capacity of another downstream converter: + - The size of the asset should be calculated within `compute()` as a function of the `max__capacity` input - with the `` specified by the `resize by flow` parameter. + - This function will utilizes the `max_commodity_ratio` parameter - e.g., if `max_commodity_ratio` is 0.7, the converter will be resized so that its output capacity is 0.7 times a connected `max__capacity` input. + - The `set_val` method will over-write any previous sizing variables to reflect the adjusted size of the converter. + +To construct a resizeable converter from an existing converter, very few changes must be made, and only to the performance model. +`ResizeablePerformanceModelBaseConfig` can replace `BaseConfig` and `ResizeablePerformanceModelBaseClass` can replace `om.ExplicitComponent`. +The setup function must be modified to include any `max__capacity` outputs or `max__capacity` inputs that can be connected to do the resizing. +Then, any `feedstock_sizing_function` or `feedstock_sizing_function` that the converter needs to resize itself should be defined, if not already. + +```{code-cell} ipython3 +from pathlib import Path + +import numpy as np +from h2integrate.core.utilities import merge_shared_inputs +from h2integrate.core.model_baseclasses import ResizeablePerformanceModelBaseClass, ResizeablePerformanceModelBaseConfig +from h2integrate.core.h2integrate_model import H2IntegrateModel +from h2integrate.core.inputs.validation import load_tech_yaml, load_driver_yaml, load_plant_yaml + + +# Set a root directory for file loading +EXAMPLE_DIR = Path("../../examples/25_sizing_modes").resolve() +``` + +```{code-cell} ipython3 +class TechPerformanceModelConfig(ResizeablePerformanceModelBaseConfig): + # Declare tech-specific config parameters + size: float = 1.0 + + +class TechPerformanceModel(ResizeablePerformanceModelBaseClass): + def setup(self): + self.config = TechPerformanceModelConfig.from_dict( + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + strict=False, + ) + super().setup() + + # Declare tech-specific inputs and outputs + self.add_input("size", val=self.config.size, units="unitless") + # Declare any commodities produced that need to be connected to downstream converters + # if this converter is in `resize_by_max_commodity` mode + self.add_input("max__capacity", val=1000.0, units="kg/h") + # Any feedstocks consumed that need to be connected to upstream converters + # if those converters are in `resize_by_max_commodity` mode + self.add_output("max__capacity", val=1000.0, units="kg/h") + + def feedstock_sizing_function(max_feedstock): + max_feedstock * 0.1231289 # random number for example + + def commodity_sizing_function(max_commodity): + max_commodity * 0.4651 # random number for example + + def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): + size_mode = discrete_inputs["size_mode"] + + # Make changes to computation based on sizing_mode: + if size_mode != "normal": + size = inputs["size"] + if size_mode == "resize_by_max_feedstock": + if inputs["flow_used_for_sizing"] == "": + feed_ratio = inputs["max_feedstock_ratio"] + size_for_max_feed = self.feedstock_sizing_function( + np.max(inputs["_in"]) + ) + size = size_for_max_feed * feed_ratio + elif size_mode == "resize_by_max_commodity": + if inputs["flow_used_for_sizing"] == "": + comm_ratio = inputs["max_commodity_ratio"] + size_for_max_comm = self.commodity_sizing_function( + np.max(inputs["max__capacity"]) + ) + size = size_for_max_comm * comm_ratio + self.set_val("size", size) +``` + +## Example plant setup + +Here, there are three technologies in the the `tech_config.yaml`: + +1. A `hopp` plant producing electricity, +2. An `electrolyzer` producing hydrogen from that electricity, and +3. An `ammonia` plant producing ammonia from that hydrogen. + +The electrolyzer and ammonia technologies are resizeable. For starters, we will set them up in `"normal"` mode + +```{code-cell} ipython3 +# Create a H2Integrate model +driver_config = load_driver_yaml(EXAMPLE_DIR / "driver_config.yaml") +plant_config = load_plant_yaml(EXAMPLE_DIR / "plant_config.yaml") +tech_config = load_tech_yaml(EXAMPLE_DIR / "tech_config.yaml") + +# Replace a relative file in the example with a hard-coded reference for the docs version +fn = tech_config["technologies"]["hopp"]["model_inputs"]["performance_parameters"]["hopp_config"]["site"]["solar_resource_file"][3:] +tech_config["technologies"]["hopp"]["model_inputs"]["performance_parameters"]["hopp_config"]["site"]["solar_resource_file"] = EXAMPLE_DIR.parent / fn + +fn = tech_config["technologies"]["hopp"]["model_inputs"]["performance_parameters"]["hopp_config"]["site"]["wind_resource_file"][3:] +tech_config["technologies"]["hopp"]["model_inputs"]["performance_parameters"]["hopp_config"]["site"]["wind_resource_file"] = EXAMPLE_DIR.parent / fn + +input_config = { + "name": "H2Integrate_config", + "system_summary": "hybrid plant containing ammonia plant and electrolyzer", + "driver_config": driver_config, + "plant_config": plant_config, + "technology_config": tech_config, +} +model = H2IntegrateModel(input_config) + +# Print the value of the size_mode tech_config parameters +for tech in ["electrolyzer", "ammonia"]: + print( + tech + + ": " + + str( + model.technology_config["technologies"][tech]["model_inputs"]["performance_parameters"][ + "size_mode" + ] + ) + ) +``` + +The `technology_interconnections` in the `plant_config` is set up to send electricity from the wind plant to the electrolyzer, then hydrogen from the electrolyzer to the ammonia plant. When set up to run in `resize_by_max_commodity` mode, there will also be an entry to send the `max_hydrogen_capacity` from the ammonia plant to the electrolyzer. Note: this will create a feedback loop within the OpenMDAO problem, which requires an iterative solver. + +```{code-cell} ipython3 +for connection in model.plant_config["technology_interconnections"]: + print(connection) +``` + +When we run this example the electrolyzer is sized to 640 MW (as set by the config), although the electricity profile going in has a max of over 1000 MW. +The LCOH is $4.49/kg H2 and the LCOA is $1.35/kg NH3. + +```{code-cell} ipython3 +# Run the model +model.run() + +# Print selected output +for value in [ + "electrolyzer.electricity_in", + "electrolyzer.electrolyzer_size_mw", + "electrolyzer.hydrogen_capacity_factor", + "ammonia.hydrogen_in", + "ammonia.max_hydrogen_capacity", + "ammonia.ammonia_capacity_factor", + "finance_subgroup_h2.LCOH", + "finance_subgroup_nh3.LCOA", +]: + print(value + ": " + str(np.max(model.prob.get_val(value)))) +``` + +### `resize_by_max_feedstock` mode + +In this case, the electrolyzer will be sized to match the maximum `electricity_in` coming from HOPP. +This increases the electrolyzer size to 1080 MW, the smallest multiple of 40 MW (the cluster size) matching the max HOPP power output of 1048 MW. +This increases the LCOH to $4.80/kg H2, and increases the LCOA to $1.54/kg NH3, since electrolyzer is now oversized to utilize all of the HOPP electricity at peak output but thus has a lower hydrogen production capacity factor. + +```{code-cell} ipython3 +# Create a H2Integrate model, modifying tech_config as necessary +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "size_mode" +] = "resize_by_max_feedstock" +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "flow_used_for_sizing" +] = "electricity" +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "max_feedstock_ratio" +] = 1.0 +input_config["technology_config"] = tech_config +model = H2IntegrateModel(input_config) + +# Run the model +model.run() + +# Print selected output +for value in [ + "electrolyzer.electricity_in", + "electrolyzer.electrolyzer_size_mw", + "electrolyzer.hydrogen_capacity_factor", + "ammonia.hydrogen_in", + "ammonia.max_hydrogen_capacity", + "ammonia.ammonia_capacity_factor", + "finance_subgroup_h2.LCOH", + "finance_subgroup_nh3.LCOA", +]: + print(value + ": " + str(np.max(model.prob.get_val(value)))) +``` + +### `resize_by_max_product` mode + +In this case, the electrolyzer will be sized to match the maximum hydrogen capacity of the ammonia plant. +This requires the `technology_interconnections` entry to send the `max_hydrogen_capacity` from the ammonia plant to the electrolyzer. +This decreases the electrolyzer size to 560 MW, the closest multiple of 40 MW (the cluster size) that will ensure an h2 production capacity that matches the ammonia plant's h2 intake at its max ammonia production capacity. +This increases the LCOH to $4.64/kg H2, but reduces the LCOA to $1.30/kg NH3, since electrolyzer size was matched to ammonia production but not HOPP. + +```{code-cell} ipython3 +# Create a H2Integrate model, modifying tech_config and plant_config as necessary +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "size_mode" +] = "resize_by_max_commodity" +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "flow_used_for_sizing" +] = "hydrogen" +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "max_commodity_ratio" +] = 1.0 +input_config["technology_config"] = tech_config +plant_config["technology_interconnections"] = [ + ["hopp", "electrolyzer", "electricity", "cable"], + ["electrolyzer", "ammonia", "hydrogen", "pipe"], + ["ammonia", "electrolyzer", "max_hydrogen_capacity"], +] +input_config["plant_config"] = plant_config + +model = H2IntegrateModel(input_config) + +# Run the model +model.run() + +# Print selected output +for value in [ + "electrolyzer.electricity_in", + "electrolyzer.electrolyzer_size_mw", + "electrolyzer.hydrogen_capacity_factor", + "ammonia.hydrogen_in", + "ammonia.max_hydrogen_capacity", + "ammonia.ammonia_capacity_factor", + "finance_subgroup_h2.LCOH", + "finance_subgroup_nh3.LCOA", +]: + print(value + ": " + str(np.max(model.prob.get_val(value)))) +``` + +## Using optimizer with multiple connections + +With both `electrolyzer` and `ammonia` in `size_by_max_feedstock` mode, the COBYLA optimizer can co-optimize the `max_feedstock_ratio` variables to minimize LCOA to $1.20/kg. This is achieved at a capacity factor of approximately 55% in both the electrolyzer and the ammonia plant. + +```{code-cell} ipython3 +# Create a H2Integrate model, modifying tech_config and driver_config as necessary +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "size_mode" +] = "resize_by_max_feedstock" +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "flow_used_for_sizing" +] = "electricity" +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "max_feedstock_ratio" +] = 1.0 +tech_config["technologies"]["ammonia"]["model_inputs"]["performance_parameters"]["size_mode"] = ( + "resize_by_max_feedstock" +) +tech_config["technologies"]["ammonia"]["model_inputs"]["performance_parameters"][ + "flow_used_for_sizing" +] = "hydrogen" +tech_config["technologies"]["ammonia"]["model_inputs"]["performance_parameters"][ + "max_feedstock_ratio" +] = 1.0 +input_config["technology_config"] = tech_config +plant_config["technology_interconnections"] = [ + ["hopp", "electrolyzer", "electricity", "cable"], + ["electrolyzer", "ammonia", "hydrogen", "pipe"], +] +input_config["plant_config"] = plant_config +driver_config["driver"]["optimization"]["flag"] = True +driver_config["design_variables"]["electrolyzer"]["max_feedstock_ratio"]["flag"] = True +driver_config["design_variables"]["ammonia"]["max_feedstock_ratio"]["flag"] = True +input_config["driver_config"] = driver_config +model = H2IntegrateModel(input_config) + +# Run the model +model.run() + +# Print selected outputs +for value in [ + "electrolyzer.electricity_in", + "electrolyzer.electrolyzer_size_mw", + "electrolyzer.hydrogen_capacity_factor", + "ammonia.hydrogen_in", + "ammonia.max_hydrogen_capacity", + "ammonia.ammonia_capacity_factor", + "finance_subgroup_h2.LCOH", + "finance_subgroup_nh3.LCOA", +]: + print(value + ": " + str(np.max(model.prob.get_val(value)))) +``` From 6ab26f5526546b2bf8e66c065a618a5d817c7a4b Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:18:32 -0800 Subject: [PATCH 16/22] add sphinx flag to ensure failed executable cells cause a build error --- docs/_config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_config.yml b/docs/_config.yml index b9e506cec..1c1b47129 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -92,3 +92,4 @@ sphinx: napoleon_use_admonition_for_notes: true napoleon_use_rtype: false nb_merge_streams: true + nb_execution_raise_on_error: true From eed568b5da8d5925d2f3699d00c6dab19dee8036 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:19:53 -0800 Subject: [PATCH 17/22] remove cases.sql parent directory output data --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1e0cf19a7..2bd02cedf 100644 --- a/.gitignore +++ b/.gitignore @@ -127,6 +127,7 @@ examples/h2integrate/*/data examples/h2integrate/*/figures examples/*/*_new.* examples/*/*[0-9].* +*wind_electrolyzer/ tests/h2integrate/reports/ tests/h2integrate/test_hydrogen/output/ **/run_*_out/ From 43be3592469e1c13ae08dd19b860e0bf8b3d65c2 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:30:34 -0800 Subject: [PATCH 18/22] convert notebook to script, stripping much of the narrative --- examples/25_sizing_modes/run_size_modes.ipynb | 992 ------------------ examples/25_sizing_modes/run_size_modes.py | 208 ++++ 2 files changed, 208 insertions(+), 992 deletions(-) delete mode 100644 examples/25_sizing_modes/run_size_modes.ipynb create mode 100644 examples/25_sizing_modes/run_size_modes.py diff --git a/examples/25_sizing_modes/run_size_modes.ipynb b/examples/25_sizing_modes/run_size_modes.ipynb deleted file mode 100644 index 72a859400..000000000 --- a/examples/25_sizing_modes/run_size_modes.ipynb +++ /dev/null @@ -1,992 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "ee08c4f9", - "metadata": {}, - "source": [ - "# Sizing Modes with Resizeable Converters\n", - "\n", - "When the size of one converter is changed, it may be desirable to have other converters in the plant resized to match.\n", - "This can be done manually by setting the sizes of each converter in the `tech_config`, but it can also be done automatically with resizeable converters.\n", - "Resizeable converters can execute their own built-in sizing methods based on how much of a feedstock can be produced upstream, or how much of a commodity can be offtaken downstream by other converters.\n", - "By connecting the capacities of converters to other converters, one can build a logical re-sizing scheme for a multi-technology plant that will resize all converters by changing just one config parameter.\n", - "\n", - "## Setting up a resizeable converter\n", - "\n", - "To set up a resizeable converter, use `ResizeablePerformanceModelBaseConfig` and `ResizeablePerformanceModelBaseClass`.\n", - "The `ResizeablePerformanceModelBaseConfig` will declare sizing performance parameters (size_mode, flow_used_for_sizing, max_feedstock_ratio, max_commodity_ratio) within the tech_config.\n", - "The `ResizeablePerformanceModelBaseClass` will automatically parse these parameters into the `inputs` and `discrete_inputs` that the performance model will need for resizing.\n", - "Here is the start of an example `tech_config` for such a converter:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "67b32b84", - "metadata": {}, - "outputs": [], - "source": [ - "tech_config = {\n", - " \"model_inputs\": {\n", - " \"shared_parameters\": {\n", - " \"production_capacity\": 1000.0,\n", - " },\n", - " \"performance_parameters\": {\n", - " \"size_mode\": \"normal\", # Always required\n", - " \"flow_used_for_sizing\": \"electricity\", # Not required in \"normal\" mode\n", - " \"max_feedstock_ratio\": 1.6, # Only used in \"resize_by_max_feedstock\"\n", - " \"max_commodity_ratio\": 0.7, # Only used in \"resize_by_max_commodity\"\n", - " },\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "0d33c32e", - "metadata": {}, - "source": [ - "Currently, there are three different modes defined for `size_mode`:\n", - "\n", - "- `normal`: In this mode, converters function as they always have previously:\n", - " - The size of the asset is fixed within `compute()`.\n", - "- `resize_by_max_feedstock`: In this mode, the size of the converter is adjusted to be able to utilize all of the available feedstock:\n", - " - The size of the asset should be calculated within `compute()` as a function of the maximum value of `_in` - with the `` specified by the `flow_used_for_sizing` parameter.\n", - " - This function will utilizes the `max_feedstock_ratio` parameter - e.g., if `max_feedstock_ratio` is 1.6, the converter will be resized so that its input capacity is 1.6 times the max of `_in`.\n", - " - The `set_val` method will over-write any previous sizing variables to reflect the adjusted size of the converter.\n", - "- `resize_by_max_commodity`: In this mode, the size of the asset is adjusted to be able to supply its product to the full capacity of another downstream converter:\n", - " - The size of the asset should be calculated within `compute()` as a function of the `max__capacity` input - with the `` specified by the `resize by flow` parameter.\n", - " - This function will utilizes the `max_commodity_ratio` parameter - e.g., if `max_commodity_ratio` is 0.7, the converter will be resized so that its output capacity is 0.7 times a connected `max__capacity` input.\n", - " - The `set_val` method will over-write any previous sizing variables to reflect the adjusted size of the converter.\n", - " \n", - "To construct a resizeable converter from an existing converter, very few changes must be made, and only to the performance model.\n", - "`ResizeablePerformanceModelBaseConfig` can replace `BaseConfig` and `ResizeablePerformanceModelBaseClass` can replace `om.ExplicitComponent`.\n", - "The setup function must be modified to include any `max__capacity` outputs or `max__capacity` inputs that can be connected to do the resizing. \n", - "Then, any `feedstock_sizing_function` or `feedstock_sizing_function` that the converter needs to resize itself should be defined, if not already." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "6bca0246", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "from h2integrate.core.utilities import merge_shared_inputs\n", - "from h2integrate.core.model_baseclasses import (\n", - " ResizeablePerformanceModelBaseClass,\n", - " ResizeablePerformanceModelBaseConfig,\n", - ")\n", - "from h2integrate.core.h2integrate_model import H2IntegrateModel\n", - "from h2integrate.core.inputs.validation import load_tech_yaml, load_driver_yaml, load_plant_yaml\n", - "\n", - "\n", - "class TechPerformanceModelConfig(ResizeablePerformanceModelBaseConfig):\n", - " # Declare tech-specific config parameters\n", - " size: float = 1.0\n", - "\n", - "\n", - "class TechPerformanceModel(ResizeablePerformanceModelBaseClass):\n", - " def setup(self):\n", - " self.config = TechPerformanceModelConfig.from_dict(\n", - " merge_shared_inputs(self.options[\"tech_config\"][\"model_inputs\"], \"performance\"),\n", - " strict=False,\n", - " )\n", - " super().setup()\n", - "\n", - " # Declare tech-specific inputs and outputs\n", - " self.add_input(\"size\", val=self.config.size, units=\"unitless\")\n", - " # Declare any commodities produced that need to be connected to downstream converters\n", - " # if this converter is in `resize_by_max_commodity` mode\n", - " self.add_input(\"max__capacity\", val=1000.0, units=\"kg/h\")\n", - " # Any feedstocks consumed that need to be connected to upstream converters\n", - " # if those converters are in `resize_by_max_commodity` mode\n", - " self.add_output(\"max__capacity\", val=1000.0, units=\"kg/h\")\n", - "\n", - " def feedstock_sizing_function(max_feedstock):\n", - " max_feedstock * 0.1231289 # random number for example\n", - "\n", - " def commodity_sizing_function(max_commodity):\n", - " max_commodity * 0.4651 # random number for example\n", - "\n", - " def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):\n", - " size_mode = discrete_inputs[\"size_mode\"]\n", - "\n", - " # Make changes to computation based on sizing_mode:\n", - " if size_mode != \"normal\":\n", - " size = inputs[\"size\"]\n", - " if size_mode == \"resize_by_max_feedstock\":\n", - " if inputs[\"flow_used_for_sizing\"] == \"\":\n", - " feed_ratio = inputs[\"max_feedstock_ratio\"]\n", - " size_for_max_feed = self.feedstock_sizing_function(\n", - " np.max(inputs[\"_in\"])\n", - " )\n", - " size = size_for_max_feed * feed_ratio\n", - " elif size_mode == \"resize_by_max_commodity\":\n", - " if inputs[\"flow_used_for_sizing\"] == \"\":\n", - " comm_ratio = inputs[\"max_commodity_ratio\"]\n", - " size_for_max_comm = self.commodity_sizing_function(\n", - " np.max(inputs[\"max__capacity\"])\n", - " )\n", - " size = size_for_max_comm * comm_ratio\n", - " self.set_val(\"size\", size)" - ] - }, - { - "cell_type": "markdown", - "id": "665e9607", - "metadata": {}, - "source": [ - "\n", - "## Example plant setup\n", - "\n", - "Here, there are three technologies in the the `tech_config.yaml`:\n", - "1. A `hopp` plant producing electricity,\n", - "2. An `electrolyzer` producing hydrogen from that electricity, and\n", - "3. An `ammonia` plant producing ammonia from that hydrogen.\n", - "\n", - "The electrolyzer and ammonia technologies are resizeable. For starters, we will set them up in `\"normal\"` mode\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "a79029a8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "electrolyzer: normal\n", - "ammonia: normal\n" - ] - } - ], - "source": [ - "# Create a H2Integrate model\n", - "driver_config = load_driver_yaml(\"driver_config.yaml\")\n", - "plant_config = load_plant_yaml(\"plant_config.yaml\")\n", - "tech_config = load_tech_yaml(\"tech_config.yaml\")\n", - "input_config = {\n", - " \"name\": \"H2Integrate_config\",\n", - " \"system_summary\": \"hybrid plant containing ammonia plant and electrolyzer\",\n", - " \"driver_config\": driver_config,\n", - " \"plant_config\": plant_config,\n", - " \"technology_config\": tech_config,\n", - "}\n", - "model = H2IntegrateModel(input_config)\n", - "\n", - "# Print the value of the size_mode tech_config parameters\n", - "for tech in [\"electrolyzer\", \"ammonia\"]:\n", - " print(\n", - " tech\n", - " + \": \"\n", - " + str(\n", - " model.technology_config[\"technologies\"][tech][\"model_inputs\"][\"performance_parameters\"][\n", - " \"size_mode\"\n", - " ]\n", - " )\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "97783113", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'site': {'data': {'lat': 32.31714,\n", - " 'lon': -100.18,\n", - " 'elev': 762,\n", - " 'year': 2013,\n", - " 'tz': -6,\n", - " 'site_boundaries': {'verts': [[0.0, 0.0],\n", - " [0.0, 20000.0],\n", - " [20000.0, 20000.0],\n", - " [20000.0, 0.0]],\n", - " 'verts_simple': [[0.0, 0.0],\n", - " [0.0, 20000.0],\n", - " [20000.0, 20000.0],\n", - " [20000.0, 0.0]]}},\n", - " 'solar_resource_file': '../02_texas_ammonia/tech_inputs/weather/solar/32.31714_-100.18_psmv3_60_2013.csv',\n", - " 'wind_resource_file': '../02_texas_ammonia/tech_inputs/weather/wind/32.31714_-100.18_windtoolkit_2013_60min_100m_120m.srw',\n", - " 'wave_resource_file': '',\n", - " 'grid_resource_file': '',\n", - " 'hub_height': 115.0,\n", - " 'capacity_hours': [],\n", - " 'solar': True,\n", - " 'wind': True,\n", - " 'wave': False,\n", - " 'wind_resource_origin': 'WTK'},\n", - " 'technologies': {'wind': {'num_turbines': 148,\n", - " 'turbine_rating_kw': 6000.0,\n", - " 'operational_losses': 10.49,\n", - " 'model_name': 'pysam',\n", - " 'timestep': [0, 8760],\n", - " 'fin_model': {'battery_system': {'batt_replacement_schedule_percent': [0],\n", - " 'batt_bank_replacement': [0],\n", - " 'batt_replacement_option': 0,\n", - " 'batt_computed_bank_capacity': 0,\n", - " 'batt_meter_position': 0},\n", - " 'system_costs': {'om_fixed': [0],\n", - " 'om_production': [0],\n", - " 'om_capacity': [0],\n", - " 'om_batt_fixed_cost': 0,\n", - " 'om_batt_variable_cost': [0],\n", - " 'om_batt_capacity_cost': 0,\n", - " 'om_batt_replacement_cost': 0,\n", - " 'om_replacement_cost_escal': 0},\n", - " 'system_use_lifetime_output': 0,\n", - " 'financial_parameters': {'inflation_rate': 0.0,\n", - " 'real_discount_rate': 6.3,\n", - " 'federal_tax_rate': 21.0,\n", - " 'state_tax_rate': 4.74,\n", - " 'property_tax_rate': 1.47,\n", - " 'insurance_rate': 1.0,\n", - " 'debt_percent': 72.4,\n", - " 'term_int_rate': 4.4,\n", - " 'months_working_reserve': 1,\n", - " 'analysis_start_year': 2030,\n", - " 'installation_months': 36,\n", - " 'sales_tax_rate_state': 0.0,\n", - " 'admin_expense_percent_of_sales': 0.0,\n", - " 'capital_gains_tax_rate': 15.0,\n", - " 'debt_type': 'Revolving debt',\n", - " 'depreciation_method': 'MACRS',\n", - " 'depreciation_period': 5},\n", - " 'cp_capacity_credit_percent': [0],\n", - " 'degradation': [0],\n", - " 'revenue': {'ppa_price_input': [0.0], 'ppa_escalation': 0}}},\n", - " 'pv': {'system_capacity_kw': 400000,\n", - " 'dc_degradation': [0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0],\n", - " 'fin_model': {'battery_system': {'batt_replacement_schedule_percent': [0],\n", - " 'batt_bank_replacement': [0],\n", - " 'batt_replacement_option': 0,\n", - " 'batt_computed_bank_capacity': 0,\n", - " 'batt_meter_position': 0},\n", - " 'system_costs': {'om_fixed': [0],\n", - " 'om_production': [0],\n", - " 'om_capacity': [0],\n", - " 'om_batt_fixed_cost': 0,\n", - " 'om_batt_variable_cost': [0],\n", - " 'om_batt_capacity_cost': 0,\n", - " 'om_batt_replacement_cost': 0,\n", - " 'om_replacement_cost_escal': 0},\n", - " 'system_use_lifetime_output': 0,\n", - " 'financial_parameters': {'inflation_rate': 0.0,\n", - " 'real_discount_rate': 5.9,\n", - " 'federal_tax_rate': 21.0,\n", - " 'state_tax_rate': 4.74,\n", - " 'property_tax_rate': 1.47,\n", - " 'insurance_rate': 1.0,\n", - " 'debt_percent': 75.3,\n", - " 'term_int_rate': 4.4,\n", - " 'months_working_reserve': 1,\n", - " 'analysis_start_year': 2030,\n", - " 'installation_months': 36,\n", - " 'sales_tax_rate_state': 0.0,\n", - " 'admin_expense_percent_of_sales': 0.0,\n", - " 'capital_gains_tax_rate': 15.0,\n", - " 'debt_type': 'Revolving debt',\n", - " 'depreciation_method': 'MACRS',\n", - " 'depreciation_period': 5},\n", - " 'cp_capacity_credit_percent': [0],\n", - " 'degradation': [0],\n", - " 'revenue': {'ppa_price_input': [0.0], 'ppa_escalation': 0}}},\n", - " 'battery': {'system_capacity_kwh': 96,\n", - " 'system_capacity_kw': 96,\n", - " 'minimum_SOC': 20.0,\n", - " 'maximum_SOC': 100.0,\n", - " 'initial_SOC': 90.0,\n", - " 'fin_model': {'battery_system': {'batt_replacement_schedule_percent': [0],\n", - " 'batt_bank_replacement': [0],\n", - " 'batt_replacement_option': 0,\n", - " 'batt_computed_bank_capacity': 0,\n", - " 'batt_meter_position': 0},\n", - " 'system_costs': {'om_fixed': [0],\n", - " 'om_production': [0],\n", - " 'om_capacity': [0],\n", - " 'om_batt_fixed_cost': 0,\n", - " 'om_batt_variable_cost': [0],\n", - " 'om_batt_capacity_cost': 0,\n", - " 'om_batt_replacement_cost': 0,\n", - " 'om_replacement_cost_escal': 0},\n", - " 'system_use_lifetime_output': 0,\n", - " 'financial_parameters': {'inflation_rate': 0.0,\n", - " 'real_discount_rate': 6.6,\n", - " 'federal_tax_rate': 21.0,\n", - " 'state_tax_rate': 4.74,\n", - " 'property_tax_rate': 1.47,\n", - " 'insurance_rate': 1.0,\n", - " 'debt_percent': 75.4,\n", - " 'term_int_rate': 4.4,\n", - " 'months_working_reserve': 1,\n", - " 'analysis_start_year': 2030,\n", - " 'installation_months': 36,\n", - " 'sales_tax_rate_state': 0.0,\n", - " 'admin_expense_percent_of_sales': 0.0,\n", - " 'capital_gains_tax_rate': 15.0,\n", - " 'debt_type': 'Revolving debt',\n", - " 'depreciation_method': 'MACRS',\n", - " 'depreciation_period': 5},\n", - " 'cp_capacity_credit_percent': [0],\n", - " 'degradation': [0],\n", - " 'revenue': {'ppa_price_input': [0.0], 'ppa_escalation': 0}}},\n", - " 'grid': {'interconnect_kw': 2000000,\n", - " 'fin_model': {'battery_system': {'batt_replacement_schedule_percent': [0],\n", - " 'batt_bank_replacement': [0],\n", - " 'batt_replacement_option': 0,\n", - " 'batt_computed_bank_capacity': 0,\n", - " 'batt_meter_position': 0},\n", - " 'system_costs': {'om_fixed': [0],\n", - " 'om_production': [0],\n", - " 'om_capacity': [0],\n", - " 'om_batt_fixed_cost': 0,\n", - " 'om_batt_variable_cost': [0],\n", - " 'om_batt_capacity_cost': 0,\n", - " 'om_batt_replacement_cost': 0,\n", - " 'om_replacement_cost_escal': 0},\n", - " 'system_use_lifetime_output': 0,\n", - " 'financial_parameters': {'inflation_rate': 0.0,\n", - " 'real_discount_rate': 6.3,\n", - " 'federal_tax_rate': 21.0,\n", - " 'state_tax_rate': 4.74,\n", - " 'property_tax_rate': 1.47,\n", - " 'insurance_rate': 1.0,\n", - " 'debt_percent': 72.4,\n", - " 'term_int_rate': 4.4,\n", - " 'months_working_reserve': 1,\n", - " 'analysis_start_year': 2030,\n", - " 'installation_months': 36,\n", - " 'sales_tax_rate_state': 0.0,\n", - " 'admin_expense_percent_of_sales': 0.0,\n", - " 'capital_gains_tax_rate': 15.0,\n", - " 'debt_type': 'Revolving debt',\n", - " 'depreciation_method': 'MACRS',\n", - " 'depreciation_period': 5},\n", - " 'cp_capacity_credit_percent': [0],\n", - " 'degradation': [0],\n", - " 'revenue': {'ppa_price_input': [0.0], 'ppa_escalation': 0}}}},\n", - " 'config': {'simulation_options': {'cache': True,\n", - " 'wind': {'skip_financial': False}},\n", - " 'dispatch_options': {'battery_dispatch': 'load_following_heuristic',\n", - " 'solver': 'cbc',\n", - " 'n_look_ahead_periods': 48,\n", - " 'grid_charging': False,\n", - " 'pv_charging_only': False,\n", - " 'include_lifecycle_count': False},\n", - " 'cost_info': {'wind_installed_cost_mw': 1380000,\n", - " 'solar_installed_cost_mw': 1323000,\n", - " 'storage_installed_cost_mwh': 310000,\n", - " 'storage_installed_cost_mw': 311000,\n", - " 'wind_om_per_kw': 29,\n", - " 'pv_om_per_kw': 18,\n", - " 'battery_om_per_kw': 15.525}}}" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "tech_config[\"technologies\"][\"hopp\"][\"model_inputs\"][\"performance_parameters\"][\"hopp_config\"]" - ] - }, - { - "cell_type": "markdown", - "id": "5b88d3b1", - "metadata": {}, - "source": [ - "\n", - "\n", - "The `technology_interconnections` in the `plant_config` is set up to send electricity from the wind plant to the electrolyzer, then hydrogen from the electrolyzer to the ammonia plant. When set up to run in `resize_by_max_commodity` mode, there will also be an entry to send the `max_hydrogen_capacity` from the ammonia plant to the electrolyzer. Note: this will create a feedback loop within the OpenMDAO problem, which requires an iterative solver. \n" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "8ddb8b90", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['hopp', 'electrolyzer', 'electricity', 'cable']\n", - "['electrolyzer', 'ammonia', 'hydrogen', 'pipe']\n" - ] - } - ], - "source": [ - "for connection in model.plant_config[\"technology_interconnections\"]:\n", - " print(connection)" - ] - }, - { - "cell_type": "markdown", - "id": "6722450b", - "metadata": {}, - "source": [ - "When we run this example the electrolyzer is sized to 640 MW (as set by the config), although the electricity profile going in has a max of over 1000 MW.\n", - "The LCOH is $4.49/kg H2 and the LCOA is $1.35/kg NH3." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "f22dd04e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", - "Initial SOC = 0.1999\n", - "Initial SOC was set to minimum value.\n", - "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", - "Initial SOC = 0.1998\n", - "Initial SOC was set to minimum value.\n", - "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", - "Initial SOC = 0.1997\n", - "Initial SOC was set to minimum value.\n", - "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", - "Initial SOC = 0.1997\n", - "Initial SOC was set to minimum value.\n", - "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", - "Initial SOC = 0.1999\n", - "Initial SOC was set to minimum value.\n", - "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", - "Initial SOC = 0.1999\n", - "Initial SOC was set to minimum value.\n", - "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", - "Initial SOC = 0.1999\n", - "Initial SOC was set to minimum value.\n", - "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", - "Initial SOC = 0.1999\n", - "Initial SOC was set to minimum value.\n", - "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", - "Initial SOC = 0.1999\n", - "Initial SOC was set to minimum value.\n", - "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", - "Initial SOC = 0.1999\n", - "Initial SOC was set to minimum value.\n", - "Warning: Storage dispatch was initialized with a state-of-charge less than minimum value!\n", - "Initial SOC = 0.1999\n", - "Initial SOC was set to minimum value.\n", - "\n", - "HOPP Results\n", - "Hybrid Annual Energy: {\"pv\": 879054490.7804216, \"wind\": 3208874816.2121835, \"battery\": -1921.9792993162905, \"hybrid\": 4087927385.0134234}\n", - "Capacity factors: {\"pv\": 32.613323002698294, \"wind\": 41.25111605028209, \"battery\": 0, \"hybrid\": 39.02516643478516}\n", - "Real LCOE from HOPP: {\"pv\": 4.606676731023486, \"wind\": 3.2778528559987556, \"battery\": 18.22936747872604, \"hybrid\": 3.5877170301555252}\n", - "electrolyzer.electricity_in: 1048680.996768964\n", - "electrolyzer.electrolyzer_size_mw: 640.0\n", - "electrolyzer.hydrogen_capacity_factor: 0.6294601817868071\n", - "ammonia.hydrogen_in: 12543.682462076946\n", - "ammonia.max_hydrogen_capacity: 10589.360138101109\n", - "ammonia.ammonia_capacity_factor: 0.7113477543498785\n", - "finance_subgroup_h2.LCOH: 4.488239219857817\n", - "finance_subgroup_nh3.LCOA: 1.3520762296405033\n" - ] - } - ], - "source": [ - "# Run the model\n", - "model.run()\n", - "\n", - "# Print selected output\n", - "for value in [\n", - " \"electrolyzer.electricity_in\",\n", - " \"electrolyzer.electrolyzer_size_mw\",\n", - " \"electrolyzer.hydrogen_capacity_factor\",\n", - " \"ammonia.hydrogen_in\",\n", - " \"ammonia.max_hydrogen_capacity\",\n", - " \"ammonia.ammonia_capacity_factor\",\n", - " \"finance_subgroup_h2.LCOH\",\n", - " \"finance_subgroup_nh3.LCOA\",\n", - "]:\n", - " print(value + \": \" + str(np.max(model.prob.get_val(value))))" - ] - }, - { - "cell_type": "markdown", - "id": "8b99e658", - "metadata": {}, - "source": [ - "### `resize_by_max_feedstock` mode\n", - "\n", - "In this case, the electrolyzer will be sized to match the maximum `electricity_in` coming from HOPP.\n", - "This increases the electrolyzer size to 1080 MW, the smallest multiple of 40 MW (the cluster size) matching the max HOPP power output of 1048 MW.\n", - "This increases the LCOH to $4.80/kg H2, and increases the LCOA to $1.54/kg NH3, since electrolyzer is now oversized to utilize all of the HOPP electricity at peak output but thus has a lower hydrogen production capacity factor." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "e3468f03", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "electrolyzer.electricity_in: 1048680.996768964\n", - "electrolyzer.electrolyzer_size_mw: 1080.0\n", - "electrolyzer.hydrogen_capacity_factor: 0.43539128050604664\n", - "ammonia.hydrogen_in: 20499.002679368747\n", - "ammonia.max_hydrogen_capacity: 10589.360138101109\n", - "ammonia.ammonia_capacity_factor: 0.7181332660231103\n", - "finance_subgroup_h2.LCOH: 4.797799731110497\n", - "finance_subgroup_nh3.LCOA: 1.5417899754056714\n" - ] - } - ], - "source": [ - "# Create a H2Integrate model, modifying tech_config as necessary\n", - "tech_config[\"technologies\"][\"electrolyzer\"][\"model_inputs\"][\"performance_parameters\"][\n", - " \"size_mode\"\n", - "] = \"resize_by_max_feedstock\"\n", - "tech_config[\"technologies\"][\"electrolyzer\"][\"model_inputs\"][\"performance_parameters\"][\n", - " \"flow_used_for_sizing\"\n", - "] = \"electricity\"\n", - "tech_config[\"technologies\"][\"electrolyzer\"][\"model_inputs\"][\"performance_parameters\"][\n", - " \"max_feedstock_ratio\"\n", - "] = 1.0\n", - "input_config[\"technology_config\"] = tech_config\n", - "model = H2IntegrateModel(input_config)\n", - "\n", - "# Run the model\n", - "model.run()\n", - "\n", - "# Print selected output\n", - "for value in [\n", - " \"electrolyzer.electricity_in\",\n", - " \"electrolyzer.electrolyzer_size_mw\",\n", - " \"electrolyzer.hydrogen_capacity_factor\",\n", - " \"ammonia.hydrogen_in\",\n", - " \"ammonia.max_hydrogen_capacity\",\n", - " \"ammonia.ammonia_capacity_factor\",\n", - " \"finance_subgroup_h2.LCOH\",\n", - " \"finance_subgroup_nh3.LCOA\",\n", - "]:\n", - " print(value + \": \" + str(np.max(model.prob.get_val(value))))" - ] - }, - { - "cell_type": "markdown", - "id": "4b8dc110", - "metadata": {}, - "source": [ - "### `resize_by_max_product` mode\n", - "\n", - "In this case, the electrolyzer will be sized to match the maximum hydrogen capacity of the ammonia plant. \n", - "This requires the `technology_interconnections` entry to send the `max_hydrogen_capacity` from the ammonia plant to the electrolyzer.\n", - "This decreases the electrolyzer size to 560 MW, the closest multiple of 40 MW (the cluster size) that will ensure an h2 production capacity that matches the ammonia plant's h2 intake at its max ammonia production capacity.\n", - "This increases the LCOH to $4.64/kg H2, but reduces the LCOA to $1.30/kg NH3, since electrolyzer size was matched to ammonia production but not HOPP." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "2402eb78", - "metadata": {}, - "outputs": [ - { - "ename": "KeyError", - "evalue": "'h2_pipe_array'", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mKeyError\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[8]\u001b[39m\u001b[32m, line 19\u001b[39m\n\u001b[32m 12\u001b[39m plant_config[\u001b[33m\"\u001b[39m\u001b[33mtechnology_interconnections\u001b[39m\u001b[33m\"\u001b[39m] = [\n\u001b[32m 13\u001b[39m [\u001b[33m\"\u001b[39m\u001b[33mhopp\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33melectrolyzer\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33melectricity\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mcable\u001b[39m\u001b[33m\"\u001b[39m],\n\u001b[32m 14\u001b[39m [\u001b[33m\"\u001b[39m\u001b[33melectrolyzer\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mammonia\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mhydrogen\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mh2_pipe_array\u001b[39m\u001b[33m\"\u001b[39m],\n\u001b[32m 15\u001b[39m [\u001b[33m\"\u001b[39m\u001b[33mammonia\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33melectrolyzer\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mmax_hydrogen_capacity\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mdirect\u001b[39m\u001b[33m\"\u001b[39m],\n\u001b[32m 16\u001b[39m ]\n\u001b[32m 17\u001b[39m input_config[\u001b[33m\"\u001b[39m\u001b[33mplant_config\u001b[39m\u001b[33m\"\u001b[39m] = plant_config\n\u001b[32m---> \u001b[39m\u001b[32m19\u001b[39m model = \u001b[43mH2IntegrateModel\u001b[49m\u001b[43m(\u001b[49m\u001b[43minput_config\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 21\u001b[39m \u001b[38;5;66;03m# Run the model\u001b[39;00m\n\u001b[32m 22\u001b[39m model.run()\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/h2integrate/lib/python3.13/site-packages/h2integrate/core/h2integrate_model.py:70\u001b[39m, in \u001b[36mH2IntegrateModel.__init__\u001b[39m\u001b[34m(self, config_input)\u001b[39m\n\u001b[32m 65\u001b[39m \u001b[38;5;28mself\u001b[39m.create_finance_model()\n\u001b[32m 67\u001b[39m \u001b[38;5;66;03m# connect technologies\u001b[39;00m\n\u001b[32m 68\u001b[39m \u001b[38;5;66;03m# technologies are connected within the `technology_interconnections` section of the\u001b[39;00m\n\u001b[32m 69\u001b[39m \u001b[38;5;66;03m# plant config\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m70\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mconnect_technologies\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 72\u001b[39m \u001b[38;5;66;03m# create driver model\u001b[39;00m\n\u001b[32m 73\u001b[39m \u001b[38;5;66;03m# might be an analysis or optimization\u001b[39;00m\n\u001b[32m 74\u001b[39m \u001b[38;5;28mself\u001b[39m.create_driver_model()\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/h2integrate/lib/python3.13/site-packages/h2integrate/core/h2integrate_model.py:891\u001b[39m, in \u001b[36mH2IntegrateModel.connect_technologies\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 889\u001b[39m \u001b[38;5;28;01mpass\u001b[39;00m\n\u001b[32m 890\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m891\u001b[39m connection_component = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43msupported_models\u001b[49m\u001b[43m[\u001b[49m\u001b[43mtransport_type\u001b[49m\u001b[43m]\u001b[49m(\n\u001b[32m 892\u001b[39m transport_item=transport_item\n\u001b[32m 893\u001b[39m )\n\u001b[32m 895\u001b[39m \u001b[38;5;66;03m# Add the connection component to the model\u001b[39;00m\n\u001b[32m 896\u001b[39m \u001b[38;5;28mself\u001b[39m.plant.add_subsystem(connection_name, connection_component)\n", - "\u001b[31mKeyError\u001b[39m: 'h2_pipe_array'" - ] - } - ], - "source": [ - "# Create a H2Integrate model, modifying tech_config and plant_config as necessary\n", - "tech_config[\"technologies\"][\"electrolyzer\"][\"model_inputs\"][\"performance_parameters\"][\n", - " \"size_mode\"\n", - "] = \"resize_by_max_commodity\"\n", - "tech_config[\"technologies\"][\"electrolyzer\"][\"model_inputs\"][\"performance_parameters\"][\n", - " \"flow_used_for_sizing\"\n", - "] = \"hydrogen\"\n", - "tech_config[\"technologies\"][\"electrolyzer\"][\"model_inputs\"][\"performance_parameters\"][\n", - " \"max_commodity_ratio\"\n", - "] = 1.0\n", - "input_config[\"technology_config\"] = tech_config\n", - "plant_config[\"technology_interconnections\"] = [\n", - " [\"hopp\", \"electrolyzer\", \"electricity\", \"cable\"],\n", - " [\"electrolyzer\", \"ammonia\", \"hydrogen\", \"h2_pipe_array\"],\n", - " [\"ammonia\", \"electrolyzer\", \"max_hydrogen_capacity\", \"direct\"],\n", - "]\n", - "input_config[\"plant_config\"] = plant_config\n", - "\n", - "model = H2IntegrateModel(input_config)\n", - "\n", - "# Run the model\n", - "model.run()\n", - "\n", - "# Print selected output\n", - "for value in [\n", - " \"electrolyzer.electricity_in\",\n", - " \"electrolyzer.electrolyzer_size_mw\",\n", - " \"electrolyzer.hydrogen_capacity_factor\",\n", - " \"ammonia.hydrogen_in\",\n", - " \"ammonia.max_hydrogen_capacity\",\n", - " \"ammonia.ammonia_capacity_factor\",\n", - " \"finance_subgroup_h2.LCOH\",\n", - " \"finance_subgroup_nh3.LCOA\",\n", - "]:\n", - " print(value + \": \" + str(np.max(model.prob.get_val(value))))" - ] - }, - { - "cell_type": "markdown", - "id": "b88e5310", - "metadata": {}, - "source": [ - "## Using optimizer with multiple connections\n", - "\n", - "With both `electrolyzer` and `ammonia` in `size_by_max_feedstock` mode, the COBYLA optimizer can co-optimize the `max_feedstock_ratio` variables to minimize LCOA to $1.20/kg. This is achieved at a capacity factor of approximately 55% in both the electrolyzer and the ammonia plant." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "204ff626", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Driver debug print for iter coord: rank0:ScipyOptimize_COBYLA|0\n", - "---------------------------------------------------------------\n", - "Design Vars\n", - "{'ammonia.max_feedstock_ratio': array([1.]),\n", - " 'electrolyzer.max_feedstock_ratio': array([1.])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_nh3.LCOA': array([1.2496865])}\n", - "\n", - "Driver debug print for iter coord: rank0:ScipyOptimize_COBYLA|1\n", - "---------------------------------------------------------------\n", - "Design Vars\n", - "{'ammonia.max_feedstock_ratio': array([1.]),\n", - " 'electrolyzer.max_feedstock_ratio': array([1.])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_nh3.LCOA': array([1.2496865])}\n", - "\n", - "Driver debug print for iter coord: rank0:ScipyOptimize_COBYLA|2\n", - "---------------------------------------------------------------\n", - "Design Vars\n", - "{'ammonia.max_feedstock_ratio': array([1.]),\n", - " 'electrolyzer.max_feedstock_ratio': array([1.2])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_nh3.LCOA': array([1.31237357])}\n", - "\n", - "Driver debug print for iter coord: rank0:ScipyOptimize_COBYLA|3\n", - "---------------------------------------------------------------\n", - "Design Vars\n", - "{'ammonia.max_feedstock_ratio': array([1.2]),\n", - " 'electrolyzer.max_feedstock_ratio': array([1.])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_nh3.LCOA': array([1.2496865])}\n", - "\n", - "Driver debug print for iter coord: rank0:ScipyOptimize_COBYLA|4\n", - "---------------------------------------------------------------\n", - "Design Vars\n", - "{'ammonia.max_feedstock_ratio': array([1.]),\n", - " 'electrolyzer.max_feedstock_ratio': array([0.8])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_nh3.LCOA': array([1.19535575])}\n", - "\n", - "Driver debug print for iter coord: rank0:ScipyOptimize_COBYLA|5\n", - "---------------------------------------------------------------\n", - "Design Vars\n", - "{'ammonia.max_feedstock_ratio': array([1.]),\n", - " 'electrolyzer.max_feedstock_ratio': array([0.6])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_nh3.LCOA': array([1.25299474])}\n", - "\n", - "Driver debug print for iter coord: rank0:ScipyOptimize_COBYLA|6\n", - "---------------------------------------------------------------\n", - "Design Vars\n", - "{'ammonia.max_feedstock_ratio': array([0.91108884]),\n", - " 'electrolyzer.max_feedstock_ratio': array([0.84576906])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_nh3.LCOA': array([1.22517452])}\n", - "\n", - "Driver debug print for iter coord: rank0:ScipyOptimize_COBYLA|7\n", - "---------------------------------------------------------------\n", - "Design Vars\n", - "{'ammonia.max_feedstock_ratio': array([1.08590905]),\n", - " 'electrolyzer.max_feedstock_ratio': array([0.85118236])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_nh3.LCOA': array([1.21217351])}\n", - "\n", - "Driver debug print for iter coord: rank0:ScipyOptimize_COBYLA|8\n", - "---------------------------------------------------------------\n", - "Design Vars\n", - "{'ammonia.max_feedstock_ratio': array([1.00916499]),\n", - " 'electrolyzer.max_feedstock_ratio': array([0.75084715])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_nh3.LCOA': array([1.19767376])}\n", - "\n", - "Driver debug print for iter coord: rank0:ScipyOptimize_COBYLA|9\n", - "---------------------------------------------------------------\n", - "Design Vars\n", - "{'ammonia.max_feedstock_ratio': array([0.97502828]),\n", - " 'electrolyzer.max_feedstock_ratio': array([0.80118888])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_nh3.LCOA': array([1.2143843])}\n", - "\n", - "Driver debug print for iter coord: rank0:ScipyOptimize_COBYLA|10\n", - "----------------------------------------------------------------\n", - "Design Vars\n", - "{'ammonia.max_feedstock_ratio': array([1.0242671]),\n", - " 'electrolyzer.max_feedstock_ratio': array([0.80600897])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_nh3.LCOA': array([1.2117977])}\n", - "\n", - "Driver debug print for iter coord: rank0:ScipyOptimize_COBYLA|11\n", - "----------------------------------------------------------------\n", - "Design Vars\n", - "{'ammonia.max_feedstock_ratio': array([1.0010803]),\n", - " 'electrolyzer.max_feedstock_ratio': array([0.79005852])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_nh3.LCOA': array([1.2011689])}\n", - "\n", - "Driver debug print for iter coord: rank0:ScipyOptimize_COBYLA|12\n", - "----------------------------------------------------------------\n", - "Design Vars\n", - "{'ammonia.max_feedstock_ratio': array([1.00497074]),\n", - " 'electrolyzer.max_feedstock_ratio': array([0.80054015])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_nh3.LCOA': array([1.19535575])}\n", - "\n", - "Driver debug print for iter coord: rank0:ScipyOptimize_COBYLA|13\n", - "----------------------------------------------------------------\n", - "Design Vars\n", - "{'ammonia.max_feedstock_ratio': array([0.9989197]),\n", - " 'electrolyzer.max_feedstock_ratio': array([0.80994148])}\n", - "\n", - "Nonlinear constraints\n", - "None\n", - "\n", - "Linear constraints\n", - "None\n", - "\n", - "Objectives\n", - "{'finance_subgroup_nh3.LCOA': array([1.20602989])}\n", - "\n", - "Optimization Complete\n", - "-----------------------------------\n", - "electrolyzer.electricity_in: 1048680.996768964\n", - "electrolyzer.electrolyzer_size_mw: 880.0\n", - "electrolyzer.hydrogen_capacity_factor: 0.5200926613154694\n", - "ammonia.hydrogen_in: 16815.91298776\n", - "ammonia.max_hydrogen_capacity: 16797.74682668353\n", - "ammonia.ammonia_capacity_factor: 0.5436065798046765\n", - "finance_subgroup_h2.LCOH: 4.530431269520775\n", - "finance_subgroup_nh3.LCOA: 1.2060298896768562\n" - ] - } - ], - "source": [ - "# Create a H2Integrate model, modifying tech_config and driver_config as necessary\n", - "tech_config[\"technologies\"][\"electrolyzer\"][\"model_inputs\"][\"performance_parameters\"][\n", - " \"size_mode\"\n", - "] = \"resize_by_max_feedstock\"\n", - "tech_config[\"technologies\"][\"electrolyzer\"][\"model_inputs\"][\"performance_parameters\"][\n", - " \"flow_used_for_sizing\"\n", - "] = \"electricity\"\n", - "tech_config[\"technologies\"][\"electrolyzer\"][\"model_inputs\"][\"performance_parameters\"][\n", - " \"max_feedstock_ratio\"\n", - "] = 1.0\n", - "tech_config[\"technologies\"][\"ammonia\"][\"model_inputs\"][\"performance_parameters\"][\"size_mode\"] = (\n", - " \"resize_by_max_feedstock\"\n", - ")\n", - "tech_config[\"technologies\"][\"ammonia\"][\"model_inputs\"][\"performance_parameters\"][\n", - " \"flow_used_for_sizing\"\n", - "] = \"hydrogen\"\n", - "tech_config[\"technologies\"][\"ammonia\"][\"model_inputs\"][\"performance_parameters\"][\n", - " \"max_feedstock_ratio\"\n", - "] = 1.0\n", - "input_config[\"technology_config\"] = tech_config\n", - "plant_config[\"technology_interconnections\"] = [\n", - " [\"hopp\", \"electrolyzer\", \"electricity\", \"cable\"],\n", - " [\"electrolyzer\", \"ammonia\", \"hydrogen\", \"pipe\"],\n", - "]\n", - "input_config[\"plant_config\"] = plant_config\n", - "driver_config[\"driver\"][\"optimization\"][\"flag\"] = True\n", - "driver_config[\"design_variables\"][\"electrolyzer\"][\"max_feedstock_ratio\"][\"flag\"] = True\n", - "driver_config[\"design_variables\"][\"ammonia\"][\"max_feedstock_ratio\"][\"flag\"] = True\n", - "input_config[\"driver_config\"] = driver_config\n", - "model = H2IntegrateModel(input_config)\n", - "\n", - "# Run the model\n", - "model.run()\n", - "\n", - "# Print selected outputs\n", - "for value in [\n", - " \"electrolyzer.electricity_in\",\n", - " \"electrolyzer.electrolyzer_size_mw\",\n", - " \"electrolyzer.hydrogen_capacity_factor\",\n", - " \"ammonia.hydrogen_in\",\n", - " \"ammonia.max_hydrogen_capacity\",\n", - " \"ammonia.ammonia_capacity_factor\",\n", - " \"finance_subgroup_h2.LCOH\",\n", - " \"finance_subgroup_nh3.LCOA\",\n", - "]:\n", - " print(value + \": \" + str(np.max(model.prob.get_val(value))))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "h2integrate", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/25_sizing_modes/run_size_modes.py b/examples/25_sizing_modes/run_size_modes.py new file mode 100644 index 000000000..1ae8eb344 --- /dev/null +++ b/examples/25_sizing_modes/run_size_modes.py @@ -0,0 +1,208 @@ +"""Pared down version of the "Sizing Modes with Resizeable Converters" User Guide example.""" + +import numpy as np + +from h2integrate.core.utilities import merge_shared_inputs +from h2integrate.core.h2integrate_model import H2IntegrateModel +from h2integrate.core.inputs.validation import load_tech_yaml, load_plant_yaml, load_driver_yaml +from h2integrate.core.model_baseclasses import ( + ResizeablePerformanceModelBaseClass, + ResizeablePerformanceModelBaseConfig, +) + + +class TechPerformanceModelConfig(ResizeablePerformanceModelBaseConfig): + # Declare tech-specific config parameters + size: float = 1.0 + + +class TechPerformanceModel(ResizeablePerformanceModelBaseClass): + def setup(self): + self.config = TechPerformanceModelConfig.from_dict( + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + strict=False, + ) + super().setup() + + # Declare tech-specific inputs and outputs + self.add_input("size", val=self.config.size, units="unitless") + # Declare any commodities produced that need to be connected to downstream converters + # if this converter is in `resize_by_max_commodity` mode + self.add_input("max__capacity", val=1000.0, units="kg/h") + # Any feedstocks consumed that need to be connected to upstream converters + # if those converters are in `resize_by_max_commodity` mode + self.add_output("max__capacity", val=1000.0, units="kg/h") + + def feedstock_sizing_function(max_feedstock): + max_feedstock * 0.1231289 # random number for example + + def commodity_sizing_function(max_commodity): + max_commodity * 0.4651 # random number for example + + def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): + size_mode = discrete_inputs["size_mode"] + + # Make changes to computation based on sizing_mode: + if size_mode != "normal": + size = inputs["size"] + if size_mode == "resize_by_max_feedstock": + if inputs["flow_used_for_sizing"] == "": + feed_ratio = inputs["max_feedstock_ratio"] + size_for_max_feed = self.feedstock_sizing_function( + np.max(inputs["_in"]) + ) + size = size_for_max_feed * feed_ratio + elif size_mode == "resize_by_max_commodity": + if inputs["flow_used_for_sizing"] == "": + comm_ratio = inputs["max_commodity_ratio"] + size_for_max_comm = self.commodity_sizing_function( + np.max(inputs["max__capacity"]) + ) + size = size_for_max_comm * comm_ratio + self.set_val("size", size) + + +driver_config = load_driver_yaml("driver_config.yaml") +plant_config = load_plant_yaml("plant_config.yaml") +tech_config = load_tech_yaml("tech_config.yaml") + +# When we run this example in resize_by_max_commodity mode, the electrolyzer is sized to +# 640 MW (as set by the config), although the electricity profile going in has a max of +# over 1000 MW. The LCOH is $4.49/kg H2 and the LCOA is $1.35/kg NH3. +input_config = { + "name": "H2Integrate_config", + "system_summary": "hybrid plant containing ammonia plant and electrolyzer", + "driver_config": driver_config, + "plant_config": plant_config, + "technology_config": tech_config, +} +model = H2IntegrateModel(input_config) +model.run() + +for value in [ + "electrolyzer.electricity_in", + "electrolyzer.electrolyzer_size_mw", + "electrolyzer.hydrogen_capacity_factor", + "ammonia.hydrogen_in", + "ammonia.max_hydrogen_capacity", + "ammonia.ammonia_capacity_factor", + "finance_subgroup_h2.LCOH", + "finance_subgroup_nh3.LCOA", +]: + print(value + ": " + str(np.max(model.prob.get_val(value)))) + +# In this case, the electrolyzer will be sized to match the maximum `electricity_in` +# coming from HOPP. This increases the electrolyzer size to 1080 MW, the smallest +# multiple of 40 MW (the cluster size) matching the max HOPP power output of 1048 MW. +# This increases the LCOH to $4.80/kg H2, and increases the LCOA to $1.54/kg NH3 +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "size_mode" +] = "resize_by_max_feedstock" +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "flow_used_for_sizing" +] = "electricity" +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "max_feedstock_ratio" +] = 1.0 +input_config["technology_config"] = tech_config + +model = H2IntegrateModel(input_config) +model.run() + +for value in [ + "electrolyzer.electricity_in", + "electrolyzer.electrolyzer_size_mw", + "electrolyzer.hydrogen_capacity_factor", + "ammonia.hydrogen_in", + "ammonia.max_hydrogen_capacity", + "ammonia.ammonia_capacity_factor", + "finance_subgroup_h2.LCOH", + "finance_subgroup_nh3.LCOA", +]: + print(value + ": " + str(np.max(model.prob.get_val(value)))) + +# In this case, the electrolyzer will be sized to match the maximum hydrogen capacity of +# the ammonia plant. This requires the `technology_interconnections` entry to send the +# `max_hydrogen_capacity` from the ammonia plant to the electrolyzer. This decreases the +# electrolyzer size to 560 MW, the closest multiple of 40 MW (the cluster size) that +# will ensure an h2 production capacity that matches the ammonia plant's h2 intake at +# its max ammonia production capacity. This increases the LCOH to $4.64/kg H2, but +# reduces the LCOA to $1.30/kg NH3, since electrolyzer size was matched to ammonia +# production but not HOPP. + +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "size_mode" +] = "resize_by_max_commodity" +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "flow_used_for_sizing" +] = "hydrogen" +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "max_commodity_ratio" +] = 1.0 +input_config["technology_config"] = tech_config +plant_config["technology_interconnections"] = [ + ["hopp", "electrolyzer", "electricity", "cable"], + ["electrolyzer", "ammonia", "hydrogen", "pipe"], + ["ammonia", "electrolyzer", "max_hydrogen_capacity"], +] +input_config["plant_config"] = plant_config + +model = H2IntegrateModel(input_config) +model.run() + +for value in [ + "electrolyzer.electricity_in", + "electrolyzer.electrolyzer_size_mw", + "electrolyzer.hydrogen_capacity_factor", + "ammonia.hydrogen_in", + "ammonia.max_hydrogen_capacity", + "ammonia.ammonia_capacity_factor", + "finance_subgroup_h2.LCOH", + "finance_subgroup_nh3.LCOA", +]: + print(value + ": " + str(np.max(model.prob.get_val(value)))) + + +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "size_mode" +] = "resize_by_max_feedstock" +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "flow_used_for_sizing" +] = "electricity" +tech_config["technologies"]["electrolyzer"]["model_inputs"]["performance_parameters"][ + "max_feedstock_ratio" +] = 1.0 +tech_config["technologies"]["ammonia"]["model_inputs"]["performance_parameters"]["size_mode"] = ( + "resize_by_max_feedstock" +) +tech_config["technologies"]["ammonia"]["model_inputs"]["performance_parameters"][ + "flow_used_for_sizing" +] = "hydrogen" +tech_config["technologies"]["ammonia"]["model_inputs"]["performance_parameters"][ + "max_feedstock_ratio" +] = 1.0 +input_config["technology_config"] = tech_config +plant_config["technology_interconnections"] = [ + ["hopp", "electrolyzer", "electricity", "cable"], + ["electrolyzer", "ammonia", "hydrogen", "pipe"], +] +input_config["plant_config"] = plant_config +driver_config["driver"]["optimization"]["flag"] = True +driver_config["design_variables"]["electrolyzer"]["max_feedstock_ratio"]["flag"] = True +driver_config["design_variables"]["ammonia"]["max_feedstock_ratio"]["flag"] = True +input_config["driver_config"] = driver_config + +model = H2IntegrateModel(input_config) +model.run() + +for value in [ + "electrolyzer.electricity_in", + "electrolyzer.electrolyzer_size_mw", + "electrolyzer.hydrogen_capacity_factor", + "ammonia.hydrogen_in", + "ammonia.max_hydrogen_capacity", + "ammonia.ammonia_capacity_factor", + "finance_subgroup_h2.LCOH", + "finance_subgroup_nh3.LCOA", +]: + print(value + ": " + str(np.max(model.prob.get_val(value)))) From cb3f901fba5cb35cba8881ab0619f49df379b50b Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:40:28 -0800 Subject: [PATCH 19/22] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 203454610..5965be1cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ - Converted the example notebooks to documentation examples, and maintain a basic working example in the examples folder: - `examples/14_wind_hydrogen_dispatch/hydrogren_dispatch.ipynb` -> `docs/control/controller_demonstrations.md` + - `examples/20_solar_electrolyzer_doe/run_csv_doe.ipynb` content added to `docs/user_guide/design_of_experiments_in_h2i.md` + - `examples/25_sizing_modes/run_size_modes.ipynb` -> `docs/user_guide/run_size_modes.md` +- `.gitignore` is updated to be more inclusive of example output data. +- Documentation builds will now fail if a demonstration errors during execution that is not marked as an allowed error, ensuring previously silent errors get caught. ## 0.5.1 [December 18, 2025] From afe6e25a3dd4a169249ea2ae7dd300dea243e5ac Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Wed, 21 Jan 2026 09:21:34 -0800 Subject: [PATCH 20/22] add \ in front of $ to avoid latex in markdown --- docs/user_guide/run_size_modes.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/user_guide/run_size_modes.md b/docs/user_guide/run_size_modes.md index 0f6d86c92..9936d652c 100644 --- a/docs/user_guide/run_size_modes.md +++ b/docs/user_guide/run_size_modes.md @@ -178,7 +178,7 @@ for connection in model.plant_config["technology_interconnections"]: ``` When we run this example the electrolyzer is sized to 640 MW (as set by the config), although the electricity profile going in has a max of over 1000 MW. -The LCOH is $4.49/kg H2 and the LCOA is $1.35/kg NH3. +The LCOH is \$4.49/kg H2 and the LCOA is \$1.35/kg NH3. ```{code-cell} ipython3 # Run the model @@ -202,7 +202,7 @@ for value in [ In this case, the electrolyzer will be sized to match the maximum `electricity_in` coming from HOPP. This increases the electrolyzer size to 1080 MW, the smallest multiple of 40 MW (the cluster size) matching the max HOPP power output of 1048 MW. -This increases the LCOH to $4.80/kg H2, and increases the LCOA to $1.54/kg NH3, since electrolyzer is now oversized to utilize all of the HOPP electricity at peak output but thus has a lower hydrogen production capacity factor. +This increases the LCOH to \$4.80/kg H2, and increases the LCOA to \$1.54/kg NH3, since electrolyzer is now oversized to utilize all of the HOPP electricity at peak output but thus has a lower hydrogen production capacity factor. ```{code-cell} ipython3 # Create a H2Integrate model, modifying tech_config as necessary @@ -240,7 +240,7 @@ for value in [ In this case, the electrolyzer will be sized to match the maximum hydrogen capacity of the ammonia plant. This requires the `technology_interconnections` entry to send the `max_hydrogen_capacity` from the ammonia plant to the electrolyzer. This decreases the electrolyzer size to 560 MW, the closest multiple of 40 MW (the cluster size) that will ensure an h2 production capacity that matches the ammonia plant's h2 intake at its max ammonia production capacity. -This increases the LCOH to $4.64/kg H2, but reduces the LCOA to $1.30/kg NH3, since electrolyzer size was matched to ammonia production but not HOPP. +This increases the LCOH to \$4.64/kg H2, but reduces the LCOA to \$1.30/kg NH3, since electrolyzer size was matched to ammonia production but not HOPP. ```{code-cell} ipython3 # Create a H2Integrate model, modifying tech_config and plant_config as necessary @@ -282,7 +282,7 @@ for value in [ ## Using optimizer with multiple connections -With both `electrolyzer` and `ammonia` in `size_by_max_feedstock` mode, the COBYLA optimizer can co-optimize the `max_feedstock_ratio` variables to minimize LCOA to $1.20/kg. This is achieved at a capacity factor of approximately 55% in both the electrolyzer and the ammonia plant. +With both `electrolyzer` and `ammonia` in `size_by_max_feedstock` mode, the COBYLA optimizer can co-optimize the `max_feedstock_ratio` variables to minimize LCOA to \$1.20/kg. This is achieved at a capacity factor of approximately 55% in both the electrolyzer and the ammonia plant. ```{code-cell} ipython3 # Create a H2Integrate model, modifying tech_config and driver_config as necessary From 7df32f30d5e057c5f2f928ea704ec5125f71ace1 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:41:18 -0800 Subject: [PATCH 21/22] update docs example for updated example --- .../turbine_models_library_preprocessing.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/misc_resources/turbine_models_library_preprocessing.md b/docs/misc_resources/turbine_models_library_preprocessing.md index 546d1bab5..43fe3cd0b 100644 --- a/docs/misc_resources/turbine_models_library_preprocessing.md +++ b/docs/misc_resources/turbine_models_library_preprocessing.md @@ -232,13 +232,13 @@ updated_parameters = { "floris_turbine_config": floris_options, } -# Update wind performance parameters in the tech config -tech_config["technologies"]["wind"]["model_inputs"]["performance_parameters"].update( +# Update distributed wind+ performance parameters in the tech config +tech_config["technologies"]["distributed_wind_plant"]["model_inputs"]["performance_parameters"].update( updated_parameters ) # The technology input for the updated wind turbine model -tech_config["technologies"]["wind"]["model_inputs"]["performance_parameters"] +tech_config["technologies"]["distributed_wind_plant"]["model_inputs"]["performance_parameters"] ``` Step 3: Run H2I with the updated tech_config dictionary for the Vestas 1.65 MW turbine @@ -258,6 +258,6 @@ h2i = H2IntegrateModel(h2i_config) h2i.run() # Get LCOE of wind plant -wind_lcoe = h2i.model.get_val("finance_subgroup_electricity.LCOE", units="USD/MW/h") +wind_lcoe = h2i.model.get_val("finance_subgroup_distributed.LCOE", units="USD/MW/h") print(f"Wind LCOE is ${wind_lcoe[0]:.2f}/MWh") ``` From 636acc5ab4466d0c7d50f097bcc640e70bd95526 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Mon, 26 Jan 2026 21:08:03 -0700 Subject: [PATCH 22/22] Updated literal lincludes for yamls in docs --- .../turbine_models_library_preprocessing.md | 35 +---- .../design_of_experiments_in_h2i.md | 49 +------ docs/user_guide/how_to_set_up_an_analysis.md | 123 ++++-------------- 3 files changed, 35 insertions(+), 172 deletions(-) diff --git a/docs/misc_resources/turbine_models_library_preprocessing.md b/docs/misc_resources/turbine_models_library_preprocessing.md index 43fe3cd0b..09a1d05e8 100644 --- a/docs/misc_resources/turbine_models_library_preprocessing.md +++ b/docs/misc_resources/turbine_models_library_preprocessing.md @@ -54,38 +54,13 @@ tech_config = load_tech_yaml(tech_config_path) This example uses the "pysam_wind_plant_performance" performance model for the wind plant. Currently, the performance model is using an 8.3MW wind turbine with a rotor diameter of 196 meters and a hub-height of 130 meters. This information is defined in the `tech_config` file: -```yaml -technologies: - wind: - performance_model: - model: "pysam_wind_plant_performance" - cost_model: - model: "atb_wind_cost" - model_inputs: - performance_parameters: - num_turbines: 100 - turbine_rating_kw: 8300 - rotor_diameter: 196. - hub_height: 130. - create_model_from: "default" - config_name: "WindPowerSingleOwner" - pysam_options: !include "pysam_options_8.3MW.yaml" - run_recalculate_power_curve: False - layout: - layout_mode: "basicgrid" - layout_options: - row_D_spacing: 10.0 - turbine_D_spacing: 10.0 - rotation_angle_deg: 0.0 - row_phase_offset: 0.0 - layout_shape: "square" - cost_parameters: - capex_per_kW: 1500.0 - opex_per_kW_per_year: 45 - cost_year: 2019 +```{literalinclude} ../../examples/08_wind_electrolyzer/tech_config.yaml +:language: yaml +:lineno-start: 4 +:linenos: true +:lines: 4-31 ``` - If we want to replace the 8.3 MW turbine with the NREL 5 MW turbine, we can do so using the `export_turbine_to_pysam_format()` function: ```{code-cell} ipython3 diff --git a/docs/user_guide/design_of_experiments_in_h2i.md b/docs/user_guide/design_of_experiments_in_h2i.md index e11138ba8..cf8b9c4cd 100644 --- a/docs/user_guide/design_of_experiments_in_h2i.md +++ b/docs/user_guide/design_of_experiments_in_h2i.md @@ -24,46 +24,9 @@ Detailed information on setting up the `driver_config.yaml` file can be found in The driver config file defines the analysis type and the optimization or design of experiments settings. For completeness, here is an example of a driver config file for a design of experiments problem: -```yaml -name: "driver_config" -description: "Runs a design sweep" - -general: - folder_output: outputs - -driver: - design_of_experiments: - flag: True - generator: "csvgen" #type of generator to use - filename: "" #this input is specific to the "csvgen" generator - debug_print: False - -design_variables: - solar: - system_capacity_DC: - flag: true - lower: 5000.0 - upper: 5000000.0 - units: "kW" - electrolyzer: - n_clusters: - flag: true - lower: 1 - upper: 25 - units: "unitless" - -constraints: #constraints are not used within the design of experiments run -# but are accessible in the recorder file as constraint variables - -objective: #the objective is not used within the design of experiments run -# but is accessible in the recorder file as the objective - name: finance_subgroup_hydrogen.LCOH - -recorder: - file: "cases.sql" - flag: True - includes: ["*"] - excludes: ['*_resource*'] +```{literalinclude} ../../examples/22_site_doe/driver_config.yaml +:language: yaml +:linenos: true ``` ## Types of Generators @@ -164,7 +127,7 @@ This `csvgen` generator example reflects the work to produce the `examples/20_so example. We use the `examples/20_solar_electrolyzer_doe/driver_config.yaml` to run a design of experiments for -varying combinations of solar power and hydrogen electrolzer capacities. +varying combinations of solar power and hydrogen electrolyzer capacities. ```{literalinclude} ../../examples/20_solar_electrolyzer_doe/driver_config.yaml :language: yaml @@ -173,7 +136,7 @@ varying combinations of solar power and hydrogen electrolzer capacities. :lines: 4-26 ``` -The different combinations of solar and electrolzyer capacities are listed in the csv file `examples/20_solar_electrolyzer_doe/csv_doe_cases.csv`: +The different combinations of solar and electrolyzer capacities are listed in the csv file `examples/20_solar_electrolyzer_doe/csv_doe_cases.csv`: ```{literalinclude} ../../examples/20_solar_electrolyzer_doe/csv_doe_cases.csv :language: csv @@ -193,7 +156,7 @@ from h2integrate.core.inputs.validation import load_driver_yaml, write_yaml ##### Setup and first attempt -First, we need to update the relative file refrerences to ensure the demonstration works. +First, we need to update the relative file references to ensure the demonstration works. ```{code-cell} ipython3 EXAMPLE_DIR = Path("../../examples/20_solar_electrolyzer_doe").resolve() diff --git a/docs/user_guide/how_to_set_up_an_analysis.md b/docs/user_guide/how_to_set_up_an_analysis.md index fbf28cab5..1babbfb37 100644 --- a/docs/user_guide/how_to_set_up_an_analysis.md +++ b/docs/user_guide/how_to_set_up_an_analysis.md @@ -23,14 +23,9 @@ The top-level config file is the main entry point for H2Integrate. Its main purpose is to define the analysis type and the configuration files for the different components of the analysis. Here is an example of a top-level config file: -```yaml -name: H2Integrate_config - -system_summary: This reference hybrid plant is located in Minnesota and contains wind, solar, and battery storage technologies. The system is designed to produce hydrogen using an electrolyzer and also produce steel using a grid-connected plant. - -driver_config: driver_config.yaml -technology_config: tech_config.yaml -plant_config: plant_config.yaml +```{literalinclude} ../../examples/08_wind_electrolyzer/wind_plant_electrolyzer.yaml +:language: yaml +:linenos: true ``` The top-level config file contains the following keys: @@ -60,12 +55,9 @@ More information about file handling in H2I can be found [here](https://h2integr The driver config file defines the analysis type and the optimization settings. If you are running a basic analysis and not an optimization, the driver config file is quite straightforward and might look like this: -```yaml -name: driver_config -description: This analysis runs a hybrid plant to match the first example in H2Integrate - -general: - folder_output: outputs +```{literalinclude} ../../examples/08_wind_electrolyzer/driver_config.yaml +:language: yaml +:linenos: true ``` If you are running an optimization, the driver config file will contain additional keys to define the optimization settings, including design variables, constraints, and objective functions. @@ -75,48 +67,16 @@ Further details of more complex instances of the driver config file can be found The technology config file defines the technologies included in the analysis, their modeling parameters, and the performance, cost, and financial models used for each technology. The yaml file is organized into sections for each technology included in the analysis under the `technologies` heading. -Here is an example of part of a technology config that is defining an energy system with only one technology, an electrolyzer: - -```yaml -name: technology_config -description: This hybrid plant produces steel - -technologies: - electrolyzer: - performance_model: - model: eco_pem_electrolyzer_performance - cost_model: - model: eco_pem_electrolyzer_cost - model_inputs: - shared_parameters: - location: onshore - electrolyzer_capex: 2000 # $/kW overnight installed capital costs - - performance_parameters: - sizing: - resize_for_enduse: False - size_for: BOL #'BOL' (generous) or 'EOL' (conservative) - hydrogen_dmd: - n_clusters: 13 - cluster_rating_MW: 40 - eol_eff_percent_loss: 13 #eol defined as x% change in efficiency from bol - uptime_hours_until_eol: 77600 #number of 'on' hours until electrolyzer reaches eol - include_degradation_penalty: True #include degradation - turndown_ratio: 0.1 #turndown_ratio = minimum_cluster_power/cluster_rating_MW - - cost_parameters: - cost_model: singlitico2021 - - financial_parameters: - capital_items: - depr_period: 7 - replacement_cost_percent: 0.15 # percent of capex - H2A default case +Here is an example of a technology config that is defining an energy system with wind and electrolyzer technologies: + +```{literalinclude} ../../examples/08_wind_electrolyzer/tech_config.yaml +:language: yaml +:linenos: true ``` -Here, we have defined a electrolyzer model that uses the built-in `eco_pem_electrolyzer_performance` and `eco_pem_electrolyzer_cost` models. +Here, we have defined a wind plant using the `pysam_wind_plant_performance` and `atb_wind_cost` models, and an electrolyzer using the `eco_pem_electrolyzer_performance` and `singlitico_electrolyzer_cost` models. The `performance_model` and `cost_model` keys define the models used for the performance and cost calculations, respectively. The `model_inputs` key contains the inputs for the models, which are organized into sections for shared parameters, performance parameters, cost parameters, and financial parameters. -Here, we do not define the `financial_model` key, so the default financial model from ProFAST is used. The `shared_parameters` section contains parameters that are common to all models, such as the rating and location of the technology. These values are defined once in the `shared_parameters` section and are used by all models that reference them. @@ -138,60 +98,25 @@ The different models are defined in the `supported_models.py` file in the `h2int The plant config file defines the system configuration, any parameters that might be shared across technologies, and how the technologies are connected together. -Here is a snippet of an example plant config file: - -```yaml -name: plant_config -description: This plant is located in MN, USA - -site: - latitude: 47.5233 - longitude: -92.5366 - elevation_m: 439.0 - -# array of arrays containing left-to-right technology -# interconnections; can support bidirectional connections -# with the reverse definition. -# this will naturally grow as we mature the interconnected tech -technology_interconnections: [ - ["wind", "electrolyzer", "electricity", "cable"], - ["electrolyzer", "h2_storage", "efficiency"], - ["electrolyzer", "h2_storage", "hydrogen", "pipe"], - ["finance_subgroup_1", "steel", "LCOH"], - ["wind", "steel", "electricity", "cable"], - # etc -] - -plant: - plant_life: 30 - grid_connection: False # option, can be turned on or off - ppa_price: 0.025 # $/kWh - hybrid_electricity_estimated_cf: 0.492 - - -finance_parameters: - finance_model: "ProFastComp" - model_inputs: - params: - analysis_start_year: 2032 - installation_time: 36 # months - cost_adjustment_parameters: - target_dollar_year: 2022 - cost_year_adjustment_inflation: 0.025 # used to adjust modeled costs to target_dollar_year - inflation_rate: 0.0 # 0 for nominal analysis -... +Here is an example plant config file: + +```{literalinclude} ../../examples/08_wind_electrolyzer/plant_config.yaml +:language: yaml +:linenos: true ``` -The `site` section contains the site parameters, such as the latitude, longitude, elevation, and time zone. -The `plant` section contains the plant parameters, such as the plant life, grid connection, PPA price, and installation time. +The `sites` section contains the site parameters, such as the latitude and longitude, and defines the resources available at each site (e.g., wind or solar resource data). +The `plant` section contains the plant parameters, such as the plant life. The `finance_parameters` section contains the financial parameters used across the plant, such as the inflation rates, financing terms, and other financial parameters. -The `technology_interconnections` section contains the interconnections between the technologies in the system and is the most complex part of the plant config file. -The interconnections are defined as an list of lists, where each sub-list defines a connection between two technologies. +The `technology_interconnections` section contains the interconnections between the technologies in the system. +The interconnections are defined as a list of lists, where each sub-list defines a connection between two technologies. The first entry in the list is the technology that is providing the input to the next technology in the list. -If the list is length 4, then the third entry in the list is what's passing passed via a transporter of the type defined in the fourth entry. +If the list is length 4, then the third entry in the list is what's being passed via a transporter of the type defined in the fourth entry. If the list is length 3, then the third entry in the list is what is connected directly between the technologies. +The `resource_to_tech_connections` section defines how resources (like wind or solar data) are connected to the technologies that use them. + ```{note} For more information on how to define and interpret technology interconnections, see the {ref}`connecting_technologies` page. ```