From b6de7705386c11c12cc854dc5eaa17d959e6a324 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Mon, 27 Apr 2026 12:41:58 -0700 Subject: [PATCH 01/40] add unintegrated, working EIA API data querier --- h2integrate/resource/ng_price.py | 169 +++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 h2integrate/resource/ng_price.py diff --git a/h2integrate/resource/ng_price.py b/h2integrate/resource/ng_price.py new file mode 100644 index 000000000..a3210e0b8 --- /dev/null +++ b/h2integrate/resource/ng_price.py @@ -0,0 +1,169 @@ +import json +from pathlib import Path +from datetime import datetime + +import attrs +import pandas as pd +import requests +from attrs import field, define + +from h2integrate.core.utilities import BaseConfig +from h2integrate.core.file_utils import get_path + + +STATES = states = [ + "AL", + "AK", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DC", + "DE", + "FL", + "GA", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "OH", + "OK", + "OR", + "PA", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VA", + "WA", + "WV", + "WI", + "WY", +] +CURRENT_YEAR = datetime.now().year + + +def get_eia_api_key(filename: Path) -> str: + """Retrieves the EIA API key from a file, and returns the key following "EIA_API_KEY:". + + Args: + filename (Path): Full file path and name of where the EIA API key is located. Must be + encoded as "EIA_API_KEY: xxxxxx" + + Returns: + str: The EIA API key. + """ + with filename.open() as f: + for line in f.readlines(): + if ":" in line: + name, val = line.strip().split(":") + if name == "EIA_API_KEY": + return val.strip() + + +@define +class EIAIndustrialNaturalGasConfig(BaseConfig): + """EIA Industrial Natural Gas Pricing API Configuration. + + Args: + state (str): Two-letter, upper case state abbreviation. Must be one of the 50 U.S. states. + resource_year (int): The YYYY-format year whose data should be retrieved. Must be between + 2001 and the current year, inclusive of endpoints. + monthly (Path): True, if monthly data is desired, False if annual data is desired. + api_key_file (Path): Full file name of the file where the API key is located. + """ + + state: str = field( + converter=str.upper, + validator=attrs.validators.in_(STATES), + ) + resource_year: int = field(validator=attrs.validators.in_(range(2001, CURRENT_YEAR + 1))) + monthly: bool = field(validator=attrs.validators.instance_of(bool)) + api_key_file: str = field(converter=get_path) + + +class EIAIndustrialNaturalGasResource: + """Create the class.""" + + def __init__(self, resource_config: dict): + # self.setup() + self.config = EIAIndustrialNaturalGasConfig.from_dict( + # self.options["resource_config"], + resource_config, + additional_cls_name=self.__class__.__name__, + ) + self.url = self.create_url() + + # def initialize(self): + # self.options.declare("plant_config", types=dict) + # self.options.declare("resource_config", types=dict) + # self.options.declare("driver_config", types=dict) + + def create_url(self): + base_url = "https://api.eia.gov/v2/natural-gas/pri/sum/data/" + frequency = f"frequency={'monthly' if self.config.monthly else 'annual'}" + data = "data[0]=value" + facet = f"facets[series][]=N3035{self.config.state}3" + start = f"start={self.config.resource_year}" + end = f"end={self.config.resource_year}" + if self.config.monthly: + start = f"{start}-01" + end = f"{start}-12" + sort_col = "sort[0][column]=period" + sort_dir = "sort[0][direction]=asc" + api_key = f"api_key={get_eia_api_key(self.config.api_key_file)}" + + url_opts = "&".join((frequency, data, facet, start, end, sort_col, sort_dir, api_key)) + url = f"{base_url}?{url_opts}" + return url + + def setup(self): + # Define inputs and outputs + self.config = EIAIndustrialNaturalGasConfig.from_dict( + self.options["resource_config"], + additional_cls_name=self.__class__.__name__, + ) + # site_config = self.options["plant_config"]["site"] + + # self.add_input("latitude", site_config.get("latitude", 0.0), units="deg") + # self.add_input("longitude", site_config.get("longitude", 0.0), units="deg") + # self.add_output("discharge", shape=8760, val=0.0, units="ft**3/s") + + def load_data(self, filename: str | Path | None = None): + # download_from_api(self.url, filename) + r = requests.get(self.url) + if r.status_code != 200: + err = json.loads(r.text)["error"] + msg = f"{err['code']}: {err['message']}" + raise requests.exceptions.HTTPError(msg) + + cols = ["period", "area-name", "value", "units"] + df = pd.DataFrame.from_dict(json.loads(r.text)["response"]["data"])[cols] + df.period = pd.to_datetime(df.period) + + if filename is not None: + df.to_csv(filename, index=False) From f0898f5f45ed8b77ad4111017f8c8e58fa5dcf22 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:52:37 -0700 Subject: [PATCH 02/40] probably working integration into H2I --- h2integrate/resource/ng_price.py | 206 +++++++++++++++++++------------ 1 file changed, 129 insertions(+), 77 deletions(-) diff --git a/h2integrate/resource/ng_price.py b/h2integrate/resource/ng_price.py index a3210e0b8..d5d8643de 100644 --- a/h2integrate/resource/ng_price.py +++ b/h2integrate/resource/ng_price.py @@ -11,59 +11,65 @@ from h2integrate.core.file_utils import get_path -STATES = states = [ - "AL", - "AK", - "AZ", - "AR", - "CA", - "CO", - "CT", - "DC", - "DE", - "FL", - "GA", - "HI", - "ID", - "IL", - "IN", - "IA", - "KS", - "KY", - "LA", - "ME", - "MD", - "MA", - "MI", - "MN", - "MS", - "MO", - "MT", - "NE", - "NV", - "NH", - "NJ", - "NM", - "NY", - "NC", - "ND", - "OH", - "OK", - "OR", - "PA", - "RI", - "SC", - "SD", - "TN", - "TX", - "UT", - "VT", - "VA", - "WA", - "WV", - "WI", - "WY", -] +STATE_MAP = states = { + "Alabama": "AL", + "Alaska": "AK", + "Arizona": "AZ", + "Arkansas": "AR", + "California": "CA", + "Colorado": "CO", + "Connecticut": "CT", + "Delaware": "DE", + "Florida": "FL", + "Georgia": "GA", + "Hawaii": "HI", + "Idaho": "ID", + "Illinois": "IL", + "Indiana": "IN", + "Iowa": "IA", + "Kansas": "KS", + "Kentucky": "KY", + "Louisiana": "LA", + "Maine": "ME", + "Maryland": "MD", + "Massachusetts": "MA", + "Michigan": "MI", + "Minnesota": "MN", + "Mississippi": "MS", + "Missouri": "MO", + "Montana": "MT", + "Nebraska": "NE", + "Nevada": "NV", + "New Hampshire": "NH", + "New Jersey": "NJ", + "New Mexico": "NM", + "New York": "NY", + "North Carolina": "NC", + "North Dakota": "ND", + "Ohio": "OH", + "Oklahoma": "OK", + "Oregon": "OR", + "Pennsylvania": "PA", + "Rhode Island": "RI", + "South Carolina": "SC", + "South Dakota": "SD", + "Tennessee": "TN", + "Texas": "TX", + "Utah": "UT", + "Vermont": "VT", + "Virginia": "VA", + "Washington": "WA", + "West Virginia": "WV", + "Wisconsin": "WI", + "Wyoming": "WY", + "District of Columbia": "DC", + "American Samoa": "AS", + "Guam": "GU", + "Northern Mariana Islands": "MP", + "Puerto Rico": "PR", + "United States Minor Outlying Islands": "UM", + "Virgin Islands, U.S.": "VI", +} CURRENT_YEAR = datetime.now().year @@ -95,19 +101,30 @@ class EIAIndustrialNaturalGasConfig(BaseConfig): 2001 and the current year, inclusive of endpoints. monthly (Path): True, if monthly data is desired, False if annual data is desired. api_key_file (Path): Full file name of the file where the API key is located. + latitude (float, optional): Latitude of the site, optional. + latitude (float, optional): Longitude of the site, optional. + filename (str, optinal): Filename for where to save the data or where the data may + already be located. If the file exists, it will be used, and filtered to the + :py:attr:`resource_year`, otherwise, the data will be saved to this location. """ - state: str = field( - converter=str.upper, - validator=attrs.validators.in_(STATES), - ) resource_year: int = field(validator=attrs.validators.in_(range(2001, CURRENT_YEAR + 1))) monthly: bool = field(validator=attrs.validators.instance_of(bool)) api_key_file: str = field(converter=get_path) + state: str = field( + converter=str.upper, + validator=attrs.validators.in_(STATE_MAP.values()), + ) + latitude: float = field(default=0) + longitude: float = field(default=0) + filename: str = field(default=None, converter=attrs.converters.optional(get_path)) class EIAIndustrialNaturalGasResource: - """Create the class.""" + """EIA Industrial Natural Gas Pricing Downloader. + + See https://www.eia.gov/dnav/ng/hist/n3035us3m.htm for further details. + """ def __init__(self, resource_config: dict): # self.setup() @@ -118,10 +135,21 @@ def __init__(self, resource_config: dict): ) self.url = self.create_url() - # def initialize(self): - # self.options.declare("plant_config", types=dict) - # self.options.declare("resource_config", types=dict) - # self.options.declare("driver_config", types=dict) + def initialize(self): + self.options.declare("plant_config", types=dict) + self.options.declare("resource_config", types=dict) + self.options.declare("driver_config", types=dict) + + def setup(self): + # Define inputs and outputs + self.config = EIAIndustrialNaturalGasConfig.from_dict( + self.options["resource_config"], + additional_cls_name=self.__class__.__name__, + ) + site_config = self.options["plant_config"]["site"] + self.add_input("latitude", site_config.get("latitude", 0.0), units="deg") + self.add_input("longitude", site_config.get("longitude", 0.0), units="deg") + self.add_output("price", shape=12, val=0.0, units="USD/(ft**3/1000)") def create_url(self): base_url = "https://api.eia.gov/v2/natural-gas/pri/sum/data/" @@ -141,29 +169,53 @@ def create_url(self): url = f"{base_url}?{url_opts}" return url - def setup(self): - # Define inputs and outputs - self.config = EIAIndustrialNaturalGasConfig.from_dict( - self.options["resource_config"], - additional_cls_name=self.__class__.__name__, - ) - # site_config = self.options["plant_config"]["site"] + def load_data(self, filename: Path | None = None) -> pd.DataFrame: + """Loads the previously saved data from :py:attr:`filename` if ``resource_year`` + is available as either annual or monthly data, otherwise queries the EIA API. + + Args: + filename (Path | None, optional): The full filename where the natural gas pricing data + should be saved to or loaded from, if available. Defaults to None. + + Raises: + requests.exceptions.HTTPError: Raised if an unsuccessful API query result is returned. + + Returns: + pandas.DataFrame: DataFrame with index "period" and column "value" with natural gas + pricing in $/MCF (USD per thousands of cubic feet) as either the monthly value + or extrapolated annual values to a monthly resolution. + """ + filename = Path(filename).resolve() + if filename.exists(): + df = pd.read_csv(filename, parse_dates=["period"]).set_index("period") + df = df.loc[df.index.dt.year.eq(self.config.resource_year)] + match df.shape[0]: + case 12: + return df + case 1: + year = self.config.resource_year + df = df.reindex( + pd.date_range(f"{year}-01", f"{year}-12", freq="MS"), method="nearest" + ) + return df + case _: + pass - # self.add_input("latitude", site_config.get("latitude", 0.0), units="deg") - # self.add_input("longitude", site_config.get("longitude", 0.0), units="deg") - # self.add_output("discharge", shape=8760, val=0.0, units="ft**3/s") - - def load_data(self, filename: str | Path | None = None): - # download_from_api(self.url, filename) r = requests.get(self.url) if r.status_code != 200: err = json.loads(r.text)["error"] msg = f"{err['code']}: {err['message']}" raise requests.exceptions.HTTPError(msg) - cols = ["period", "area-name", "value", "units"] + cols = ["period", "value"] df = pd.DataFrame.from_dict(json.loads(r.text)["response"]["data"])[cols] df.period = pd.to_datetime(df.period) + df = df.set_index("period") if filename is not None: - df.to_csv(filename, index=False) + df.to_csv(filename, index_label="period") + return df + + def compute(self, inputs, outputs): + ng_price_monthly = self.load_data(self.config.filename) + outputs["price"] = ng_price_monthly.to_numpy() From 194f5922a2f3612ccf411d1390b748321453d0ef Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:59:09 -0700 Subject: [PATCH 03/40] ensure data are always in monthly format --- h2integrate/resource/ng_price.py | 37 ++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/h2integrate/resource/ng_price.py b/h2integrate/resource/ng_price.py index d5d8643de..e0e81f90f 100644 --- a/h2integrate/resource/ng_price.py +++ b/h2integrate/resource/ng_price.py @@ -73,6 +73,28 @@ CURRENT_YEAR = datetime.now().year +def convert_to_monthly(df: pd.DataFrame, year: int) -> pd.DataFrame | None: + """Converts an annual timeseries to monthly by repeating the one value, or returns + the data passed, if already monthly. + + Args: + df (pd.DataFrame): The annual or monthly natural gas pricing data. + year (int): The resource year. + + Returns: + pd.DataFrame | None: Returns back the monthly data if the original data have either + 1 or 12 data entries, otherwise None is returned. + """ + match df.shape[0]: + case 12: + return df + case 1: + df = df.reindex(pd.date_range(f"{year}-01", f"{year}-12", freq="MS"), method="nearest") + return df + case _: + pass + + def get_eia_api_key(filename: Path) -> str: """Retrieves the EIA API key from a file, and returns the key following "EIA_API_KEY:". @@ -189,17 +211,9 @@ def load_data(self, filename: Path | None = None) -> pd.DataFrame: if filename.exists(): df = pd.read_csv(filename, parse_dates=["period"]).set_index("period") df = df.loc[df.index.dt.year.eq(self.config.resource_year)] - match df.shape[0]: - case 12: - return df - case 1: - year = self.config.resource_year - df = df.reindex( - pd.date_range(f"{year}-01", f"{year}-12", freq="MS"), method="nearest" - ) - return df - case _: - pass + df = convert_to_monthly(df, self.config.resource_year) + if df is not None: + return df r = requests.get(self.url) if r.status_code != 200: @@ -211,6 +225,7 @@ def load_data(self, filename: Path | None = None) -> pd.DataFrame: df = pd.DataFrame.from_dict(json.loads(r.text)["response"]["data"])[cols] df.period = pd.to_datetime(df.period) df = df.set_index("period") + df = convert_to_monthly(df) if filename is not None: df.to_csv(filename, index_label="period") From 80987d2f17aad8efac8f51e54ca9c93325dbf3e6 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:03:52 -0700 Subject: [PATCH 04/40] add openmdao --- h2integrate/resource/ng_price.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/h2integrate/resource/ng_price.py b/h2integrate/resource/ng_price.py index e0e81f90f..07a1a848f 100644 --- a/h2integrate/resource/ng_price.py +++ b/h2integrate/resource/ng_price.py @@ -5,6 +5,7 @@ import attrs import pandas as pd import requests +import openmdao.api as om from attrs import field, define from h2integrate.core.utilities import BaseConfig @@ -142,7 +143,7 @@ class EIAIndustrialNaturalGasConfig(BaseConfig): filename: str = field(default=None, converter=attrs.converters.optional(get_path)) -class EIAIndustrialNaturalGasResource: +class EIAIndustrialNaturalGasResource(om.ExplicitComponent): """EIA Industrial Natural Gas Pricing Downloader. See https://www.eia.gov/dnav/ng/hist/n3035us3m.htm for further details. From aaa333ad4db5d076181a99a0850b9dc55f1563ea Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:19:39 -0700 Subject: [PATCH 05/40] move feedstocks to its own folder --- h2integrate/core/supported_models.py | 2 +- h2integrate/feedstocks/__init__.py | 1 + .../{core => feedstocks}/feedstocks.py | 0 h2integrate/feedstocks/test/__init__.py | 0 h2integrate/feedstocks/test/conftest.py | 68 +++++++++++++++++++ .../test/test_feedstocks.py | 0 6 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 h2integrate/feedstocks/__init__.py rename h2integrate/{core => feedstocks}/feedstocks.py (100%) create mode 100644 h2integrate/feedstocks/test/__init__.py create mode 100644 h2integrate/feedstocks/test/conftest.py rename h2integrate/{core => feedstocks}/test/test_feedstocks.py (100%) diff --git a/h2integrate/core/supported_models.py b/h2integrate/core/supported_models.py index 62e197de9..0f12de1d9 100644 --- a/h2integrate/core/supported_models.py +++ b/h2integrate/core/supported_models.py @@ -1,6 +1,6 @@ +from h2integrate.feedstocks import FeedstockCostModel, FeedstockPerformanceModel from h2integrate.resource.river import RiverResource from h2integrate.resource.tidal import TidalResource -from h2integrate.core.feedstocks import FeedstockCostModel, FeedstockPerformanceModel from h2integrate.transporters.pipe import PipePerformanceModel from h2integrate.transporters.cable import CablePerformanceModel from h2integrate.converters.grid.grid import GridCostModel, GridPerformanceModel diff --git a/h2integrate/feedstocks/__init__.py b/h2integrate/feedstocks/__init__.py new file mode 100644 index 000000000..791d3f64e --- /dev/null +++ b/h2integrate/feedstocks/__init__.py @@ -0,0 +1 @@ +from h2integrate.feedstocks.feedstocks import FeedstockPerformanceModel, FeedstockCostModel diff --git a/h2integrate/core/feedstocks.py b/h2integrate/feedstocks/feedstocks.py similarity index 100% rename from h2integrate/core/feedstocks.py rename to h2integrate/feedstocks/feedstocks.py diff --git a/h2integrate/feedstocks/test/__init__.py b/h2integrate/feedstocks/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/h2integrate/feedstocks/test/conftest.py b/h2integrate/feedstocks/test/conftest.py new file mode 100644 index 000000000..378efd555 --- /dev/null +++ b/h2integrate/feedstocks/test/conftest.py @@ -0,0 +1,68 @@ +""" +Pytest configuration file. +""" + +import os + +from h2integrate import EXAMPLE_DIR + +from test.conftest import ( # noqa: F401 + temp_dir, + temp_dir_module, + temp_copy_of_example, + pytest_collection_modifyitems, + temp_copy_of_example_module_scope, +) + + +def pytest_sessionstart(session): + initial_om_report_setting = os.getenv("OPENMDAO_REPORTS") + if initial_om_report_setting is not None: + os.environ["TMP_OPENMDAO_REPORTS"] = initial_om_report_setting + + os.environ["OPENMDAO_REPORTS"] = "none" + + # set environment variables used for + # tests in h2integrate/core/test/test_recorder.py + os.environ["TEST_RECORDER_OUTPUT_EXAMPLE"] = "05_wind_h2_opt" + os.environ["TEST_RECORDER_OUTPUT_DIR"] = "testingtesting_output_dir" + os.environ["TEST_RECORDER_OUTPUT_FILE0"] = "testingtesting_filename.sql" + os.environ["TEST_RECORDER_OUTPUT_FILE1"] = "testingtesting_filename0.sql" + os.environ["TEST_RECORDER_OUTPUT_FILE2"] = "testingtesting_filename1.sql" + + +def pytest_sessionfinish(session, exitstatus): + initial_om_report_setting = os.getenv("TMP_OPENMDAO_REPORTS") + if initial_om_report_setting is not None: + os.environ["OPENMDAO_REPORTS"] = initial_om_report_setting + os.environ.pop("TMP_OPENMDAO_REPORTS", None) + + # remove files that were created in h2integrate/core/test/test_recorder.py + if os.getenv("TEST_RECORDER_OUTPUT_EXAMPLE") is not None: + test_dir = ( + EXAMPLE_DIR + / os.getenv("TEST_RECORDER_OUTPUT_EXAMPLE") + / os.getenv("TEST_RECORDER_OUTPUT_DIR") + ) + file0path = test_dir / os.getenv("TEST_RECORDER_OUTPUT_FILE0") + file1path = test_dir / os.getenv("TEST_RECORDER_OUTPUT_FILE1") + file2path = test_dir / os.getenv("TEST_RECORDER_OUTPUT_FILE2") + if file0path.exists(): + file0path.unlink() + if file1path.exists(): + file1path.unlink() + if file2path.exists(): + file2path.unlink() + # remove folder created in h2integrate/core/test/test_recorder.py + if test_dir.exists(): + files_in_test_folder = list(test_dir.iterdir()) + if len(files_in_test_folder) == 0: + test_dir.rmdir() + + # remove environment variables used for tests in + # h2integrate/core/test/test_recorder.py + os.environ.pop("TEST_RECORDER_OUTPUT_EXAMPLE", None) + os.environ.pop("TEST_RECORDER_OUTPUT_DIR", None) + os.environ.pop("TEST_RECORDER_OUTPUT_FILE0", None) + os.environ.pop("TEST_RECORDER_OUTPUT_FILE1", None) + os.environ.pop("TEST_RECORDER_OUTPUT_FILE2", None) diff --git a/h2integrate/core/test/test_feedstocks.py b/h2integrate/feedstocks/test/test_feedstocks.py similarity index 100% rename from h2integrate/core/test/test_feedstocks.py rename to h2integrate/feedstocks/test/test_feedstocks.py From f8fd1be7de56d8d788a58cf73d5dda50ddb55e53 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:27:38 -0700 Subject: [PATCH 06/40] move natural gas pricing to feedstocks and convert config for compliance --- .../eia_ng_pricing.py} | 184 ++++++++++++------ 1 file changed, 121 insertions(+), 63 deletions(-) rename h2integrate/{resource/ng_price.py => feedstocks/eia_ng_pricing.py} (54%) diff --git a/h2integrate/resource/ng_price.py b/h2integrate/feedstocks/eia_ng_pricing.py similarity index 54% rename from h2integrate/resource/ng_price.py rename to h2integrate/feedstocks/eia_ng_pricing.py index 07a1a848f..d3780260b 100644 --- a/h2integrate/resource/ng_price.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -3,16 +3,17 @@ from datetime import datetime import attrs +import numpy as np import pandas as pd import requests -import openmdao.api as om from attrs import field, define -from h2integrate.core.utilities import BaseConfig from h2integrate.core.file_utils import get_path +from h2integrate.core.model_baseclasses import BaseConfig, CostModelBaseClass -STATE_MAP = states = { +MCF_to_MMBTU = 1 / 0.964 +STATE_MAP = { "Alabama": "AL", "Alaska": "AK", "Arizona": "AZ", @@ -64,15 +65,21 @@ "Wisconsin": "WI", "Wyoming": "WY", "District of Columbia": "DC", - "American Samoa": "AS", - "Guam": "GU", - "Northern Mariana Islands": "MP", - "Puerto Rico": "PR", - "United States Minor Outlying Islands": "UM", - "Virgin Islands, U.S.": "VI", + "United States": "US", } CURRENT_YEAR = datetime.now().year +EIA_FACET = { + "wellhead": "N9190{}3", + "imports": "N9100{}3", + "citygate": "N3050{}3", + "residential": "N3010{}3", + "commercial": "N3020{}3", + "industrial": "N3035{}3", + "electrical_power": "N3045{}3", + "exports": "N9130{}3", +} + def convert_to_monthly(df: pd.DataFrame, year: int) -> pd.DataFrame | None: """Converts an annual timeseries to monthly by repeating the one value, or returns @@ -96,6 +103,33 @@ def convert_to_monthly(df: pd.DataFrame, year: int) -> pd.DataFrame | None: pass +def convert_state_value(state: str) -> str: + """Convert potential two-letter state abbreviations to upper case and all else to title + casing to align with the ``STATE_MAP`` keys and values. + + Args: + state (str): Either a two-letter state abbreviation or full state name. + + Returns: + str: Upper case state abbreviation or title case state name. + """ + if len(state) == 2: + return state.upper() + return state.title() + + +def convert_state_to_code(state: str) -> str: + """Converts the :py:attr:`state` name to a two-letter abbreviation or returns the input value. + + Args: + state (str): Full state name in title casing or two-letter state abbreviation in upper case. + + Returns: + str: Two-letter state abbreviation. + """ + return STATE_MAP.get(state, state) + + def get_eia_api_key(filename: Path) -> str: """Retrieves the EIA API key from a file, and returns the key following "EIA_API_KEY:". @@ -115,70 +149,68 @@ def get_eia_api_key(filename: Path) -> str: @define -class EIAIndustrialNaturalGasConfig(BaseConfig): - """EIA Industrial Natural Gas Pricing API Configuration. +class EIANaturalGasFeedstockConfig(BaseConfig): + """EIA Industrial Natural Gas Pricing API configuration and downloader for the US and all 50 US + states, in $/MCF, converted to $/MMBtu. Please see + https://www.eia.gov/opendata/browser/natural-gas/pri/sum for further details about data + availability. Args: - state (str): Two-letter, upper case state abbreviation. Must be one of the 50 U.S. states. + state (str): Full name of the state or two-letter state abbreviation, such as + "United States" or "US". Only the "US" or all 50 states will produce valid results. resource_year (int): The YYYY-format year whose data should be retrieved. Must be between 2001 and the current year, inclusive of endpoints. + cost_year (int): dollar-year for costs. Defaults to the current year. monthly (Path): True, if monthly data is desired, False if annual data is desired. api_key_file (Path): Full file name of the file where the API key is located. - latitude (float, optional): Latitude of the site, optional. - latitude (float, optional): Longitude of the site, optional. filename (str, optinal): Filename for where to save the data or where the data may - already be located. If the file exists, it will be used, and filtered to the - :py:attr:`resource_year`, otherwise, the data will be saved to this location. + already be located. If the file exists, the columns "period", "state", and "price" must + exist, otherwise the file will not be used. "period" should be of the form YYYY or + YYYY-MM, and state should be either the full state name or the two-letter abbreviation. + annual_cost (float, optional): fixed cost associated with the feedstock in USD/year. + Defaults to 0.0. + start_up_cost (float, optional): one-time capital cost associated with the feedstock in USD. + Defaults to 0.0. + commodity_rate_units (str): feedstock usage rate units (such as "galUS/h", "kg/h" or "kW") + commodity_amount_units (str, optional): the amount units of the commodity (i.e., + "galUS", "kg" or "kW*h"). If None, will be set as `commodity_rate_units*h` """ resource_year: int = field(validator=attrs.validators.in_(range(2001, CURRENT_YEAR + 1))) monthly: bool = field(validator=attrs.validators.instance_of(bool)) api_key_file: str = field(converter=get_path) state: str = field( - converter=str.upper, - validator=attrs.validators.in_(STATE_MAP.values()), + converter=attrs.converters.pipe(convert_state_value, convert_state_to_code), + validator=attrs.validators.in_([*STATE_MAP, *STATE_MAP.values()]), ) - latitude: float = field(default=0) - longitude: float = field(default=0) + price_category: str = field(converter=str.lower, validator=attrs.validators.in_(EIA_FACET)) + commodity_rate_units: str = field() + commodity_amount_units: str = field() + + url: str = field(init=False) + series: str = field(init=False) + price: np.ndarray = field(init=False) + cost_year: int = field(default=CURRENT_YEAR) + annual_cost: float = field(default=0.0, converter=float) + start_up_cost: float = field(default=0.0, converter=float) + commodity: str = field(default="natural_gas", init=False) filename: str = field(default=None, converter=attrs.converters.optional(get_path)) - -class EIAIndustrialNaturalGasResource(om.ExplicitComponent): - """EIA Industrial Natural Gas Pricing Downloader. - - See https://www.eia.gov/dnav/ng/hist/n3035us3m.htm for further details. - """ - - def __init__(self, resource_config: dict): - # self.setup() - self.config = EIAIndustrialNaturalGasConfig.from_dict( - # self.options["resource_config"], - resource_config, - additional_cls_name=self.__class__.__name__, - ) + def __attrs_post_init__(self): + """Creates the EIA natural gas facet series code based on validated user inputs, sets the + :py:attr:`commodity_amount_units` if not given a value, and fetches the EIA natural gas + price. + """ + self.series = EIA_FACET[self.price_category].format(self.state) + if self.commodity_amount_units is None: + self.commodity_amount_units = f"({self.commodity_rate_units})*h" self.url = self.create_url() - def initialize(self): - self.options.declare("plant_config", types=dict) - self.options.declare("resource_config", types=dict) - self.options.declare("driver_config", types=dict) - - def setup(self): - # Define inputs and outputs - self.config = EIAIndustrialNaturalGasConfig.from_dict( - self.options["resource_config"], - additional_cls_name=self.__class__.__name__, - ) - site_config = self.options["plant_config"]["site"] - self.add_input("latitude", site_config.get("latitude", 0.0), units="deg") - self.add_input("longitude", site_config.get("longitude", 0.0), units="deg") - self.add_output("price", shape=12, val=0.0, units="USD/(ft**3/1000)") - def create_url(self): base_url = "https://api.eia.gov/v2/natural-gas/pri/sum/data/" frequency = f"frequency={'monthly' if self.config.monthly else 'annual'}" data = "data[0]=value" - facet = f"facets[series][]=N3035{self.config.state}3" + facet = f"facets[series][]={self.config.series}" start = f"start={self.config.resource_year}" end = f"end={self.config.resource_year}" if self.config.monthly: @@ -192,9 +224,9 @@ def create_url(self): url = f"{base_url}?{url_opts}" return url - def load_data(self, filename: Path | None = None) -> pd.DataFrame: + def get_data(self, filename: Path | None = None) -> pd.DataFrame: """Loads the previously saved data from :py:attr:`filename` if ``resource_year`` - is available as either annual or monthly data, otherwise queries the EIA API. + is available as either annual or monthly data, otherwise data is retrieved from the EIA API. Args: filename (Path | None, optional): The full filename where the natural gas pricing data @@ -205,15 +237,18 @@ def load_data(self, filename: Path | None = None) -> pd.DataFrame: Returns: pandas.DataFrame: DataFrame with index "period" and column "value" with natural gas - pricing in $/MCF (USD per thousands of cubic feet) as either the monthly value - or extrapolated annual values to a monthly resolution. + pricing in $/MMBtu (converted from the EIA's USD per thousands of cubic feet) as + either the monthly value or extrapolated annual values to a monthly resolution. """ - filename = Path(filename).resolve() - if filename.exists(): - df = pd.read_csv(filename, parse_dates=["period"]).set_index("period") - df = df.loc[df.index.dt.year.eq(self.config.resource_year)] - df = convert_to_monthly(df, self.config.resource_year) - if df is not None: + if filename is None: + filename = self.filename + + if filename is not None: + filename = Path(filename).resolve() + if filename.exists(): + df = pd.read_csv(filename, parse_dates=["period"]).set_index("period") + df = df.loc[df.index.dt.year.eq(self.config.resource_year)] + df = convert_to_monthly(df, self.config.resource_year) return df r = requests.get(self.url) @@ -223,15 +258,38 @@ def load_data(self, filename: Path | None = None) -> pd.DataFrame: raise requests.exceptions.HTTPError(msg) cols = ["period", "value"] - df = pd.DataFrame.from_dict(json.loads(r.text)["response"]["data"])[cols] + df = pd.DataFrame.from_dict(json.loads(r.text)["response"]["data"]) + if df.size == 0: + raise ValueError(f"No data for combination {self.state=}, {self.price_category=}") + df.period = pd.to_datetime(df.period) - df = df.set_index("period") + df = df[cols].set_index("period") df = convert_to_monthly(df) if filename is not None: df.to_csv(filename, index_label="period") return df + +class EIANaturalGasFeedstockCostModel(CostModelBaseClass): + """ """ + + _time_step_bounds = (3600, 3600) # (min, max) time step lengths (seconds) allowed + + def setup(self): + # Define inputs and outputs + self.config = EIANaturalGasFeedstockConfig.from_dict( + self.options["resource_config"], + additional_cls_name=self.__class__.__name__, + ) + self.config.get_data() + self.n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) + int(self.options["plant_config"]["plant"]["plant_life"]) + site_config = self.options["plant_config"]["site"] + self.add_input("latitude", site_config.get("latitude", 0.0), units="deg") + self.add_input("longitude", site_config.get("longitude", 0.0), units="deg") + self.add_output("price", shape=12, val=0.0, units="USD/(ft**3/1000)") + def compute(self, inputs, outputs): ng_price_monthly = self.load_data(self.config.filename) outputs["price"] = ng_price_monthly.to_numpy() From 6e978f6d22c14c82c932a5f35535d5011676b115 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:07:22 -0700 Subject: [PATCH 07/40] update long comment and start building out NG cost model --- h2integrate/feedstocks/eia_ng_pricing.py | 120 +++++++++++++++++++---- h2integrate/feedstocks/feedstocks.py | 4 +- 2 files changed, 101 insertions(+), 23 deletions(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index d3780260b..a037ed58b 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -3,7 +3,6 @@ from datetime import datetime import attrs -import numpy as np import pandas as pd import requests from attrs import field, define @@ -12,6 +11,8 @@ from h2integrate.core.model_baseclasses import BaseConfig, CostModelBaseClass +HOURS_PER_YEAR = 8760 +SECONDS_PER_HOUR = 3600 MCF_to_MMBTU = 1 / 0.964 STATE_MAP = { "Alabama": "AL", @@ -171,9 +172,6 @@ class EIANaturalGasFeedstockConfig(BaseConfig): Defaults to 0.0. start_up_cost (float, optional): one-time capital cost associated with the feedstock in USD. Defaults to 0.0. - commodity_rate_units (str): feedstock usage rate units (such as "galUS/h", "kg/h" or "kW") - commodity_amount_units (str, optional): the amount units of the commodity (i.e., - "galUS", "kg" or "kW*h"). If None, will be set as `commodity_rate_units*h` """ resource_year: int = field(validator=attrs.validators.in_(range(2001, CURRENT_YEAR + 1))) @@ -184,16 +182,16 @@ class EIANaturalGasFeedstockConfig(BaseConfig): validator=attrs.validators.in_([*STATE_MAP, *STATE_MAP.values()]), ) price_category: str = field(converter=str.lower, validator=attrs.validators.in_(EIA_FACET)) - commodity_rate_units: str = field() - commodity_amount_units: str = field() url: str = field(init=False) series: str = field(init=False) - price: np.ndarray = field(init=False) + price: pd.DataFrame = field(init=False, validator=attrs.validators.instance_of(pd.DataFrame)) cost_year: int = field(default=CURRENT_YEAR) annual_cost: float = field(default=0.0, converter=float) start_up_cost: float = field(default=0.0, converter=float) commodity: str = field(default="natural_gas", init=False) + commodity_rate_units: str = field(default="MMBtu/h", init=False) + commodity_amount_units: str = field(default="MMBtu", init=False) filename: str = field(default=None, converter=attrs.converters.optional(get_path)) def __attrs_post_init__(self): @@ -204,9 +202,10 @@ def __attrs_post_init__(self): self.series = EIA_FACET[self.price_category].format(self.state) if self.commodity_amount_units is None: self.commodity_amount_units = f"({self.commodity_rate_units})*h" - self.url = self.create_url() + self.url = self.create_eia_api_url() + self.price = self.get_data() - def create_url(self): + def create_eia_api_url(self): base_url = "https://api.eia.gov/v2/natural-gas/pri/sum/data/" frequency = f"frequency={'monthly' if self.config.monthly else 'annual'}" data = "data[0]=value" @@ -249,7 +248,8 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: df = pd.read_csv(filename, parse_dates=["period"]).set_index("period") df = df.loc[df.index.dt.year.eq(self.config.resource_year)] df = convert_to_monthly(df, self.config.resource_year) - return df + if df is not None: + return df r = requests.get(self.url) if r.status_code != 200: @@ -265,6 +265,7 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: df.period = pd.to_datetime(df.period) df = df[cols].set_index("period") df = convert_to_monthly(df) + df.value *= MCF_to_MMBTU if filename is not None: df.to_csv(filename, index_label="period") @@ -282,14 +283,93 @@ def setup(self): self.options["resource_config"], additional_cls_name=self.__class__.__name__, ) - self.config.get_data() self.n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) - int(self.options["plant_config"]["plant"]["plant_life"]) - site_config = self.options["plant_config"]["site"] - self.add_input("latitude", site_config.get("latitude", 0.0), units="deg") - self.add_input("longitude", site_config.get("longitude", 0.0), units="deg") - self.add_output("price", shape=12, val=0.0, units="USD/(ft**3/1000)") - - def compute(self, inputs, outputs): - ng_price_monthly = self.load_data(self.config.filename) - outputs["price"] = ng_price_monthly.to_numpy() + + self.add_output("price", shape=12, val=self.config.price, units="USD/MMBtu") + + super().setup() + + self.dt = self.options["plant_config"]["plant"]["simulation"]["dt"] + self.plant_life = int(self.options["plant_config"]["plant"]["plant_life"]) + self.fraction_of_year_simulated = ( + self.dt / SECONDS_PER_HOUR * self.n_timesteps / HOURS_PER_YEAR + ) + + self.add_input( + f"{self.config.commodity}_consumed", + val=0.0, + shape=self.n_timesteps, + units=self.config.commodity_rate_units, + desc=f"Consumption profile of {self.config.commodity}", + ) + self.add_input( + f"{self.config.commodity}_out", + val=0, + shape=self.n_timesteps, + units=self.config.commodity_rate_units, + ) + self.add_input( + "price", + val=self.config.price, + units=f"USD/({self.config.commodity_amount_units})", + desc=f"Price profile of {self.config.commodity}", + ) + + self.add_output( + f"total_{self.config.commodity}_consumed", + val=0.0, + units=self.config.commodity_amount_units, + ) + + self.add_output( + f"annual_{self.config.commodity}_consumed", + val=0.0, + shape=self.plant_life, + units=f"({self.config.commodity_amount_units})/year", + ) + + # Capacity factor is feedstock_consumed/max_feedstock_available + self.add_output( + "capacity_factor", + val=0.0, + shape=self.plant_life, + units="unitless", + desc="Capacity factor", + ) + + # The should be equal to the commodity_capacity input of the FeedstockPerformanceModel + self.add_output( + f"rated_{self.config.commodity}_production", + val=0, + units=self.config.commodity_rate_units, + ) + + # lifetime estimate of item replacements, represented as a fraction of the capacity. + self.add_output("replacement_schedule", val=0.0, shape=self.plant_life, units="unitless") + + def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): + outputs["capacity_factor"] = ( + inputs[f"{self.config.commodity}_consumed"].sum() + / inputs[f"{self.config.commodity}_out"].sum() + ) + outputs[f"total_{self.config.commodity}_consumed"] = inputs[ + f"{self.config.commodity}_consumed" + ].sum() * (self.dt / 3600) + + # TODO: update to handle varying consumption levels when feedstock consumption is available + outputs[f"annual_{self.config.commodity}_consumed"] = outputs[ + f"total_{self.config.commodity}_consumed" + ] * (1 / self.fraction_of_year_simulated) + + outputs[f"rated_{self.config.commodity}_production"] = inputs[ + f"{self.config.commodity}_out" + ].max() + + # TODO: Calculate costs + price = inputs["price"] + hourly_consumption = inputs[f"{self.config.commodity}_consumed"] + cost_per_year = sum(price * hourly_consumption) + + outputs["CapEx"] = self.config.start_up_cost + outputs["OpEx"] = self.config.annual_cost + outputs["VarOpEx"] = cost_per_year diff --git a/h2integrate/feedstocks/feedstocks.py b/h2integrate/feedstocks/feedstocks.py index a0f457c3c..d6b7e553c 100644 --- a/h2integrate/feedstocks/feedstocks.py +++ b/h2integrate/feedstocks/feedstocks.py @@ -177,9 +177,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): f"{self.config.commodity}_consumed" ].sum() * (self.dt / 3600) - # Estimate annual consumption based on consumption over the simulation - # NOTE: once we standardize feedstock consumption outputs in models, this should - # be updated to handle consumption that varies over years of operation + # TODO: update to handle varying consumption levels when feedstock consumption is available outputs[f"annual_{self.config.commodity}_consumed"] = outputs[ f"total_{self.config.commodity}_consumed" ] * (1 / self.fraction_of_year_simulated) From 52a5bcbb0d57b0ff7f234ba96d76be26daf7e9ee Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:30:22 -0700 Subject: [PATCH 08/40] update compute docstring and include feedstocks folder in api docs --- docs/api.md | 1 + h2integrate/feedstocks/feedstocks.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/api.md b/docs/api.md index 6cf44f280..794ce6363 100644 --- a/docs/api.md +++ b/docs/api.md @@ -8,6 +8,7 @@ :recursive: core + feedstocks converters control finances diff --git a/h2integrate/feedstocks/feedstocks.py b/h2integrate/feedstocks/feedstocks.py index d6b7e553c..4717c5cbb 100644 --- a/h2integrate/feedstocks/feedstocks.py +++ b/h2integrate/feedstocks/feedstocks.py @@ -166,13 +166,21 @@ def setup(self): self.add_output("replacement_schedule", val=0.0, shape=plant_life, units="unitless") def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - # Capacity factor is the total amount consumed / the total amount available + """Calculates the following outputs: + + - ``capacity_factor``: commodity_consumed / commodity_out + - ``total_commodity_consumed``: sum of commodity_consumed divided by number + of hours simulated. + - ``anual_commodity_consumed``: :py:attr:`total_commodity_consumed` * (1 / years simulated) + - ``rated_commodity_production``: maximum input ``commodity_out``. + - ``CapEx``: :py:attr:`FeedstockCostConfig.start_up_cost`. + - ``OpEx``: :py:attr:`FeedstockCostConfig.annual_cost`. + - ``VarOpEx``: sum of (:py:attr:`FeedstockCostConfig.price` * input ``commodity_consumed``). + """ outputs["capacity_factor"] = ( inputs[f"{self.config.commodity}_consumed"].sum() / inputs[f"{self.config.commodity}_out"].sum() ) - - # Sum the amount consumed outputs[f"total_{self.config.commodity}_consumed"] = inputs[ f"{self.config.commodity}_consumed" ].sum() * (self.dt / 3600) @@ -186,7 +194,6 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): f"{self.config.commodity}_out" ].max() - # Calculate costs price = inputs["price"] hourly_consumption = inputs[f"{self.config.commodity}_consumed"] cost_per_year = sum(price * hourly_consumption) From 17b62dd34586b6623aef6d354e96adfc672128f8 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:59:08 -0700 Subject: [PATCH 09/40] update inline comment usage --- h2integrate/feedstocks/feedstocks.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/h2integrate/feedstocks/feedstocks.py b/h2integrate/feedstocks/feedstocks.py index 4717c5cbb..e1ac007b8 100644 --- a/h2integrate/feedstocks/feedstocks.py +++ b/h2integrate/feedstocks/feedstocks.py @@ -100,6 +100,7 @@ def setup(self): ) self.n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) plant_life = int(self.options["plant_config"]["plant"]["plant_life"]) + plant_life *= 1 # Set cost outputs super().setup() @@ -138,15 +139,12 @@ def setup(self): val=0.0, units=self.config.commodity_amount_units, ) - self.add_output( f"annual_{self.config.commodity}_consumed", val=0.0, shape=self.plant_life, units=f"({self.config.commodity_amount_units})/year", ) - - # Capacity factor is feedstock_consumed/max_feedstock_available self.add_output( "capacity_factor", val=0.0, @@ -154,17 +152,22 @@ def setup(self): units="unitless", desc="Capacity factor", ) + self.add_output( + "replacement_schedule", + val=0.0, + shape=self.plant_life, + units="unitless", + desc="Lifetime estimate of item replacements as a fraction of capacity", + ) - # The should be equal to the commodity_capacity input of the FeedstockPerformanceModel + # TODO: Update to the commodity_capacity input of the FeedstockPerformanceModel + # NOTE: Should I set this to rated_capacity if it's available? self.add_output( f"rated_{self.config.commodity}_production", val=0, units=self.config.commodity_rate_units, ) - # lifetime estimate of item replacements, represented as a fraction of the capacity. - self.add_output("replacement_schedule", val=0.0, shape=plant_life, units="unitless") - def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): """Calculates the following outputs: From febfa219e0d549cce712198d480f1e50253ed4d5 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:59:46 -0700 Subject: [PATCH 10/40] convert price to hourly over full year and update comments/docstrings --- h2integrate/feedstocks/eia_ng_pricing.py | 56 ++++++++++++++++++------ 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index a037ed58b..1c69fd72a 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -96,9 +96,9 @@ def convert_to_monthly(df: pd.DataFrame, year: int) -> pd.DataFrame | None: """ match df.shape[0]: case 12: - return df + return df.resample("MS").bfill() # ensure it's start of the month case 1: - df = df.reindex(pd.date_range(f"{year}-01", f"{year}-12", freq="MS"), method="nearest") + df = df.resample("MS").nearest() return df case _: pass @@ -248,6 +248,7 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: df = pd.read_csv(filename, parse_dates=["period"]).set_index("period") df = df.loc[df.index.dt.year.eq(self.config.resource_year)] df = convert_to_monthly(df, self.config.resource_year) + df = df.rename(columns={"value": "price"}) if df is not None: return df @@ -265,7 +266,8 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: df.period = pd.to_datetime(df.period) df = df[cols].set_index("period") df = convert_to_monthly(df) - df.value *= MCF_to_MMBTU + df = df.rename(columns={"value": "price"}) + df.price *= MCF_to_MMBTU if filename is not None: df.to_csv(filename, index_label="period") @@ -277,16 +279,28 @@ class EIANaturalGasFeedstockCostModel(CostModelBaseClass): _time_step_bounds = (3600, 3600) # (min, max) time step lengths (seconds) allowed + def _extrapolate_price(self) -> pd.DataFrame: + """Converts the monthly EIA price timeseries to an hourly time series for ``plant_life`` + number of years. + """ + price = self.config.price.copy() + + last = price.iloc[[-1]].resample("ME").ffill() + last.index = [pd.to_datetime(last.index[0].to_pydatetime().replace(hour=23))] + price = pd.concat((price, last)).resample("h").ffill() + return price + def setup(self): - # Define inputs and outputs + """Defines the inputs and outputs of the model and converts the + :py:attr:`EIANaturalGasFeedstockConfig.price` to an hourly timeseries for the + ``plant_life``. + """ self.config = EIANaturalGasFeedstockConfig.from_dict( self.options["resource_config"], additional_cls_name=self.__class__.__name__, ) self.n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) - self.add_output("price", shape=12, val=self.config.price, units="USD/MMBtu") - super().setup() self.dt = self.options["plant_config"]["plant"]["simulation"]["dt"] @@ -294,6 +308,7 @@ def setup(self): self.fraction_of_year_simulated = ( self.dt / SECONDS_PER_HOUR * self.n_timesteps / HOURS_PER_YEAR ) + price = self._extrapolate_price() self.add_input( f"{self.config.commodity}_consumed", @@ -310,7 +325,7 @@ def setup(self): ) self.add_input( "price", - val=self.config.price, + val=price.price.to_numpy(), units=f"USD/({self.config.commodity_amount_units})", desc=f"Price profile of {self.config.commodity}", ) @@ -320,15 +335,12 @@ def setup(self): val=0.0, units=self.config.commodity_amount_units, ) - self.add_output( f"annual_{self.config.commodity}_consumed", val=0.0, shape=self.plant_life, units=f"({self.config.commodity_amount_units})/year", ) - - # Capacity factor is feedstock_consumed/max_feedstock_available self.add_output( "capacity_factor", val=0.0, @@ -336,18 +348,34 @@ def setup(self): units="unitless", desc="Capacity factor", ) + self.add_output( + "replacement_schedule", + val=0.0, + shape=self.plant_life, + units="unitless", + desc="Lifetime estimate of item replacements as a fraction of capacity", + ) - # The should be equal to the commodity_capacity input of the FeedstockPerformanceModel + # TODO: Update to the commodity_capacity input of the FeedstockPerformanceModel + # NOTE: Should I set this to rated_capacity if it's available? self.add_output( f"rated_{self.config.commodity}_production", val=0, units=self.config.commodity_rate_units, ) - # lifetime estimate of item replacements, represented as a fraction of the capacity. - self.add_output("replacement_schedule", val=0.0, shape=self.plant_life, units="unitless") - def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): + """Calculates the following outputs: + + - ``capacity_factor``: commodity_consumed / commodity_out + - ``total_commodity_consumed``: sum of commodity_consumed divided by number + of hours simulated. + - ``anual_commodity_consumed``: :py:attr:`total_commodity_consumed` * (1 / years simulated) + - ``rated_commodity_production``: maximum input ``commodity_out``. + - ``CapEx``: :py:attr:`FeedstockCostConfig.start_up_cost`. + - ``OpEx``: :py:attr:`FeedstockCostConfig.annual_cost`. + - ``VarOpEx``: sum of (:py:attr:`FeedstockCostConfig.price` * input ``commodity_consumed``). + """ outputs["capacity_factor"] = ( inputs[f"{self.config.commodity}_consumed"].sum() / inputs[f"{self.config.commodity}_out"].sum() From fcf54c54db2bc698232c7a49b5604294abef937e Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:21:46 -0700 Subject: [PATCH 11/40] remove placeholder for lint ignore --- h2integrate/feedstocks/feedstocks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/h2integrate/feedstocks/feedstocks.py b/h2integrate/feedstocks/feedstocks.py index 2f237ec88..35344754b 100644 --- a/h2integrate/feedstocks/feedstocks.py +++ b/h2integrate/feedstocks/feedstocks.py @@ -101,7 +101,6 @@ def setup(self): ) self.n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) plant_life = int(self.options["plant_config"]["plant"]["plant_life"]) - plant_life *= 1 # Set cost outputs super().setup() From 7675156c00f45effb74ed49b70365aa1ae610839 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:22:32 -0700 Subject: [PATCH 12/40] ensure price timeseries matches n_timesteps --- h2integrate/feedstocks/eia_ng_pricing.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index 1c69fd72a..05d8bd4ab 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -279,7 +279,7 @@ class EIANaturalGasFeedstockCostModel(CostModelBaseClass): _time_step_bounds = (3600, 3600) # (min, max) time step lengths (seconds) allowed - def _extrapolate_price(self) -> pd.DataFrame: + def _extrapolate_price_to_hourly(self) -> pd.DataFrame: """Converts the monthly EIA price timeseries to an hourly time series for ``plant_life`` number of years. """ @@ -288,6 +288,12 @@ def _extrapolate_price(self) -> pd.DataFrame: last = price.iloc[[-1]].resample("ME").ffill() last.index = [pd.to_datetime(last.index[0].to_pydatetime().replace(hour=23))] price = pd.concat((price, last)).resample("h").ffill() + if price.shape[0] != self.n_timesteps: + msg = ( + "An error occurred converting EIA data to hourly to match size: " + f"{price.shape[0]} to simulation {self.n_timesteps=}" + ) + raise ValueError(msg) return price def setup(self): @@ -308,7 +314,7 @@ def setup(self): self.fraction_of_year_simulated = ( self.dt / SECONDS_PER_HOUR * self.n_timesteps / HOURS_PER_YEAR ) - price = self._extrapolate_price() + price = self._extrapolate_price_to_hourly() self.add_input( f"{self.config.commodity}_consumed", From 829835d820cf6f2028ed204ee7229ec87eaddc7c Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:18:28 -0700 Subject: [PATCH 13/40] add leap year handling for clean 8760s --- h2integrate/feedstocks/eia_ng_pricing.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index 05d8bd4ab..7eb19736e 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -287,7 +287,12 @@ def _extrapolate_price_to_hourly(self) -> pd.DataFrame: last = price.iloc[[-1]].resample("ME").ffill() last.index = [pd.to_datetime(last.index[0].to_pydatetime().replace(hour=23))] - price = pd.concat((price, last)).resample("h").ffill() + price = ( + pd.concat((price, last)) + .resample("h") + .ffill() + .drop(price.loc[(price.index.month == 2) & (price.index.day == 29)].index) + ) if price.shape[0] != self.n_timesteps: msg = ( "An error occurred converting EIA data to hourly to match size: " From d5baed6e3ddb31551c6eb7319715457cae3b7c60 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:24:35 -0700 Subject: [PATCH 14/40] update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 997316642..d4a65cc0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased + - Change commodity in DRI and EAF model from pig iron to sponge iron based on likely carbon content [PR 670](https://github.com/NatLabRockies/H2Integrate/pull/670) - Bugfix for round-trip efficiency handling when calling `check_inputs` around `StoragePerformanceModel` [PR 684](https://github.com/NatLabRockies/H2Integrate/pull/684) - Bugfix. Include nuclear in electricity producing tech list and improve error message for zero-length electricity producing techs in model when electricity is specified as the commodity. [PR 685](https://github.com/NatLabRockies/H2Integrate/pull/685) @@ -17,8 +18,13 @@ - Add `{commodity}_set_point` as an input to hydrogen fuel cell model [PR 709](https://github.com/NatLabRockies/H2Integrate/pull/709) - Rename `n_control_window` to `n_control_window_hours` for unit clarity [PR 712](https://github.com/NatLabRockies/H2Integrate/pull/712) - Update N2 diagram for demand openloop control from static and outdated to dynamic and interactive [PR 714](https://github.com/NatLabRockies/H2Integrate/pull/714) +- `feedstocks.py` has moved from `h2integrate/core/` to `h2_integrate/feedstocks` +- Creates the `EIANaturalGasFeedstockConfig` and `EIANaturalGasFeedstockCostModel` to load EIA + natural gas prices from file or to retrieve them from the EIA API. The model is able to retrieve + the US or any of the 50 states' annual or monthly values, which will be converted into an hourly timeseries. ## 0.8 [April 15, 2026] + - Updated README and docs intro page with expanded H2I description, reorganized sections, and streamlined installation instructions [PR 677](https://github.com/NatLabRockies/H2Integrate/pull/677) - Update energy conversion ratio in H2 SMR model [PR 606](https://github.com/NatLabRockies/H2Integrate/pull/606) - Update iron models and examples [PR 601](https://github.com/NatLabRockies/H2Integrate/pull/601) From adbb8d824b80f23f654ba5b4e23779dbc4f42efa Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 30 Apr 2026 17:00:46 -0700 Subject: [PATCH 15/40] fix typos, make api_key_file consistent, and enable environment variables --- h2integrate/feedstocks/eia_ng_pricing.py | 50 ++++++++++++++++-------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index 7eb19736e..6def47917 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -1,3 +1,4 @@ +import os import json from pathlib import Path from datetime import datetime @@ -131,22 +132,38 @@ def convert_state_to_code(state: str) -> str: return STATE_MAP.get(state, state) -def get_eia_api_key(filename: Path) -> str: +def get_eia_api_key(api_key_file: Path | None) -> str: """Retrieves the EIA API key from a file, and returns the key following "EIA_API_KEY:". Args: - filename (Path): Full file path and name of where the EIA API key is located. Must be - encoded as "EIA_API_KEY: xxxxxx" + api_key_file (Path, optional): Full file path and name of where the EIA API key is located. + If none is provided, then the API key is retrieved from the environment variables. Must + be encoded as "EIA_API_KEY: xxxxxx" + + Raises: + ValueError: Raised either if no file is provided and an environment variable has not be + defined, or if a filename is provided but "EIA_API_KEY" is not found. Returns: str: The EIA API key. """ - with filename.open() as f: + if api_key_file is None: + key = os.environ.get("EIA_API_KEY") + if key is None: + msg = ( + "No `api_key_file` provided for the EIA API, and 'EIA_API_KEY' is not defined as an" + "environment variable." + ) + raise ValueError(msg) + return key + + with api_key_file.open() as f: for line in f.readlines(): if ":" in line: name, val = line.strip().split(":") if name == "EIA_API_KEY": return val.strip() + raise ValueError(f"No 'EIA_API_KEY' defined in {api_key_file=}") @define @@ -163,8 +180,9 @@ class EIANaturalGasFeedstockConfig(BaseConfig): 2001 and the current year, inclusive of endpoints. cost_year (int): dollar-year for costs. Defaults to the current year. monthly (Path): True, if monthly data is desired, False if annual data is desired. - api_key_file (Path): Full file name of the file where the API key is located. - filename (str, optinal): Filename for where to save the data or where the data may + api_key_file (Path, optional): Full file name of the file where the API key is located. If + no file name is provided, then the environment variables ``EIA_API_KEY`` is used. + filename (str, optional): Filename for where to save the data or where the data may already be located. If the file exists, the columns "period", "state", and "price" must exist, otherwise the file will not be used. "period" should be of the form YYYY or YYYY-MM, and state should be either the full state name or the two-letter abbreviation. @@ -176,7 +194,7 @@ class EIANaturalGasFeedstockConfig(BaseConfig): resource_year: int = field(validator=attrs.validators.in_(range(2001, CURRENT_YEAR + 1))) monthly: bool = field(validator=attrs.validators.instance_of(bool)) - api_key_file: str = field(converter=get_path) + api_key_file: str | None = field(converter=attrs.converters.optional(get_path)) state: str = field( converter=attrs.converters.pipe(convert_state_value, convert_state_to_code), validator=attrs.validators.in_([*STATE_MAP, *STATE_MAP.values()]), @@ -207,17 +225,17 @@ def __attrs_post_init__(self): def create_eia_api_url(self): base_url = "https://api.eia.gov/v2/natural-gas/pri/sum/data/" - frequency = f"frequency={'monthly' if self.config.monthly else 'annual'}" + frequency = f"frequency={'monthly' if self.monthly else 'annual'}" data = "data[0]=value" - facet = f"facets[series][]={self.config.series}" - start = f"start={self.config.resource_year}" - end = f"end={self.config.resource_year}" - if self.config.monthly: + facet = f"facets[series][]={self.series}" + start = f"start={self.resource_year}" + end = f"end={self.resource_year}" + if self.monthly: start = f"{start}-01" end = f"{start}-12" sort_col = "sort[0][column]=period" sort_dir = "sort[0][direction]=asc" - api_key = f"api_key={get_eia_api_key(self.config.api_key_file)}" + api_key = f"api_key={get_eia_api_key(self.api_key_file)}" url_opts = "&".join((frequency, data, facet, start, end, sort_col, sort_dir, api_key)) url = f"{base_url}?{url_opts}" @@ -242,12 +260,13 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: if filename is None: filename = self.filename + cols = ["period", "value"] if filename is not None: filename = Path(filename).resolve() if filename.exists(): df = pd.read_csv(filename, parse_dates=["period"]).set_index("period") - df = df.loc[df.index.dt.year.eq(self.config.resource_year)] - df = convert_to_monthly(df, self.config.resource_year) + df = df.loc[df.index.dt.year.eq(self.resource_year) & df.state.eq(self.state)] + df = convert_to_monthly(df, self.resource_year) df = df.rename(columns={"value": "price"}) if df is not None: return df @@ -258,7 +277,6 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: msg = f"{err['code']}: {err['message']}" raise requests.exceptions.HTTPError(msg) - cols = ["period", "value"] df = pd.DataFrame.from_dict(json.loads(r.text)["response"]["data"]) if df.size == 0: raise ValueError(f"No data for combination {self.state=}, {self.price_category=}") From 2cbcfdc1d9af9c7af6ecc29d8e34c2a27050834b Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 30 Apr 2026 17:08:39 -0700 Subject: [PATCH 16/40] update docstrings and spacing --- h2integrate/feedstocks/eia_ng_pricing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index 6def47917..731c1f95a 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -180,6 +180,9 @@ class EIANaturalGasFeedstockConfig(BaseConfig): 2001 and the current year, inclusive of endpoints. cost_year (int): dollar-year for costs. Defaults to the current year. monthly (Path): True, if monthly data is desired, False if annual data is desired. + price_category (str): One of "wellhead", "imports", "citygate", "residential", "commercial", + "industrial", "electrical_power", or "exports". Note that not all categories will return + state-level data. api_key_file (Path, optional): Full file name of the file where the API key is located. If no file name is provided, then the environment variables ``EIA_API_KEY`` is used. filename (str, optional): Filename for where to save the data or where the data may @@ -200,7 +203,6 @@ class EIANaturalGasFeedstockConfig(BaseConfig): validator=attrs.validators.in_([*STATE_MAP, *STATE_MAP.values()]), ) price_category: str = field(converter=str.lower, validator=attrs.validators.in_(EIA_FACET)) - url: str = field(init=False) series: str = field(init=False) price: pd.DataFrame = field(init=False, validator=attrs.validators.instance_of(pd.DataFrame)) From ddba1d18fb5034d229c6904a686a1287dd8535f4 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 30 Apr 2026 17:11:33 -0700 Subject: [PATCH 17/40] add the EIA model to the docs page --- docs/technology_models/feedstocks.md | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/technology_models/feedstocks.md b/docs/technology_models/feedstocks.md index fed95df68..7fae7a1df 100644 --- a/docs/technology_models/feedstocks.md +++ b/docs/technology_models/feedstocks.md @@ -32,6 +32,7 @@ technology_interconnections: [ ``` Where: + - `name_of_feedstock_source`: Name of your feedstock source - `consuming_technology`: Technology that uses the feedstock - `commodity`: Type identifier (e.g., "natural_gas", "water", "electricity") @@ -85,9 +86,54 @@ The `price` parameter is flexible - you can specify constant pricing with a sing ``` ### Consumed Feedstock Outputs + The feedstock model outputs cost and performance information about the consumed feedstock. The most notable outputs are: - `VarOpEx`: cost of the feedstock consumed (in `USD/yr`) - `total_{commodity}_consumed`: total feedstock consumed over simulation (in `commodity_amount_units`) - `annual_{commodity}_consumed`: annual feedstock consumed (in `commodity_amount_units/yr`) - `rated_{commodity}_production`: this is equal to the the `rated_capacity` of the feedstock model (in `commodity_rate_units`) - `capacity_factor`: ratio of the feedstock consumed to the maximum feedstock available + +## EIA Natural Gas Pricing + +A special case of the feedstock cost model `EIANaturalGasFeedstockCostModel` (see +[the relevant API docs](https://h2integrate.readthedocs.io/en/latest/_autosummary/h2integrate.feedstocks.eia_ng_pricing.html) +for complete details) exists to enable users to download data from the EIA API's natural gas price +portal. Access to the wellhead, import, citygate, residential, commercial, industrial, electrical +power, and exports price facets are supported for all 50 US states and the US as a whole, though +it is best to see which data are +[availble online in the EIA API documentation](https://www.eia.gov/opendata/browser/natural-gas/pri/sum) +prior to using in an analysis. + +Users are expected to get an EIA API key from the +[EIA Open Data portal](https://www.eia.gov/opendata/), or to download the data as as CSV file for +the model to load. + +At present, the EIA natural gas cost model uses only a single year of price data (annual or monthly) +and extrapolates it to an hourly timeseries automatically. + +### Configuring the EIA Cost Model + +Similar to the standard feedstock model, the following variables are able to be set by the user + +- `cost_year` (int): Dollar year for cost inputs +- `annual_cost` (float, optional): Fixed cost per year in USD/year. Defaults to 0.0 +- `start_up_cost` (float, optional): One-time capital cost in USD. Defaults to 0.0 + +Additionally, there are a few other settings that users will need to provide for the model to work +that differ from the standard cost model. + +- `resource_year` (int): Which year to obtain the data from in the range [2001, 2026]. +- `monthly` (bool): If True, the monthly data are retrieved, otherwise the annual price is used. +- `state` (str): Full name of the state or two-letter state abbreviation, such as "United States" or + "US". Only the "US" or all 50 states will produce valid results +- `price_category` (str): One of "wellhead", "imports", "citygate", "residential", "commercial", + "industrial", "electrical_power", or "exports". Note that not all categories will return + state-level data. +- `api_key_file` (str, optional): The file where the user's API key is stored. If storing in a file, + define it on its own line using the convention "EIA_API_KEY: xxxx", or have the API key defined as + an environment variable set as "EIA_API_KEY". + +- `commodity`: Set to "natural_gas" internally. +- `commodity_rate_units`: Set to "MMBtu/h" internally. +- `commodity_amount_units`: Set to "MMBtu" internally. From d714c6d0cb87cd3304854f843578d57dbbde337f Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 30 Apr 2026 17:11:57 -0700 Subject: [PATCH 18/40] add requests instead of relying on other packages requiring it --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 0a67eb712..942e7e98e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ dependencies = [ "ProFAST", "Pyomo>=6.1.2", "rainflow", + "requests", "requests-cache", "retry_requests", "rich>=13.7.0", From 786bd0e8ac2a76b0d2506cb9cb10c160d1fd65cf Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 30 Apr 2026 17:35:25 -0700 Subject: [PATCH 19/40] add to supported models --- h2integrate/core/supported_models.py | 7 ++++++- h2integrate/feedstocks/__init__.py | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/h2integrate/core/supported_models.py b/h2integrate/core/supported_models.py index 0f12de1d9..628578b09 100644 --- a/h2integrate/core/supported_models.py +++ b/h2integrate/core/supported_models.py @@ -1,4 +1,8 @@ -from h2integrate.feedstocks import FeedstockCostModel, FeedstockPerformanceModel +from h2integrate.feedstocks import ( + FeedstockCostModel, + FeedstockPerformanceModel, + EIANaturalGasFeedstockCostModel, +) from h2integrate.resource.river import RiverResource from h2integrate.resource.tidal import TidalResource from h2integrate.transporters.pipe import PipePerformanceModel @@ -310,6 +314,7 @@ # Feedstock "FeedstockPerformanceModel": FeedstockPerformanceModel, "FeedstockCostModel": FeedstockCostModel, + "EIANaturalGasFeedstockCostModel": EIANaturalGasFeedstockCostModel, # Grid "GridPerformanceModel": GridPerformanceModel, "GridCostModel": GridCostModel, diff --git a/h2integrate/feedstocks/__init__.py b/h2integrate/feedstocks/__init__.py index 791d3f64e..f65cb6562 100644 --- a/h2integrate/feedstocks/__init__.py +++ b/h2integrate/feedstocks/__init__.py @@ -1 +1,2 @@ from h2integrate.feedstocks.feedstocks import FeedstockPerformanceModel, FeedstockCostModel +from h2integrate.feedstocks.eia_ng_pricing import EIANaturalGasFeedstockCostModel From 789a41746ae92a79064c2fe84475c719bdb1cfc1 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 30 Apr 2026 17:37:33 -0700 Subject: [PATCH 20/40] update class hierarchy --- docs/_static/class_hierarchy.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/_static/class_hierarchy.html b/docs/_static/class_hierarchy.html index acd5c8d55..57f48046e 100644 --- a/docs/_static/class_hierarchy.html +++ b/docs/_static/class_hierarchy.html @@ -380,8 +380,8 @@

// parsing and collecting nodes and edges from the python - nodes = new vis.DataSet([{"borderWidth": 5.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PyomoRuleBaseClass", "label": "PyomoRuleBaseClass", "shape": "hexagon", "size": 19.5, "title": "PyomoRuleBaseClass\ncontrol/control_rules/pyomo_rule_baseclass.py\n[Control / General]", "x": 654.0, "y": 0.0}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PyomoDispatchGenericConverter", "label": "PyomoDispatchGenericConverter", "shape": "dot", "size": 18.0, "title": "PyomoDispatchGenericConverter\ncontrol/control_rules/converters/generic_converter.py\n[Converter / Other]", "x": 354.00000000000006, "y": 519.6152422706632}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PyomoRuleStorageBaseclass", "label": "PyomoRuleStorageBaseclass", "shape": "diamond", "size": 18.0, "title": "PyomoRuleStorageBaseclass\ncontrol/control_rules/storage/pyomo_storage_rule_baseclass.py\n[Storage / General]", "x": 354.00000000000006, "y": -519.6152422706632}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HeuristicLoadFollowingController", "label": "HeuristicLoadFollowingController", "shape": "hexagon", "size": 18.0, "title": "HeuristicLoadFollowingController\ncontrol/control_strategies/heuristic_pyomo_controller.py\n[Control / General]", "x": 681.5146849936184, "y": 83.93066263524416}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "OptimizedDispatchController", "label": "OptimizedDispatchController", "shape": "hexagon", "size": 18.0, "title": "OptimizedDispatchController\ncontrol/control_strategies/optimized_pyomo_controller.py\n[Control / General]", "x": 595.9704659224221, "y": 137.9411572197277}, {"borderWidth": 5.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PyomoControllerBaseClass", "label": "PyomoControllerBaseClass", "shape": "hexagon", "size": 19.5, "title": "PyomoControllerBaseClass\ncontrol/control_strategies/pyomo_controller_baseclass.py\n[Control / General]", "x": 490.497033242125, "y": 100.30628231184586}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DemandOpenLoopConverterController", "label": "DemandOpenLoopConverterController", "shape": "dot", "size": 18.0, "title": "DemandOpenLoopConverterController\ncontrol/control_strategies/converters/demand_openloop_converter_controller.py\n[Converter / Other]", "x": 381.5146849936184, "y": 603.5459049059074}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "FlexibleDemandOpenLoopConverterController", "label": "FlexibleDemandOpenLoopConverterController", "shape": "dot", "size": 18.0, "title": "FlexibleDemandOpenLoopConverterController\ncontrol/control_strategies/converters/flexible_demand_openloop_controller.py\n[Converter / Other]", "x": 295.9704659224222, "y": 657.556399490391}, {"borderWidth": 5.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ConverterOpenLoopControlBase", "label": "ConverterOpenLoopControlBase", "shape": "dot", "size": 19.5, "title": "ConverterOpenLoopControlBase\ncontrol/control_strategies/converters/openloop_controller_base.py\n[Converter / Other]", "x": 190.49703324212504, "y": 619.921524582509}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DemandOpenLoopStorageController", "label": "DemandOpenLoopStorageController", "shape": "diamond", "size": 18.0, "title": "DemandOpenLoopStorageController\ncontrol/control_strategies/storage/demand_openloop_storage_controller.py\n[Storage / General]", "x": 381.5146849936184, "y": -435.684579635419}, {"borderWidth": 5.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "StorageOpenLoopControlBase", "label": "StorageOpenLoopControlBase", "shape": "diamond", "size": 19.5, "title": "StorageOpenLoopControlBase\ncontrol/control_strategies/storage/openloop_storage_control_base.py\n[Storage / General]", "x": 295.9704659224222, "y": -381.6740850509355}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleStorageOpenLoopController", "label": "SimpleStorageOpenLoopController", "shape": "diamond", "size": 18.0, "title": "SimpleStorageOpenLoopController\ncontrol/control_strategies/storage/simple_openloop_controller.py\n[Storage / General]", "x": 190.49703324212504, "y": -419.30895995881735}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GenericConverterCostModel", "label": "GenericConverterCostModel", "shape": "dot", "size": 18.0, "title": "GenericConverterCostModel\nconverters/generic_converter_cost.py\n[Converter / Other]", "x": 145.46396870697228, "y": 510.5789248680738}, {"borderWidth": 3.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "AmmoniaSynLoopPerformanceModel", "label": "AmmoniaSynLoopPerformanceModel", "shape": "dot", "size": 18.0, "title": "AmmoniaSynLoopPerformanceModel\nconverters/ammonia/ammonia_synloop.py\n[Converter / Ammonia]", "x": 196.07066428268575, "y": 399.2836455167026}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "AmmoniaSynLoopCostModel", "label": "AmmoniaSynLoopCostModel", "shape": "dot", "size": 18.0, "title": "AmmoniaSynLoopCostModel\nconverters/ammonia/ammonia_synloop.py\n[Converter / Ammonia]", "x": 314.1748353171905, "y": 358.23657563925707}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleAmmoniaPerformanceModel", "label": "SimpleAmmoniaPerformanceModel", "shape": "dot", "size": 18.0, "title": "SimpleAmmoniaPerformanceModel\nconverters/ammonia/simple_ammonia_model.py\n[Converter / Ammonia]", "x": 427.3866955453086, "y": 415.9296970001345}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleAmmoniaCostModel", "label": "SimpleAmmoniaCostModel", "shape": "dot", "size": 18.0, "title": "SimpleAmmoniaCostModel\nconverters/ammonia/simple_ammonia_model.py\n[Converter / Ammonia]", "x": 464.86869651385996, "y": 538.9624102758452}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DOCPerformanceModel", "label": "DOCPerformanceModel", "shape": "dot", "size": 18.0, "title": "DOCPerformanceModel\nconverters/co2/marine/direct_ocean_capture.py\n[Converter / CO2]", "x": 401.8380100526995, "y": 652.4752426790114}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DOCCostModel", "label": "DOCCostModel", "shape": "dot", "size": 18.0, "title": "DOCCostModel\nconverters/co2/marine/direct_ocean_capture.py\n[Converter / CO2]", "x": 275.4766306653483, "y": 686.367077656095}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "OAEPerformanceModel", "label": "OAEPerformanceModel", "shape": "dot", "size": 18.0, "title": "OAEPerformanceModel\nconverters/co2/marine/ocean_alkalinity_enhancement.py\n[Converter / CO2]", "x": 162.51973411654936, "y": 618.7587064658168}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "OAECostModel", "label": "OAECostModel", "shape": "dot", "size": 18.0, "title": "OAECostModel\nconverters/co2/marine/ocean_alkalinity_enhancement.py\n[Converter / CO2]", "x": 132.30008363629267, "y": 489.9260504531493}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "OAECostAndFinancialModel", "label": "OAECostAndFinancialModel", "shape": "dot", "size": 18.0, "title": "OAECostAndFinancialModel\nconverters/co2/marine/ocean_alkalinity_enhancement.py\n[Converter / CO2]", "x": 204.07169197994392, "y": 378.05691605701645}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GridPerformanceModel", "label": "GridPerformanceModel", "shape": "dot", "size": 18.0, "title": "GridPerformanceModel\nconverters/grid/grid.py\n[Converter / Grid]", "x": 334.8356346312979, "y": 351.5883439482972}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GridCostModel", "label": "GridCostModel", "shape": "dot", "size": 18.0, "title": "GridCostModel\nconverters/grid/grid.py\n[Converter / Grid]", "x": 445.2483626468303, "y": 427.2576287598383}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HOPPComponent", "label": "HOPPComponent", "shape": "dot", "size": 18.0, "title": "HOPPComponent\nconverters/hopp/hopp_wrapper.py\n[Converter / HOPP]", "x": 467.89945575036916, "y": 559.5711144381876}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "BasicElectrolyzerCostModel", "label": "BasicElectrolyzerCostModel", "shape": "dot", "size": 18.0, "title": "BasicElectrolyzerCostModel\nconverters/hydrogen/basic_cost_model.py\n[Converter / Hydrogen]", "x": 388.52479069439454, "y": 668.2502362408371}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CustomElectrolyzerCostModel", "label": "CustomElectrolyzerCostModel", "shape": "dot", "size": 18.0, "title": "CustomElectrolyzerCostModel\nconverters/hydrogen/custom_electrolyzer_cost_model.py\n[Converter / Hydrogen]", "x": 254.95587519115207, "y": 687.0298062682064}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ElectrolyzerPerformanceBaseClass", "label": "ElectrolyzerPerformanceBaseClass", "shape": "dot", "size": 18.75, "title": "ElectrolyzerPerformanceBaseClass\nconverters/hydrogen/electrolyzer_baseclass.py\n[Converter / Hydrogen]", "x": 148.23188605097528, "y": 604.1026945245679}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ElectrolyzerCostBaseClass", "label": "ElectrolyzerCostBaseClass", "shape": "dot", "size": 20.25, "title": "ElectrolyzerCostBaseClass\nconverters/hydrogen/electrolyzer_baseclass.py\n[Converter / Hydrogen]", "x": 133.36725042373115, "y": 469.52006517094185}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "LinearH2FuelCellPerformanceModel", "label": "LinearH2FuelCellPerformanceModel", "shape": "dot", "size": 18.0, "title": "LinearH2FuelCellPerformanceModel\nconverters/hydrogen/h2_fuel_cell.py\n[Converter / Hydrogen]", "x": 219.71628617567444, "y": 364.9365146910501}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "H2FuelCellCostModel", "label": "H2FuelCellCostModel", "shape": "dot", "size": 18.0, "title": "H2FuelCellCostModel\nconverters/hydrogen/h2_fuel_cell.py\n[Converter / Hydrogen]", "x": 355.1041492280802, "y": 354.021250329648}, {"borderWidth": 2.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ECOElectrolyzerPerformanceModel", "label": "ECOElectrolyzerPerformanceModel", "shape": "dot", "size": 18.75, "title": "ECOElectrolyzerPerformanceModel\nconverters/hydrogen/pem_electrolyzer.py\n[Converter / Hydrogen]", "x": 457.3868701242386, "y": 443.67489981359984}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SingliticoCostModel", "label": "SingliticoCostModel", "shape": "dot", "size": 18.0, "title": "SingliticoCostModel\nconverters/hydrogen/singlitico_cost_model.py\n[Converter / Hydrogen]", "x": 464.32599185808436, "y": 579.6816222169927}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SteamMethaneReformerPerformanceModel", "label": "SteamMethaneReformerPerformanceModel", "shape": "dot", "size": 18.0, "title": "SteamMethaneReformerPerformanceModel\nconverters/hydrogen/steam_methane_reformer.py\n[Converter / Hydrogen]", "x": 371.4771426730072, "y": 679.5211142634946}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SteamMethaneReformerCostModel", "label": "SteamMethaneReformerCostModel", "shape": "dot", "size": 18.0, "title": "SteamMethaneReformerCostModel\nconverters/hydrogen/steam_methane_reformer.py\n[Converter / Hydrogen]", "x": 235.02263686980024, "y": 682.4640066455394}, {"borderWidth": 1, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WOMBATElectrolyzerModel", "label": "WOMBATElectrolyzerModel", "shape": "dot", "size": 18.0, "title": "WOMBATElectrolyzerModel\nconverters/hydrogen/wombat_model.py\n[Converter / Hydrogen]", "x": 137.75520350257173, "y": 586.5247035561051}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "AspenGeoH2SurfacePerformanceModel", "label": "AspenGeoH2SurfacePerformanceModel", "shape": "dot", "size": 18.0, "title": "AspenGeoH2SurfacePerformanceModel\nconverters/hydrogen/geologic/aspen_surface_processing.py\n[Converter / Hydrogen]", "x": 138.82266933071406, "y": 449.78253020788407}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "AspenGeoH2SurfaceCostModel", "label": "AspenGeoH2SurfaceCostModel", "shape": "dot", "size": 18.0, "title": "AspenGeoH2SurfaceCostModel\nconverters/hydrogen/geologic/aspen_surface_processing.py\n[Converter / Hydrogen]", "x": 237.75031112092074, "y": 355.20533917946307}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GeoH2SubsurfacePerformanceBaseClass", "label": "GeoH2SubsurfacePerformanceBaseClass", "shape": "dot", "size": 19.5, "title": "GeoH2SubsurfacePerformanceBaseClass\nconverters/hydrogen/geologic/h2_well_subsurface_baseclass.py\n[Converter / Hydrogen]", "x": 374.6281389037414, "y": 360.29187496794947}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GeoH2SubsurfaceCostBaseClass", "label": "GeoH2SubsurfaceCostBaseClass", "shape": "dot", "size": 18.75, "title": "GeoH2SubsurfaceCostBaseClass\nconverters/hydrogen/geologic/h2_well_subsurface_baseclass.py\n[Converter / Hydrogen]", "x": 466.4055623972124, "y": 462.10704538085054}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GeoH2SurfacePerformanceBaseClass", "label": "GeoH2SurfacePerformanceBaseClass", "shape": "dot", "size": 18.75, "title": "GeoH2SurfacePerformanceBaseClass\nconverters/hydrogen/geologic/h2_well_surface_baseclass.py\n[Converter / Hydrogen]", "x": 457.29625514695636, "y": 598.974685153}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GeoH2SurfaceCostBaseClass", "label": "GeoH2SurfaceCostBaseClass", "shape": "dot", "size": 18.75, "title": "GeoH2SurfaceCostBaseClass\nconverters/hydrogen/geologic/h2_well_surface_baseclass.py\n[Converter / Hydrogen]", "x": 352.69394266907716, "y": 687.8500909879936}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GeoH2SubsurfaceCostModel", "label": "GeoH2SubsurfaceCostModel", "shape": "dot", "size": 18.0, "title": "GeoH2SubsurfaceCostModel\nconverters/hydrogen/geologic/mathur_modified.py\n[Converter / Hydrogen]", "x": 215.97749629487083, "y": 674.7190103458506}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NaturalGeoH2PerformanceModel", "label": "NaturalGeoH2PerformanceModel", "shape": "dot", "size": 18.0, "title": "NaturalGeoH2PerformanceModel\nconverters/hydrogen/geologic/simple_natural_geoh2.py\n[Converter / Hydrogen]", "x": 130.10006459565557, "y": 567.4301139070021}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "StimulatedGeoH2PerformanceModel", "label": "StimulatedGeoH2PerformanceModel", "shape": "dot", "size": 18.0, "title": "StimulatedGeoH2PerformanceModel\nconverters/hydrogen/geologic/templeton_serpentinization.py\n[Converter / Hydrogen]", "x": 147.24746231494296, "y": 431.00196408502995}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HumbertEwinPerformanceComponent", "label": "HumbertEwinPerformanceComponent", "shape": "dot", "size": 18.0, "title": "HumbertEwinPerformanceComponent\nconverters/iron/humbert_ewin_perf.py\n[Converter / Iron]", "x": 257.1218087348483, "y": 348.2128765835568}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HumbertStinnEwinCostComponent", "label": "HumbertStinnEwinCostComponent", "shape": "dot", "size": 18.0, "title": "HumbertStinnEwinCostComponent\nconverters/iron/humbert_stinn_ewin_cost.py\n[Converter / Iron]", "x": 393.127798919391, "y": 369.3668678529177}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "IronReductionPlantBasePerformanceComponent", "label": "IronReductionPlantBasePerformanceComponent", "shape": "dot", "size": 19.5, "title": "IronReductionPlantBasePerformanceComponent\nconverters/iron/iron_dri_base.py\n[Converter / Iron]", "x": 472.7432425021856, "y": 481.72468051933606}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "IronReductionPlantBaseCostComponent", "label": "IronReductionPlantBaseCostComponent", "shape": "dot", "size": 19.5, "title": "IronReductionPlantBaseCostComponent\nconverters/iron/iron_dri_base.py\n[Converter / Iron]", "x": 447.5964940759568, "y": 617.1774165566931}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HydrogenIronReductionPlantCostComponent", "label": "HydrogenIronReductionPlantCostComponent", "shape": "dot", "size": 18.0, "title": "HydrogenIronReductionPlantCostComponent\nconverters/iron/iron_dri_plant.py\n[Converter / Iron]", "x": 332.8582311277839, "y": 693.5386065641161}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NaturalGasIronReductionPlantCostComponent", "label": "NaturalGasIronReductionPlantCostComponent", "shape": "dot", "size": 18.0, "title": "NaturalGasIronReductionPlantCostComponent\nconverters/iron/iron_dri_plant.py\n[Converter / Iron]", "x": 198.08741067383664, "y": 664.4169227814615}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HydrogenIronReductionPlantPerformanceComponent", "label": "HydrogenIronReductionPlantPerformanceComponent", "shape": "dot", "size": 18.0, "title": "HydrogenIronReductionPlantPerformanceComponent\nconverters/iron/iron_dri_plant.py\n[Converter / Iron]", "x": 125.05667606340373, "y": 547.4023747662839}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NaturalGasIronReductionPlantPerformanceComponent", "label": "NaturalGasIronReductionPlantPerformanceComponent", "shape": "dot", "size": 18.0, "title": "NaturalGasIronReductionPlantPerformanceComponent\nconverters/iron/iron_dri_plant.py\n[Converter / Iron]", "x": 158.13159569226642, "y": 413.4399350698954}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "IronTransportCostComponent", "label": "IronTransportCostComponent", "shape": "dot", "size": 18.0, "title": "IronTransportCostComponent\nconverters/iron/iron_transport.py\n[Converter / Iron]", "x": 277.3170451038727, "y": 343.8116602929422}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MartinIronMineCostComponent", "label": "MartinIronMineCostComponent", "shape": "dot", "size": 18.0, "title": "MartinIronMineCostComponent\nconverters/iron/martin_mine_cost_model.py\n[Converter / Iron]", "x": 410.34667103931827, "y": 380.8143317189373}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MartinIronMinePerformanceComponent", "label": "MartinIronMinePerformanceComponent", "shape": "dot", "size": 18.0, "title": "MartinIronMinePerformanceComponent\nconverters/iron/martin_mine_perf_model.py\n[Converter / Iron]", "x": 476.5045216453125, "y": 502.0640467626806}, {"borderWidth": 3.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CO2HMethanolPlantPerformanceModel", "label": "CO2HMethanolPlantPerformanceModel", "shape": "dot", "size": 18.0, "title": "CO2HMethanolPlantPerformanceModel\nconverters/methanol/co2h_methanol_plant.py\n[Converter / Methanol]", "x": 435.6032844812971, "y": 634.0383483160249}, {"borderWidth": 3.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CO2HMethanolPlantCostModel", "label": "CO2HMethanolPlantCostModel", "shape": "dot", "size": 18.0, "title": "CO2HMethanolPlantCostModel\nconverters/methanol/co2h_methanol_plant.py\n[Converter / Methanol]", "x": 312.397198395929, "y": 696.6617329784204}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CO2HMethanolPlantFinanceModel", "label": "CO2HMethanolPlantFinanceModel", "shape": "dot", "size": 18.0, "title": "CO2HMethanolPlantFinanceModel\nconverters/methanol/co2h_methanol_plant.py\n[Converter / Methanol]", "x": 181.59887823138087, "y": 651.8947433487028}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MethanolPerformanceBaseClass", "label": "MethanolPerformanceBaseClass", "shape": "dot", "size": 19.5, "title": "MethanolPerformanceBaseClass\nconverters/methanol/methanol_baseclass.py\n[Converter / Methanol]", "x": 122.57016631214177, "y": 526.8414257008284}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MethanolCostBaseClass", "label": "MethanolCostBaseClass", "shape": "dot", "size": 19.5, "title": "MethanolCostBaseClass\nconverters/methanol/methanol_baseclass.py\n[Converter / Methanol]", "x": 171.16653718660294, "y": 397.3379277912476}, {"borderWidth": 5.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MethanolFinanceBaseClass", "label": "MethanolFinanceBaseClass", "shape": "dot", "size": 19.5, "title": "MethanolFinanceBaseClass\nconverters/methanol/methanol_baseclass.py\n[Converter / Methanol]", "x": 297.9567320757243, "y": 341.96032536534904}, {"borderWidth": 3.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SMRMethanolPlantPerformanceModel", "label": "SMRMethanolPlantPerformanceModel", "shape": "dot", "size": 18.0, "title": "SMRMethanolPlantPerformanceModel\nconverters/methanol/smr_methanol_plant.py\n[Converter / Methanol]", "x": 426.0483695992057, "y": 394.3462137236294}, {"borderWidth": 3.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SMRMethanolPlantCostModel", "label": "SMRMethanolPlantCostModel", "shape": "dot", "size": 18.0, "title": "SMRMethanolPlantCostModel\nconverters/methanol/smr_methanol_plant.py\n[Converter / Methanol]", "x": 477.7221481121271, "y": 522.7617594581504}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SMRMethanolPlantFinanceModel", "label": "SMRMethanolPlantFinanceModel", "shape": "dot", "size": 18.0, "title": "SMRMethanolPlantFinanceModel\nconverters/methanol/smr_methanol_plant.py\n[Converter / Methanol]", "x": 421.5900357312307, "y": 649.3263064239829}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleGasProducerPerformance", "label": "SimpleGasProducerPerformance", "shape": "dot", "size": 18.0, "title": "SimpleGasProducerPerformance\nconverters/natural_gas/dummy_gas_components.py\n[Converter / Natural Gas]", "x": 291.6617846858414, "y": 697.2472342697002}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleGasConsumerPerformance", "label": "SimpleGasConsumerPerformance", "shape": "dot", "size": 18.0, "title": "SimpleGasConsumerPerformance\nconverters/natural_gas/dummy_gas_components.py\n[Converter / Natural Gas]", "x": 166.7377307793895, "y": 637.4155597628255}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleGasProducerCost", "label": "SimpleGasProducerCost", "shape": "dot", "size": 18.0, "title": "SimpleGasProducerCost\nconverters/natural_gas/dummy_gas_components.py\n[Converter / Natural Gas]", "x": 122.61501748970383, "y": 506.0883069258821}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleGasConsumerCost", "label": "SimpleGasConsumerCost", "shape": "dot", "size": 18.0, "title": "SimpleGasConsumerCost\nconverters/natural_gas/dummy_gas_components.py\n[Converter / Natural Gas]", "x": 186.0962842524805, "y": 382.9162899098914}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NaturalGasPerformanceModel", "label": "NaturalGasPerformanceModel", "shape": "dot", "size": 18.0, "title": "NaturalGasPerformanceModel\nconverters/natural_gas/natural_gas_cc_ct.py\n[Converter / Natural Gas]", "x": 318.70784523855275, "y": 342.6335096083759}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NaturalGasCostModel", "label": "NaturalGasCostModel", "shape": "dot", "size": 18.0, "title": "NaturalGasCostModel\nconverters/natural_gas/natural_gas_cc_ct.py\n[Converter / Natural Gas]", "x": 440.01818000446065, "y": 409.711151044547}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleASUPerformanceModel", "label": "SimpleASUPerformanceModel", "shape": "dot", "size": 18.0, "title": "SimpleASUPerformanceModel\nconverters/nitrogen/simple_ASU.py\n[Converter / Nitrogen]", "x": 476.4229423877047, "y": 543.4914097987054}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleASUCostModel", "label": "SimpleASUCostModel", "shape": "dot", "size": 18.0, "title": "SimpleASUCostModel\nconverters/nitrogen/simple_ASU.py\n[Converter / Nitrogen]", "x": 405.8053310426993, "y": 662.8323620651722}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "QuinnNuclearPerformanceModel", "label": "QuinnNuclearPerformanceModel", "shape": "dot", "size": 18.0, "title": "QuinnNuclearPerformanceModel\nconverters/nuclear/nuclear_plant.py\n[Converter / Nuclear]", "x": 270.9728236746558, "y": 695.3246470713278}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "QuinnNuclearCostModel", "label": "QuinnNuclearCostModel", "shape": "dot", "size": 18.0, "title": "QuinnNuclearCostModel\nconverters/nuclear/nuclear_plant.py\n[Converter / Nuclear]", "x": 153.70695713111738, "y": 621.2265965964602}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ATBResComPVCostModel", "label": "ATBResComPVCostModel", "shape": "dot", "size": 18.0, "title": "ATBResComPVCostModel\nconverters/solar/atb_res_com_pv_cost.py\n[Converter / Solar]", "x": 125.15798881477986, "y": 485.45904675387027}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ATBUtilityPVCostModel", "label": "ATBUtilityPVCostModel", "shape": "dot", "size": 18.0, "title": "ATBUtilityPVCostModel\nconverters/solar/atb_utility_pv_cost.py\n[Converter / Solar]", "x": 202.6738837183378, "y": 370.3719161929008}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SolarPerformanceBaseClass", "label": "SolarPerformanceBaseClass", "shape": "dot", "size": 18.75, "title": "SolarPerformanceBaseClass\nconverters/solar/solar_baseclass.py\n[Converter / Solar]", "x": 339.2585979029505, "y": 345.79348732987785}, {"borderWidth": 3.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PYSAMSolarPlantPerformanceModel", "label": "PYSAMSolarPlantPerformanceModel", "shape": "dot", "size": 18.0, "title": "PYSAMSolarPlantPerformanceModel\nconverters/solar/solar_pysam.py\n[Converter / Solar]", "x": 452.065454128473, "y": 426.66163127900427}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SteelPerformanceModel", "label": "SteelPerformanceModel", "shape": "dot", "size": 18.0, "title": "SteelPerformanceModel\nconverters/steel/steel.py\n[Converter / Steel]", "x": 472.649734666257, "y": 563.9450473646525}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SteelCostAndFinancialModel", "label": "SteelCostAndFinancialModel", "shape": "dot", "size": 18.0, "title": "SteelCostAndFinancialModel\nconverters/steel/steel.py\n[Converter / Steel]", "x": 388.49787317589113, "y": 674.3722639236605}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SteelPerformanceBaseClass", "label": "SteelPerformanceBaseClass", "shape": "dot", "size": 18.75, "title": "SteelPerformanceBaseClass\nconverters/steel/steel_baseclass.py\n[Converter / Steel]", "x": 250.63471198908354, "y": 690.9423987961834}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SteelCostBaseClass", "label": "SteelCostBaseClass", "shape": "dot", "size": 18.75, "title": "SteelCostBaseClass\nconverters/steel/steel_baseclass.py\n[Converter / Steel]", "x": 142.68426480640017, "y": 603.5782213775121}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ElectricArcFurnacePlantBasePerformanceComponent", "label": "ElectricArcFurnacePlantBasePerformanceComponent", "shape": "dot", "size": 19.5, "title": "ElectricArcFurnacePlantBasePerformanceComponent\nconverters/steel/steel_eaf_base.py\n[Converter / Steel]", "x": 130.14466399280232, "y": 465.25467439672315}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ElectricArcFurnacePlantBaseCostComponent", "label": "ElectricArcFurnacePlantBaseCostComponent", "shape": "dot", "size": 19.5, "title": "ElectricArcFurnacePlantBaseCostComponent\nconverters/steel/steel_eaf_base.py\n[Converter / Steel]", "x": 220.6469531807619, "y": 359.87582716793}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HydrogenEAFPlantCostComponent", "label": "HydrogenEAFPlantCostComponent", "shape": "dot", "size": 18.0, "title": "HydrogenEAFPlantCostComponent\nconverters/steel/steel_eaf_plant.py\n[Converter / Steel]", "x": 359.31121758299275, "y": 351.37954310208585}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NaturalGasEAFPlantCostComponent", "label": "NaturalGasEAFPlantCostComponent", "shape": "dot", "size": 18.0, "title": "NaturalGasEAFPlantCostComponent\nconverters/steel/steel_eaf_plant.py\n[Converter / Steel]", "x": 462.0259973538852, "y": 444.94300653746575}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HydrogenEAFPlantPerformanceComponent", "label": "HydrogenEAFPlantPerformanceComponent", "shape": "dot", "size": 18.0, "title": "HydrogenEAFPlantPerformanceComponent\nconverters/steel/steel_eaf_plant.py\n[Converter / Steel]", "x": 466.469783435919, "y": 583.8281056844546}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NaturalGasEAFPlantPerformanceComponent", "label": "NaturalGasEAFPlantPerformanceComponent", "shape": "dot", "size": 18.0, "title": "NaturalGasEAFPlantPerformanceComponent\nconverters/steel/steel_eaf_plant.py\n[Converter / Steel]", "x": 369.92474578553976, "y": 683.7887775311128}, {"borderWidth": 3.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ReverseOsmosisPerformanceModel", "label": "ReverseOsmosisPerformanceModel", "shape": "dot", "size": 18.0, "title": "ReverseOsmosisPerformanceModel\nconverters/water/desal/desalination.py\n[Converter / Water]", "x": 230.93881303278727, "y": 684.1744802901646}, {"borderWidth": 3.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ReverseOsmosisCostModel", "label": "ReverseOsmosisCostModel", "shape": "dot", "size": 18.0, "title": "ReverseOsmosisCostModel\nconverters/water/desal/desalination.py\n[Converter / Water]", "x": 133.81979889995569, "y": 584.7300584166642}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DesalinationPerformanceBaseClass", "label": "DesalinationPerformanceBaseClass", "shape": "dot", "size": 18.75, "title": "DesalinationPerformanceBaseClass\nconverters/water/desal/desalination_baseclass.py\n[Converter / Water]", "x": 137.49417600907128, "y": 445.7633149496428}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DesalinationCostBaseClass", "label": "DesalinationCostBaseClass", "shape": "dot", "size": 18.75, "title": "DesalinationCostBaseClass\nconverters/water/desal/desalination_baseclass.py\n[Converter / Water]", "x": 239.75327641839016, "y": 351.5709546286854}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "RunOfRiverHydroPerformanceModel", "label": "RunOfRiverHydroPerformanceModel", "shape": "dot", "size": 18.0, "title": "RunOfRiverHydroPerformanceModel\nconverters/water_power/hydro_plant_run_of_river.py\n[Converter / Water Power]", "x": 378.58088334190944, "y": 359.30382820899604}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "RunOfRiverHydroCostModel", "label": "RunOfRiverHydroCostModel", "shape": "dot", "size": 18.0, "title": "RunOfRiverHydroCostModel\nconverters/water_power/hydro_plant_run_of_river.py\n[Converter / Water Power]", "x": 469.76420957583946, "y": 464.2904617494691}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PySAMMarineCostModel", "label": "PySAMMarineCostModel", "shape": "dot", "size": 18.0, "title": "PySAMMarineCostModel\nconverters/water_power/pysam_marine_cost.py\n[Converter / Water Power]", "x": 457.9779920949609, "y": 602.8591583936793}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PySAMTidalPerformanceModel", "label": "PySAMTidalPerformanceModel", "shape": "dot", "size": 18.0, "title": "PySAMTidalPerformanceModel\nconverters/water_power/tidal_pysam.py\n[Converter / Water Power]", "x": 350.3533328633538, "y": 690.9537471100347}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ATBWindPlantCostModel", "label": "ATBWindPlantCostModel", "shape": "dot", "size": 18.0, "title": "ATBWindPlantCostModel\nconverters/wind/atb_wind_cost.py\n[Converter / Wind]", "x": 212.16304848236933, "y": 675.1228946485747}, {"borderWidth": 3.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "FlorisWindPlantPerformanceModel", "label": "FlorisWindPlantPerformanceModel", "shape": "dot", "size": 18.0, "title": "FlorisWindPlantPerformanceModel\nconverters/wind/floris.py\n[Converter / Wind]", "x": 127.23416415600352, "y": 564.9519998347746}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WindArdPerformanceCompatibilityComponent", "label": "WindArdPerformanceCompatibilityComponent", "shape": "dot", "size": 18.0, "title": "WindArdPerformanceCompatibilityComponent\nconverters/wind/wind_plant_ard.py\n[Converter / Wind]", "x": 147.0974013238951, "y": 427.25925952607975}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WindArdCostCompatibilityComponent", "label": "WindArdCostCompatibilityComponent", "shape": "dot", "size": 18.0, "title": "WindArdCostCompatibilityComponent\nconverters/wind/wind_plant_ard.py\n[Converter / Wind]", "x": 259.72053996522834, "y": 345.57025167411484}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WindPerformanceBaseClass", "label": "WindPerformanceBaseClass", "shape": "dot", "size": 19.5, "title": "WindPerformanceBaseClass\nconverters/wind/wind_plant_baseclass.py\n[Converter / Wind]", "x": 396.79707304437557, "y": 369.4500991809332}, {"borderWidth": 3.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PYSAMWindPlantPerformanceModel", "label": "PYSAMWindPlantPerformanceModel", "shape": "dot", "size": 18.0, "title": "PYSAMWindPlantPerformanceModel\nconverters/wind/wind_pysam.py\n[Converter / Wind]", "x": 475.1748837158849, "y": 484.4293708946534}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "FeedstockCostModel", "label": "FeedstockCostModel", "shape": "ellipse", "size": 18.0, "title": "FeedstockCostModel\ncore/feedstocks.py\n[Core / General]", "x": -245.9999999999999, "y": 519.6152422706632}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PerformanceModelBaseClass", "label": "PerformanceModelBaseClass", "shape": "ellipse", "size": 39.0, "title": "PerformanceModelBaseClass\ncore/model_baseclasses.py\n[Core / General]", "x": -218.48531500638154, "y": 603.5459049059074}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CostModelBaseClass", "label": "CostModelBaseClass", "shape": "ellipse", "size": 45.0, "title": "CostModelBaseClass\ncore/model_baseclasses.py\n[Core / General]", "x": -304.02953407757775, "y": 657.556399490391}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ResizeablePerformanceModelBaseClass", "label": "ResizeablePerformanceModelBaseClass", "shape": "ellipse", "size": 19.5, "title": "ResizeablePerformanceModelBaseClass\ncore/model_baseclasses.py\n[Core / General]", "x": -409.5029667578749, "y": 619.921524582509}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CacheBaseClass", "label": "CacheBaseClass", "shape": "ellipse", "size": 19.5, "title": "CacheBaseClass\ncore/model_baseclasses.py\n[Core / General]", "x": -454.5360312930277, "y": 510.5789248680738}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SiteBaseComponent", "label": "SiteBaseComponent", "shape": "ellipse", "size": 18.75, "title": "SiteBaseComponent\ncore/sites.py\n[Core / General]", "x": -403.9293357173142, "y": 399.2836455167026}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SiteLocationComponent", "label": "SiteLocationComponent", "shape": "ellipse", "size": 18.0, "title": "SiteLocationComponent\ncore/sites.py\n[Core / General]", "x": -285.82516468280943, "y": 358.23657563925707}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ProFastBase", "label": "ProFastBase", "shape": "star", "size": 19.5, "title": "ProFastBase\nfinances/profast_base.py\n[Finance / General]", "x": -546.0, "y": 7.347880794884119e-14}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ProFastLCO", "label": "ProFastLCO", "shape": "star", "size": 18.0, "title": "ProFastLCO\nfinances/profast_lco.py\n[Finance / General]", "x": -518.4853150063816, "y": 83.93066263524423}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ProFastNPV", "label": "ProFastNPV", "shape": "star", "size": 18.0, "title": "ProFastNPV\nfinances/profast_npv.py\n[Finance / General]", "x": -604.0295340775779, "y": 137.94115721972778}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ResourceBaseAPIModel", "label": "ResourceBaseAPIModel", "shape": "triangle", "size": 19.5, "title": "ResourceBaseAPIModel\nresource/resource_base.py\n[Resource / General]", "x": -246.00000000000028, "y": -519.6152422706631}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NLRDeveloperAPISolarResourceBase", "label": "NLRDeveloperAPISolarResourceBase", "shape": "triangle", "size": 24.75, "title": "NLRDeveloperAPISolarResourceBase\nresource/solar/nlr_developer_api_base.py\n[Resource / General]", "x": -218.48531500638194, "y": -435.6845796354189}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GOESAggregatedSolarAPI", "label": "GOESAggregatedSolarAPI", "shape": "triangle", "size": 18.0, "title": "GOESAggregatedSolarAPI\nresource/solar/nlr_developer_goes_api_models.py\n[Resource / General]", "x": -304.02953407757815, "y": -381.6740850509354}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GOESConusSolarAPI", "label": "GOESConusSolarAPI", "shape": "triangle", "size": 18.0, "title": "GOESConusSolarAPI\nresource/solar/nlr_developer_goes_api_models.py\n[Resource / General]", "x": -409.5029667578753, "y": -419.30895995881724}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GOESFullDiscSolarAPI", "label": "GOESFullDiscSolarAPI", "shape": "triangle", "size": 18.0, "title": "GOESFullDiscSolarAPI\nresource/solar/nlr_developer_goes_api_models.py\n[Resource / General]", "x": -454.53603129302803, "y": -528.6515596732525}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GOESTMYSolarAPI", "label": "GOESTMYSolarAPI", "shape": "triangle", "size": 18.0, "title": "GOESTMYSolarAPI\nresource/solar/nlr_developer_goes_api_models.py\n[Resource / General]", "x": -403.9293357173146, "y": -639.9468390246236}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "Himawari7SolarAPI", "label": "Himawari7SolarAPI", "shape": "triangle", "size": 18.0, "title": "Himawari7SolarAPI\nresource/solar/nlr_developer_himawari_api_models.py\n[Resource / General]", "x": -285.8251646828098, "y": -680.9939089020693}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "Himawari8SolarAPI", "label": "Himawari8SolarAPI", "shape": "triangle", "size": 18.0, "title": "Himawari8SolarAPI\nresource/solar/nlr_developer_himawari_api_models.py\n[Resource / General]", "x": -172.61330445469173, "y": -623.3007875411918}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HimawariTMYSolarAPI", "label": "HimawariTMYSolarAPI", "shape": "triangle", "size": 18.0, "title": "HimawariTMYSolarAPI\nresource/solar/nlr_developer_himawari_api_models.py\n[Resource / General]", "x": -135.13130348614035, "y": -500.2680742654811}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MeteosatPrimeMeridianSolarAPI", "label": "MeteosatPrimeMeridianSolarAPI", "shape": "triangle", "size": 18.0, "title": "MeteosatPrimeMeridianSolarAPI\nresource/solar/nlr_developer_meteosat_prime_meridian_models.py\n[Resource / General]", "x": -198.16198994730087, "y": -386.75524186231485}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MeteosatPrimeMeridianTMYSolarAPI", "label": "MeteosatPrimeMeridianTMYSolarAPI", "shape": "triangle", "size": 18.0, "title": "MeteosatPrimeMeridianTMYSolarAPI\nresource/solar/nlr_developer_meteosat_prime_meridian_models.py\n[Resource / General]", "x": -324.52336933465205, "y": -352.86340688523126}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "OpenMeteoHistoricalSolarResource", "label": "OpenMeteoHistoricalSolarResource", "shape": "triangle", "size": 18.0, "title": "OpenMeteoHistoricalSolarResource\nresource/solar/openmeteo_solar.py\n[Resource / General]", "x": -437.480265883451, "y": -420.47177807550946}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SolarResourceBaseAPIModel", "label": "SolarResourceBaseAPIModel", "shape": "triangle", "size": 19.5, "title": "SolarResourceBaseAPIModel\nresource/solar/solar_resource_base.py\n[Resource / General]", "x": -467.69991636370764, "y": -549.304434088177}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WTKNLRDeveloperAPIWindResource", "label": "WTKNLRDeveloperAPIWindResource", "shape": "triangle", "size": 18.0, "title": "WTKNLRDeveloperAPIWindResource\nresource/wind/nlr_developer_wtk_api.py\n[Resource / General]", "x": -395.9283080200564, "y": -661.1735684843098}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "OpenMeteoHistoricalWindResource", "label": "OpenMeteoHistoricalWindResource", "shape": "triangle", "size": 18.0, "title": "OpenMeteoHistoricalWindResource\nresource/wind/openmeteo_wind.py\n[Resource / General]", "x": -265.1643653687024, "y": -687.6421405930291}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WindResourceBaseAPIModel", "label": "WindResourceBaseAPIModel", "shape": "triangle", "size": 19.5, "title": "WindResourceBaseAPIModel\nresource/wind/wind_resource_base.py\n[Resource / General]", "x": -154.75163735317008, "y": -611.972855781488}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GenericStorageCostModel", "label": "GenericStorageCostModel", "shape": "diamond", "size": 18.0, "title": "GenericStorageCostModel\nstorage/generic_storage_cost.py\n[Storage / General]", "x": 145.46396870697228, "y": -528.6515596732526}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "StorageAutoSizingModel", "label": "StorageAutoSizingModel", "shape": "diamond", "size": 18.0, "title": "StorageAutoSizingModel\nstorage/simple_storage_auto_sizing.py\n[Storage / General]", "x": 196.07066428268575, "y": -639.9468390246238}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "StoragePerformanceBase", "label": "StoragePerformanceBase", "shape": "diamond", "size": 20.25, "title": "StoragePerformanceBase\nstorage/storage_baseclass.py\n[Storage / General]", "x": 314.1748353171905, "y": -680.9939089020693}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "StoragePerformanceModel", "label": "StoragePerformanceModel", "shape": "diamond", "size": 18.0, "title": "StoragePerformanceModel\nstorage/storage_performance_model.py\n[Storage / General]", "x": 427.3866955453086, "y": -623.3007875411919}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ATBBatteryCostModel", "label": "ATBBatteryCostModel", "shape": "diamond", "size": 18.0, "title": "ATBBatteryCostModel\nstorage/battery/atb_battery_cost.py\n[Storage / General]", "x": 464.86869651385996, "y": -500.26807426548123}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PySAMBatteryPerformanceModel", "label": "PySAMBatteryPerformanceModel", "shape": "diamond", "size": 18.0, "title": "PySAMBatteryPerformanceModel\nstorage/battery/pysam_battery.py\n[Storage / General]", "x": 401.8380100526995, "y": -386.75524186231496}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HydrogenStorageBaseCostModel", "label": "HydrogenStorageBaseCostModel", "shape": "diamond", "size": 20.25, "title": "HydrogenStorageBaseCostModel\nstorage/hydrogen/h2_storage_cost.py\n[Storage / General]", "x": 275.4766306653483, "y": -352.86340688523137}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "LinedRockCavernStorageCostModel", "label": "LinedRockCavernStorageCostModel", "shape": "diamond", "size": 18.0, "title": "LinedRockCavernStorageCostModel\nstorage/hydrogen/h2_storage_cost.py\n[Storage / General]", "x": 162.51973411654936, "y": -420.4717780755096}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SaltCavernStorageCostModel", "label": "SaltCavernStorageCostModel", "shape": "diamond", "size": 18.0, "title": "SaltCavernStorageCostModel\nstorage/hydrogen/h2_storage_cost.py\n[Storage / General]", "x": 132.30008363629267, "y": -549.3044340881771}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PipeStorageCostModel", "label": "PipeStorageCostModel", "shape": "diamond", "size": 18.0, "title": "PipeStorageCostModel\nstorage/hydrogen/h2_storage_cost.py\n[Storage / General]", "x": 204.07169197994392, "y": -661.17356848431}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MCHTOLStorageCostModel", "label": "MCHTOLStorageCostModel", "shape": "diamond", "size": 18.0, "title": "MCHTOLStorageCostModel\nstorage/hydrogen/mch_storage.py\n[Storage / General]", "x": 334.8356346312979, "y": -687.6421405930291}]); - edges = new vis.DataSet([{"arrows": "to", "from": "PyomoRuleBaseClass", "to": "PyomoDispatchGenericConverter"}, {"arrows": "to", "from": "PyomoRuleBaseClass", "to": "PyomoRuleStorageBaseclass"}, {"arrows": "to", "from": "PyomoControllerBaseClass", "to": "HeuristicLoadFollowingController"}, {"arrows": "to", "from": "PyomoControllerBaseClass", "to": "OptimizedDispatchController"}, {"arrows": "to", "from": "ConverterOpenLoopControlBase", "to": "DemandOpenLoopConverterController"}, {"arrows": "to", "from": "ConverterOpenLoopControlBase", "to": "FlexibleDemandOpenLoopConverterController"}, {"arrows": "to", "from": "StorageOpenLoopControlBase", "to": "DemandOpenLoopStorageController"}, {"arrows": "to", "from": "StorageOpenLoopControlBase", "to": "SimpleStorageOpenLoopController"}, {"arrows": "to", "from": "ElectrolyzerPerformanceBaseClass", "to": "ECOElectrolyzerPerformanceModel"}, {"arrows": "to", "from": "ElectrolyzerCostBaseClass", "to": "BasicElectrolyzerCostModel"}, {"arrows": "to", "from": "ElectrolyzerCostBaseClass", "to": "CustomElectrolyzerCostModel"}, {"arrows": "to", "from": "ElectrolyzerCostBaseClass", "to": "SingliticoCostModel"}, {"arrows": "to", "from": "ECOElectrolyzerPerformanceModel", "to": "WOMBATElectrolyzerModel"}, {"arrows": "to", "from": "GeoH2SubsurfacePerformanceBaseClass", "to": "NaturalGeoH2PerformanceModel"}, {"arrows": "to", "from": "GeoH2SubsurfacePerformanceBaseClass", "to": "StimulatedGeoH2PerformanceModel"}, {"arrows": "to", "from": "GeoH2SubsurfaceCostBaseClass", "to": "GeoH2SubsurfaceCostModel"}, {"arrows": "to", "from": "GeoH2SurfacePerformanceBaseClass", "to": "AspenGeoH2SurfacePerformanceModel"}, {"arrows": "to", "from": "GeoH2SurfaceCostBaseClass", "to": "AspenGeoH2SurfaceCostModel"}, {"arrows": "to", "from": "IronReductionPlantBasePerformanceComponent", "to": "HydrogenIronReductionPlantPerformanceComponent"}, {"arrows": "to", "from": "IronReductionPlantBasePerformanceComponent", "to": "NaturalGasIronReductionPlantPerformanceComponent"}, {"arrows": "to", "from": "IronReductionPlantBaseCostComponent", "to": "HydrogenIronReductionPlantCostComponent"}, {"arrows": "to", "from": "IronReductionPlantBaseCostComponent", "to": "NaturalGasIronReductionPlantCostComponent"}, {"arrows": "to", "from": "MethanolPerformanceBaseClass", "to": "CO2HMethanolPlantPerformanceModel"}, {"arrows": "to", "from": "MethanolPerformanceBaseClass", "to": "SMRMethanolPlantPerformanceModel"}, {"arrows": "to", "from": "MethanolCostBaseClass", "to": "CO2HMethanolPlantCostModel"}, {"arrows": "to", "from": "MethanolCostBaseClass", "to": "SMRMethanolPlantCostModel"}, {"arrows": "to", "from": "MethanolFinanceBaseClass", "to": "CO2HMethanolPlantFinanceModel"}, {"arrows": "to", "from": "MethanolFinanceBaseClass", "to": "SMRMethanolPlantFinanceModel"}, {"arrows": "to", "from": "SolarPerformanceBaseClass", "to": "PYSAMSolarPlantPerformanceModel"}, {"arrows": "to", "from": "SteelPerformanceBaseClass", "to": "SteelPerformanceModel"}, {"arrows": "to", "from": "SteelCostBaseClass", "to": "SteelCostAndFinancialModel"}, {"arrows": "to", "from": "ElectricArcFurnacePlantBasePerformanceComponent", "to": "HydrogenEAFPlantPerformanceComponent"}, {"arrows": "to", "from": "ElectricArcFurnacePlantBasePerformanceComponent", "to": "NaturalGasEAFPlantPerformanceComponent"}, {"arrows": "to", "from": "ElectricArcFurnacePlantBaseCostComponent", "to": "HydrogenEAFPlantCostComponent"}, {"arrows": "to", "from": "ElectricArcFurnacePlantBaseCostComponent", "to": "NaturalGasEAFPlantCostComponent"}, {"arrows": "to", "from": "DesalinationPerformanceBaseClass", "to": "ReverseOsmosisPerformanceModel"}, {"arrows": "to", "from": "DesalinationCostBaseClass", "to": "ReverseOsmosisCostModel"}, {"arrows": "to", "from": "WindPerformanceBaseClass", "to": "FlorisWindPlantPerformanceModel"}, {"arrows": "to", "from": "WindPerformanceBaseClass", "to": "PYSAMWindPlantPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SimpleAmmoniaPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "DOCPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "OAEPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "GridPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "HOPPComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "LinearH2FuelCellPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SteamMethaneReformerPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "GeoH2SubsurfacePerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "GeoH2SurfacePerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "HumbertEwinPerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "IronReductionPlantBasePerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "MartinIronMinePerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "MethanolPerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SimpleGasProducerPerformance"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SimpleGasConsumerPerformance"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "NaturalGasPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SimpleASUPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "QuinnNuclearPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SolarPerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SteelPerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "ElectricArcFurnacePlantBasePerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "DesalinationPerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "RunOfRiverHydroPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "PySAMTidalPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "WindArdPerformanceCompatibilityComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "WindPerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "ResizeablePerformanceModelBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "StoragePerformanceBase"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "GenericConverterCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "AmmoniaSynLoopCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SimpleAmmoniaCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "DOCCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "OAECostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "OAECostAndFinancialModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "GridCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "ElectrolyzerCostBaseClass"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "H2FuelCellCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SteamMethaneReformerCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "GeoH2SubsurfaceCostBaseClass"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "GeoH2SurfaceCostBaseClass"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "HumbertStinnEwinCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "IronReductionPlantBaseCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "IronTransportCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "MartinIronMineCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "MethanolCostBaseClass"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SimpleGasProducerCost"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SimpleGasConsumerCost"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "NaturalGasCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SimpleASUCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "QuinnNuclearCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "ATBResComPVCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "ATBUtilityPVCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SteelCostBaseClass"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "ElectricArcFurnacePlantBaseCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "DesalinationCostBaseClass"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "RunOfRiverHydroCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "PySAMMarineCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "ATBWindPlantCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "WindArdCostCompatibilityComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "FeedstockCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "GenericStorageCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "ATBBatteryCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "HydrogenStorageBaseCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "MCHTOLStorageCostModel"}, {"arrows": "to", "from": "ResizeablePerformanceModelBaseClass", "to": "AmmoniaSynLoopPerformanceModel"}, {"arrows": "to", "from": "ResizeablePerformanceModelBaseClass", "to": "ElectrolyzerPerformanceBaseClass"}, {"arrows": "to", "from": "CacheBaseClass", "to": "HOPPComponent"}, {"arrows": "to", "from": "CacheBaseClass", "to": "FlorisWindPlantPerformanceModel"}, {"arrows": "to", "from": "SiteBaseComponent", "to": "SiteLocationComponent"}, {"arrows": "to", "from": "ProFastBase", "to": "ProFastLCO"}, {"arrows": "to", "from": "ProFastBase", "to": "ProFastNPV"}, {"arrows": "to", "from": "ResourceBaseAPIModel", "to": "SolarResourceBaseAPIModel"}, {"arrows": "to", "from": "ResourceBaseAPIModel", "to": "WindResourceBaseAPIModel"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "GOESAggregatedSolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "GOESConusSolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "GOESFullDiscSolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "GOESTMYSolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "Himawari7SolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "Himawari8SolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "HimawariTMYSolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "MeteosatPrimeMeridianSolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "MeteosatPrimeMeridianTMYSolarAPI"}, {"arrows": "to", "from": "SolarResourceBaseAPIModel", "to": "NLRDeveloperAPISolarResourceBase"}, {"arrows": "to", "from": "SolarResourceBaseAPIModel", "to": "OpenMeteoHistoricalSolarResource"}, {"arrows": "to", "from": "WindResourceBaseAPIModel", "to": "WTKNLRDeveloperAPIWindResource"}, {"arrows": "to", "from": "WindResourceBaseAPIModel", "to": "OpenMeteoHistoricalWindResource"}, {"arrows": "to", "from": "StoragePerformanceBase", "to": "StorageAutoSizingModel"}, {"arrows": "to", "from": "StoragePerformanceBase", "to": "StoragePerformanceModel"}, {"arrows": "to", "from": "StoragePerformanceBase", "to": "PySAMBatteryPerformanceModel"}, {"arrows": "to", "from": "HydrogenStorageBaseCostModel", "to": "LinedRockCavernStorageCostModel"}, {"arrows": "to", "from": "HydrogenStorageBaseCostModel", "to": "SaltCavernStorageCostModel"}, {"arrows": "to", "from": "HydrogenStorageBaseCostModel", "to": "PipeStorageCostModel"}]); + nodes = new vis.DataSet([{"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SiteBaseComponent", "label": "SiteBaseComponent", "shape": "ellipse", "size": 18.710526315789473, "title": "SiteBaseComponent\ncore/sites.py\n[Core / General]", "x": -79.5125603737886, "y": 584.9567473090942}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SiteLocationComponent", "label": "SiteLocationComponent", "shape": "ellipse", "size": 18.0, "title": "SiteLocationComponent\ncore/sites.py\n[Core / General]", "x": -51.99787538017026, "y": 668.8874099443384}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PerformanceModelBaseClass", "label": "PerformanceModelBaseClass", "shape": "ellipse", "size": 40.026315789473685, "title": "PerformanceModelBaseClass\ncore/model_baseclasses.py\n[Core / General]", "x": -137.54209445136647, "y": 722.897904528822}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CostModelBaseClass", "label": "CostModelBaseClass", "shape": "ellipse", "size": 45.0, "title": "CostModelBaseClass\ncore/model_baseclasses.py\n[Core / General]", "x": -243.0155271316636, "y": 685.2630296209401}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ResizeablePerformanceModelBaseClass", "label": "ResizeablePerformanceModelBaseClass", "shape": "ellipse", "size": 19.42105263157895, "title": "ResizeablePerformanceModelBaseClass\ncore/model_baseclasses.py\n[Core / General]", "x": -288.0485916668164, "y": 575.9204299065049}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CacheBaseClass", "label": "CacheBaseClass", "shape": "ellipse", "size": 19.42105263157895, "title": "CacheBaseClass\ncore/model_baseclasses.py\n[Core / General]", "x": -237.4418960911029, "y": 464.62515055513364}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GenericConverterCostModel", "label": "GenericConverterCostModel", "shape": "dot", "size": 18.0, "title": "GenericConverterCostModel\nconverters/generic_converter_cost.py\n[Converter / Other]", "x": 428.09388111524015, "y": 469.0988894808179}, {"borderWidth": 3.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PYSAMSolarPlantPerformanceModel", "label": "PYSAMSolarPlantPerformanceModel", "shape": "dot", "size": 18.0, "title": "PYSAMSolarPlantPerformanceModel\nconverters/solar/solar_pysam.py\n[Converter / Solar]", "x": 455.6085661088585, "y": 553.0295521160621}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ATBUtilityPVCostModel", "label": "ATBUtilityPVCostModel", "shape": "dot", "size": 18.0, "title": "ATBUtilityPVCostModel\nconverters/solar/atb_utility_pv_cost.py\n[Converter / Solar]", "x": 370.0643470376623, "y": 607.0400467005456}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ATBResComPVCostModel", "label": "ATBResComPVCostModel", "shape": "dot", "size": 18.0, "title": "ATBResComPVCostModel\nconverters/solar/atb_res_com_pv_cost.py\n[Converter / Solar]", "x": 264.59091435736514, "y": 569.4051717926637}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SolarPerformanceBaseClass", "label": "SolarPerformanceBaseClass", "shape": "dot", "size": 18.710526315789473, "title": "SolarPerformanceBaseClass\nconverters/solar/solar_baseclass.py\n[Converter / Solar]", "x": 219.55784982221238, "y": 460.06257207822847}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ElectrolyzerPerformanceBaseClass", "label": "ElectrolyzerPerformanceBaseClass", "shape": "dot", "size": 18.710526315789473, "title": "ElectrolyzerPerformanceBaseClass\nconverters/hydrogen/electrolyzer_baseclass.py\n[Converter / Hydrogen]", "x": 270.16454539792585, "y": 348.7672927268573}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ElectrolyzerCostBaseClass", "label": "ElectrolyzerCostBaseClass", "shape": "dot", "size": 20.13157894736842, "title": "ElectrolyzerCostBaseClass\nconverters/hydrogen/electrolyzer_baseclass.py\n[Converter / Hydrogen]", "x": 388.2687164324306, "y": 307.72022284941175}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SingliticoCostModel", "label": "SingliticoCostModel", "shape": "dot", "size": 18.0, "title": "SingliticoCostModel\nconverters/hydrogen/singlitico_cost_model.py\n[Converter / Hydrogen]", "x": 501.4805766605487, "y": 365.4133442102892}, {"borderWidth": 1, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WOMBATElectrolyzerModel", "label": "WOMBATElectrolyzerModel", "shape": "dot", "size": 18.0, "title": "WOMBATElectrolyzerModel\nconverters/hydrogen/wombat_model.py\n[Converter / Hydrogen]", "x": 538.9625776291001, "y": 488.44605748599986}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "LinearH2FuelCellPerformanceModel", "label": "LinearH2FuelCellPerformanceModel", "shape": "dot", "size": 18.0, "title": "LinearH2FuelCellPerformanceModel\nconverters/hydrogen/h2_fuel_cell.py\n[Converter / Hydrogen]", "x": 475.93189116793957, "y": 601.9588898891661}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "H2FuelCellCostModel", "label": "H2FuelCellCostModel", "shape": "dot", "size": 18.0, "title": "H2FuelCellCostModel\nconverters/hydrogen/h2_fuel_cell.py\n[Converter / Hydrogen]", "x": 349.5705117805884, "y": 635.8507248662497}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SteamMethaneReformerPerformanceModel", "label": "SteamMethaneReformerPerformanceModel", "shape": "dot", "size": 18.0, "title": "SteamMethaneReformerPerformanceModel\nconverters/hydrogen/steam_methane_reformer.py\n[Converter / Hydrogen]", "x": 236.61361523178945, "y": 568.2423536759715}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SteamMethaneReformerCostModel", "label": "SteamMethaneReformerCostModel", "shape": "dot", "size": 18.0, "title": "SteamMethaneReformerCostModel\nconverters/hydrogen/steam_methane_reformer.py\n[Converter / Hydrogen]", "x": 206.39396475153276, "y": 439.409697663304}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "BasicElectrolyzerCostModel", "label": "BasicElectrolyzerCostModel", "shape": "dot", "size": 18.0, "title": "BasicElectrolyzerCostModel\nconverters/hydrogen/basic_cost_model.py\n[Converter / Hydrogen]", "x": 278.165573095184, "y": 327.54056326717114}, {"borderWidth": 2.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ECOElectrolyzerPerformanceModel", "label": "ECOElectrolyzerPerformanceModel", "shape": "dot", "size": 18.710526315789473, "title": "ECOElectrolyzerPerformanceModel\nconverters/hydrogen/pem_electrolyzer.py\n[Converter / Hydrogen]", "x": 408.929515746538, "y": 301.0719911584519}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CustomElectrolyzerCostModel", "label": "CustomElectrolyzerCostModel", "shape": "dot", "size": 18.0, "title": "CustomElectrolyzerCostModel\nconverters/hydrogen/custom_electrolyzer_cost_model.py\n[Converter / Hydrogen]", "x": 519.3422437620703, "y": 376.741275969993}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GeoH2SubsurfaceCostModel", "label": "GeoH2SubsurfaceCostModel", "shape": "dot", "size": 18.0, "title": "GeoH2SubsurfaceCostModel\nconverters/hydrogen/geologic/mathur_modified.py\n[Converter / Hydrogen]", "x": 541.9933368656093, "y": 509.0547616483423}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GeoH2SubsurfacePerformanceBaseClass", "label": "GeoH2SubsurfacePerformanceBaseClass", "shape": "dot", "size": 19.42105263157895, "title": "GeoH2SubsurfacePerformanceBaseClass\nconverters/hydrogen/geologic/h2_well_subsurface_baseclass.py\n[Converter / Hydrogen]", "x": 462.61867180963463, "y": 617.7338834509918}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GeoH2SubsurfaceCostBaseClass", "label": "GeoH2SubsurfaceCostBaseClass", "shape": "dot", "size": 18.710526315789473, "title": "GeoH2SubsurfaceCostBaseClass\nconverters/hydrogen/geologic/h2_well_subsurface_baseclass.py\n[Converter / Hydrogen]", "x": 329.04975630639217, "y": 636.513453478361}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "AspenGeoH2SurfacePerformanceModel", "label": "AspenGeoH2SurfacePerformanceModel", "shape": "dot", "size": 18.0, "title": "AspenGeoH2SurfacePerformanceModel\nconverters/hydrogen/geologic/aspen_surface_processing.py\n[Converter / Hydrogen]", "x": 222.32576716621537, "y": 553.5863417347226}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "AspenGeoH2SurfaceCostModel", "label": "AspenGeoH2SurfaceCostModel", "shape": "dot", "size": 18.0, "title": "AspenGeoH2SurfaceCostModel\nconverters/hydrogen/geologic/aspen_surface_processing.py\n[Converter / Hydrogen]", "x": 207.46113153897124, "y": 419.00371238109653}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NaturalGeoH2PerformanceModel", "label": "NaturalGeoH2PerformanceModel", "shape": "dot", "size": 18.0, "title": "NaturalGeoH2PerformanceModel\nconverters/hydrogen/geologic/simple_natural_geoh2.py\n[Converter / Hydrogen]", "x": 293.81016729091453, "y": 314.4201619012048}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "StimulatedGeoH2PerformanceModel", "label": "StimulatedGeoH2PerformanceModel", "shape": "dot", "size": 18.0, "title": "StimulatedGeoH2PerformanceModel\nconverters/hydrogen/geologic/templeton_serpentinization.py\n[Converter / Hydrogen]", "x": 429.1980303433203, "y": 303.5048975398027}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GeoH2SurfacePerformanceBaseClass", "label": "GeoH2SurfacePerformanceBaseClass", "shape": "dot", "size": 18.710526315789473, "title": "GeoH2SurfacePerformanceBaseClass\nconverters/hydrogen/geologic/h2_well_surface_baseclass.py\n[Converter / Hydrogen]", "x": 531.4807512394786, "y": 393.1585470237545}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GeoH2SurfaceCostBaseClass", "label": "GeoH2SurfaceCostBaseClass", "shape": "dot", "size": 18.710526315789473, "title": "GeoH2SurfaceCostBaseClass\nconverters/hydrogen/geologic/h2_well_surface_baseclass.py\n[Converter / Hydrogen]", "x": 538.4198729733245, "y": 529.1652694271473}, {"borderWidth": 3.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ReverseOsmosisPerformanceModel", "label": "ReverseOsmosisPerformanceModel", "shape": "dot", "size": 18.0, "title": "ReverseOsmosisPerformanceModel\nconverters/water/desal/desalination.py\n[Converter / Water]", "x": 445.5710237882473, "y": 629.0047614736493}, {"borderWidth": 3.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ReverseOsmosisCostModel", "label": "ReverseOsmosisCostModel", "shape": "dot", "size": 18.0, "title": "ReverseOsmosisCostModel\nconverters/water/desal/desalination.py\n[Converter / Water]", "x": 309.11651798504033, "y": 631.947653855694}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DesalinationPerformanceBaseClass", "label": "DesalinationPerformanceBaseClass", "shape": "dot", "size": 18.710526315789473, "title": "DesalinationPerformanceBaseClass\nconverters/water/desal/desalination_baseclass.py\n[Converter / Water]", "x": 211.84908461781183, "y": 536.0083507662598}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DesalinationCostBaseClass", "label": "DesalinationCostBaseClass", "shape": "dot", "size": 18.710526315789473, "title": "DesalinationCostBaseClass\nconverters/water/desal/desalination_baseclass.py\n[Converter / Water]", "x": 212.91655044595416, "y": 399.26617741803875}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "QuinnNuclearPerformanceModel", "label": "QuinnNuclearPerformanceModel", "shape": "dot", "size": 18.0, "title": "QuinnNuclearPerformanceModel\nconverters/nuclear/nuclear_plant.py\n[Converter / Nuclear]", "x": 311.84419223616084, "y": 304.68898638961775}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "QuinnNuclearCostModel", "label": "QuinnNuclearCostModel", "shape": "dot", "size": 18.0, "title": "QuinnNuclearCostModel\nconverters/nuclear/nuclear_plant.py\n[Converter / Nuclear]", "x": 448.7220200189815, "y": 309.77552217810415}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CMUElectricArcFurnaceCostModel", "label": "CMUElectricArcFurnaceCostModel", "shape": "dot", "size": 18.0, "title": "CMUElectricArcFurnaceCostModel\nconverters/steel/cmu_eaf_cost.py\n[Converter / Steel]", "x": 540.4994435124524, "y": 411.5906925910052}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HydrogenEAFPlantCostComponent", "label": "HydrogenEAFPlantCostComponent", "shape": "dot", "size": 18.0, "title": "HydrogenEAFPlantCostComponent\nconverters/steel/steel_eaf_plant.py\n[Converter / Steel]", "x": 531.3901362621964, "y": 548.4583323631547}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NaturalGasEAFPlantCostComponent", "label": "NaturalGasEAFPlantCostComponent", "shape": "dot", "size": 18.0, "title": "NaturalGasEAFPlantCostComponent\nconverters/steel/steel_eaf_plant.py\n[Converter / Steel]", "x": 426.78782378431725, "y": 637.3337381981482}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HydrogenEAFPlantPerformanceComponent", "label": "HydrogenEAFPlantPerformanceComponent", "shape": "dot", "size": 18.0, "title": "HydrogenEAFPlantPerformanceComponent\nconverters/steel/steel_eaf_plant.py\n[Converter / Steel]", "x": 290.0713774101109, "y": 624.2026575560053}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NaturalGasEAFPlantPerformanceComponent", "label": "NaturalGasEAFPlantPerformanceComponent", "shape": "dot", "size": 18.0, "title": "NaturalGasEAFPlantPerformanceComponent\nconverters/steel/steel_eaf_plant.py\n[Converter / Steel]", "x": 204.19394571089566, "y": 516.9137611171568}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CMUElectricArcFurnaceScrapOnlyPerformanceComponent", "label": "CMUElectricArcFurnaceScrapOnlyPerformanceComponent", "shape": "dot", "size": 18.0, "title": "CMUElectricArcFurnaceScrapOnlyPerformanceComponent\nconverters/steel/cmu_electric_arc_furnace_scrap.py\n[Converter / Steel]", "x": 221.34134343018306, "y": 380.48561129518464}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ElectricArcFurnacePlantBasePerformanceComponent", "label": "ElectricArcFurnacePlantBasePerformanceComponent", "shape": "dot", "size": 19.42105263157895, "title": "ElectricArcFurnacePlantBasePerformanceComponent\nconverters/steel/steel_eaf_base.py\n[Converter / Steel]", "x": 331.21568985008844, "y": 297.6965237937115}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ElectricArcFurnacePlantBaseCostComponent", "label": "ElectricArcFurnacePlantBaseCostComponent", "shape": "dot", "size": 19.42105263157895, "title": "ElectricArcFurnacePlantBaseCostComponent\nconverters/steel/steel_eaf_base.py\n[Converter / Steel]", "x": 467.2216800346311, "y": 318.8505150630724}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CMUElectricArcFurnaceDRIPerformanceComponent", "label": "CMUElectricArcFurnaceDRIPerformanceComponent", "shape": "dot", "size": 18.0, "title": "CMUElectricArcFurnaceDRIPerformanceComponent\nconverters/steel/cmu_electric_arc_furnace_dri.py\n[Converter / Steel]", "x": 546.8371236174256, "y": 431.20832772949075}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SteelPerformanceBaseClass", "label": "SteelPerformanceBaseClass", "shape": "dot", "size": 18.710526315789473, "title": "SteelPerformanceBaseClass\nconverters/steel/steel_baseclass.py\n[Converter / Steel]", "x": 521.6903751911968, "y": 566.6610637668477}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SteelCostBaseClass", "label": "SteelCostBaseClass", "shape": "dot", "size": 18.710526315789473, "title": "SteelCostBaseClass\nconverters/steel/steel_baseclass.py\n[Converter / Steel]", "x": 406.952112243024, "y": 643.0222537742708}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SteelPerformanceModel", "label": "SteelPerformanceModel", "shape": "dot", "size": 18.0, "title": "SteelPerformanceModel\nconverters/steel/steel.py\n[Converter / Steel]", "x": 272.18129178907674, "y": 613.9005699916162}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SteelCostAndFinancialModel", "label": "SteelCostAndFinancialModel", "shape": "dot", "size": 18.0, "title": "SteelCostAndFinancialModel\nconverters/steel/steel.py\n[Converter / Steel]", "x": 199.15055717864382, "y": 496.88602197643854}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleASUPerformanceModel", "label": "SimpleASUPerformanceModel", "shape": "dot", "size": 18.0, "title": "SimpleASUPerformanceModel\nconverters/nitrogen/simple_ASU.py\n[Converter / Nitrogen]", "x": 232.2254768075065, "y": 362.9235822800501}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleASUCostModel", "label": "SimpleASUCostModel", "shape": "dot", "size": 18.0, "title": "SimpleASUCostModel\nconverters/nitrogen/simple_ASU.py\n[Converter / Nitrogen]", "x": 351.4109262191128, "y": 293.29530750309686}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ATBWindPlantCostModel", "label": "ATBWindPlantCostModel", "shape": "dot", "size": 18.0, "title": "ATBWindPlantCostModel\nconverters/wind/atb_wind_cost.py\n[Converter / Wind]", "x": 484.44055215455836, "y": 330.297978929092}, {"borderWidth": 3.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PYSAMWindPlantPerformanceModel", "label": "PYSAMWindPlantPerformanceModel", "shape": "dot", "size": 18.0, "title": "PYSAMWindPlantPerformanceModel\nconverters/wind/wind_pysam.py\n[Converter / Wind]", "x": 550.5984027605526, "y": 451.5476939728353}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WindPerformanceBaseClass", "label": "WindPerformanceBaseClass", "shape": "dot", "size": 19.42105263157895, "title": "WindPerformanceBaseClass\nconverters/wind/wind_plant_baseclass.py\n[Converter / Wind]", "x": 509.69716559653716, "y": 583.5219955261796}, {"borderWidth": 3.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "FlorisWindPlantPerformanceModel", "label": "FlorisWindPlantPerformanceModel", "shape": "dot", "size": 18.0, "title": "FlorisWindPlantPerformanceModel\nconverters/wind/floris.py\n[Converter / Wind]", "x": 386.4910795111691, "y": 646.1453801885751}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WindArdPerformanceCompatibilityComponent", "label": "WindArdPerformanceCompatibilityComponent", "shape": "dot", "size": 18.0, "title": "WindArdPerformanceCompatibilityComponent\nconverters/wind/wind_plant_ard.py\n[Converter / Wind]", "x": 255.69275934662096, "y": 601.3783905588575}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WindArdCostCompatibilityComponent", "label": "WindArdCostCompatibilityComponent", "shape": "dot", "size": 18.0, "title": "WindArdCostCompatibilityComponent\nconverters/wind/wind_plant_ard.py\n[Converter / Wind]", "x": 196.66404742738186, "y": 476.325072910983}, {"borderWidth": 3.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SMRMethanolPlantPerformanceModel", "label": "SMRMethanolPlantPerformanceModel", "shape": "dot", "size": 18.0, "title": "SMRMethanolPlantPerformanceModel\nconverters/methanol/smr_methanol_plant.py\n[Converter / Methanol]", "x": 245.26041830184303, "y": 346.82157500140227}, {"borderWidth": 3.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SMRMethanolPlantCostModel", "label": "SMRMethanolPlantCostModel", "shape": "dot", "size": 18.0, "title": "SMRMethanolPlantCostModel\nconverters/methanol/smr_methanol_plant.py\n[Converter / Methanol]", "x": 372.0506131909644, "y": 291.4439725755037}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SMRMethanolPlantFinanceModel", "label": "SMRMethanolPlantFinanceModel", "shape": "dot", "size": 18.0, "title": "SMRMethanolPlantFinanceModel\nconverters/methanol/smr_methanol_plant.py\n[Converter / Methanol]", "x": 500.1422507144458, "y": 343.82986093378406}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MethanolPerformanceBaseClass", "label": "MethanolPerformanceBaseClass", "shape": "dot", "size": 19.42105263157895, "title": "MethanolPerformanceBaseClass\nconverters/methanol/methanol_baseclass.py\n[Converter / Methanol]", "x": 551.8160292273673, "y": 472.2454066683052}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MethanolCostBaseClass", "label": "MethanolCostBaseClass", "shape": "dot", "size": 19.42105263157895, "title": "MethanolCostBaseClass\nconverters/methanol/methanol_baseclass.py\n[Converter / Methanol]", "x": 495.6839168464708, "y": 598.8099536341376}, {"borderWidth": 5.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MethanolFinanceBaseClass", "label": "MethanolFinanceBaseClass", "shape": "dot", "size": 19.42105263157895, "title": "MethanolFinanceBaseClass\nconverters/methanol/methanol_baseclass.py\n[Converter / Methanol]", "x": 365.7556658010815, "y": 646.7308814798549}, {"borderWidth": 3.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CO2HMethanolPlantPerformanceModel", "label": "CO2HMethanolPlantPerformanceModel", "shape": "dot", "size": 18.0, "title": "CO2HMethanolPlantPerformanceModel\nconverters/methanol/co2h_methanol_plant.py\n[Converter / Methanol]", "x": 240.8316118946296, "y": 586.8992069729802}, {"borderWidth": 3.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CO2HMethanolPlantCostModel", "label": "CO2HMethanolPlantCostModel", "shape": "dot", "size": 18.0, "title": "CO2HMethanolPlantCostModel\nconverters/methanol/co2h_methanol_plant.py\n[Converter / Methanol]", "x": 196.70889860494393, "y": 455.57195413603677}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CO2HMethanolPlantFinanceModel", "label": "CO2HMethanolPlantFinanceModel", "shape": "dot", "size": 18.0, "title": "CO2HMethanolPlantFinanceModel\nconverters/methanol/co2h_methanol_plant.py\n[Converter / Methanol]", "x": 260.1901653677206, "y": 332.3999371200461}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HydrogenIronReductionPlantCostComponent", "label": "HydrogenIronReductionPlantCostComponent", "shape": "dot", "size": 18.0, "title": "HydrogenIronReductionPlantCostComponent\nconverters/iron/iron_dri_plant.py\n[Converter / Iron]", "x": 392.80172635379284, "y": 292.1171568185306}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NaturalGasIronReductionPlantCostComponent", "label": "NaturalGasIronReductionPlantCostComponent", "shape": "dot", "size": 18.0, "title": "NaturalGasIronReductionPlantCostComponent\nconverters/iron/iron_dri_plant.py\n[Converter / Iron]", "x": 514.1120611197007, "y": 359.1947982547017}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HydrogenIronReductionPlantPerformanceComponent", "label": "HydrogenIronReductionPlantPerformanceComponent", "shape": "dot", "size": 18.0, "title": "HydrogenIronReductionPlantPerformanceComponent\nconverters/iron/iron_dri_plant.py\n[Converter / Iron]", "x": 550.5168235029448, "y": 492.97505700886006}, {"borderWidth": 3.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NaturalGasIronReductionPlantPerformanceComponent", "label": "NaturalGasIronReductionPlantPerformanceComponent", "shape": "dot", "size": 18.0, "title": "NaturalGasIronReductionPlantPerformanceComponent\nconverters/iron/iron_dri_plant.py\n[Converter / Iron]", "x": 479.8992121579394, "y": 612.3160092753269}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HumbertEwinPerformanceComponent", "label": "HumbertEwinPerformanceComponent", "shape": "dot", "size": 18.0, "title": "HumbertEwinPerformanceComponent\nconverters/iron/humbert_ewin_perf.py\n[Converter / Iron]", "x": 345.0667047898959, "y": 644.8082942814825}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MartinIronMineCostComponent", "label": "MartinIronMineCostComponent", "shape": "dot", "size": 18.0, "title": "MartinIronMineCostComponent\nconverters/iron/martin_mine_cost_model.py\n[Converter / Iron]", "x": 227.80083824635747, "y": 570.7102438066149}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "IronTransportCostComponent", "label": "IronTransportCostComponent", "shape": "dot", "size": 18.0, "title": "IronTransportCostComponent\nconverters/iron/iron_transport.py\n[Converter / Iron]", "x": 199.25186993001995, "y": 434.94269396402495}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "IronReductionPlantBasePerformanceComponent", "label": "IronReductionPlantBasePerformanceComponent", "shape": "dot", "size": 19.42105263157895, "title": "IronReductionPlantBasePerformanceComponent\nconverters/iron/iron_dri_base.py\n[Converter / Iron]", "x": 276.76776483357787, "y": 319.8555634030555}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "IronReductionPlantBaseCostComponent", "label": "IronReductionPlantBaseCostComponent", "shape": "dot", "size": 19.42105263157895, "title": "IronReductionPlantBaseCostComponent\nconverters/iron/iron_dri_base.py\n[Converter / Iron]", "x": 413.3524790181906, "y": 295.27713454003253}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MartinIronMinePerformanceComponent", "label": "MartinIronMinePerformanceComponent", "shape": "dot", "size": 18.0, "title": "MartinIronMinePerformanceComponent\nconverters/iron/martin_mine_perf_model.py\n[Converter / Iron]", "x": 526.1593352437131, "y": 376.14527848915895}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HumbertStinnEwinCostComponent", "label": "HumbertStinnEwinCostComponent", "shape": "dot", "size": 18.0, "title": "HumbertStinnEwinCostComponent\nconverters/iron/humbert_stinn_ewin_cost.py\n[Converter / Iron]", "x": 546.743615781497, "y": 513.4286945748072}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HOPPComponent", "label": "HOPPComponent", "shape": "dot", "size": 18.0, "title": "HOPPComponent\nconverters/hopp/hopp_wrapper.py\n[Converter / HOPP]", "x": 462.5917542911312, "y": 623.8559111338152}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PySAMTidalPerformanceModel", "label": "PySAMTidalPerformanceModel", "shape": "dot", "size": 18.0, "title": "PySAMTidalPerformanceModel\nconverters/water_power/tidal_pysam.py\n[Converter / Water Power]", "x": 324.72859310432364, "y": 640.4260460063381}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PySAMMarineCostModel", "label": "PySAMMarineCostModel", "shape": "dot", "size": 18.0, "title": "PySAMMarineCostModel\nconverters/water_power/pysam_marine_cost.py\n[Converter / Water Power]", "x": 216.77814592164026, "y": 553.0618685876668}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "RunOfRiverHydroPerformanceModel", "label": "RunOfRiverHydroPerformanceModel", "shape": "dot", "size": 18.0, "title": "RunOfRiverHydroPerformanceModel\nconverters/water_power/hydro_plant_run_of_river.py\n[Converter / Water Power]", "x": 204.23854510804242, "y": 414.73832160687783}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "RunOfRiverHydroCostModel", "label": "RunOfRiverHydroCostModel", "shape": "dot", "size": 18.0, "title": "RunOfRiverHydroCostModel\nconverters/water_power/hydro_plant_run_of_river.py\n[Converter / Water Power]", "x": 294.740834296002, "y": 309.3594743780847}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleAmmoniaPerformanceModel", "label": "SimpleAmmoniaPerformanceModel", "shape": "dot", "size": 18.0, "title": "SimpleAmmoniaPerformanceModel\nconverters/ammonia/simple_ammonia_model.py\n[Converter / Ammonia]", "x": 433.40509869823285, "y": 300.86319031224053}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleAmmoniaCostModel", "label": "SimpleAmmoniaCostModel", "shape": "dot", "size": 18.0, "title": "SimpleAmmoniaCostModel\nconverters/ammonia/simple_ammonia_model.py\n[Converter / Ammonia]", "x": 536.1198784691253, "y": 394.42665374762043}, {"borderWidth": 3.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "AmmoniaSynLoopPerformanceModel", "label": "AmmoniaSynLoopPerformanceModel", "shape": "dot", "size": 18.0, "title": "AmmoniaSynLoopPerformanceModel\nconverters/ammonia/ammonia_synloop.py\n[Converter / Ammonia]", "x": 540.5636645511591, "y": 533.3117528946093}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "AmmoniaSynLoopCostModel", "label": "AmmoniaSynLoopCostModel", "shape": "dot", "size": 18.0, "title": "AmmoniaSynLoopCostModel\nconverters/ammonia/ammonia_synloop.py\n[Converter / Ammonia]", "x": 444.01862690077985, "y": 633.2724247412674}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DOCPerformanceModel", "label": "DOCPerformanceModel", "shape": "dot", "size": 18.0, "title": "DOCPerformanceModel\nconverters/co2/marine/direct_ocean_capture.py\n[Converter / CO2]", "x": 305.03269414802736, "y": 633.6581275003193}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DOCCostModel", "label": "DOCCostModel", "shape": "dot", "size": 18.0, "title": "DOCCostModel\nconverters/co2/marine/direct_ocean_capture.py\n[Converter / CO2]", "x": 207.91368001519578, "y": 534.2137056268189}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "OAEPerformanceModel", "label": "OAEPerformanceModel", "shape": "dot", "size": 18.0, "title": "OAEPerformanceModel\nconverters/co2/marine/ocean_alkalinity_enhancement.py\n[Converter / CO2]", "x": 211.58805712431138, "y": 395.2469621597975}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "OAECostModel", "label": "OAECostModel", "shape": "dot", "size": 18.0, "title": "OAECostModel\nconverters/co2/marine/ocean_alkalinity_enhancement.py\n[Converter / CO2]", "x": 313.84715753363025, "y": 301.0546018388401}, {"borderWidth": 4.0, "color": {"background": "#66BB6A", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "OAECostAndFinancialModel", "label": "OAECostAndFinancialModel", "shape": "dot", "size": 18.0, "title": "OAECostAndFinancialModel\nconverters/co2/marine/ocean_alkalinity_enhancement.py\n[Converter / CO2]", "x": 452.67476445714954, "y": 308.7874754191507}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GridPerformanceModel", "label": "GridPerformanceModel", "shape": "dot", "size": 18.0, "title": "GridPerformanceModel\nconverters/grid/grid.py\n[Converter / Grid]", "x": 543.8580906910796, "y": 413.7741089596238}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GridCostModel", "label": "GridCostModel", "shape": "dot", "size": 18.0, "title": "GridCostModel\nconverters/grid/grid.py\n[Converter / Grid]", "x": 532.0718732102009, "y": 552.342805603834}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NaturalGasPerformanceModel", "label": "NaturalGasPerformanceModel", "shape": "dot", "size": 18.0, "title": "NaturalGasPerformanceModel\nconverters/natural_gas/natural_gas_cc_ct.py\n[Converter / Natural Gas]", "x": 424.4472139785939, "y": 640.4373943201894}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NaturalGasCostModel", "label": "NaturalGasCostModel", "shape": "dot", "size": 18.0, "title": "NaturalGasCostModel\nconverters/natural_gas/natural_gas_cc_ct.py\n[Converter / Natural Gas]", "x": 286.2569295976094, "y": 624.6065418587294}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleGasProducerPerformance", "label": "SimpleGasProducerPerformance", "shape": "dot", "size": 18.0, "title": "SimpleGasProducerPerformance\nconverters/natural_gas/dummy_gas_components.py\n[Converter / Natural Gas]", "x": 201.3280452712436, "y": 514.4356470449293}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleGasConsumerPerformance", "label": "SimpleGasConsumerPerformance", "shape": "dot", "size": 18.0, "title": "SimpleGasConsumerPerformance\nconverters/natural_gas/dummy_gas_components.py\n[Converter / Natural Gas]", "x": 221.19128243913522, "y": 376.74290673623443}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleGasProducerCost", "label": "SimpleGasProducerCost", "shape": "dot", "size": 18.0, "title": "SimpleGasProducerCost\nconverters/natural_gas/dummy_gas_components.py\n[Converter / Natural Gas]", "x": 333.8144210804685, "y": 295.05389888426953}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleGasConsumerCost", "label": "SimpleGasConsumerCost", "shape": "dot", "size": 18.0, "title": "SimpleGasConsumerCost\nconverters/natural_gas/dummy_gas_components.py\n[Converter / Natural Gas]", "x": 470.89095415961566, "y": 318.9337463910879}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "StoragePerformanceModel", "label": "StoragePerformanceModel", "shape": "diamond", "size": 18.0, "title": "StoragePerformanceModel\nstorage/storage_performance_model.py\n[Storage / General]", "x": 428.09388111524004, "y": -469.09888948081795}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "StoragePerformanceBase", "label": "StoragePerformanceBase", "shape": "diamond", "size": 20.13157894736842, "title": "StoragePerformanceBase\nstorage/storage_baseclass.py\n[Storage / General]", "x": 455.6085661088584, "y": -385.16822684557377}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "StorageAutoSizingModel", "label": "StorageAutoSizingModel", "shape": "diamond", "size": 18.0, "title": "StorageAutoSizingModel\nstorage/simple_storage_auto_sizing.py\n[Storage / General]", "x": 370.0643470376622, "y": -331.15773226109025}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GenericStorageCostModel", "label": "GenericStorageCostModel", "shape": "diamond", "size": 18.0, "title": "GenericStorageCostModel\nstorage/generic_storage_cost.py\n[Storage / General]", "x": 264.590914357365, "y": -368.7926071689721}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MCHTOLStorageCostModel", "label": "MCHTOLStorageCostModel", "shape": "diamond", "size": 18.0, "title": "MCHTOLStorageCostModel\nstorage/hydrogen/mch_storage.py\n[Storage / General]", "x": 219.55784982221226, "y": -478.13520688340736}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HydrogenStorageBaseCostModel", "label": "HydrogenStorageBaseCostModel", "shape": "diamond", "size": 20.842105263157894, "title": "HydrogenStorageBaseCostModel\nstorage/hydrogen/h2_storage_cost.py\n[Storage / General]", "x": 270.16454539792574, "y": -589.4304862347785}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "LinedRockCavernStorageCostModel", "label": "LinedRockCavernStorageCostModel", "shape": "diamond", "size": 18.0, "title": "LinedRockCavernStorageCostModel\nstorage/hydrogen/h2_storage_cost.py\n[Storage / General]", "x": 388.2687164324305, "y": -630.4775561122241}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SaltCavernStorageCostModel", "label": "SaltCavernStorageCostModel", "shape": "diamond", "size": 18.0, "title": "SaltCavernStorageCostModel\nstorage/hydrogen/h2_storage_cost.py\n[Storage / General]", "x": 501.4805766605486, "y": -572.7844347513467}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PipeStorageCostModel", "label": "PipeStorageCostModel", "shape": "diamond", "size": 18.0, "title": "PipeStorageCostModel\nstorage/hydrogen/h2_storage_cost.py\n[Storage / General]", "x": 538.9625776291, "y": -449.75172147563603}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CompressedGasStorageCostModel", "label": "CompressedGasStorageCostModel", "shape": "diamond", "size": 18.0, "title": "CompressedGasStorageCostModel\nstorage/hydrogen/h2_storage_cost.py\n[Storage / General]", "x": 475.93189116793945, "y": -336.23888907246976}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PySAMBatteryPerformanceModel", "label": "PySAMBatteryPerformanceModel", "shape": "diamond", "size": 18.0, "title": "PySAMBatteryPerformanceModel\nstorage/battery/pysam_battery.py\n[Storage / General]", "x": 349.5705117805883, "y": -302.34705409538617}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ATBBatteryCostModel", "label": "ATBBatteryCostModel", "shape": "diamond", "size": 18.0, "title": "ATBBatteryCostModel\nstorage/battery/atb_battery_cost.py\n[Storage / General]", "x": 236.61361523178934, "y": -369.9554252856643}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "FeedstockCostModel", "label": "FeedstockCostModel", "shape": "dot", "size": 18.0, "title": "FeedstockCostModel\nfeedstocks/feedstocks.py\n[Other / Other]", "x": -486.58132074145146, "y": -260.3302434705348}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "EIANaturalGasFeedstockCostModel", "label": "EIANaturalGasFeedstockCostModel", "shape": "dot", "size": 18.0, "title": "EIANaturalGasFeedstockCostModel\nfeedstocks/eia_ng_pricing.py\n[Other / Other]", "x": -459.0666357478331, "y": -176.39958083529064}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ProFastLCO", "label": "ProFastLCO", "shape": "star", "size": 18.0, "title": "ProFastLCO\nfinances/profast_lco.py\n[Finance / General]", "x": -486.58132074145146, "y": 260.3302434705349}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ProFastBase", "label": "ProFastBase", "shape": "star", "size": 19.42105263157895, "title": "ProFastBase\nfinances/profast_base.py\n[Finance / General]", "x": -459.0666357478331, "y": 344.2609061057791}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ProFastNPV", "label": "ProFastNPV", "shape": "star", "size": 18.0, "title": "ProFastNPV\nfinances/profast_npv.py\n[Finance / General]", "x": -544.6108548190293, "y": 398.2714006902626}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ResourceBaseAPIModel", "label": "ResourceBaseAPIModel", "shape": "triangle", "size": 19.42105263157895, "title": "ResourceBaseAPIModel\nresource/resource_base.py\n[Resource / General]", "x": -79.51256037378874, "y": -584.9567473090942}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MeteosatPrimeMeridianSolarAPI", "label": "MeteosatPrimeMeridianSolarAPI", "shape": "triangle", "size": 18.0, "title": "MeteosatPrimeMeridianSolarAPI\nresource/solar/nlr_developer_meteosat_prime_meridian_models.py\n[Resource / General]", "x": -51.9978753801704, "y": -501.02608467385005}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "MeteosatPrimeMeridianTMYSolarAPI", "label": "MeteosatPrimeMeridianTMYSolarAPI", "shape": "triangle", "size": 18.0, "title": "MeteosatPrimeMeridianTMYSolarAPI\nresource/solar/nlr_developer_meteosat_prime_meridian_models.py\n[Resource / General]", "x": -137.5420944513666, "y": -447.0155900893665}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SolarResourceBaseAPIModel", "label": "SolarResourceBaseAPIModel", "shape": "triangle", "size": 19.42105263157895, "title": "SolarResourceBaseAPIModel\nresource/solar/solar_resource_base.py\n[Resource / General]", "x": -243.01552713166376, "y": -484.6504649972484}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "OpenMeteoHistoricalSolarResource", "label": "OpenMeteoHistoricalSolarResource", "shape": "triangle", "size": 18.0, "title": "OpenMeteoHistoricalSolarResource\nresource/solar/openmeteo_solar.py\n[Resource / General]", "x": -288.0485916668165, "y": -593.9930647116836}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "Himawari7SolarAPI", "label": "Himawari7SolarAPI", "shape": "triangle", "size": 18.0, "title": "Himawari7SolarAPI\nresource/solar/nlr_developer_himawari_api_models.py\n[Resource / General]", "x": -237.44189609110305, "y": -705.2883440630549}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "Himawari8SolarAPI", "label": "Himawari8SolarAPI", "shape": "triangle", "size": 18.0, "title": "Himawari8SolarAPI\nresource/solar/nlr_developer_himawari_api_models.py\n[Resource / General]", "x": -119.33772505659829, "y": -746.3354139405003}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HimawariTMYSolarAPI", "label": "HimawariTMYSolarAPI", "shape": "triangle", "size": 18.0, "title": "HimawariTMYSolarAPI\nresource/solar/nlr_developer_himawari_api_models.py\n[Resource / General]", "x": -6.125864828480175, "y": -688.6422925796229}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NLRDeveloperAPISolarResourceBase", "label": "NLRDeveloperAPISolarResourceBase", "shape": "triangle", "size": 24.394736842105264, "title": "NLRDeveloperAPISolarResourceBase\nresource/solar/nlr_developer_api_base.py\n[Resource / General]", "x": 31.356136140071186, "y": -565.6095793039123}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GOESAggregatedSolarAPI", "label": "GOESAggregatedSolarAPI", "shape": "triangle", "size": 18.0, "title": "GOESAggregatedSolarAPI\nresource/solar/nlr_developer_goes_api_models.py\n[Resource / General]", "x": -31.67455032108934, "y": -452.096746900746}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GOESConusSolarAPI", "label": "GOESConusSolarAPI", "shape": "triangle", "size": 18.0, "title": "GOESConusSolarAPI\nresource/solar/nlr_developer_goes_api_models.py\n[Resource / General]", "x": -158.0359297084405, "y": -418.2049119236624}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GOESFullDiscSolarAPI", "label": "GOESFullDiscSolarAPI", "shape": "triangle", "size": 18.0, "title": "GOESFullDiscSolarAPI\nresource/solar/nlr_developer_goes_api_models.py\n[Resource / General]", "x": -270.99282625723947, "y": -485.8132831139406}, {"borderWidth": 2.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GOESTMYSolarAPI", "label": "GOESTMYSolarAPI", "shape": "triangle", "size": 18.0, "title": "GOESTMYSolarAPI\nresource/solar/nlr_developer_goes_api_models.py\n[Resource / General]", "x": -301.2124767374961, "y": -614.6459391266081}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "OpenMeteoHistoricalWindResource", "label": "OpenMeteoHistoricalWindResource", "shape": "triangle", "size": 18.0, "title": "OpenMeteoHistoricalWindResource\nresource/wind/openmeteo_wind.py\n[Resource / General]", "x": -229.44086839384488, "y": -726.515073522741}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WindResourceBaseAPIModel", "label": "WindResourceBaseAPIModel", "shape": "triangle", "size": 19.42105263157895, "title": "WindResourceBaseAPIModel\nresource/wind/wind_resource_base.py\n[Resource / General]", "x": -98.67692574249088, "y": -752.9836456314601}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WTKNLRDeveloperAPIWindResource", "label": "WTKNLRDeveloperAPIWindResource", "shape": "triangle", "size": 18.0, "title": "WTKNLRDeveloperAPIWindResource\nresource/wind/nlr_developer_wtk_api.py\n[Resource / General]", "x": 11.735802273041458, "y": -677.3143608199191}, {"borderWidth": 3.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "GenericDemandComponent", "label": "GenericDemandComponent", "shape": "dot", "size": 18.0, "title": "GenericDemandComponent\ndemand/generic_demand.py\n[Other / Other]", "x": -544.6108548190293, "y": -122.38908625080711}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DemandComponentBase", "label": "DemandComponentBase", "shape": "dot", "size": 19.42105263157895, "title": "DemandComponentBase\ndemand/demand_base.py\n[Other / Other]", "x": -650.0842874993265, "y": -160.02396115868896}, {"borderWidth": 3.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "FlexibleDemandComponent", "label": "FlexibleDemandComponent", "shape": "dot", "size": 18.0, "title": "FlexibleDemandComponent\ndemand/flexible_demand.py\n[Other / Other]", "x": -695.1173520344793, "y": -269.36656087312423}, {"borderWidth": 5.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PyomoStorageControllerBaseClass", "label": "PyomoStorageControllerBaseClass", "shape": "diamond", "size": 19.42105263157895, "title": "PyomoStorageControllerBaseClass\ncontrol/control_strategies/pyomo_storage_controller_baseclass.py\n[Storage / General]", "x": 206.39396475153265, "y": -498.78808129833186}, {"borderWidth": 5.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "StorageOpenLoopControlBase", "label": "StorageOpenLoopControlBase", "shape": "diamond", "size": 20.13157894736842, "title": "StorageOpenLoopControlBase\ncontrol/control_strategies/storage/openloop_storage_control_base.py\n[Storage / General]", "x": 278.1655730951839, "y": -610.6572156944648}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DemandOpenLoopStorageController", "label": "DemandOpenLoopStorageController", "shape": "diamond", "size": 18.0, "title": "DemandOpenLoopStorageController\ncontrol/control_strategies/storage/demand_openloop_storage_controller.py\n[Storage / General]", "x": 408.9295157465379, "y": -637.1257878031839}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HeuristicLoadFollowingStorageController", "label": "HeuristicLoadFollowingStorageController", "shape": "diamond", "size": 18.0, "title": "HeuristicLoadFollowingStorageController\ncontrol/control_strategies/storage/heuristic_pyomo_controller.py\n[Storage / General]", "x": 519.3422437620702, "y": -561.4565029916428}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "OptimizedDispatchStorageController", "label": "OptimizedDispatchStorageController", "shape": "diamond", "size": 18.0, "title": "OptimizedDispatchStorageController\ncontrol/control_strategies/storage/optimized_pyomo_controller.py\n[Storage / General]", "x": 541.9933368656092, "y": -429.1430173132935}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleStorageOpenLoopController", "label": "SimpleStorageOpenLoopController", "shape": "diamond", "size": 18.0, "title": "SimpleStorageOpenLoopController\ncontrol/control_strategies/storage/simple_openloop_controller.py\n[Storage / General]", "x": 462.6186718096345, "y": -320.4638955106441}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PeakLoadManagementHeuristicOpenLoopStorageController", "label": "PeakLoadManagementHeuristicOpenLoopStorageController", "shape": "diamond", "size": 18.0, "title": "PeakLoadManagementHeuristicOpenLoopStorageController\ncontrol/control_strategies/storage/plm_openloop_storage_controller.py\n[Storage / General]", "x": 329.04975630639206, "y": -301.68432548327485}, {"borderWidth": 5.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PyomoRuleBaseClass", "label": "PyomoRuleBaseClass", "shape": "hexagon", "size": 19.42105263157895, "title": "PyomoRuleBaseClass\ncontrol/control_rules/pyomo_rule_baseclass.py\n[Control / General]", "x": 654.0, "y": 0.0}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PyomoDispatchGenericConverter", "label": "PyomoDispatchGenericConverter", "shape": "dot", "size": 18.0, "title": "PyomoDispatchGenericConverter\ncontrol/control_rules/converters/generic_converter.py\n[Converter / Other]", "x": 549.2687648311249, "y": 433.9130181048081}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PyomoRuleStorageBaseclass", "label": "PyomoRuleStorageBaseclass", "shape": "diamond", "size": 18.0, "title": "PyomoRuleStorageBaseclass\ncontrol/control_rules/storage/pyomo_storage_rule_baseclass.py\n[Storage / General]", "x": 222.32576716621526, "y": -384.6114372269132}]); + edges = new vis.DataSet([{"arrows": "to", "from": "SiteBaseComponent", "to": "SiteLocationComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "ResizeablePerformanceModelBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SolarPerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "LinearH2FuelCellPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SteamMethaneReformerPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "GeoH2SubsurfacePerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "GeoH2SurfacePerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "DesalinationPerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "QuinnNuclearPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "CMUElectricArcFurnaceScrapOnlyPerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "ElectricArcFurnacePlantBasePerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "CMUElectricArcFurnaceDRIPerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SteelPerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SimpleASUPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "WindPerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "WindArdPerformanceCompatibilityComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "MethanolPerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "HumbertEwinPerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "IronReductionPlantBasePerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "MartinIronMinePerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "HOPPComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "PySAMTidalPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "RunOfRiverHydroPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SimpleAmmoniaPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "DOCPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "OAEPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "GridPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "NaturalGasPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SimpleGasProducerPerformance"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SimpleGasConsumerPerformance"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "StoragePerformanceBase"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "DemandComponentBase"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "GenericConverterCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "ATBUtilityPVCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "ATBResComPVCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "ElectrolyzerCostBaseClass"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "H2FuelCellCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SteamMethaneReformerCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "GeoH2SubsurfaceCostBaseClass"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "GeoH2SurfaceCostBaseClass"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "DesalinationCostBaseClass"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "QuinnNuclearCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "CMUElectricArcFurnaceCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "ElectricArcFurnacePlantBaseCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SteelCostBaseClass"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SimpleASUCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "ATBWindPlantCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "WindArdCostCompatibilityComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "MethanolCostBaseClass"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "MartinIronMineCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "IronTransportCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "IronReductionPlantBaseCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "HumbertStinnEwinCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "PySAMMarineCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "RunOfRiverHydroCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SimpleAmmoniaCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "AmmoniaSynLoopCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "DOCCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "OAECostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "OAECostAndFinancialModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "GridCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "NaturalGasCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SimpleGasProducerCost"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SimpleGasConsumerCost"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "GenericStorageCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "MCHTOLStorageCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "HydrogenStorageBaseCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "ATBBatteryCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "FeedstockCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "EIANaturalGasFeedstockCostModel"}, {"arrows": "to", "from": "ResizeablePerformanceModelBaseClass", "to": "ElectrolyzerPerformanceBaseClass"}, {"arrows": "to", "from": "ResizeablePerformanceModelBaseClass", "to": "AmmoniaSynLoopPerformanceModel"}, {"arrows": "to", "from": "CacheBaseClass", "to": "FlorisWindPlantPerformanceModel"}, {"arrows": "to", "from": "CacheBaseClass", "to": "HOPPComponent"}, {"arrows": "to", "from": "SolarPerformanceBaseClass", "to": "PYSAMSolarPlantPerformanceModel"}, {"arrows": "to", "from": "ElectrolyzerPerformanceBaseClass", "to": "ECOElectrolyzerPerformanceModel"}, {"arrows": "to", "from": "ElectrolyzerCostBaseClass", "to": "SingliticoCostModel"}, {"arrows": "to", "from": "ElectrolyzerCostBaseClass", "to": "BasicElectrolyzerCostModel"}, {"arrows": "to", "from": "ElectrolyzerCostBaseClass", "to": "CustomElectrolyzerCostModel"}, {"arrows": "to", "from": "ECOElectrolyzerPerformanceModel", "to": "WOMBATElectrolyzerModel"}, {"arrows": "to", "from": "GeoH2SubsurfacePerformanceBaseClass", "to": "NaturalGeoH2PerformanceModel"}, {"arrows": "to", "from": "GeoH2SubsurfacePerformanceBaseClass", "to": "StimulatedGeoH2PerformanceModel"}, {"arrows": "to", "from": "GeoH2SubsurfaceCostBaseClass", "to": "GeoH2SubsurfaceCostModel"}, {"arrows": "to", "from": "GeoH2SurfacePerformanceBaseClass", "to": "AspenGeoH2SurfacePerformanceModel"}, {"arrows": "to", "from": "GeoH2SurfaceCostBaseClass", "to": "AspenGeoH2SurfaceCostModel"}, {"arrows": "to", "from": "DesalinationPerformanceBaseClass", "to": "ReverseOsmosisPerformanceModel"}, {"arrows": "to", "from": "DesalinationCostBaseClass", "to": "ReverseOsmosisCostModel"}, {"arrows": "to", "from": "ElectricArcFurnacePlantBasePerformanceComponent", "to": "HydrogenEAFPlantPerformanceComponent"}, {"arrows": "to", "from": "ElectricArcFurnacePlantBasePerformanceComponent", "to": "NaturalGasEAFPlantPerformanceComponent"}, {"arrows": "to", "from": "ElectricArcFurnacePlantBaseCostComponent", "to": "HydrogenEAFPlantCostComponent"}, {"arrows": "to", "from": "ElectricArcFurnacePlantBaseCostComponent", "to": "NaturalGasEAFPlantCostComponent"}, {"arrows": "to", "from": "SteelPerformanceBaseClass", "to": "SteelPerformanceModel"}, {"arrows": "to", "from": "SteelCostBaseClass", "to": "SteelCostAndFinancialModel"}, {"arrows": "to", "from": "WindPerformanceBaseClass", "to": "PYSAMWindPlantPerformanceModel"}, {"arrows": "to", "from": "WindPerformanceBaseClass", "to": "FlorisWindPlantPerformanceModel"}, {"arrows": "to", "from": "MethanolPerformanceBaseClass", "to": "SMRMethanolPlantPerformanceModel"}, {"arrows": "to", "from": "MethanolPerformanceBaseClass", "to": "CO2HMethanolPlantPerformanceModel"}, {"arrows": "to", "from": "MethanolCostBaseClass", "to": "SMRMethanolPlantCostModel"}, {"arrows": "to", "from": "MethanolCostBaseClass", "to": "CO2HMethanolPlantCostModel"}, {"arrows": "to", "from": "MethanolFinanceBaseClass", "to": "SMRMethanolPlantFinanceModel"}, {"arrows": "to", "from": "MethanolFinanceBaseClass", "to": "CO2HMethanolPlantFinanceModel"}, {"arrows": "to", "from": "IronReductionPlantBasePerformanceComponent", "to": "HydrogenIronReductionPlantPerformanceComponent"}, {"arrows": "to", "from": "IronReductionPlantBasePerformanceComponent", "to": "NaturalGasIronReductionPlantPerformanceComponent"}, {"arrows": "to", "from": "IronReductionPlantBaseCostComponent", "to": "HydrogenIronReductionPlantCostComponent"}, {"arrows": "to", "from": "IronReductionPlantBaseCostComponent", "to": "NaturalGasIronReductionPlantCostComponent"}, {"arrows": "to", "from": "StoragePerformanceBase", "to": "StoragePerformanceModel"}, {"arrows": "to", "from": "StoragePerformanceBase", "to": "StorageAutoSizingModel"}, {"arrows": "to", "from": "StoragePerformanceBase", "to": "PySAMBatteryPerformanceModel"}, {"arrows": "to", "from": "HydrogenStorageBaseCostModel", "to": "LinedRockCavernStorageCostModel"}, {"arrows": "to", "from": "HydrogenStorageBaseCostModel", "to": "SaltCavernStorageCostModel"}, {"arrows": "to", "from": "HydrogenStorageBaseCostModel", "to": "PipeStorageCostModel"}, {"arrows": "to", "from": "HydrogenStorageBaseCostModel", "to": "CompressedGasStorageCostModel"}, {"arrows": "to", "from": "ProFastBase", "to": "ProFastLCO"}, {"arrows": "to", "from": "ProFastBase", "to": "ProFastNPV"}, {"arrows": "to", "from": "ResourceBaseAPIModel", "to": "SolarResourceBaseAPIModel"}, {"arrows": "to", "from": "ResourceBaseAPIModel", "to": "WindResourceBaseAPIModel"}, {"arrows": "to", "from": "SolarResourceBaseAPIModel", "to": "OpenMeteoHistoricalSolarResource"}, {"arrows": "to", "from": "SolarResourceBaseAPIModel", "to": "NLRDeveloperAPISolarResourceBase"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "MeteosatPrimeMeridianSolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "MeteosatPrimeMeridianTMYSolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "Himawari7SolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "Himawari8SolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "HimawariTMYSolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "GOESAggregatedSolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "GOESConusSolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "GOESFullDiscSolarAPI"}, {"arrows": "to", "from": "NLRDeveloperAPISolarResourceBase", "to": "GOESTMYSolarAPI"}, {"arrows": "to", "from": "WindResourceBaseAPIModel", "to": "OpenMeteoHistoricalWindResource"}, {"arrows": "to", "from": "WindResourceBaseAPIModel", "to": "WTKNLRDeveloperAPIWindResource"}, {"arrows": "to", "from": "DemandComponentBase", "to": "GenericDemandComponent"}, {"arrows": "to", "from": "DemandComponentBase", "to": "FlexibleDemandComponent"}, {"arrows": "to", "from": "PyomoStorageControllerBaseClass", "to": "HeuristicLoadFollowingStorageController"}, {"arrows": "to", "from": "PyomoStorageControllerBaseClass", "to": "OptimizedDispatchStorageController"}, {"arrows": "to", "from": "StorageOpenLoopControlBase", "to": "DemandOpenLoopStorageController"}, {"arrows": "to", "from": "StorageOpenLoopControlBase", "to": "SimpleStorageOpenLoopController"}, {"arrows": "to", "from": "StorageOpenLoopControlBase", "to": "PeakLoadManagementHeuristicOpenLoopStorageController"}, {"arrows": "to", "from": "PyomoRuleBaseClass", "to": "PyomoDispatchGenericConverter"}, {"arrows": "to", "from": "PyomoRuleBaseClass", "to": "PyomoRuleStorageBaseclass"}]); nodeColors = {}; allNodes = nodes.get({ returnType: "Object" }); @@ -446,7 +446,7 @@

Chemical
Control
Core / General
Hydrogen
Metal
Other
Other Elec. Generators
Renewables

Shape = Model Category
- Control
Converter
Core
Finance
Resource
Storage + Control
Converter
Core
Finance
Other
Resource
Storage

Border width = inheritance depth
From 5464b494c552374a2e716e72f351a296e65d7eae Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 1 May 2026 08:11:43 -0700 Subject: [PATCH 21/40] fix import --- h2integrate/feedstocks/test/test_feedstocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2integrate/feedstocks/test/test_feedstocks.py b/h2integrate/feedstocks/test/test_feedstocks.py index bbf4772e1..760bd3b7d 100644 --- a/h2integrate/feedstocks/test/test_feedstocks.py +++ b/h2integrate/feedstocks/test/test_feedstocks.py @@ -10,7 +10,7 @@ import openmdao.api as om from pytest import fixture -from h2integrate.core.feedstocks import FeedstockCostModel, FeedstockPerformanceModel +from h2integrate.feedstocks.feedstocks import FeedstockCostModel, FeedstockPerformanceModel @fixture From 78e18e96c53c22129eb5b840c91c55cd0e8a215f Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 1 May 2026 08:56:58 -0700 Subject: [PATCH 22/40] fix typo --- h2integrate/feedstocks/eia_ng_pricing.py | 2 +- h2integrate/feedstocks/feedstocks.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index 731c1f95a..1c2196f8d 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -401,7 +401,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - ``capacity_factor``: commodity_consumed / commodity_out - ``total_commodity_consumed``: sum of commodity_consumed divided by number of hours simulated. - - ``anual_commodity_consumed``: :py:attr:`total_commodity_consumed` * (1 / years simulated) + - ``annual_commodity_consumed``: :py:attr:`total_commodity_consumed` * (1 / years simulated) - ``rated_commodity_production``: maximum input ``commodity_out``. - ``CapEx``: :py:attr:`FeedstockCostConfig.start_up_cost`. - ``OpEx``: :py:attr:`FeedstockCostConfig.annual_cost`. diff --git a/h2integrate/feedstocks/feedstocks.py b/h2integrate/feedstocks/feedstocks.py index 35344754b..687545b64 100644 --- a/h2integrate/feedstocks/feedstocks.py +++ b/h2integrate/feedstocks/feedstocks.py @@ -190,7 +190,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - ``capacity_factor``: commodity_consumed / commodity_out - ``total_commodity_consumed``: sum of commodity_consumed divided by number of hours simulated. - - ``anual_commodity_consumed``: :py:attr:`total_commodity_consumed` * (1 / years simulated) + - ``annual_commodity_consumed``: :py:attr:`total_commodity_consumed` * (1 / years simulated) - ``rated_commodity_production``: maximum input ``commodity_out``. - ``CapEx``: :py:attr:`FeedstockCostConfig.start_up_cost`. - ``OpEx``: :py:attr:`FeedstockCostConfig.annual_cost`. From f5c068f357aca350c41c9c657374067bc00f6822 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 1 May 2026 11:22:29 -0700 Subject: [PATCH 23/40] better column handling for price --- h2integrate/feedstocks/eia_ng_pricing.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index 1c2196f8d..890b6322e 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -249,7 +249,8 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: Args: filename (Path | None, optional): The full filename where the natural gas pricing data - should be saved to or loaded from, if available. Defaults to None. + should be saved to or loaded from, if available. Must have columns "period" and + "price". Defaults to None. Raises: requests.exceptions.HTTPError: Raised if an unsuccessful API query result is returned. @@ -262,14 +263,13 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: if filename is None: filename = self.filename - cols = ["period", "value"] + cols = ["period", "price"] if filename is not None: filename = Path(filename).resolve() if filename.exists(): df = pd.read_csv(filename, parse_dates=["period"]).set_index("period") df = df.loc[df.index.dt.year.eq(self.resource_year) & df.state.eq(self.state)] df = convert_to_monthly(df, self.resource_year) - df = df.rename(columns={"value": "price"}) if df is not None: return df @@ -284,9 +284,8 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: raise ValueError(f"No data for combination {self.state=}, {self.price_category=}") df.period = pd.to_datetime(df.period) - df = df[cols].set_index("period") + df = df.set_index("period").rename(columns={"value": "price"})[cols] df = convert_to_monthly(df) - df = df.rename(columns={"value": "price"}) df.price *= MCF_to_MMBTU if filename is not None: From aa0f68cff2f45c63f718e842f4a660a27e8026f0 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 1 May 2026 14:45:06 -0700 Subject: [PATCH 24/40] add compliant performance model to accompany the cost model --- h2integrate/feedstocks/__init__.py | 5 +- h2integrate/feedstocks/eia_ng_pricing.py | 60 +++++++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/h2integrate/feedstocks/__init__.py b/h2integrate/feedstocks/__init__.py index f65cb6562..5af123871 100644 --- a/h2integrate/feedstocks/__init__.py +++ b/h2integrate/feedstocks/__init__.py @@ -1,2 +1,5 @@ from h2integrate.feedstocks.feedstocks import FeedstockPerformanceModel, FeedstockCostModel -from h2integrate.feedstocks.eia_ng_pricing import EIANaturalGasFeedstockCostModel +from h2integrate.feedstocks.eia_ng_pricing import ( + EIANaturalGasFeedstockCostModel, + EIANaturalGasFeedstockPerformanceModel, +) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index 890b6322e..130263e5f 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -4,10 +4,13 @@ from datetime import datetime import attrs +import numpy as np import pandas as pd import requests +import openmdao.api as om from attrs import field, define +from h2integrate.core.utilities import merge_shared_inputs from h2integrate.core.file_utils import get_path from h2integrate.core.model_baseclasses import BaseConfig, CostModelBaseClass @@ -166,6 +169,58 @@ def get_eia_api_key(api_key_file: Path | None) -> str: raise ValueError(f"No 'EIA_API_KEY' defined in {api_key_file=}") +@define(kw_only=True) +class EIANaturalGasFeedstockPerformanceConfig(BaseConfig): + """Configuration class for the EIA natural gas price feedstock, which uses base units of MMBtu. + + Attributes: + rated_capacity (float): The rated capacity of the feedstock in `commodity_rate_units`. + This is used to size the feedstock supply to meet the plant's needs. + """ + + rated_capacity: float = field() + commodity: str = field(default="natural_gas", init=False) + commodity_rate_units: str = field(default="MMBtu/h", init=False) + + +class EIANaturalGasFeedstockPerformanceModel(om.ExplicitComponent): + """Feedstock performance model compatible with the hard-coded units and commodity inputs + from the :py:class:`EIANaturalGasFeedstockCostModel` and + :py:class:`EIANaturalGasFeedstockConfig`. + """ + + _time_step_bounds = (3600, 3600) # (min, max) time step lengths (seconds) allowed + + def initialize(self): + self.options.declare("driver_config", types=dict) + self.options.declare("plant_config", types=dict) + self.options.declare("tech_config", types=dict) + + def setup(self): + self.config = EIANaturalGasFeedstockPerformanceConfig.from_dict( + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, + ) + self.n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] + self.add_input( + f"{self.config.commodity}_capacity", + val=self.config.rated_capacity, + units=self.config.commodity_rate_units, + ) + + self.add_output( + f"{self.config.commodity}_out", + shape=self.n_timesteps, + units=self.config.commodity_rate_units, + ) + + def compute(self, inputs, outputs): + """Generates the feedstock array operating at full capacity for a full year.""" + outputs[f"{self.config.commodity}_out"] = np.full( + self.n_timesteps, inputs[f"{self.config.commodity}_capacity"][0] + ) + + @define class EIANaturalGasFeedstockConfig(BaseConfig): """EIA Industrial Natural Gas Pricing API configuration and downloader for the US and all 50 US @@ -294,7 +349,10 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: class EIANaturalGasFeedstockCostModel(CostModelBaseClass): - """ """ + """Feedstock cost model based on the EIA natural gas price API results that uses + annual or monthly data to model an hourly time step for a single year to model the + price of natural gas used in the model. + """ _time_step_bounds = (3600, 3600) # (min, max) time step lengths (seconds) allowed From 75bab15912d9754e088cae72413af39e42446196 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 1 May 2026 14:46:41 -0700 Subject: [PATCH 25/40] update changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4a65cc0b..0ead92810 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,9 @@ - `feedstocks.py` has moved from `h2integrate/core/` to `h2_integrate/feedstocks` - Creates the `EIANaturalGasFeedstockConfig` and `EIANaturalGasFeedstockCostModel` to load EIA natural gas prices from file or to retrieve them from the EIA API. The model is able to retrieve - the US or any of the 50 states' annual or monthly values, which will be converted into an hourly timeseries. + the US or any of the 50 states' annual or monthly values, which will be converted into an hourly timeseries. Additionally, a `EIANaturalGasFeedstockPerformanceConfig` and + `EIANaturalGasFeedstockPerformanceModel` are created to be compatible with the hard-coded + definitions from the cost model. ## 0.8 [April 15, 2026] From db2411719001b4d6daf8cdfd90624accc5bc1662 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 1 May 2026 14:50:06 -0700 Subject: [PATCH 26/40] update supported models --- h2integrate/core/supported_models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/h2integrate/core/supported_models.py b/h2integrate/core/supported_models.py index 628578b09..fe261fd39 100644 --- a/h2integrate/core/supported_models.py +++ b/h2integrate/core/supported_models.py @@ -2,6 +2,7 @@ FeedstockCostModel, FeedstockPerformanceModel, EIANaturalGasFeedstockCostModel, + EIANaturalGasFeedstockPerformanceModel, ) from h2integrate.resource.river import RiverResource from h2integrate.resource.tidal import TidalResource @@ -315,6 +316,7 @@ "FeedstockPerformanceModel": FeedstockPerformanceModel, "FeedstockCostModel": FeedstockCostModel, "EIANaturalGasFeedstockCostModel": EIANaturalGasFeedstockCostModel, + "EIANaturalGasFeedstockPerformanceModel": EIANaturalGasFeedstockPerformanceModel, # Grid "GridPerformanceModel": GridPerformanceModel, "GridCostModel": GridCostModel, From db00870d0c774a1c2a2ad28c6e0f08c4a3578908 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 1 May 2026 14:55:05 -0700 Subject: [PATCH 27/40] remove lingering resource reference --- h2integrate/feedstocks/eia_ng_pricing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index 130263e5f..ac0e6106b 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -384,7 +384,7 @@ def setup(self): ``plant_life``. """ self.config = EIANaturalGasFeedstockConfig.from_dict( - self.options["resource_config"], + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), additional_cls_name=self.__class__.__name__, ) self.n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) From 319c1f50a1e1bce73bf3642998c74a39a5e99942 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 1 May 2026 14:57:01 -0700 Subject: [PATCH 28/40] update column filtering from previous modification --- h2integrate/feedstocks/eia_ng_pricing.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index ac0e6106b..fbfd74de7 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -318,7 +318,6 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: if filename is None: filename = self.filename - cols = ["period", "price"] if filename is not None: filename = Path(filename).resolve() if filename.exists(): @@ -339,7 +338,7 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: raise ValueError(f"No data for combination {self.state=}, {self.price_category=}") df.period = pd.to_datetime(df.period) - df = df.set_index("period").rename(columns={"value": "price"})[cols] + df = df.set_index("period").rename(columns={"value": "price"})[["price"]] df = convert_to_monthly(df) df.price *= MCF_to_MMBTU From 2f48312d2f9441360d4441a40e445233485b769a Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 1 May 2026 14:58:16 -0700 Subject: [PATCH 29/40] remove deprecated arg --- h2integrate/feedstocks/eia_ng_pricing.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index fbfd74de7..7a1f71817 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -86,13 +86,12 @@ } -def convert_to_monthly(df: pd.DataFrame, year: int) -> pd.DataFrame | None: +def convert_to_monthly(df: pd.DataFrame) -> pd.DataFrame | None: """Converts an annual timeseries to monthly by repeating the one value, or returns the data passed, if already monthly. Args: df (pd.DataFrame): The annual or monthly natural gas pricing data. - year (int): The resource year. Returns: pd.DataFrame | None: Returns back the monthly data if the original data have either From 4a5f199095ac0e23c5ac98b3bd9502e687edfb67 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 1 May 2026 15:54:34 -0700 Subject: [PATCH 30/40] fix miscellaneous data handling issues --- h2integrate/feedstocks/eia_ng_pricing.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index 7a1f71817..414a96753 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -266,13 +266,17 @@ class EIANaturalGasFeedstockConfig(BaseConfig): commodity: str = field(default="natural_gas", init=False) commodity_rate_units: str = field(default="MMBtu/h", init=False) commodity_amount_units: str = field(default="MMBtu", init=False) - filename: str = field(default=None, converter=attrs.converters.optional(get_path)) + filename: str = field(default=None) def __attrs_post_init__(self): """Creates the EIA natural gas facet series code based on validated user inputs, sets the :py:attr:`commodity_amount_units` if not given a value, and fetches the EIA natural gas price. """ + try: + self.filename = get_path(self.filename) + except FileNotFoundError: + self.filename = Path(self.filename).resolve() self.series = EIA_FACET[self.price_category].format(self.state) if self.commodity_amount_units is None: self.commodity_amount_units = f"({self.commodity_rate_units})*h" @@ -280,15 +284,16 @@ def __attrs_post_init__(self): self.price = self.get_data() def create_eia_api_url(self): + year = self.resource_year base_url = "https://api.eia.gov/v2/natural-gas/pri/sum/data/" frequency = f"frequency={'monthly' if self.monthly else 'annual'}" data = "data[0]=value" facet = f"facets[series][]={self.series}" - start = f"start={self.resource_year}" - end = f"end={self.resource_year}" + start = f"start={year}" + end = f"end={year}" if self.monthly: start = f"{start}-01" - end = f"{start}-12" + end = f"{end}-12" sort_col = "sort[0][column]=period" sort_dir = "sort[0][direction]=asc" api_key = f"api_key={get_eia_api_key(self.api_key_file)}" @@ -321,22 +326,24 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: filename = Path(filename).resolve() if filename.exists(): df = pd.read_csv(filename, parse_dates=["period"]).set_index("period") - df = df.loc[df.index.dt.year.eq(self.resource_year) & df.state.eq(self.state)] - df = convert_to_monthly(df, self.resource_year) + df = df.loc[ + (df.index.year == self.resource_year) & df.state.eq(self.state), ["price"] + ] + df = convert_to_monthly(df) if df is not None: return df r = requests.get(self.url) if r.status_code != 200: err = json.loads(r.text)["error"] - msg = f"{err['code']}: {err['message']}" - raise requests.exceptions.HTTPError(msg) + raise requests.exceptions.HTTPError(err) df = pd.DataFrame.from_dict(json.loads(r.text)["response"]["data"]) if df.size == 0: raise ValueError(f"No data for combination {self.state=}, {self.price_category=}") df.period = pd.to_datetime(df.period) + df.value = df.value.astype(float) df = df.set_index("period").rename(columns={"value": "price"})[["price"]] df = convert_to_monthly(df) df.price *= MCF_to_MMBTU From 6da03da32dab8b6b8fb78e63d37065cb3bde3981 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 1 May 2026 15:56:01 -0700 Subject: [PATCH 31/40] ensure saved data is compatible with reload --- h2integrate/feedstocks/eia_ng_pricing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index 414a96753..45e99ea71 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -344,13 +344,13 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: df.period = pd.to_datetime(df.period) df.value = df.value.astype(float) - df = df.set_index("period").rename(columns={"value": "price"})[["price"]] + df = df.set_index("period").rename(columns={"value": "price"})[["state", "price"]] df = convert_to_monthly(df) df.price *= MCF_to_MMBTU if filename is not None: df.to_csv(filename, index_label="period") - return df + return df[["price"]] class EIANaturalGasFeedstockCostModel(CostModelBaseClass): From df2f8371c291dd00586c89e04208618761f9ac19 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 1 May 2026 16:21:40 -0700 Subject: [PATCH 32/40] fix final data handling issue --- h2integrate/feedstocks/eia_ng_pricing.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index 45e99ea71..5b035e716 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -344,7 +344,11 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: df.period = pd.to_datetime(df.period) df.value = df.value.astype(float) - df = df.set_index("period").rename(columns={"value": "price"})[["state", "price"]] + df = ( + df.set_index("period") + .rename(columns={"value": "price", "area-name": "state"}) + .replace("U.S.", "US") + )[["state", "price"]] df = convert_to_monthly(df) df.price *= MCF_to_MMBTU From 8f39425a15b4abd94c6cc2db3efbb9bed1353b2f Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Fri, 1 May 2026 16:25:48 -0700 Subject: [PATCH 33/40] update changelog for pr number --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ead92810..43572638e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,11 +19,12 @@ - Rename `n_control_window` to `n_control_window_hours` for unit clarity [PR 712](https://github.com/NatLabRockies/H2Integrate/pull/712) - Update N2 diagram for demand openloop control from static and outdated to dynamic and interactive [PR 714](https://github.com/NatLabRockies/H2Integrate/pull/714) - `feedstocks.py` has moved from `h2integrate/core/` to `h2_integrate/feedstocks` + [PR 719](https://github.com/NatLabRockies/H2Integrate/pull/719) - Creates the `EIANaturalGasFeedstockConfig` and `EIANaturalGasFeedstockCostModel` to load EIA natural gas prices from file or to retrieve them from the EIA API. The model is able to retrieve the US or any of the 50 states' annual or monthly values, which will be converted into an hourly timeseries. Additionally, a `EIANaturalGasFeedstockPerformanceConfig` and `EIANaturalGasFeedstockPerformanceModel` are created to be compatible with the hard-coded - definitions from the cost model. + definitions from the cost model. [PR 719](https://github.com/NatLabRockies/H2Integrate/pull/719) ## 0.8 [April 15, 2026] From cb56bb60ff5c1d92aa378600a49faaeb2f7bb9ee Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 5 May 2026 15:50:37 -0700 Subject: [PATCH 34/40] add latitude and longitude, and handle no api key file input --- h2integrate/feedstocks/eia_ng_pricing.py | 28 +++++++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index 5b035e716..a3bfd7680 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -251,15 +251,18 @@ class EIANaturalGasFeedstockConfig(BaseConfig): resource_year: int = field(validator=attrs.validators.in_(range(2001, CURRENT_YEAR + 1))) monthly: bool = field(validator=attrs.validators.instance_of(bool)) - api_key_file: str | None = field(converter=attrs.converters.optional(get_path)) + price_category: str = field(converter=str.lower, validator=attrs.validators.in_(EIA_FACET)) + url: str = field(default=None, init=False) + series: str = field(init=False) + price: pd.DataFrame = field(init=False, validator=attrs.validators.instance_of(pd.DataFrame)) + api_key_file: str | None = field(default=None, converter=attrs.converters.optional(get_path)) state: str = field( + default=None, converter=attrs.converters.pipe(convert_state_value, convert_state_to_code), validator=attrs.validators.in_([*STATE_MAP, *STATE_MAP.values()]), ) - price_category: str = field(converter=str.lower, validator=attrs.validators.in_(EIA_FACET)) - url: str = field(init=False) - series: str = field(init=False) - price: pd.DataFrame = field(init=False, validator=attrs.validators.instance_of(pd.DataFrame)) + latitude: float = field(default=0.0, validator=attrs.validators.instance_of(float)) + longitude: float = field(default=0.0, validator=attrs.validators.instance_of(float)) cost_year: int = field(default=CURRENT_YEAR) annual_cost: float = field(default=0.0, converter=float) start_up_cost: float = field(default=0.0, converter=float) @@ -280,7 +283,8 @@ def __attrs_post_init__(self): self.series = EIA_FACET[self.price_category].format(self.state) if self.commodity_amount_units is None: self.commodity_amount_units = f"({self.commodity_rate_units})*h" - self.url = self.create_eia_api_url() + if self.api_key_file is not None: + self.url = self.create_eia_api_url() self.price = self.get_data() def create_eia_api_url(self): @@ -333,6 +337,13 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: if df is not None: return df + if self.url is None: + msg = ( + "One of `api_key_file` or `filename` with existing data provided to use the" + "`EIANaturalGasFeedstock` cost and performance models." + ) + raise ValueError(msg) + r = requests.get(self.url) if r.status_code != 200: err = json.loads(r.text)["error"] @@ -392,9 +403,14 @@ def setup(self): :py:attr:`EIANaturalGasFeedstockConfig.price` to an hourly timeseries for the ``plant_life``. """ + # TODO: figure out mult-site or single site usage for coordinates input + # if (site_config := self.options["plant_config"].get("site")) is None: + # raise ValueError("Single-site definition is missing from the plant configuration.") self.config = EIANaturalGasFeedstockConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + # merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") | site_config, # noqa: E501 additional_cls_name=self.__class__.__name__, + strict=False, ) self.n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) From 05a4866eb4349c3bb2311add43cfdd51b3449f28 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 5 May 2026 16:09:30 -0700 Subject: [PATCH 35/40] add reverse geocoding --- h2integrate/feedstocks/eia_ng_pricing.py | 56 ++++++++++++++++++++---- pyproject.toml | 3 +- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index a3bfd7680..046bd510e 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -107,6 +107,31 @@ def convert_to_monthly(df: pd.DataFrame) -> pd.DataFrame | None: pass +def get_state_from_coords(latitude: float, longitude: float) -> str: + """Reverse geocodes a :py:attr:`latitude` and :py:attr:`longitude` pair to get the + state containing the coordinate pair. + + Args: + latitude (float): Site latitude. + longitude (float): Site longitude. + + Returns: + str: 2-letter state code (i.e., "Alabama" -> "AL"). + """ + try: + import reverse_geocoder as rg + except ModuleNotFoundError as e: + msg = ( + "EIA natural gas feedstock coordinate input requires `reverse_geocoder` to be" + " installed. Directly `pip install reverse_geocoder` or use" + " `pip install h2integrate[gis]`." + ) + raise ModuleNotFoundError(msg) from e + + result = rg.search((latitude, longitude)) + return convert_state_to_code(convert_state_value(result["admin1"])) + + def convert_state_value(state: str) -> str: """Convert potential two-letter state abbreviations to upper case and all else to title casing to align with the ``STATE_MAP`` keys and values. @@ -154,7 +179,7 @@ def get_eia_api_key(api_key_file: Path | None) -> str: if key is None: msg = ( "No `api_key_file` provided for the EIA API, and 'EIA_API_KEY' is not defined as an" - "environment variable." + " environment variable." ) raise ValueError(msg) return key @@ -258,11 +283,15 @@ class EIANaturalGasFeedstockConfig(BaseConfig): api_key_file: str | None = field(default=None, converter=attrs.converters.optional(get_path)) state: str = field( default=None, - converter=attrs.converters.pipe(convert_state_value, convert_state_to_code), - validator=attrs.validators.in_([*STATE_MAP, *STATE_MAP.values()]), + converter=attrs.converters.optional( + attrs.converters.pipe(convert_state_value, convert_state_to_code) + ), + validator=attrs.validators.optional( + attrs.validators.in_([*STATE_MAP, *STATE_MAP.values()]) + ), ) - latitude: float = field(default=0.0, validator=attrs.validators.instance_of(float)) - longitude: float = field(default=0.0, validator=attrs.validators.instance_of(float)) + latitude: float = field(default=999.9, validator=attrs.validators.instance_of(float)) + longitude: float = field(default=999.9, validator=attrs.validators.instance_of(float)) cost_year: int = field(default=CURRENT_YEAR) annual_cost: float = field(default=0.0, converter=float) start_up_cost: float = field(default=0.0, converter=float) @@ -280,6 +309,17 @@ def __attrs_post_init__(self): self.filename = get_path(self.filename) except FileNotFoundError: self.filename = Path(self.filename).resolve() + + if self.state is None: + if self.latitude == 999.9 or self.longitude == 999.9: + msg = ( + "The EIA natural gas feedstock model require one of `state` or" + " `latitude` and `longitude`." + ) + raise ValueError(msg) + + self.state = get_state_from_coords(self.latitude, self.longitude) + self.series = EIA_FACET[self.price_category].format(self.state) if self.commodity_amount_units is None: self.commodity_amount_units = f"({self.commodity_rate_units})*h" @@ -340,7 +380,7 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: if self.url is None: msg = ( "One of `api_key_file` or `filename` with existing data provided to use the" - "`EIANaturalGasFeedstock` cost and performance models." + " `EIANaturalGasFeedstock` cost and performance models." ) raise ValueError(msg) @@ -392,8 +432,8 @@ def _extrapolate_price_to_hourly(self) -> pd.DataFrame: ) if price.shape[0] != self.n_timesteps: msg = ( - "An error occurred converting EIA data to hourly to match size: " - f"{price.shape[0]} to simulation {self.n_timesteps=}" + "An error occurred converting EIA data to hourly to match size:" + f" {price.shape[0]} to simulation {self.n_timesteps=}" ) raise ValueError(msg) return price diff --git a/pyproject.toml b/pyproject.toml index 942e7e98e..a83fe08d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,8 @@ examples = [ ] gis = [ "geopandas", - "contextily" + "contextily", + "reverse_geocoder" ] ard = ["ard-nrel"] extras = ["h2integrate[gis,ard]"] From 3794a6eb6d72316f0c3dbd8d541795c74a855315 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 5 May 2026 16:16:56 -0700 Subject: [PATCH 36/40] update docs for reverse geocoding --- docs/technology_models/feedstocks.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/technology_models/feedstocks.md b/docs/technology_models/feedstocks.md index 7fae7a1df..dc69b6344 100644 --- a/docs/technology_models/feedstocks.md +++ b/docs/technology_models/feedstocks.md @@ -123,10 +123,22 @@ Similar to the standard feedstock model, the following variables are able to be Additionally, there are a few other settings that users will need to provide for the model to work that differ from the standard cost model. +:::{important} +If relying on site coordinate data (`latitude` and `longitude`), then the `reverse_geocoder` package +is required, which can be pip installed directly or through the `gis` library extras. +::: + - `resource_year` (int): Which year to obtain the data from in the range [2001, 2026]. - `monthly` (bool): If True, the monthly data are retrieved, otherwise the annual price is used. -- `state` (str): Full name of the state or two-letter state abbreviation, such as "United States" or - "US". Only the "US" or all 50 states will produce valid results +- location data options 1) use `state` or 2) use `latitude` and `longitude`: + - `state` (str): Full name of the state or two-letter state abbreviation, such as "United States" or + "US". Only the "US" or all 50 states will produce valid results. When `state` is provided, the + site coordinate data will be ignored. + - `latitude` (float): Latitude of the natural gas plant site. Only used when `state` is not + provided, and will be filled from the plant configuration's site data if not provided. + - `longitude` (float): Longitude of the natural gas plant site. Only used when `state` is not + provided, and will be filled from the plant configuration's site data if not provided. + - `price_category` (str): One of "wellhead", "imports", "citygate", "residential", "commercial", "industrial", "electrical_power", or "exports". Note that not all categories will return state-level data. From 8e37cd997a452010d68cdc5c439c4af3cce4cb47 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 5 May 2026 16:47:10 -0700 Subject: [PATCH 37/40] add back correct annual reindex logic --- h2integrate/feedstocks/eia_ng_pricing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index 046bd510e..dc0944cea 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -101,7 +101,9 @@ def convert_to_monthly(df: pd.DataFrame) -> pd.DataFrame | None: case 12: return df.resample("MS").bfill() # ensure it's start of the month case 1: - df = df.resample("MS").nearest() + year = df.index.year[0] + ix = pd.date_range(f"{year}-01", f"{year}-12", freq="MS") + df = df.reindex(ix, method="nearest") return df case _: pass From 1cad6b1f05681e001b1e0f4dc63a46783034fee9 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 5 May 2026 17:31:02 -0700 Subject: [PATCH 38/40] add test for all but feedstock cost model and fix minor issues --- h2integrate/feedstocks/eia_ng_pricing.py | 2 +- .../feedstocks/test/test_eia_ng_feedstock.py | 132 ++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 h2integrate/feedstocks/test/test_eia_ng_feedstock.py diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index dc0944cea..f6c717cfd 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -130,7 +130,7 @@ def get_state_from_coords(latitude: float, longitude: float) -> str: ) raise ModuleNotFoundError(msg) from e - result = rg.search((latitude, longitude)) + result = rg.search((latitude, longitude))[0] return convert_state_to_code(convert_state_value(result["admin1"])) diff --git a/h2integrate/feedstocks/test/test_eia_ng_feedstock.py b/h2integrate/feedstocks/test/test_eia_ng_feedstock.py new file mode 100644 index 000000000..da86c5448 --- /dev/null +++ b/h2integrate/feedstocks/test/test_eia_ng_feedstock.py @@ -0,0 +1,132 @@ +import os +import importlib + +import numpy as np +import pandas as pd +import pytest + +from h2integrate.feedstocks import eia_ng_pricing as eia + + +DUMMY_KEY = "xxxxxx" + + +@pytest.fixture +def EIA_API_key_file(temp_dir): + """Creates a dummy EIA API key configuration file and returns the file path object.""" + good_api_fn = temp_dir / ".eiarc" + bad_api_fn = temp_dir / ".badeiarc" + with good_api_fn.open("w") as f: + f.write(f"EIA_API_KEY: {DUMMY_KEY}") + with bad_api_fn.open("w") as f: + f.write(f"EIA_API: {DUMMY_KEY}") + return good_api_fn, bad_api_fn + + +@pytest.mark.unit +def test_convert_to_monthly(subtests): + """Test the annual and month-start conversions.""" + correct_ix = pd.DatetimeIndex(pd.date_range("2000-01", "2000-12", freq="MS"), name="period") + + annual_df = pd.DataFrame( + [[10]], columns=["price"], index=pd.Index(pd.to_datetime(["2000-01-01"]), name="period") + ) + with subtests.test("Convert annual to monthly value"): + df = eia.convert_to_monthly(annual_df) + assert (df.index == correct_ix).all() + assert all(df.price.values == 10) + + ms_ix = pd.to_datetime([f"2000-{x:02d}-01" for x in range(1, 13)]) + me_ix = pd.to_datetime([f"2000-{x:02d}-28" for x in range(1, 13)]) + correct_monthly_vals = np.arange(1, 13) + + with subtests.test("Test month start inputs for monthly conversion to month starts"): + df = pd.DataFrame( + correct_monthly_vals, columns=["price"], index=pd.Index(ms_ix, name="period") + ) + df = eia.convert_to_monthly(df) + assert (df.index == correct_ix).all() + assert (df.price.to_numpy() == correct_monthly_vals).all() + + with subtests.test("Test month start inputs for monthly conversion to month starts"): + df = pd.DataFrame( + correct_monthly_vals, columns=["price"], index=pd.Index(me_ix, name="period") + ) + df = eia.convert_to_monthly(df) + assert (df.index == correct_ix).all() + assert (df.price.to_numpy() == correct_monthly_vals).all() + + +@pytest.mark.unit +@pytest.mark.skipif( + importlib.util.find_spec("reverse_geocoder") is None, reason="reverse_geocoder is not installed" +) +def test_get_state_from_coords(subtests): + """Test the reverse geocoding for state data functionality.""" + best_trailer_in_colorado_coords = (39.9140081, -105.2249155) + definitely_not_the_us_coords = (53.5265263, -113.657807) + + with subtests.test("Test valid US coordinate pair"): + assert "CO" == eia.get_state_from_coords(*best_trailer_in_colorado_coords) + + with subtests.test("Test invalid US coordinate pair"): + result = eia.get_state_from_coords(*definitely_not_the_us_coords) + assert result not in eia.STATE_MAP.values() + assert result == "Alberta" + + +@pytest.mark.unit +@pytest.mark.skipif( + importlib.util.find_spec("reverse_geocoder") is not None, reason="reverse_geocoder is installed" +) +def test_get_state_from_coords_fail(): + """Tests that the correct error is raised when ``reverse_geocoder` is missing.""" + msg = "EIA natural gas feedstock coordinate input requires `reverse_geocoder`" + with pytest.raises(ModuleNotFoundError, match=msg): + eia.get_state_from_coords(0, 0) + + +@pytest.mark.unit +def test_convert_state_value(): + """Tests the conversion of the state value to a compliant name or code format.""" + assert eia.convert_state_value("united states") == "United States" + assert eia.convert_state_value("us") == "US" + + +@pytest.mark.unit +def test_convert_state_to_code(): + """Tests the conversion of a state name to a 2 letter code.""" + assert eia.convert_state_to_code("Washington") == "WA" + assert eia.convert_state_to_code("DC") == "DC" + assert eia.convert_state_to_code("JK") == "JK" + assert eia.convert_state_to_code("washington") == "washington" + + +@pytest.mark.unit +def test_get_eia_api_key(subtests, EIA_API_key_file): + """Tests the API Key retrieval.""" + good_api_fn, bad_api_fn = EIA_API_key_file + + with subtests.test("Use a dummy file"): + assert eia.get_eia_api_key(good_api_fn) == DUMMY_KEY + + if (api_key := os.environ.get("EIA_API_KEY")) is None: + api_key = DUMMY_KEY + os.environ["EIA_API_KEY"] = api_key + with subtests.test("Use the environment variable"): + assert eia.get_eia_api_key(None) == api_key + del os.environ["EIA_API_KEY"] + + with subtests.test("Error is raised for no file nor env variable"): + msg = "No `api_key_file` provided for the EIA API, and 'EIA_API_KEY'" + with pytest.raises(ValueError, match=msg): + eia.get_eia_api_key(None) + + with subtests.test("Error is raised for file with bad key name"): + msg = "No 'EIA_API_KEY' defined" + with pytest.raises(ValueError, match=msg): + eia.get_eia_api_key(bad_api_fn) + + +@pytest.mark.unit +def test_EIANaturalGasFeedstockConfig(): ... From 67c2fb94e27bb83bdbc652321ce5c48b7bd58767 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 5 May 2026 17:37:41 -0700 Subject: [PATCH 39/40] add missing docstring --- h2integrate/feedstocks/eia_ng_pricing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index f6c717cfd..7abbf092c 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -330,6 +330,7 @@ def __attrs_post_init__(self): self.price = self.get_data() def create_eia_api_url(self): + """Creates the full EIA natural gas API url.""" year = self.resource_year base_url = "https://api.eia.gov/v2/natural-gas/pri/sum/data/" frequency = f"frequency={'monthly' if self.monthly else 'annual'}" From 68070f3e51b17cf92b04231ada72de70a4250c00 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Wed, 6 May 2026 10:39:16 -0700 Subject: [PATCH 40/40] add test for configuration class and fix class issues --- h2integrate/feedstocks/eia_ng_pricing.py | 23 +++----- .../feedstocks/test/test_eia_ng_feedstock.py | 59 ++++++++++++++++++- 2 files changed, 67 insertions(+), 15 deletions(-) diff --git a/h2integrate/feedstocks/eia_ng_pricing.py b/h2integrate/feedstocks/eia_ng_pricing.py index 7abbf092c..335ae9a2c 100644 --- a/h2integrate/feedstocks/eia_ng_pricing.py +++ b/h2integrate/feedstocks/eia_ng_pricing.py @@ -307,10 +307,11 @@ def __attrs_post_init__(self): :py:attr:`commodity_amount_units` if not given a value, and fetches the EIA natural gas price. """ - try: - self.filename = get_path(self.filename) - except FileNotFoundError: - self.filename = Path(self.filename).resolve() + if self.filename is not None: + try: + self.filename = get_path(self.filename) + except FileNotFoundError: + self.filename = Path(self.filename).resolve() if self.state is None: if self.latitude == 999.9 or self.longitude == 999.9: @@ -325,12 +326,13 @@ def __attrs_post_init__(self): self.series = EIA_FACET[self.price_category].format(self.state) if self.commodity_amount_units is None: self.commodity_amount_units = f"({self.commodity_rate_units})*h" - if self.api_key_file is not None: - self.url = self.create_eia_api_url() + self.url = self.create_eia_api_url() self.price = self.get_data() def create_eia_api_url(self): """Creates the full EIA natural gas API url.""" + api_key = get_eia_api_key(self.api_key_file) + year = self.resource_year base_url = "https://api.eia.gov/v2/natural-gas/pri/sum/data/" frequency = f"frequency={'monthly' if self.monthly else 'annual'}" @@ -343,7 +345,7 @@ def create_eia_api_url(self): end = f"{end}-12" sort_col = "sort[0][column]=period" sort_dir = "sort[0][direction]=asc" - api_key = f"api_key={get_eia_api_key(self.api_key_file)}" + api_key = f"api_key={api_key}" url_opts = "&".join((frequency, data, facet, start, end, sort_col, sort_dir, api_key)) url = f"{base_url}?{url_opts}" @@ -380,13 +382,6 @@ def get_data(self, filename: Path | None = None) -> pd.DataFrame: if df is not None: return df - if self.url is None: - msg = ( - "One of `api_key_file` or `filename` with existing data provided to use the" - " `EIANaturalGasFeedstock` cost and performance models." - ) - raise ValueError(msg) - r = requests.get(self.url) if r.status_code != 200: err = json.loads(r.text)["error"] diff --git a/h2integrate/feedstocks/test/test_eia_ng_feedstock.py b/h2integrate/feedstocks/test/test_eia_ng_feedstock.py index da86c5448..b1ab09101 100644 --- a/h2integrate/feedstocks/test/test_eia_ng_feedstock.py +++ b/h2integrate/feedstocks/test/test_eia_ng_feedstock.py @@ -4,6 +4,7 @@ import numpy as np import pandas as pd import pytest +from requests.exceptions import HTTPError from h2integrate.feedstocks import eia_ng_pricing as eia @@ -129,4 +130,60 @@ def test_get_eia_api_key(subtests, EIA_API_key_file): @pytest.mark.unit -def test_EIANaturalGasFeedstockConfig(): ... +def test_EIANaturalGasFeedstockConfig(subtests, EIA_API_key_file): + """Tests a failed API for basic parameterizations.""" + if (api_key := os.environ.get("EIA_API_KEY")) is None: + api_key = DUMMY_KEY + os.environ["EIA_API_KEY"] = api_key + + correct_url = ( + "https://api.eia.gov/v2/natural-gas/pri/sum/data/" + "?frequency=monthly" + "&data[0]=value" + "&facets[series][]=N3035AK3" + "&start=2022-01" + "&end=2022-12" + "&sort[0][column]=period" + "&sort[0][direction]=asc" + f"&api_key={api_key}" + ) + if api_key == DUMMY_KEY: + with subtests.test("Ensure API URL is correct for no API key"): + with pytest.raises(HTTPError): + config = eia.EIANaturalGasFeedstockConfig( + state="ak", + resource_year=2022, + cost_year=2025, + monthly=True, + price_category="industrial", + ) + assert config.url == correct_url + del os.environ["EIA_API_KEY"] + else: + with subtests.test("Ensure API works if a valid API environment variable exists"): + config = eia.EIANaturalGasFeedstockConfig( + state="ak", + resource_year=2022, + cost_year=2025, + monthly=True, + price_category="industrial", + ) + assert config.url == correct_url + assert isinstance(config.price, pd.DataFrame) + assert config.price.shape == (12, 1) + assert ( + config.price.index == pd.Index(pd.date_range("2022-01", "2022-12", freq="MS")) + ).all() + + with subtests.test("Check no location data failure"): + msg = ( + "The EIA natural gas feedstock model require one of `state` or" + " `latitude` and `longitude`." + ) + with pytest.raises(ValueError, match=msg): + eia.EIANaturalGasFeedstockConfig( + resource_year=2022, + cost_year=2025, + monthly=True, + price_category="industrial", + )