diff --git a/.gitignore b/.gitignore index 9096bc44e..2bd02cedf 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/* @@ -125,11 +125,16 @@ tests/h2integrate/test_hydrogen/output.txt *speed_dir_data.csv examples/h2integrate/*/data examples/h2integrate/*/figures +examples/*/*_new.* +examples/*/*[0-9].* +*wind_electrolyzer/ tests/h2integrate/reports/ tests/h2integrate/test_hydrogen/output/ **/run_*_out/ *_out/ **/test_*_out/ +docs/misc_resources/wind_electrolyzer/ +docs/_autosummary output* snopt_history.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 174474ef1..41730e77b 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 @@ -10,6 +12,16 @@ - Minor reorg for profast tools - Added ability to plot multi-layer geospatial point heat map and simple straight line transport routes with GeoPandas and Contextily [PR 413](https://github.com/NREL/H2Integrate/pull/413) - Removed hydrogen tank cost and performance models that were unused +- 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` + - `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. - `pyproject.toml` is tidied up after moving past Python 3.9 and early H2I limitations. - Cleans up unnecessary ignore rules in the ruff settings. - Removes duplicate dependency listings, and alphabetizes for legibility with NLR packages diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 773db94c4..441544b02 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -55,6 +55,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: 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 diff --git a/docs/_toc.yml b/docs/_toc.yml index 84ba992ea..65a8d886b 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -22,7 +22,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 - file: user_guide/plotting_geospatial_data_with_geopandas - caption: Technology Models @@ -62,6 +62,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/docs/misc_resources/turbine_models_library_preprocessing.ipynb b/docs/misc_resources/turbine_models_library_preprocessing.ipynb deleted file mode 100644 index db1a23fd0..000000000 --- a/docs/misc_resources/turbine_models_library_preprocessing.ipynb +++ /dev/null @@ -1,1160 +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\"][\"distributed_wind_plant\"][\"model_inputs\"][\n", - " \"performance_parameters\"\n", - "].update(updated_parameters)\n", - "\n", - "# The technology input for the updated wind turbine model\n", - "tech_config[\"technologies\"][\"distributed_wind_plant\"][\"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_distributed.LCOE\", units=\"USD/MW/h\")\n", - "print(f\"Wind LCOE is ${wind_lcoe[0]:.2f}/MWh\")" - ] - } - ], - "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" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "e55566d5f9cb5003b92ad1d2254e8146f3d62519cfa868f35d73d51fb57327c6" - } - } - }, - "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..09a1d05e8 --- /dev/null +++ b/docs/misc_resources/turbine_models_library_preprocessing.md @@ -0,0 +1,238 @@ +--- +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: + +```{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 +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 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"]["distributed_wind_plant"]["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_distributed.LCOE", units="USD/MW/h") +print(f"Wind LCOE is ${wind_lcoe[0]:.2f}/MWh") +``` diff --git a/docs/user_guide/design_of_experiments_in_h2i.md b/docs/user_guide/design_of_experiments_in_h2i.md index 487866f47..cf8b9c4cd 100644 --- a/docs/user_guide/design_of_experiments_in_h2i.md +++ b/docs/user_guide/design_of_experiments_in_h2i.md @@ -1,59 +1,38 @@ +--- +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. 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 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 + 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 +54,7 @@ driver: ``` ### FullFactorial + ```yaml driver: design_of_experiments: @@ -86,13 +66,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 +83,7 @@ driver: ``` ### BoxBehnken + ```yaml driver: design_of_experiments: @@ -109,6 +92,7 @@ driver: ``` ### LatinHypercube + ```yaml driver: design_of_experiments: @@ -119,8 +103,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 @@ -136,3 +120,114 @@ 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 electrolyzer 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 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 +``` + +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 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 references 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", + str(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() +``` 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 b260ed3ac..000000000 --- a/docs/user_guide/how_to_set_up_an_analysis.ipynb +++ /dev/null @@ -1,392 +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", - "sites:\n", - " site:\n", - " latitude: 47.5233\n", - " longitude: -92.5366\n", - " elevation: 439.0 #meters\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 # years\n", - " simulation:\n", - " n_timesteps: 8760 # timesteps per simulation\n", - " dt: 3600 # seconds per timestep, defaults to hourly\n", - " timezone: 0 #timezone offset from UTC\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 `sites` section contains the parameters for each site, such as the latitude, longitude, elevation, and resource models.\n", - "The `plant` section contains the plant parameters, such as the plant life and simulation parameters (such as simulation duration, timestep size, and timezone).\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": "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HEql0tS1EREREVWY0eEmOTkZK1euxPr163H9+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+6MixcvIjw8HAqFAkuXLjVJPURERFSzGX1B8blz5xAUFISNGzfC09MTHTt2xPz583H27NmnKsDCwgJubm66l7Ozc5l9Dx8+jC5duiA0NBQNGjRAr169EBISgmPHjj1VDVS1irUCx5IyAADHkjJQrBUSV0RERHJidLjx9PTEa6+9hp9//hk3b97E9OnTcfbsWXTr1g0NGzbE9OnT8csvv6C4uLhCBSQmJqJu3bpo2LAhwsLCkJKSUmbfzp074+TJk7owc+XKFezatQv/+te/KvSeJJ098Rp0XfQLxnx9HAAw5uvj6LroF+yJ10hcGRERycUT/baUvb09QkJCEBISgsLCQuzfvx87d+7E6NGjkZOTg08++aTU3556VMeOHbFmzRp4e3tDo9EgOjoa3bp1Q3x8POzs7Az6h4aG4tatW+jatSuEECgqKsLEiRN1v0ZemoKCAhQUFOiWs7OzAQCFhYW6V8kyVa6fz93EjA1nIAAozR6crVGaCdzJvYfp607io6HPIaAZHylQmfh5lwbHXRocd2lU1rhXZH8KIYRJ5wROnz6NoqIitG/fvsLbZmZmwtPTE0uXLsXYsWMN1sfFxWHYsGF477330LFjR1y6dAnTpk3D+PHj8c4775S6z9IuWgYeXL9j6gukiYiIqHLk5eUhNDQUWVlZUKvV5fZ96nBz+/Zt/Pnnn/Dx8THJRcbt27dHQEAAFi5caLCuW7du6NSpk96Pd65duxavvPIKcnNzYWZmOMtW2pkbDw8P3Lp1C2q1GoWFhYiNjUVgYCAsLS2fun4q3bGkDN1UFPDgjM28dlq8c8IMBVqFrv2rUe3RwctRihKfCfy8S4PjLg2OuzQqa9yzs7Ph7OxsVLip0LTUf/7zHwDAhAkTAABnzpyBv78/srKyYG1tja1btz7Vs29yc3Nx+fJljBgxotT1eXl5BgHG3NwcAFBWRlMqlVAqlQbtlpaWeoP+6DKZ1q28IhQUKwzaC7QKvfZbeUX8c6gC/LxLg+MuDY67NEw97hXZV4V+fuHzzz/Xu5spMjISL730ErKzs/HGG29gzpw5FdkdZs6ciQMHDiA5ORmHDx/GgAEDYG5ujpCQEADAyJEjERERoesfHByMlStXYv369UhKSkJsbCzeeecdBAcH60IOVU+udsY9h8jYfkRERGUx6szNr7/+CiEErly5gqysLN3y/v37sXjxYpw6dQpt27bFkiVL8OuvvwIAunfv/tj9Xr9+HSEhIbh9+zZcXFzQtWtXHD16FC4uLgCAlJQUvTM1c+fOhUKhwNy5c5GamgoXFxcEBwdj/vz5T3LsVIU6eDnC3V6FtKx8lHaOTQHAzV7FKSkiInpqRoWbpKQkAIBWq4VGo4G5uTkSExNhbm4OGxsbJCUloaioCMXFxUhOToYQwqhws379+nLXx8XF6RdrYYHIyEhERkYaUzZVI+ZmCkQG+2DS2lN4dHKqZDky2AfmZoZTV0RERBVhVLgZNWoUgAfTUteuXcOYMWOwb98+BAQEYOTIkQCAixcvom7durplokf19nXHyuFtEL0zARm593TtbvYqRAb7oLevu4TVERGRXFToguJ58+ahf//+umtvfvnlF926devWoWfPniYvkOSlt687An3ccPRSOm6dO4qvRrVHp8auPGNDREQmU6Fw4+/vj5SUFFy6dAne3t6wtbXVrXvppZfg7s5/edPjmZsp0MHLEbvOPbgWh8GGiIhMqcJPKLa3t0fbtm0N2lu3bm2SgoiIiIiehlG3gh89etToHebl5eGvv/564oKIiIiInoZR4WbEiBEICgrCpk2bcPfu3VL7JCQk4O2330ajRo1w8uRJkxZJREREZCyjpqUSEhKwcuVKzJ07F6GhoWjSpAnq1q0LlUqFO3fu4Pz588jNzcWAAQPw008/oUWLFpVdNxEREVGpjAo3lpaWmDp1KqZOnYoTJ07g0KFDuHr1Ku7du4dWrVphxowZ8Pf3h6MjH8BGRERE0qrwBcXt2rVDu3btKqMWIiIioqdWod+WIiIiIqruGG6IiIhIVhhuiIiISFYYboiIiEhWGG6IiIhIVoy+W+rjjz82qt/UqVOfuBgiIiKip2V0uPnoo4/0lq9duwZ3d3dYWPxvFwqFguGGiIiIJGV0uElKStJbtrOzw4EDB9CwYUOTF0VERET0pHjNDREREckKww0RERHJCsMNERERyYrR19xkZ2frLSsUCuTm5hq0q9Vq01RGRERE9ASMDjcODg5QKBS6ZSEEWrdurbesUChQXFxs2gqJiIiIKsDocLN///7KrIOIiIjIJIwONz169KjMOoiIiIhMwuhwUyI1NRVbtmzBxYsXAQDe3t54+eWXUa9ePZMXR0RERFRRFQo3K1aswOuvv4779+/rLhzOzs7Gm2++iaVLl2Ly5MmVUiQRERGRsYy+FfzHH3/E1KlT8eqrryI1NRWZmZnIzMxEamoqJk+ejGnTpmHXrl2VWSsRERHRYxl95uaDDz7A7Nmz8d577+m1u7u7Y+nSpbCxscHixYvxr3/9y+RFEhERERnL6DM3p06dwogRI8pcP2LECJw6dcokRRERERE9KaPDTXFxMSwtLctcb2lpyWfcEBERkeSMDjfNmzfH9u3by1y/bds2NG/e3CRFERERET0po6+5mTJlCiZNmgSlUolXXnkFFhYPNi0qKsJ//vMfzJ07FytWrKi0QomIiIiMYXS4GTVqFM6ePYtXX30VERERaNSoEYQQuHLlCnJzczF16lSEh4dXYqlEREREj1eh59x8+OGHGDRoENatW4fExEQAD55cPGzYMHTq1KlSCiQiIiKqiAo/obhTp04mCzJRUVGIjo7Wa/P29sb58+fL3CYzMxNz5szB1q1bkZGRAU9PTyxbtoy3oBMRERGAClxQnJiYiJCQEGRnZxusy8rKQmhoKK5cuVLhApo3bw6NRqN7HTp0qMy+9+/fR2BgIJKTk7F582ZcuHABn3/+OX/6gYiIiHQq9BA/Dw8P3c8uPMze3h4eHh744IMPsHLlyooVYGEBNzc3o/p+9dVXyMjIwOHDh3W3pTdo0KBC70dERETyZnS4OXDgANauXVvm+iFDhiA0NLTCBSQmJqJu3bpQqVTw8/PDwoULUb9+/VL77tixA35+fpgyZQq2b98OFxcXhIaGYtasWTA3Ny91m4KCAhQUFOiWS848FRYW6l4ly1R1OO7S4LhLg+MuDY67NCpr3CuyP4UQQhjT0draGufPn4enp2ep669evYpmzZohLy/P6DffvXs3cnNz4e3tDY1Gg+joaKSmpiI+Ph52dnYG/Zs2bYrk5GSEhYVh8uTJuHTpEiZPnoypU6ciMjKy1Pco7boeAIiJiYGNjY3RtRIREZF08vLyEBoaiqysrFJnkR5mdLhxc3NDTEwMevbsWer6ffv2ISwsDGlpaRWv+L8yMzPh6emJpUuXYuzYsQbrmzRpgvz8fCQlJenO1CxduhQffPABNBpNqfss7cyNh4cHbt26BbVajcLCQsTGxiIwMLDcJzCTaXHcpcFxlwbHXRocd2lU1rhnZ2fD2dnZqHBj9LRU9+7d8cknn5QZbj7++GN069atYpU+wsHBAU2aNMGlS5dKXe/u7g5LS0u9KahmzZohLS0N9+/fh5WVlcE2SqUSSqXSoN3S0lJv0B9dpqrBcZcGx10aHHdpcNylYepxr8i+jL5bKiIiArt378agQYNw7NgxZGVlISsrC7///jsGDhyIvXv3IiIi4okKLpGbm4vLly/D3d291PVdunTBpUuXoNVqdW0XL16Eu7t7qcGGiIiInj1Gh5vWrVtj8+bN+PXXX+Hn5wdHR0c4Ojqic+fOOHjwIDZu3Ig2bdpU6M1nzpyJAwcOIDk5GYcPH8aAAQNgbm6OkJAQAMDIkSP1AtOkSZOQkZGBadOm4eLFi/jxxx+xYMECTJkypULvS0RERPJVoYf49e3bF1evXsWePXtw6dIlCCHQpEkT9OrV64kuzr1+/TpCQkJw+/ZtuLi4oGvXrjh69ChcXFwAACkpKTAz+1/+8vDwwN69ezFjxgy0bNkS9erVw7Rp0zBr1qwKvzcRERHJU4WfUGxtbY0BAwYYtF+/fh3//ve/sWrVKqP3tX79+nLXx8XFGbT5+fnh6NGjRr8HERERPVuMnpZ6nNu3b+PLL7801e6IqlyxVuDI5dvYfiYVRy7fRrHWqBsJiYiomqnwmRsiOdoTr0H0zgRosvJ1be72KkQG+6C3b+kXuBMRUfVksjM3RDXVnngNJq09pRdsACAtKx+T1p7CnvjSn6FERETVE8MNPdOKtQLROxNQ2gRUSVv0zgROURER1SBGT0u9/PLL5a7PzMx82lqIqtyxpAyDMzYPEwA0Wfk4lpQBv0ZOVVcYERE9MaPDjb29/WPXjxw58qkLIqpK6TllB5sn6UdERNIzOtysXr26MusgkoSrncqk/YiISHq85oaeaR28HOFur4KijPUKPLhrqoOXY1WWRURET4Hhhp5p5mYKRAb7AIBBwClZjgz2gblZWfGHiIiqG4Ybeub19nXHyuFt4GavP/XkZq/CyuFt+JwbIqIahg/xI8KDgBPo44ZjSRlIz8mHq92DqSiesSEiqnkYboj+y9xMwdu9iYhkgNNSREREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkK5KGm6ioKCgUCr1X06ZNjdp2/fr1UCgU6N+/f+UWSURERDWKhdQFNG/eHD///LNu2cLi8SUlJydj5syZ6NatW2WWRkRERDWQ5OHGwsICbm5uRvcvLi5GWFgYoqOjcfDgQWRmZlZecURERFTjSB5uEhMTUbduXahUKvj5+WHhwoWoX79+mf3//e9/w9XVFWPHjsXBgwcfu/+CggIUFBTolrOzswEAhYWFulfJMlUdjrs0OO7S4LhLg+Mujcoa94rsTyGEECZ99wrYvXs3cnNz4e3tDY1Gg+joaKSmpiI+Ph52dnYG/Q8dOoRhw4bhzJkzcHZ2Rnh4ODIzM7Ft27Yy3yMqKgrR0dEG7TExMbCxsTHl4RAREVElycvLQ2hoKLKysqBWq8vtK2m4eVRmZiY8PT2xdOlSjB07Vm9dTk4OWrZsiRUrVuDFF18EAKPCTWlnbjw8PHDr1i2o1WoUFhYiNjYWgYGBsLS0rJTjIkMcd2lw3KXBcZcGx10alTXu2dnZcHZ2NircSD4t9TAHBwc0adIEly5dMlh3+fJlJCcnIzg4WNem1WoBPLhu58KFC2jUqJHBdkqlEkql0qDd0tJSb9AfXaaqwXGXBsddGhx3aXDcq06xVuB0UgYA4PT1HHRq7ApzM4VJ9l2RP8NqFW5yc3Nx+fJljBgxwmBd06ZNcfbsWb22uXPnIicnB8uXL4eHh0dVlUlERESP2BOvQfTOBGTk3sPiDsCYr4/D0dYakcE+6O3rXqW1SBpuZs6cieDgYHh6euLGjRuIjIyEubk5QkJCAAAjR45EvXr1sHDhQqhUKvj6+upt7+DgAAAG7URERFR19sRrMGntKQgASvP/tadl5WPS2lNYObxNlQYcScPN9evXERISgtu3b8PFxQVdu3bF0aNH4eLiAgBISUmBmRkfokxERFRdFWsFoncmoLQLeAUABYDonQkI9HEz2RTV40gabtavX1/u+ri4uHLXr1mzxnTFEFVDxVqBY0kZSM/Jh6udCh28HKvsLwciImMcS8qAJiu/zPUCgCYrH8eSMuDXyKlKaqpW19wQ0f+UzF8//JeGu71KkvlrIqKypOeUHWyepJ8pcM6HqBoqmb9+9F9DJfPXe+I1ElVGRKTP1U5l0n6mwHBDVM08bv4aeDB/XaytNo+oIqJnWAcvR7jbq1DWhLkCD846d/ByrLKaGG6IqpmKzF8TEUnN3EyByGAfADAIOCXLkcE+VXq9IMMNUTVTHeeviYjK09vXHSuHt4Gbvf7Uk5u9qspvAwd4QTFRtVMd56+JiB6nt687An3ccPRSOm6dO4qvRrU36ROKK4Jnboiqmeo4f01EZAxzM4Xu7yYpH13BcENUzVTH+WsiopqE4YaoGqpu89dERDUJr7khqqZK5q/5hGIioophuCGqxszNFFX2uHIiIrngtBQRERHJCsMNERERyQrDDREREckKww0RERHJCsMNERERyQrDDREREckKbwUnIiKqYYq1gs/AKgfDDRERUQ2yJ16D6J0J0GTl69rc7VWIDPbh08v/i9NSRERENcSeeA0mrT2lF2wAIC0rH5PWnsKeeI1ElVUvDDdEREQ1QLFWIHpnAkQp60raoncmoFhbWo9nC8MNERFRDXAsKcPgjM3DBABNVj6OJWVUXVHVFMMNERFRDZCeU3aweZJ+csZwQ0REVAO42qlM2k/OGG6IiIhqgA5ejnC3V6GsG74VeHDXVAcvx6osq1piuCEiIqoBzM0UiAz2AQCDgFOyHBnsw+fdgOGGiIioxujt646Vw9vAzV5/6snNXoWVw9vwOTf/xYf4ERER1SC9fd0R6OPGJxSXg+GGiIiohjE3U8CvkZPUZVRbnJYiIiIiWWG4ISIiIllhuCEiIiJZYbghIiIiWZE03ERFRUGhUOi9mjZtWmb/zz//HN26dUPt2rVRu3ZtBAQE4NixY1VYMREREVV3kp+5ad68OTQaje516NChMvvGxcUhJCQE+/fvx5EjR+Dh4YFevXohNTW1CismIiKi6kzyW8EtLCzg5uZmVN/vvvtOb/mLL77Ali1bsG/fPowcObIyyiOiUhRrBZ+xQUTVluThJjExEXXr1oVKpYKfnx8WLlyI+vXrG7VtXl4eCgsL4ejI39Egqip74jWI3pkATdb/fnnY3V6FyGAfPh2ViKoFScNNx44dsWbNGnh7e0Oj0SA6OhrdunVDfHw87OzsHrv9rFmzULduXQQEBJTZp6CgAAUFBbrl7OxsAEBhYaHuVbJMVYfjLo2nHfefz93EjA1nIAAozf/Xfif3HqavO4mPhj6HgGZ1TFCpvPDzLg2OuzQqa9wrsj+FEEKY9N2fQmZmJjw9PbF06VKMHTu23L7vv/8+Fi9ejLi4OLRs2bLMflFRUYiOjjZoj4mJgY2NzVPXTERERJUvLy8PoaGhyMrKglqtLrdvtQo3ANC+fXsEBARg4cKFZfb58MMP8d577+Hnn39Gu3btyt1faWduPDw8cOvWLajVahQWFiI2NhaBgYGwtLQ02XFQ+Tju0niacT+WlIExXx9/bL+vRrVHBy9OFT+Mn3dpmGrci7UCJ6/ewa3cAjjbKtHWszavMStHZX3es7Oz4ezsbFS4kfyam4fl5ubi8uXLGDFiRJl9Fi9ejPnz52Pv3r2PDTYAoFQqoVQqDdotLS31Bv3RZaoaHHdpPMm438orQkHx4/9Cv5VXxD/TMvDzLo2nGXdeY/bkTP15r8i+JL0VfObMmThw4ACSk5Nx+PBhDBgwAObm5ggJCQEAjBw5EhEREbr+ixYtwjvvvIOvvvoKDRo0QFpaGtLS0pCbmyvVIRA9M1ztVCbtR1Td7YnXYNLaU3rBBgDSsvIxae0p7InXSFQZPY6k4eb69esICQmBt7c3hgwZAicnJxw9ehQuLi4AgJSUFGg0//vwrFy5Evfv38egQYPg7u6ue3344YdSHQLRM6ODlyPc7VUo69yNAg/+RcspKZKDYq1A9M4ElHbdRklb9M4EFGur1ZUd9F+STkutX7++3PVxcXF6y8nJyZVXDBGVy9xMgchgH0xaewoKQO8v/ZLAExnsw2sRSBaOJWUYnLF5mACgycrHsaQM+DVyqrrCyCiSP6GYiGqO3r7uWDm8Ddzs9aee3OxVWDm8Da9BINlIzyk72DxJP6pa1eqCYiKq/nr7uiPQx41PKCZZ4zVmNRvDDRFVmLmZgqfiSdZKrjFLy8ov9bobBR6cseQ1ZtUTp6WIiIgeUXKNGQCDi+h5jVn1x3BDRERUCl5jVnNxWoqIiKgMvMasZmK4ISIiKgevMat5OC1FREREssJwQ0RERLLCcENERESywnBDREREssJwQ0RERLLCcENERESywnBDREREssJwQ0RERLLCcENERESywnBDREREssKfXyAiomqhWCv4G05kEgw3RCQL/GKs2fbEaxC9MwGarHxdm7u9CpHBPvz1baowhhsiqvH4xViz7YnXYNLaUxCPtKdl5WPS2lNYObwN/xypQnjNDRHVaCVfjA8HG+B/X4x74jUSVUbGKNYKRO9MMAg2AHRt0TsTUKwtrQdR6RhuiKjG4hdjzXcsKcMgmD5MANBk5eNYUkbVFUU1HsMNEdVY/GKs+dJzyv7ze5J+RADDDRHVYPxirPlc7VQm7UcEMNwQUQ3GL8aar4OXI9ztVSjrvjYFHlwc3sHLsSrLohqO4YaIaix+MdZ85mYKRAb7AIDBn2PJcmSwD2/rpwphuCGiGotfjPLQ29cdK4e3gZu9/hk2N3sVbwOnJ8Ln3BBRjVbyxfjoc27c+JybGqW3rzsCfdz4IEYyCYYbIqrx+MUoD+ZmCvg1cpK6DJIBhhsikgV+MRJRCV5zQ0RERLLCcENERESywnBDREREsiJpuImKioJCodB7NW3atNxtNm3ahKZNm0KlUqFFixbYtWtXFVVLRERENYHkZ26aN28OjUajex06dKjMvocPH0ZISAjGjh2L06dPo3///ujfvz/i4+OrsGIiIiKqziQPNxYWFnBzc9O9nJ2dy+y7fPly9O7dG2+++SaaNWuGefPmoU2bNvj000+rsGIiIiKqziQPN4mJiahbty4aNmyIsLAwpKSklNn3yJEjCAgI0GsLCgrCkSNHKrtMIiIiqiEkfc5Nx44dsWbNGnh7e0Oj0SA6OhrdunVDfHw87OzsDPqnpaWhTp06em116tRBWlpame9RUFCAgoIC3XJ2djYAoLCwUPcqWaaqw3GXBse96hVrBY5f+RsAcPRSOto3dOHDBasIP+/SqKxxr8j+JA03L774ou6/W7ZsiY4dO8LT0xMbN27E2LFjTfIeCxcuRHR0tEH7Tz/9BBsbG91ybGysSd6PKobjLg2OuzQyLp7A3otSV/Hs4eddGqYe97y8PKP7VqsnFDs4OKBJkya4dOlSqevd3Nxw8+ZNvbabN2/Czc2tzH1GRETg9ddf1y1nZ2fDw8MDvXr1glqtRmFhIWJjYxEYGAhLS0vTHAg9FsddGhz3qvPzuZuYseEMBAClmcC8dlq8c8IM97UPztp8NPQ5BDSrU/5OKkmxVuDk1Tu4lVsAZ1sl2nrWluXZJH7epVFZ414y82KMahVucnNzcfnyZYwYMaLU9X5+fti3bx+mT5+ua4uNjYWfn1+Z+1QqlVAqlQbtlpaWeoP+6DJVDY67NDjulatYK/DvHy8gv1g/MBRoFSgoVkAB4N8/XkAv33pVHir2xGsMfmTUXeY/MsrPuzRMPe4V2ZekFxTPnDkTBw4cQHJyMg4fPowBAwbA3NwcISEhAICRI0ciIiJC13/atGnYs2cPlixZgvPnzyMqKgonTpzAq6++KtUhEBEZOJaUoRceHiUAaLLycSwpo+qKwoNgM2ntKYPa0rLyMWntKeyJ11RpPUSVRdJwc/36dYSEhMDb2xtDhgyBk5MTjh49ChcXFwBASkoKNJr//c/WuXNnxMTEYNWqVWjVqhU2b96Mbdu2wdfXV6pDICIykJ5TdrB5kn6mUKwViN6ZAFHKupK26J0JKNaW1oOoZpF0Wmr9+vXlro+LizNoGzx4MAYPHlxJFRERPT1XO5VJ+5lCRc4m8dfVqaaT/Dk3RERy08HLEe72KpR1NY0CD65z6eDlWGU1VcezSUSVheGGiMjEzM0UiAz2AQCDgFOyHBnsU6UXE1fHs0lElYXhhoioEvT2dcfK4W3gZq8fFtzsVVg5vE2V35lUHc8mEVWWanUrOBGRnPT2dUegjxuOXkrHrXNH8dWo9ujU2FWSZ8qUnE2atPYUFIDehcVSnU0iqiw8c0NEVInMzRS6syEdvBwlDQ/V7WwSUWXhmRsiomdIydmkY0kZSM/Jh6udSvLQRWRqDDdERM8YczMFb/cmWeO0FBEREckKww0RERHJCsMNERERyQrDDREREckKww0RERHJCsMNERERyQrDDREREckKww0RERHJCsMNERERycoz94RiIR78XFx2djYAoLCwEHl5ecjOzoalpaWUpT1TOO7S4LhLg+MuDY67NCpr3Eu+t0u+x8vzzIWbnJwcAICHh4fElRAREVFF5eTkwN7evtw+CmFMBJIRrVaLGzduwM7ODgqFAtnZ2fDw8MC1a9egVqulLu+ZwXGXBsddGhx3aXDcpVFZ4y6EQE5ODurWrQszs/KvqnnmztyYmZnhH//4h0G7Wq3mh18CHHdpcNylwXGXBsddGpUx7o87Y1OCFxQTERGRrDDcEBERkaw88+FGqVQiMjISSqVS6lKeKRx3aXDcpcFxlwbHXRrVYdyfuQuKiYiISN6e+TM3REREJC8MN0RERCQrDDdEREQkKww3REREJCuyDDcrV65Ey5YtdQ8Q8vPzw+7du3Xr8/PzMWXKFDg5OcHW1hYDBw7EzZs39faRkpKCPn36wMbGBq6urnjzzTdRVFRU1YdSo73//vtQKBSYPn26ro1jb3pRUVFQKBR6r6ZNm+rWc8wrT2pqKoYPHw4nJydYW1ujRYsWOHHihG69EALvvvsu3N3dYW1tjYCAACQmJurtIyMjA2FhYVCr1XBwcMDYsWORm5tb1YdSYzRo0MDg865QKDBlyhQA/LxXluLiYrzzzjvw8vKCtbU1GjVqhHnz5un9zlO1+rwLGdqxY4f48ccfxcWLF8WFCxfE22+/LSwtLUV8fLwQQoiJEycKDw8PsW/fPnHixAnRqVMn0blzZ932RUVFwtfXVwQEBIjTp0+LXbt2CWdnZxERESHVIdU4x44dEw0aNBAtW7YU06ZN07Vz7E0vMjJSNG/eXGg0Gt3r77//1q3nmFeOjIwM4enpKcLDw8Xvv/8urly5Ivbu3SsuXbqk6/P+++8Le3t7sW3bNvHHH3+Il156SXh5eYl79+7p+vTu3Vu0atVKHD16VBw8eFA0btxYhISESHFINUJ6erreZz02NlYAEPv37xdC8PNeWebPny+cnJzEDz/8IJKSksSmTZuEra2tWL58ua5Pdfq8yzLclKZ27driiy++EJmZmcLS0lJs2rRJt+7cuXMCgDhy5IgQQohdu3YJMzMzkZaWpuuzcuVKoVarRUFBQZXXXtPk5OSIf/7znyI2Nlb06NFDF2449pUjMjJStGrVqtR1HPPKM2vWLNG1a9cy12u1WuHm5iY++OADXVtmZqZQKpVi3bp1QgghEhISBABx/PhxXZ/du3cLhUIhUlNTK694GZk2bZpo1KiR0Gq1/LxXoj59+ogxY8botb388ssiLCxMCFH9Pu+ynJZ6WHFxMdavX4+7d+/Cz88PJ0+eRGFhIQICAnR9mjZtivr16+PIkSMAgCNHjqBFixaoU6eOrk9QUBCys7Px119/Vfkx1DRTpkxBnz599MYYAMe+EiUmJqJu3bpo2LAhwsLCkJKSAoBjXpl27NiBdu3aYfDgwXB1dUXr1q3x+eef69YnJSUhLS1Nb+zt7e3RsWNHvbF3cHBAu3btdH0CAgJgZmaG33//veoOpoa6f/8+1q5dizFjxkChUPDzXok6d+6Mffv24eLFiwCAP/74A4cOHcKLL74IoPp93mX7w5lnz56Fn58f8vPzYWtri++//x4+Pj44c+YMrKys4ODgoNe/Tp06SEtLAwCkpaXpffBL1peso7KtX78ep06dwvHjxw3WpaWlcewrQceOHbFmzRp4e3tDo9EgOjoa3bp1Q3x8PMe8El25cgUrV67E66+/jrfffhvHjx/H1KlTYWVlhVGjRunGrrSxfXjsXV1d9dZbWFjA0dGRY2+Ebdu2ITMzE+Hh4QD4d0xlmj17NrKzs9G0aVOYm5ujuLgY8+fPR1hYGABUu8+7bMONt7c3zpw5g6ysLGzevBmjRo3CgQMHpC5L1q5du4Zp06YhNjYWKpVK6nKeGSX/cgKAli1bomPHjvD09MTGjRthbW0tYWXyptVq0a5dOyxYsAAA0Lp1a8THx+Ozzz7DqFGjJK7u2fDll1/ixRdfRN26daUuRfY2btyI7777DjExMWjevDnOnDmD6dOno27dutXy8y7baSkrKys0btwYbdu2xcKFC9GqVSssX74cbm5uuH//PjIzM/X637x5E25ubgAANzc3g6vrS5ZL+pChkydPIj09HW3atIGFhQUsLCxw4MABfPzxx7CwsECdOnU49lXAwcEBTZo0waVLl/h5r0Tu7u7w8fHRa2vWrJluSrBk7Eob24fHPj09XW99UVERMjIyOPaPcfXqVfz8888YN26cro2f98rz5ptvYvbs2Rg2bBhatGiBESNGYMaMGVi4cCGA6vd5l224eZRWq0VBQQHatm0LS0tL7Nu3T7fuwoULSElJgZ+fHwDAz88PZ8+e1ftDiI2NhVqtNvjLjP7nhRdewNmzZ3HmzBndq127dggLC9P9N8e+8uXm5uLy5ctwd3fn570SdenSBRcuXNBru3jxIjw9PQEAXl5ecHNz0xv77Oxs/P7773pjn5mZiZMnT+r6/PLLL9BqtejYsWMVHEXNtXr1ari6uqJPnz66Nn7eK09eXh7MzPQjg7m5ObRaLYBq+Hk36eXJ1cTs2bPFgQMHRFJSkvjzzz/F7NmzhUKhED/99JMQ4sGtgvXr1xe//PKLOHHihPDz8xN+fn667UtuFezVq5c4c+aM2LNnj3BxceGtgk/g4bulhODYV4Y33nhDxMXFiaSkJPHbb7+JgIAA4ezsLNLT04UQHPPKcuzYMWFhYSHmz58vEhMTxXfffSdsbGzE2rVrdX3ef/994eDgILZv3y7+/PNP0a9fv1JvjW3durX4/fffxaFDh8Q///lP3gr+GMXFxaJ+/fpi1qxZBuv4ea8co0aNEvXq1dPdCr5161bh7Ows3nrrLV2f6vR5l2W4GTNmjPD09BRWVlbCxcVFvPDCC7pgI4QQ9+7dE5MnTxa1a9cWNjY2YsCAAUKj0ejtIzk5Wbz44ovC2tpaODs7izfeeEMUFhZW9aHUeI+GG4696Q0dOlS4u7sLKysrUa9ePTF06FC9Z61wzCvPzp07ha+vr1AqlaJp06Zi1apVeuu1Wq145513RJ06dYRSqRQvvPCCuHDhgl6f27dvi5CQEGFrayvUarUYPXq0yMnJqcrDqHH27t0rABiMpRD8vFeW7OxsMW3aNFG/fn2hUqlEw4YNxZw5c/Run69On3eFEA89XpCIiIiohntmrrkhIiKiZwPDDREREckKww0RERHJCsMNERERyQrDDREREckKww0RERHJCsMNERERyQrDDVENoVAosG3bNqnLKFVUVBSee+45qcuocg0aNMCyZcuq5L327duHZs2aobi4GEDljfns2bPx2muvmXy/RFWJ4YaoGggPD4dCoTB49e7du9LeszqHJVOJi4vTG08XFxf861//wtmzZyu0nzVr1sDBwcGg/fjx43jllVdMVG353nrrLcydOxfm5uaP7ZubmwtLS0usX79er33YsGFQKBRITk7Wa2/QoAHeeecdAMDMmTPx9ddf48qVKyarnaiqMdwQVRO9e/eGRqPRe61bt07Smu7fvy/p+xvrcXVeuHABGo0Ge/fuRUFBAfr06WOSY3NxcYGNjc1T7+dxDh06hMuXL2PgwIFG9be1tUW7du0QFxen1x4XFwcPDw+99qSkJFy9ehU9e/YEADg7OyMoKAgrV640VflEVY7hhqiaUCqVcHNz03vVrl27zP7Xrl3DkCFD4ODgAEdHR/Tr18/gX+RfffUVmjdvDqVSCXd3d7z66qsAHvxLHQAGDBgAhUKhWy6Z6vjiiy/g5eUFlUoFAEhJSUG/fv1ga2sLtVqNIUOG4ObNm6XW9euvv8LS0hJpaWl67dOnT0e3bt0AAM8//3ypZ6pK6s/MzMS4cePg4uICtVqNnj174o8//tDtq6w6y+Lq6go3Nze0adMG06dPx7Vr13D+/Hnd+qVLl6JFixaoVasWPDw8MHnyZOTm5gJ4EAhGjx6NrKwsXZ1RUVG6cXx4WkqhUOCLL77AgAEDYGNjg3/+85/YsWOHXi07duzAP//5T6hUKvj7++Prr7+GQqFAZmZmmfWvX78egYGB5R7n5cuX0bBhQ7z66qsQQsDf318vxJw7dw75+fmYNGmSXntcXByUSqXul5sBIDg42OCsD1FNwnBDVAMVFhYiKCgIdnZ2OHjwIH777TfY2tqid+/eujMSK1euxJQpU/DKK6/g7Nmz2LFjBxo3bgzgwXQKAKxevRoajUa3DACXLl3Cli1bsHXrVpw5cwZarRb9+vVDRkYGDhw4gNjYWFy5cgVDhw4ttbbu3bujYcOG+Pbbb/Xq/e677zBmzBgAwNatW/XOUL388svw9vZGnTp1AACDBw9Geno6du/ejZMnT6JNmzZ44YUXkJGRUWadxsjKytJ9aVtZWenazczM8PHHH+Ovv/7C119/jV9++QVvvfUWAKBz585YtmwZ1Gq1rt6ZM2eW+R7R0dEYMmQI/vzzT/zrX/9CWFiYru6kpCQMGjQI/fv3xx9//IEJEyZgzpw5j6374MGDaNeuXZnr//zzT3Tt2hWhoaH49NNPoVAo4O/vrztjBQD79+9H165d0bNnT71ws3//fvj5+ekFpw4dOuD69esGYZmoxjD5T3ESUYWNGjVKmJubi1q1aum95s+fr+sDQHz//fdCCCG+/fZb4e3tLbRarW59QUGBsLa2Fnv37hVCCFG3bl0xZ86cMt/z4f2ViIyMFJaWliI9PV3X9tNPPwlzc3ORkpKia/vrr78EAHHs2DHddq1atdKtX7RokWjWrJluecuWLcLW1lbk5uYa1LF06VLh4OCg+/XggwcPCrVaLfLz8/X6NWrUSPznP/8ps87S7N+/XwDQjScAAUC89NJL5W63adMm4eTkpFtevXq1sLe3N+jn6ekpPvroI90yADF37lzdcm5urgAgdu/eLYQQYtasWcLX11dvH3PmzBEAxJ07d8qsx97eXnzzzTd6bSVj/ttvv4natWuLDz/8UG/93bt3hZWVlYiJiRFCCDF48GCxePFiUVhYKGrVqiWuXLkihBCifv36Ijo6Wm/brKwsAUDExcWVWRNRdWYhVagiIn3+/v4G1zk4OjqW2vePP/7ApUuXYGdnp9een5+Py5cvIz09HTdu3MALL7xQ4To8PT3h4uKiWz537hw8PDzg4eGha/Px8YGDgwPOnTuH9u3bG+wjPDwcc+fOxdGjR9GpUyesWbMGQ4YMQa1atfT67d69G7Nnz8bOnTvRpEkT3bHl5ubCyclJr++9e/dw+fLlMussz8GDB2FjY4OjR49iwYIF+Oyzz/TW//zzz1i4cCHOnz+P7OxsFBUVIT8/H3l5eRW+pqZly5a6/65VqxbUajXS09MBPLj259Hx6tChw2P3ee/evVKnpFJSUhAYGIj58+dj+vTpeutsbGzQvn17xMXFISQkBAcOHMCbb74JCwsLdO7cGXFxcRBCICUlBf7+/nrbWltbAwDy8vKMOmai6obhhqiaqFWrlm7a6HFyc3PRtm1bfPfddwbrXFxcYGb25DPOjwaQJ+Hq6org4GCsXr0aXl5e2L17t8HFrQkJCRg2bBjef/999OrVS9eem5sLd3d3g/4A9O5YqkidXl5ecHBwgLe3N9LT0zF06FD8+uuvAIDk5GT07dsXkyZNwvz58+Ho6IhDhw5h7NixuH//foXDjaWlpd6yQqGAVqut0D4e5ezsjDt37hi0u7i4oG7duli3bh3GjBkDtVqtt97f3x8bNmzAX3/9hXv37qFNmzYAgB49emD//v3QarWwsbFBx44d9bYrmUYzNjwSVTe85oaoBmrTpg0SExPh6uqKxo0b673s7e1hZ2eHBg0aYN++fWXuw9LSUvfMlPI0a9YM165dw7Vr13RtCQkJyMzMhI+PT5nbjRs3Dhs2bMCqVavQqFEjdOnSRbfu1q1bCA4OxsCBAzFjxgyDY0tLS4OFhYXBsTk7Oz+23seZMmUK4uPj8f333wMATp48Ca1WiyVLlqBTp05o0qQJbty4obeNlZWVUWP1ON7e3jhx4oRe28PXO5WldevWSEhIMGi3trbGDz/8AJVKhaCgIOTk5Oit9/f3R2JiImJiYtC1a1fdbeTdu3fHgQMHEBcXhy5duuhdfwQA8fHxsLS0RPPmzSt6iETVAsMNUTVRUFCAtLQ0vdetW7dK7RsWFgZnZ2f069cPBw8eRFJSEuLi4jB16lRcv34dwIM7ipYsWYKPP/4YiYmJOHXqFD755BPdPkrCT1paWqlnBUoEBASgRYsWCAsLw6lTp3Ds2DGMHDkSPXr0KPci16CgIKjVarz33nsYPXq03rqBAwfCxsYGUVFResdbXFyMgIAA+Pn5oX///vjpp5+QnJyMw4cPY86cOQbB4EnY2Nhg/PjxiIyMhBACjRs3RmFhIT755BNcuXIF3377rcG0VYMGDZCbm4t9+/bh1q1bTzxdM2HCBJw/fx6zZs3CxYsXsXHjRqxZswbAgzM8ZQkKCsKhQ4dKXVerVi38+OOPsLCwwIsvvqi7ywt4cDG0UqnEJ598gh49eujaO3TogPT0dGzfvt1gSgp4MI3XrVs33fQUUU3DcENUTezZswfu7u56r65du5ba18bGBr/++ivq16+Pl19+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..1babbfb37 --- /dev/null +++ b/docs/user_guide/how_to_set_up_an_analysis.md @@ -0,0 +1,228 @@ +--- +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: + +```{literalinclude} ../../examples/08_wind_electrolyzer/wind_plant_electrolyzer.yaml +:language: yaml +:linenos: true +``` + +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: + +```{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. +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 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 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. + +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 an example plant config file: + +```{literalinclude} ../../examples/08_wind_electrolyzer/plant_config.yaml +:language: yaml +:linenos: true +``` + +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. +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 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. +``` + +## 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 + +``` diff --git a/docs/user_guide/run_size_modes.md b/docs/user_guide/run_size_modes.md new file mode 100644 index 000000000..9936d652c --- /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)))) +``` 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() 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() 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 94897ed44..000000000 --- a/examples/25_sizing_modes/run_size_modes.ipynb +++ /dev/null @@ -1,735 +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 ResizeablePerformanceModelBaseConfig, merge_shared_inputs\n", - "from h2integrate.core.model_baseclasses import ResizeablePerformanceModelBaseClass\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": null, - "id": "a79029a8", - "metadata": {}, - "outputs": [ - { - "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", - "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": "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": 4, - "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": 5, - "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']" - ] - } - ], - "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": null, - "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.43539128043319353\n", - "ammonia.hydrogen_in: 20499.002679502206\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" - ] - } - ], - "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": null, - "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" - ] - } - ], - "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": "hopp", - "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" - } - }, - "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))))