From f1815c612bc085e3edb2667ec021de07e5c9ab98 Mon Sep 17 00:00:00 2001 From: Jared Thomas Date: Wed, 23 Sep 2026 17:32:20 -0600 Subject: [PATCH 1/4] enable resource handling for multiple years and non-annual time steps --- h2integrate/resource/resource_base.py | 233 ++++++++- h2integrate/resource/resource_base_hpc.py | 3 +- .../resource/solar/nlr_developer_api_base.py | 19 +- h2integrate/resource/solar/openmeteo_solar.py | 33 +- .../resource/test/test_resource_base.py | 193 +++++++ .../utilities/test/test_time_tools.py | 492 ++++++++++++++++++ h2integrate/resource/utilities/time_tools.py | 375 +++++++++++-- .../wind/nlr_developer_wtk_api_base.py | 19 +- .../wind/nlr_developer_wtk_api_models.py | 4 +- h2integrate/resource/wind/openmeteo_wind.py | 32 +- 10 files changed, 1289 insertions(+), 114 deletions(-) create mode 100644 h2integrate/resource/test/test_resource_base.py create mode 100644 h2integrate/resource/utilities/test/test_time_tools.py diff --git a/h2integrate/resource/resource_base.py b/h2integrate/resource/resource_base.py index 4af0726c1..c8c89b6a6 100644 --- a/h2integrate/resource/resource_base.py +++ b/h2integrate/resource/resource_base.py @@ -1,3 +1,4 @@ +import copy import warnings from pathlib import Path @@ -7,7 +8,12 @@ from h2integrate.core.utilities import BaseConfig from h2integrate.core.file_utils import check_resource_dir -from h2integrate.resource.utilities.time_tools import add_resource_start_end_times +from h2integrate.resource.utilities.time_tools import ( + concatenate_resource_years, + add_resource_start_end_times, + resample_resource_data_to_dt, + conform_resource_data_to_n_timesteps, +) from h2integrate.resource.utilities.download_tools import download_from_api @@ -42,8 +48,17 @@ class ResourceBaseAPIConfig(BaseConfig): Defaults to an empty dictionary. resource_dir (str | Path, optional): Folder to save resource files to or load resource files from. Defaults to "". - resource_filename (str, optional): Filename to save resource data to or load - resource data from. Defaults to None. + resource_filename (str | Path | list, optional): Filename to save resource data to + or load resource data from. For multi-year simulations, provide a list of + filenames (one per consecutive year, in chronological order starting at + ``resource_year``). Defaults to "". + upsample_method (str, optional): interpolation method passed to + ``pandas.DataFrame.interpolate`` when resampling to a finer timestep than the + data provides. Defaults to "time". The "time" method uses linear interpolation + but accounting for the actual time step. + downsample_method (str, optional): aggregation passed to the pandas resampler + when resampling to a coarser timestep than the data provides. Defaults to + "mean". Attributes: dataset_desc (str): description of the dataset, used in file naming. @@ -60,8 +75,10 @@ class ResourceBaseAPIConfig(BaseConfig): dataset_desc: str = field(default="default", init=False) resource_type: str = field(default="none", init=False) resource_data: dict | object = field(default={}) - resource_filename: Path | str = field(default="") + resource_filename: Path | str | list = field(default="") resource_dir: Path | str | None = field(default=None) + upsample_method: str = field(default="time") + downsample_method: str = field(default="mean") class ResourceBaseAPIModel(om.ExplicitComponent): @@ -129,7 +146,7 @@ def helper_setup_method(self): return resource_specs - def create_filename(self, latitude, longitude): + def create_filename(self, latitude, longitude, resource_year=None): """Create default filename to save downloaded data to. Suggested filename formatting is: "{latitude}_{longitude}_{resource_year}_{dataset_desc}_{interval}min_{tz_desc}_tz.csv" @@ -138,6 +155,8 @@ def create_filename(self, latitude, longitude): Args: latitude (float): latitude corresponding to location for resource data longitude (float): longitude corresponding to location for resource data + resource_year (int | str | None): resource year to build the filename for. When + None, ``self.config.resource_year`` is used. Returns: str: filename for resource data to be saved to or loaded from. @@ -145,12 +164,14 @@ def create_filename(self, latitude, longitude): raise NotImplementedError("This method should be implemented in a subclass.") - def create_url(self, latitude, longitude): + def create_url(self, latitude, longitude, resource_year=None): """Create url for data download. Args: latitude (float): latitude corresponding to location for resource data longitude (float): longitude corresponding to location for resource data + resource_year (int | str | None): resource year to build the url for. When None, + ``self.config.resource_year`` is used. Returns: str: url to use for API call. @@ -172,12 +193,15 @@ def download_data(self, url, fpath): success = download_from_api(url, fpath) return success - def load_data(self, fpath): + def load_data(self, fpath, resource_year=None): """Loads data from a file, reformats data to follow a standardized naming convention, converts data to standardized units, and creates a data time profile. Args: fpath (str | fpath): filepath to load the data from. + resource_year (int | str | None): resource year the file corresponds to, used by + datasets that filter a multi-year file down to a single year. When None, + ``self.config.resource_year`` is used. Raises: NotImplementedError: this method should be implemented in a subclass. @@ -219,8 +243,6 @@ def get_data(self, latitude, longitude, first_call=True): Returns: Any: resource data in the format expected by the subclass. """ - site_changed = False - site_changed = not np.allclose([latitude, longitude], self.resource_site, atol=1e-6, rtol=0) # 0) If site hasn't changed and resource data has already been loaded @@ -229,14 +251,65 @@ def get_data(self, latitude, longitude, first_call=True): if self.resource_data is not None: return self.resource_data - # 1) check if user provided data, add start and end times if so - # and return the data + # 1) Get the resource data: either the user-provided data (resampled to the + # simulation timestep) or enough downloaded/loaded years to cover the horizon. if bool(self.config.resource_data): - data = add_resource_start_end_times(self.config.resource_data) - return data + data = self._resample_to_sim_dt(self.config.resource_data) + else: + data = self._acquire_resource_data(latitude, longitude, site_changed) + + # 2) Slice the data to exactly n_timesteps and add start/end times. + data = conform_resource_data_to_n_timesteps(data, self.n_timesteps) + data = add_resource_start_end_times(data) + return data + + def _resample_to_sim_dt(self, data): + """Resample resource data from its native timestep to the simulation timestep. + + Uses the up/downsampling strategies configured on the resource model. + + Args: + data (dict): raw resource data at its native timestep. + + Returns: + dict: resource data resampled to ``self.dt``. + """ + return resample_resource_data_to_dt( + data, + self.dt, + getattr(self.config, "upsample_method", "time"), + getattr(self.config, "downsample_method", "mean"), + ) + + def _load_single_year_data( + self, latitude, longitude, site_changed, resource_filename=None, resource_year=None + ): + """Resolve, load, or download one year of resource data. + + Performs Steps 2-7 described in :py:meth:`get_data` for a single year (without + slicing to the simulation horizon). + + Args: + latitude (float): latitude corresponding to location for resource data + longitude (float): longitude corresponding to location for resource data + site_changed (bool): whether the site location changed from the last call. + resource_filename (str | Path | None): specific filename to load this year's + data from. When None, the default naming convention is used. + resource_year (int | str | None): resource year to load. When None, + ``self.config.resource_year`` is used. For multi-year horizons the caller + passes the advanced year so filenames, URLs, and any per-year filtering use + the correct year without modifying ``self.config``. + + Raises: + ValueError: If data was not successfully downloaded from the API. + + Returns: + dict: raw resource data for the requested year. + """ + resource_year = self.config.resource_year if resource_year is None else resource_year # check if user provided directory or filename - provided_filename = False if self.config.resource_filename == "" else True + provided_filename = bool(resource_filename) provided_dir = False if self.config.resource_dir is None else True # 2a) check if file exists directly within resource directory @@ -245,10 +318,10 @@ def get_data(self, latitude, longitude, first_call=True): # 3a) Create a filename if resource_filename was input if provided_filename and not site_changed: # If a filename was input, use resource_filename as the filename. - filepath = resource_dir / self.config.resource_filename + filepath = resource_dir / resource_filename # Otherwise, create a filename with the method `create_filename()`. else: - filename = self.create_filename(latitude, longitude) + filename = self.create_filename(latitude, longitude, resource_year=resource_year) filepath = resource_dir / filename # if file doesn't exist, continue to Step 2b if not filepath.is_file(): @@ -266,10 +339,10 @@ def get_data(self, latitude, longitude, first_call=True): # 3) Create a filename if resource_filename was input if provided_filename and not site_changed: # If a filename was input, use resource_filename as the filename. - filepath = resource_dir / self.config.resource_filename + filepath = resource_dir / resource_filename # Otherwise, create a filename with the method `create_filename()`. else: - filename = self.create_filename(latitude, longitude) + filename = self.create_filename(latitude, longitude, resource_year=resource_year) filepath = resource_dir / filename # Check if the filename was provided by the user and the site hasn't changed @@ -277,35 +350,139 @@ def get_data(self, latitude, longitude, first_call=True): # If the user-provided filename wasn't found, throw a warning if not filepath.is_file(): msg = ( - f"User provided resource filename {self.config.resource_filename} " + f"User provided resource filename {resource_filename} " f"not found in {resource_dir}. Data will be downloaded for this site." ) warnings.warn(msg, UserWarning) # 4) If the resulting resource_dir and filename from Steps 2 and 3 make a valid - # filepath, load data using `load_data()` + # filepath, load data using `load_data()` and resample to desired the dt if filepath.is_file(): self.filepath = filepath - data = self.load_data(filepath) - data = add_resource_start_end_times(data) - return data + return self._resample_to_sim_dt(self.load_data(filepath, resource_year=resource_year)) # If the filepath (resource_dir/filename) does not exist, download data self.filepath = filepath # 5) Create the url to download data using `create_url()` and continue to Step 6. - url = self.create_url(latitude, longitude) + url = self.create_url(latitude, longitude, resource_year=resource_year) # 6) Download data from the url created in Step 5 and save to a filepath created from # the resulting resource_dir and filename from Steps 2 and 3. success = self.download_data(url, filepath) if success: - # 7) Load data from the file created in Step 6 using `load_data()` - data = self.load_data(filepath) - data = add_resource_start_end_times(data) - return data + # 7) Load data from the file created in Step 6 using `load_data()` and resample + # to the desired dt + return self._resample_to_sim_dt(self.load_data(filepath, resource_year=resource_year)) else: raise ValueError("Did not successfully download resource data.") + def _acquire_resource_data(self, latitude, longitude, site_changed): + """Acquire enough resource data to cover the simulation horizon. + + Loads the configured ``resource_year`` and, if a single year does not provide + enough timesteps to cover ``n_timesteps`` (after resampling), continues loading + consecutive years until the horizon is covered. Because this is driven by the + actual number of timesteps in each loaded year, it naturally handles years of + different lengths -- for example a leap year when leap days are retained. + + Datasets whose ``resource_year`` is a typical meteorological/representative year + (a non-integer value such as ``"tmy-2022"``) have no meaningful "next year", so the + same representative year is reused for each additional year needed to cover a + multi-year horizon. + + Args: + latitude (float): latitude corresponding to location for resource data + longitude (float): longitude corresponding to location for resource data + site_changed (bool): whether the site location changed from the last call. + + Raises: + ValueError: if not enough resource data is available to cover the horizon + (a required year is outside the dataset range, a provided list of files + is exhausted, or a single provided filename cannot cover multiple years). + + Returns: + dict: a resource data dictionary spanning enough time to cover the horizon. + """ + resource_filename = self.config.resource_filename + filename_list = ( + list(resource_filename) if isinstance(resource_filename, list | tuple) else None + ) + base_year = self.config.resource_year + + yearly_data = [] + total_timesteps = 0 + offset = 0 + while total_timesteps < self.n_timesteps: + # Resolve the filename to use for this year, if any + if filename_list is not None: + if offset >= len(filename_list): + msg = ( + f"{type(self).__name__} was given {len(filename_list)} resource " + f"file(s) covering only {total_timesteps} timesteps, fewer than the " + f"{self.n_timesteps} timesteps required by the simulation horizon. " + "Provide additional resource files or shorten the horizon." + ) + raise ValueError(msg) + year_filename = filename_list[offset] + elif offset == 0: + year_filename = resource_filename or None + else: + # A single provided filename cannot supply additional years + if resource_filename: + msg = ( + f"{type(self).__name__} cannot satisfy a multi-year simulation " + "horizon from a single resource_filename. Provide a list of " + "filenames (one per consecutive year), or remove resource_filename " + "so the required years can be downloaded." + ) + raise ValueError(msg) + year_filename = None + + # Determine the resource year to load. Years are kept as local values so the + # model's own ``self.config`` is never modified. When a later year must be + # downloaded (no explicit filename) for an integer-year dataset, the advanced + # year is validated against the dataset's rules using a throwaway duplicate + # config. The first year, non-integer (typical-year) datasets, and explicit + # filenames reuse the base year, so a typical year repeats to fill a multi-year + # horizon. + load_year = base_year + if year_filename is None and offset > 0 and isinstance(base_year, int): + load_year = base_year + offset + validation_config = copy.copy(self.config) + try: + # Assigning resource_year on the duplicate runs the dataset validator, + # which raises if the year is outside the supported range. + validation_config.resource_year = load_year + except (ValueError, TypeError) as e: + msg = ( + f"Not enough resource data available for {type(self).__name__} to " + f"cover the requested simulation horizon of {self.n_timesteps} " + f"timesteps. Year {load_year} is outside the range " + "supported by this dataset." + ) + raise ValueError(msg) from e + + year_data = self._load_single_year_data( + latitude, longitude, site_changed, year_filename, resource_year=load_year + ) + yearly_data.append(year_data) + total_timesteps += self._resource_length(year_data) + offset += 1 + + return concatenate_resource_years(yearly_data) + + @staticmethod + def _resource_length(data): + """Return the number of timesteps in a resource data dictionary.""" + for key in ("year", "month", "day", "hour", "minute"): + if key in data: + return len(np.asarray(data[key])) + # Fall back to the first array-like value + for value in data.values(): + if isinstance(value, np.ndarray | list | tuple) and not isinstance(value, str | bytes): + return len(value) + return 0 + def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # update the resource data based on the input latitude and longitude data = self.get_data(inputs["latitude"][0], inputs["longitude"][0], first_call=False) diff --git a/h2integrate/resource/resource_base_hpc.py b/h2integrate/resource/resource_base_hpc.py index 9ae9d7669..afc76fe5a 100644 --- a/h2integrate/resource/resource_base_hpc.py +++ b/h2integrate/resource/resource_base_hpc.py @@ -463,8 +463,7 @@ def get_data(self, latitude, longitude, first_call=True): # Sample data to the proper timestep interval data = self.sample_data_to_interval(data) # Remove leap day (if necessary) - # data = self.process_leap_day(data) - data = process_leap_day(data, self.config.include_leap_day, self.n_timesteps) + data = process_leap_day(data, self.config.include_leap_day) # Add start/end times to the resource data data = add_resource_start_end_times(data) diff --git a/h2integrate/resource/solar/nlr_developer_api_base.py b/h2integrate/resource/solar/nlr_developer_api_base.py index 908abcc4c..4d23d6237 100644 --- a/h2integrate/resource/solar/nlr_developer_api_base.py +++ b/h2integrate/resource/solar/nlr_developer_api_base.py @@ -38,7 +38,7 @@ def setup(self): "solar_resource_data", val=data, desc="Dict of solar resource data" ) - def create_filename(self, latitude, longitude): + def create_filename(self, latitude, longitude, resource_year=None): """Create default filename to save downloaded data to. Filename is formatted as "{latitude}_{longitude}_{resource_year}_{config.dataset_desc}_{interval}min_{tz_desc}_tz.csv" where "tz_desc" is "utc" if the timezone is zero, or "local" otherwise. @@ -46,35 +46,40 @@ def create_filename(self, latitude, longitude): Args: latitude (float): latitude corresponding to location for resource data longitude (float): longitude corresponding to location for resource data + resource_year (int | str | None): resource year to build the filename for. When + None, ``self.config.resource_year`` is used. Returns: str: filename for resource data to be saved to or loaded from. """ - # TODO: update to handle multiple years - # TODO: update to handle nonstandard time intervals + + resource_year = self.config.resource_year if resource_year is None else resource_year if self.utc: tz_desc = "utc" else: tz_desc = "local" filename = ( - f"{latitude}_{longitude}_{self.config.resource_year}_" + f"{latitude}_{longitude}_{resource_year}_" f"{self.config.dataset_desc}_{self.interval}min_{tz_desc}_tz.csv" ) return filename - def create_url(self, latitude, longitude): + def create_url(self, latitude, longitude, resource_year=None): """Create url for data download. Args: latitude (float): latitude corresponding to location for resource data longitude (float): longitude corresponding to location for resource data + resource_year (int | str | None): resource year to build the url for. When None, + ``self.config.resource_year`` is used. Returns: str: url to use for API call. """ + resource_year = self.config.resource_year if resource_year is None else resource_year input_data = { "wkt": f"POINT({longitude} {latitude})", - "names": [str(self.config.resource_year)], # TODO: update to handle multiple years + "names": [str(resource_year)], "interval": str(self.interval), "utc": str(self.utc).lower(), "api_key": get_nlr_developer_api_key(), @@ -83,7 +88,7 @@ def create_url(self, latitude, longitude): url = self.base_url + urllib.parse.urlencode(input_data, True) return url - def load_data(self, fpath): + def load_data(self, fpath, resource_year=None): """Load data from a file and format as a dictionary that: 1) follows naming convention described in SolarResourceBase. diff --git a/h2integrate/resource/solar/openmeteo_solar.py b/h2integrate/resource/solar/openmeteo_solar.py index 93441c81d..664c4b902 100644 --- a/h2integrate/resource/solar/openmeteo_solar.py +++ b/h2integrate/resource/solar/openmeteo_solar.py @@ -104,7 +104,7 @@ def setup(self): "solar_resource_data", val=data, desc="Dict of solar resource data" ) - def create_filename(self, latitude, longitude): + def create_filename(self, latitude, longitude, resource_year=None): """Create default filename to save downloaded data to. Filename is formatted as "{latitude}_{longitude}_{resource_year}_openmeteo_archive_{interval}min_{tz_desc}_tz.csv" where "tz_desc" is "utc" if the timezone is zero, or "local" otherwise. @@ -112,35 +112,39 @@ def create_filename(self, latitude, longitude): Args: latitude (float): latitude corresponding to location for resource data longitude (float): longitude corresponding to location for resource data + resource_year (int | str | None): resource year to build the filename for. When + None, ``self.config.resource_year`` is used. Returns: str: filename for resource data to be saved to or loaded from. """ - # TODO: update to handle multiple years - # TODO: update to handle nonstandard time intervals + + resource_year = self.config.resource_year if resource_year is None else resource_year if self.utc: tz_desc = "utc" else: tz_desc = "local" filename = ( - f"{latitude}_{longitude}_{self.config.resource_year}_" + f"{latitude}_{longitude}_{resource_year}_" f"{self.config.dataset_desc}_{self.interval}min_{tz_desc}_tz.csv" ) return filename - def create_url(self, latitude, longitude): + def create_url(self, latitude, longitude, resource_year=None): """Create url for data download. Args: latitude (float): latitude corresponding to location for resource data longitude (float): longitude corresponding to location for resource data + resource_year (int | str | None): resource year to build the url for. When None, + ``self.config.resource_year`` is used. Returns: str: url to use for API call. """ - - start_year = int(self.config.resource_year - 1) - end_year = int(self.config.resource_year + 1) + resource_year = self.config.resource_year if resource_year is None else resource_year + start_year = int(resource_year - 1) + end_year = int(resource_year + 1) input_data = { "latitude": latitude, @@ -241,7 +245,7 @@ def download_data(self, url, fpath): return success - def load_data(self, fpath): + def load_data(self, fpath, resource_year=None): """Load data from a file and format as a dictionary that: 1) follows naming convention described in SolarResourceBase. @@ -257,11 +261,14 @@ def load_data(self, fpath): Args: fpath (str | Path): filepath to file containing the data + resource_year (int | None): resource year to select from the downloaded file. + When None, ``self.config.resource_year`` is used. Returns: dict: dictionary of data in standardized units and naming convention. Time information is found in the 'time' key. """ + resource_year = self.config.resource_year if resource_year is None else resource_year header = pd.read_csv(fpath, nrows=2, header=None) header_dict = dict(zip(header.iloc[0].to_list(), header.iloc[1].to_list())) @@ -295,11 +302,15 @@ def load_data(self, fpath): data["Hour"] = time.hour data["Minute"] = time.minute - data = data[data["Year"] == self.config.resource_year] + data = data[data["Year"] == resource_year] data = data.reset_index(drop=True) - data = process_leap_day(data, self.config.include_leap_day, self.n_timesteps) + # Handle the leap day according to include_leap_day: remove it when present but + # not wanted, keep it when present and wanted, and error if a leap year's data is + # missing it. The resource base then resamples this native data to the simulation + # timestep and slices it to the requested horizon. + data = process_leap_day(data, self.config.include_leap_day) data, data_units = self.format_timeseries_data(data) # make units for data in openmdao-compatible units diff --git a/h2integrate/resource/test/test_resource_base.py b/h2integrate/resource/test/test_resource_base.py new file mode 100644 index 000000000..7b4cd9733 --- /dev/null +++ b/h2integrate/resource/test/test_resource_base.py @@ -0,0 +1,193 @@ +"""Unit tests for ``ResourceBaseAPIModel`` multi-year / leap-safe acquisition.""" + +from types import SimpleNamespace + +import numpy as np +import pandas as pd +import pytest + +from h2integrate.resource.resource_base import ResourceBaseAPIModel + + +def _year_data(year, length): + """Build a synthetic one-year resource data dict with time columns.""" + idx = pd.date_range(f"{year}-01-01 00:00", periods=length, freq="1h") + return { + "wind_speed_100m": np.zeros(length, dtype=float), + "year": idx.year.to_numpy().astype(float), + "month": idx.month.to_numpy().astype(float), + "day": idx.day.to_numpy().astype(float), + "hour": idx.hour.to_numpy().astype(float), + "minute": idx.minute.to_numpy().astype(float), + "units": {"wind_speed_100m": "m/s"}, + } + + +def _fake_model(resource_filename="", resource_year=2012, n_timesteps=8760, year_length=8760): + """Build a bare model whose per-year loader returns fixed-length synthetic data.""" + model = object.__new__(ResourceBaseAPIModel) + model.config = SimpleNamespace(resource_filename=resource_filename, resource_year=resource_year) + model.n_timesteps = n_timesteps + + def _load(latitude, longitude, site_changed, year_filename=None, resource_year=None): + year = model.config.resource_year if resource_year is None else resource_year + return _year_data(year, year_length) + + model._load_single_year_data = _load + return model + + +class _StubConfig: + """Model-agnostic stand-in config for exercising base-class acquisition logic. + + The base class does not know about any specific dataset; it only relies on the + contract that assigning ``resource_year`` runs the config's validator (raising for an + unsupported year). When ``max_year`` is set this stub mimics that contract by rejecting + integer years beyond it, so the base class's year-advance handling can be verified + without depending on a particular resource model. Real per-dataset validators and + loaders are tested in each resource model's own test module. + """ + + def __init__(self, resource_year, resource_filename="", max_year=None): + self.resource_filename = resource_filename + self.max_year = max_year + self._resource_year = resource_year + + @property + def resource_year(self): + return self._resource_year + + @resource_year.setter + def resource_year(self, value): + if self.max_year is not None and isinstance(value, int) and value > self.max_year: + raise ValueError(f"resource_year {value} exceeds supported max {self.max_year}") + self._resource_year = value + + +def _fake_model_from_config(config, n_timesteps, year_length=8760): + """Build a model backed by ``config``, stubbing only the per-year loader. + + The loader records the ``resource_year`` seen on each call so tests can confirm which + year was used for each load. + """ + model = object.__new__(ResourceBaseAPIModel) + model.config = config + model.n_timesteps = n_timesteps + model.seen_years = [] + + def _load(latitude, longitude, site_changed, year_filename=None, resource_year=None): + year = model.config.resource_year if resource_year is None else resource_year + model.seen_years.append(year) + return _year_data(2012, year_length) + + model._load_single_year_data = _load + return model + + +@pytest.mark.unit +def test_resource_length(): + data = _year_data(2012, 100) + assert ResourceBaseAPIModel._resource_length(data) == 100 + + +@pytest.mark.unit +def test_single_year_covers_sub_annual_horizon(): + model = _fake_model(n_timesteps=100, year_length=8760) + data = model._acquire_resource_data(0.0, 0.0, False) + # a single year is enough; acquisition returns that one year + assert ResourceBaseAPIModel._resource_length(data) == 8760 + + +@pytest.mark.unit +def test_loads_multiple_years_until_horizon_covered(subtests): + model = _fake_model(n_timesteps=2 * 8760, year_length=8760) + data = model._acquire_resource_data(0.0, 0.0, False) + with subtests.test("resource length covers both years"): + assert ResourceBaseAPIModel._resource_length(data) == 2 * 8760 + with subtests.test("resource year restored after acquisition"): + assert model.config.resource_year == 2012 + + +@pytest.mark.unit +def test_leap_year_single_year_not_split(): + # a retained leap year (8784) covers an 8784-step horizon with a single year + model = _fake_model(n_timesteps=8784, resource_year=2012, year_length=8784) + data = model._acquire_resource_data(0.0, 0.0, False) + assert ResourceBaseAPIModel._resource_length(data) == 8784 + + +@pytest.mark.unit +def test_filename_list_used_in_order(): + model = _fake_model( + resource_filename=["a.csv", "b.csv"], n_timesteps=2 * 8760, year_length=8760 + ) + data = model._acquire_resource_data(0.0, 0.0, False) + assert ResourceBaseAPIModel._resource_length(data) == 2 * 8760 + + +@pytest.mark.unit +def test_filename_list_exhausted_raises(): + model = _fake_model(resource_filename=["a.csv"], n_timesteps=2 * 8760, year_length=8760) + with pytest.raises(ValueError, match="resource file"): + model._acquire_resource_data(0.0, 0.0, False) + + +@pytest.mark.unit +def test_single_filename_multiyear_raises(): + model = _fake_model(resource_filename="a.csv", n_timesteps=2 * 8760, year_length=8760) + with pytest.raises(ValueError, match="cannot satisfy a multi-year"): + model._acquire_resource_data(0.0, 0.0, False) + + +@pytest.mark.unit +@pytest.mark.parametrize("n_timesteps", [100, 8760]) +def test_tmy_string_year_single_year_covers_horizon(n_timesteps, subtests): + # A typical-meteorological-year dataset (string resource_year) covers a sub-annual or + # single-year horizon with one load of the representative year. + model = _fake_model_from_config( + _StubConfig("tmy-2022"), n_timesteps=n_timesteps, year_length=8760 + ) + data = model._acquire_resource_data(0.0, 0.0, False) + with subtests.test("resource length is the representative year"): + assert ResourceBaseAPIModel._resource_length(data) == 8760 + with subtests.test("representative year loaded exactly once"): + assert model.seen_years == ["tmy-2022"] + with subtests.test("resource year unchanged"): + assert model.config.resource_year == "tmy-2022" + + +@pytest.mark.unit +def test_tmy_string_year_multiyear_reuses_same_year(subtests): + # A multi-year horizon with a non-integer (typical-year) resource_year reuses the same + # representative year for each additional year rather than advancing to a "next" year. + model = _fake_model_from_config(_StubConfig("tmy-2022"), n_timesteps=3 * 8760, year_length=8760) + data = model._acquire_resource_data(0.0, 0.0, False) + with subtests.test("resource length covers all three years"): + assert ResourceBaseAPIModel._resource_length(data) == 3 * 8760 + with subtests.test("same representative year reused for each year"): + assert model.seen_years == ["tmy-2022", "tmy-2022", "tmy-2022"] + with subtests.test("resource year unchanged after acquisition"): + assert model.config.resource_year == "tmy-2022" + + +@pytest.mark.unit +def test_integer_year_out_of_range_raises(): + # When advancing to consecutive years runs past the range accepted by the config's + # validator, the base class raises a clear error instead of a cryptic validator failure. + model = _fake_model_from_config(_StubConfig(2012, max_year=2012), n_timesteps=2 * 8760) + with pytest.raises(ValueError, match="outside the range"): + model._acquire_resource_data(0.0, 0.0, False) + + +@pytest.mark.unit +def test_integer_year_restored_after_multiyear(subtests): + # A multi-year integer-year acquisition advances the year on a duplicate config but + # leaves the original config untouched, so the configured year is unchanged afterward. + model = _fake_model_from_config(_StubConfig(2013, max_year=2100), n_timesteps=2 * 8760) + data = model._acquire_resource_data(0.0, 0.0, False) + with subtests.test("resource length covers both years"): + assert ResourceBaseAPIModel._resource_length(data) == 2 * 8760 + with subtests.test("consecutive years loaded in order"): + assert model.seen_years == [2013, 2014] + with subtests.test("resource year unchanged"): + assert model.config.resource_year == 2013 diff --git a/h2integrate/resource/utilities/test/test_time_tools.py b/h2integrate/resource/utilities/test/test_time_tools.py new file mode 100644 index 000000000..5331f30f2 --- /dev/null +++ b/h2integrate/resource/utilities/test/test_time_tools.py @@ -0,0 +1,492 @@ +"""Tests for the resource conform/concatenate helpers in ``time_tools``.""" + +import numpy as np +import pandas as pd +import pytest + +from h2integrate.resource.utilities.time_tools import ( + process_leap_day, + concatenate_resource_years, + resample_resource_data_to_dt, + conform_resource_data_to_n_timesteps, +) + + +def _feb_mar_days(year, include_feb29): + """Build a small daily resource dict spanning Feb 28 - Mar 1 for ``year``.""" + + if include_feb29: + dates = pd.date_range(f"{year}-02-28", f"{year}-03-01", freq="1D") + else: + dates = pd.DatetimeIndex([pd.Timestamp(f"{year}-02-28"), pd.Timestamp(f"{year}-03-01")]) + return { + "year": dates.year.to_numpy().astype(float), + "month": dates.month.to_numpy().astype(float), + "day": dates.day.to_numpy().astype(float), + "ws": np.arange(len(dates), dtype=float), + } + + +@pytest.mark.unit +def test_leap_day_removed_when_not_wanted(subtests): + result = process_leap_day(_feb_mar_days(2012, include_feb29=True), include_leap_day=False) + with subtests.test("leap day removed"): + assert 29 not in result["day"].astype(int) + with subtests.test("length reduced to two days"): + assert len(result["day"]) == 2 + + +@pytest.mark.unit +def test_no_leap_day_unchanged_when_not_wanted(): + data = _feb_mar_days(2013, include_feb29=False) + result = process_leap_day(data, include_leap_day=False) + assert len(result["day"]) == 2 + + +@pytest.mark.unit +def test_leap_day_kept_when_wanted(subtests): + result = process_leap_day(_feb_mar_days(2012, include_feb29=True), include_leap_day=True) + with subtests.test("leap day retained"): + assert 29 in result["day"].astype(int) + with subtests.test("length remains three days"): + assert len(result["day"]) == 3 + + +@pytest.mark.unit +def test_missing_leap_day_in_leap_year_raises_when_wanted(): + data = _feb_mar_days(2012, include_feb29=False) # 2012 is a leap year + with pytest.raises(ValueError, match="does not contain a leap day"): + process_leap_day(data, include_leap_day=True) + + +@pytest.mark.unit +def test_non_leap_year_no_error_when_wanted(): + data = _feb_mar_days(2013, include_feb29=False) # 2013 is not a leap year + result = process_leap_day(data, include_leap_day=True) + assert len(result["day"]) == 2 + + +def _make_annual_data(native_len=8760, year=2012): + """Build a minimal one-year resource data dictionary for testing.""" + index = np.arange(native_len) + hours = index % 24 + days = (index // 24) % 28 + 1 + months = (index // (24 * 28)) % 12 + 1 + return { + "wind_speed_100m": index.astype(float), + "temperature_2m": (index * 0.1).astype(float), + "year": np.full(native_len, float(year)), + "month": months.astype(float), + "day": days.astype(float), + "hour": hours.astype(float), + "minute": np.zeros(native_len), + # scalar metadata that must be preserved unchanged + "site_lat": 35.2, + "site_lon": -101.9, + "units": {"wind_speed_100m": "m/s"}, + "filepath": "dummy.csv", + } + + +@pytest.mark.unit +def test_no_op_when_length_matches(): + data = _make_annual_data(8760) + result = conform_resource_data_to_n_timesteps(data, 8760) + assert result is data + + +@pytest.mark.unit +def test_slices_for_sub_annual_horizon(subtests): + data = _make_annual_data(8760) + result = conform_resource_data_to_n_timesteps(data, 4380) + + with subtests.test("wind length matches horizon"): + assert len(result["wind_speed_100m"]) == 4380 + with subtests.test("temperature length matches horizon"): + assert len(result["temperature_2m"]) == 4380 + with subtests.test("wind values are sliced from the front"): + np.testing.assert_array_equal(result["wind_speed_100m"], np.arange(4380, dtype=float)) + + +@pytest.mark.unit +def test_slices_multiyear_data_down_to_horizon(): + # Two real years concatenated (17520) sliced to a slightly shorter horizon + data = _make_annual_data(2 * 8760) + result = conform_resource_data_to_n_timesteps(data, 2 * 8760 - 240) + + assert len(result["wind_speed_100m"]) == 2 * 8760 - 240 + + +@pytest.mark.unit +def test_raises_when_not_enough_data(): + data = _make_annual_data(8760) + with pytest.raises(ValueError, match="Not enough resource data"): + conform_resource_data_to_n_timesteps(data, 2 * 8760) + + +@pytest.mark.unit +def test_scalar_metadata_preserved(subtests): + data = _make_annual_data(8760) + result = conform_resource_data_to_n_timesteps(data, 4380) + + with subtests.test("site latitude preserved"): + assert result["site_lat"] == 35.2 + with subtests.test("site longitude preserved"): + assert result["site_lon"] == -101.9 + with subtests.test("units preserved"): + assert result["units"] == {"wind_speed_100m": "m/s"} + with subtests.test("filepath preserved"): + assert result["filepath"] == "dummy.csv" + + +@pytest.mark.unit +def test_returns_input_when_n_timesteps_is_none(): + data = _make_annual_data(8760) + assert conform_resource_data_to_n_timesteps(data, None) is data + + +@pytest.mark.unit +def test_infers_length_without_time_columns(subtests): + data = { + "wind_speed_100m": np.arange(10, dtype=float), + "temperature_2m": np.arange(10, dtype=float), + "site_lat": 1.0, + } + result = conform_resource_data_to_n_timesteps(data, 5) + + with subtests.test("wind length inferred and sliced"): + assert len(result["wind_speed_100m"]) == 5 + with subtests.test("temperature length inferred and sliced"): + assert len(result["temperature_2m"]) == 5 + with subtests.test("scalar metadata preserved"): + assert result["site_lat"] == 1.0 + + +@pytest.mark.unit +def test_concatenate_single_year_is_passthrough(): + data = _make_annual_data(8760) + assert concatenate_resource_years([data]) is data + + +@pytest.mark.unit +def test_concatenate_multiple_years_combines_timeseries(subtests): + year1 = _make_annual_data(8760, year=2012) + year2 = _make_annual_data(8760, year=2013) + combined = concatenate_resource_years([year1, year2]) + + with subtests.test("combined wind length"): + assert len(combined["wind_speed_100m"]) == 2 * 8760 + with subtests.test("second year values appended in order"): + np.testing.assert_array_equal(combined["wind_speed_100m"][8760:], year2["wind_speed_100m"]) + with subtests.test("scalar metadata comes from first year"): + assert combined["units"] == {"wind_speed_100m": "m/s"} + + +@pytest.mark.unit +def test_concatenate_leap_removed_preserves_real_timestamps_minus_feb29(subtests): + # With the leap day removed, the concatenated timestamps must match the real + # downloaded calendar with only February 29 missing -- every other timestamp is + # preserved, and the series stays strictly increasing across the year boundary. + + def _real_year(year, remove_leap=False): + idx = pd.date_range(f"{year}-01-01 00:30", f"{year}-12-31 23:30", freq="1h") + if remove_leap: + idx = idx[~((idx.month == 2) & (idx.day == 29))] + data = { + "wind_speed_100m": np.arange(len(idx), dtype=float), + "year": idx.year.to_numpy().astype(float), + "month": idx.month.to_numpy().astype(float), + "day": idx.day.to_numpy().astype(float), + "hour": idx.hour.to_numpy().astype(float), + "minute": idx.minute.to_numpy().astype(float), + } + return data, idx + + y2012, idx2012 = _real_year(2012, remove_leap=True) # leap year, leap day removed -> 8760 + y2013, idx2013 = _real_year(2013) # non-leap -> 8760 + combined = concatenate_resource_years([y2012, y2013]) + + result = pd.to_datetime( + {k: combined[k].astype(int) for k in ("year", "month", "day", "hour", "minute")} + ) + expected = idx2012.append(idx2013) + + with subtests.test("timestamps match downloaded calendar except feb 29"): + np.testing.assert_array_equal(result.to_numpy(), expected.to_numpy()) + with subtests.test("february 29 removed"): + assert not ( + (combined["month"] == 2) & (combined["day"] == 29) & (combined["year"] == 2012) + ).any() + with subtests.test("timestamps remain strictly increasing"): + assert (result.diff().dropna() > pd.Timedelta(0)).all() + + +@pytest.mark.unit +def test_concatenate_mixed_leap_and_non_leap_years_preserves_all_data(subtests): + # A leap year (leap day retained, 8784 hourly) followed by a non-leap year (8760). + leap = _make_annual_data(8784, year=2012) + non_leap = _make_annual_data(8760, year=2013) + combined = concatenate_resource_years([leap, non_leap]) + + with subtests.test("combined length preserves both years"): + assert len(combined["wind_speed_100m"]) == 8784 + 8760 + with subtests.test("leap year values preserved"): + np.testing.assert_array_equal(combined["wind_speed_100m"][:8784], leap["wind_speed_100m"]) + with subtests.test("non leap year values preserved"): + np.testing.assert_array_equal( + combined["wind_speed_100m"][8784:], non_leap["wind_speed_100m"] + ) + + +@pytest.mark.unit +def test_concatenate_non_leap_then_leap_preserves_all_data(subtests): + # Order reversed: non-leap year first, then a leap year with the leap day retained. + non_leap = _make_annual_data(8760, year=2013) + leap = _make_annual_data(8784, year=2016) + combined = concatenate_resource_years([non_leap, leap]) + + with subtests.test("combined length preserves both years"): + assert len(combined["wind_speed_100m"]) == 8760 + 8784 + with subtests.test("non leap year values preserved"): + np.testing.assert_array_equal( + combined["wind_speed_100m"][:8760], non_leap["wind_speed_100m"] + ) + with subtests.test("leap year values preserved"): + np.testing.assert_array_equal(combined["wind_speed_100m"][8760:], leap["wind_speed_100m"]) + + +@pytest.mark.unit +def test_concatenate_leap_kept_timestamps_match_real_calendar(subtests): + # When the leap day is retained the yearly data is contiguous real calendar, so the + # concatenated (rebuilt) time columns must reproduce the real calendar exactly. + + def _year(year): + idx = pd.date_range(f"{year}-01-01 00:30", f"{year}-12-31 23:30", freq="1h") + return { + "wind_speed_100m": np.arange(len(idx), dtype=float), + "year": idx.year.to_numpy().astype(float), + "month": idx.month.to_numpy().astype(float), + "day": idx.day.to_numpy().astype(float), + "hour": idx.hour.to_numpy().astype(float), + "minute": idx.minute.to_numpy().astype(float), + } + + combined = concatenate_resource_years([_year(2012), _year(2013)]) # leap kept, then non-leap + + rebuilt = pd.to_datetime( + {k: combined[k].astype(int) for k in ("year", "month", "day", "hour", "minute")} + ).to_numpy() + real = pd.date_range("2012-01-01 00:30", "2013-12-31 23:30", freq="1h").to_numpy() + + with subtests.test("rebuilt length matches combined years"): + assert len(rebuilt) == 8784 + 8760 + with subtests.test("timestamps match real calendar"): + np.testing.assert_array_equal(rebuilt, real) + with subtests.test("retained leap day remains present"): + assert bool( + ((combined["month"] == 2) & (combined["day"] == 29) & (combined["year"] == 2012)).any() + ) + + +def _make_timeseries(n, freq_seconds, start="2012-01-01 00:00", values=None): + """Build a resource data dict with time columns spaced at ``freq_seconds``.""" + + idx = pd.date_range(start=start, periods=n, freq=pd.Timedelta(seconds=freq_seconds)) + if values is None: + values = np.arange(n, dtype=float) + return { + "wind_speed_100m": np.asarray(values, dtype=float), + "year": idx.year.to_numpy().astype(float), + "month": idx.month.to_numpy().astype(float), + "day": idx.day.to_numpy().astype(float), + "hour": idx.hour.to_numpy().astype(float), + "minute": idx.minute.to_numpy().astype(float), + "units": {"wind_speed_100m": "m/s"}, + "site_lat": 1.0, + } + + +@pytest.mark.unit +def test_resample_no_op_when_dt_matches(): + data = _make_timeseries(10, 3600) + result = resample_resource_data_to_dt(data, 3600) + assert result is data + + +@pytest.mark.unit +def test_resample_no_time_columns_raises(): + data = {"wind_speed_100m": np.arange(10, dtype=float), "site_lat": 1.0} + with pytest.raises(ValueError, match="no time columns"): + resample_resource_data_to_dt(data, 1800) + + +@pytest.mark.unit +def test_resample_target_dt_larger_than_span_raises(): + # 5 hourly samples span only a few hours; a 10-day timestep yields no full step + data = _make_timeseries(5, 3600) + with pytest.raises(ValueError, match="larger than the total time span"): + resample_resource_data_to_dt(data, 10 * 86400) + + +@pytest.mark.unit +def test_resample_non_increasing_timestamps_raises(): + # The first two timestamps are identical, so the native timestep is non-positive + data = { + "wind_speed_100m": np.arange(3, dtype=float), + "year": np.array([2012, 2012, 2012], dtype=float), + "month": np.array([1, 1, 1], dtype=float), + "day": np.array([1, 1, 1], dtype=float), + "hour": np.array([0, 0, 1], dtype=float), + "minute": np.array([0, 0, 0], dtype=float), + } + with pytest.raises(ValueError, match="non-positive"): + resample_resource_data_to_dt(data, 1800) + + +@pytest.mark.unit +def test_upsample_interpolation(subtests): + # hourly data upsampled to 30-minute resolution + data = _make_timeseries(5, 3600, values=[0, 1, 2, 3, 4]) + result = resample_resource_data_to_dt(data, 1800) + + with subtests.test("upsampled length doubles"): + assert len(result["wind_speed_100m"]) == 10 + expected = np.array([0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4], dtype=float) + with subtests.test("interpolated values match expectation"): + np.testing.assert_allclose(result["wind_speed_100m"], expected) + with subtests.test("minutes alternate on 30 minute grid"): + np.testing.assert_array_equal(result["minute"][:4], [0, 30, 0, 30]) + + +@pytest.mark.unit +def test_downsample_average(subtests): + # 30-minute data downsampled to hourly resolution via pandas mean aggregation + data = _make_timeseries(10, 1800, values=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + result = resample_resource_data_to_dt(data, 3600) + + with subtests.test("downsampled length halves"): + assert len(result["wind_speed_100m"]) == 5 + expected = np.array([0.5, 2.5, 4.5, 6.5, 8.5], dtype=float) + with subtests.test("hourly means match expectation"): + np.testing.assert_allclose(result["wind_speed_100m"], expected) + with subtests.test("minutes stay on hour"): + np.testing.assert_array_equal(result["minute"], np.zeros(5)) + + +@pytest.mark.unit +def test_resample_keeps_leap_day_excluded_at_new_timestep(subtests): + # Hourly data for a leap year with the leap day removed, resampled to 2-hour. + # February 29 must stay excluded in the regenerated calendar at the new timestep. + + idx = pd.date_range("2012-01-01 00:30", "2012-12-31 23:30", freq="1h") + idx = idx[~((idx.month == 2) & (idx.day == 29))] # 8760 hourly, leap day removed + data = { + "wind_speed_100m": np.arange(len(idx), dtype=float), + "year": idx.year.to_numpy().astype(float), + "month": idx.month.to_numpy().astype(float), + "day": idx.day.to_numpy().astype(float), + "hour": idx.hour.to_numpy().astype(float), + "minute": idx.minute.to_numpy().astype(float), + } + + result = resample_resource_data_to_dt(data, 7200) # 2-hour timestep + + with subtests.test("result length matches excluded leap year at 2 hour dt"): + assert len(result["wind_speed_100m"]) == 4380 + with subtests.test("february 29 remains excluded"): + assert not ((result["month"] == 2) & (result["day"] == 29)).any() + with subtests.test("starts on january 1"): + assert int(result["month"][0]) == 1 and int(result["day"][0]) == 1 + with subtests.test("ends on december 31"): + assert int(result["month"][-1]) == 12 and int(result["day"][-1]) == 31 + + +@pytest.mark.unit +def test_resample_keeps_leap_day_when_present_at_new_timestep(subtests): + # Leap year with the leap day retained, resampled to 2-hour: Feb 29 must remain. + + idx = pd.date_range("2012-01-01 00:30", "2012-12-31 23:30", freq="1h") # 8784, leap kept + data = { + "wind_speed_100m": np.arange(len(idx), dtype=float), + "year": idx.year.to_numpy().astype(float), + "month": idx.month.to_numpy().astype(float), + "day": idx.day.to_numpy().astype(float), + "hour": idx.hour.to_numpy().astype(float), + "minute": idx.minute.to_numpy().astype(float), + } + + result = resample_resource_data_to_dt(data, 7200) # 2-hour timestep + + with subtests.test("result length matches leap year at 2 hour dt"): + assert len(result["wind_speed_100m"]) == 4392 + with subtests.test("leap day remains present"): + assert ((result["month"] == 2) & (result["day"] == 29)).any() + + +@pytest.mark.unit +def test_downsample_preserves_mean(): + rng = np.random.default_rng(0) + values = rng.random(24) + data = _make_timeseries(24, 900, values=values) # 15-min data + result = resample_resource_data_to_dt(data, 3600) # hourly + + assert len(result["wind_speed_100m"]) == 6 + # overall mean is conserved by the averaging + np.testing.assert_allclose(result["wind_speed_100m"].mean(), values.mean()) + + +@pytest.mark.unit +def test_downsample_with_calendar_gap_preserves_mean(subtests): + # Hourly data whose timestamps have a one-day gap in the middle (as when a leap + # day is removed). Resampling must treat the samples as an evenly spaced sequence, + # so the downsampled mean matches the exact pairwise mean. + + part1 = pd.date_range("2012-02-28 00:30", periods=4, freq="1h") + part2 = pd.date_range("2012-03-01 00:30", periods=4, freq="1h") + idx = part1.append(part2) + values = np.arange(8, dtype=float) + data = { + "wind_speed_100m": values.copy(), + "year": idx.year.to_numpy().astype(float), + "month": idx.month.to_numpy().astype(float), + "day": idx.day.to_numpy().astype(float), + "hour": idx.hour.to_numpy().astype(float), + "minute": idx.minute.to_numpy().astype(float), + } + result = resample_resource_data_to_dt(data, 7200) # to 2-hour + + with subtests.test("downsampled length matches expected bins"): + assert len(result["wind_speed_100m"]) == 4 + with subtests.test("pairwise averages preserved across gap"): + np.testing.assert_allclose(result["wind_speed_100m"], [0.5, 2.5, 4.5, 6.5]) + with subtests.test("mean preserved despite gap"): + np.testing.assert_allclose(result["wind_speed_100m"].mean(), values.mean()) + + +@pytest.mark.unit +def test_resample_scalar_metadata_preserved(subtests): + data = _make_timeseries(5, 3600, values=[0, 1, 2, 3, 4]) + result = resample_resource_data_to_dt(data, 1800) + with subtests.test("units preserved"): + assert result["units"] == {"wind_speed_100m": "m/s"} + with subtests.test("site latitude preserved"): + assert result["site_lat"] == 1.0 + + +@pytest.mark.unit +def test_resample_unknown_upsample_method_raises(): + data = _make_timeseries(5, 3600) + # An invalid pandas interpolation method raises. The exact message differs across + # pandas versions, but the offending method name is always reported. + with pytest.raises(ValueError, match="not_a_method"): + resample_resource_data_to_dt(data, 1800, upsample_method="not_a_method") + + +@pytest.mark.unit +def test_resample_unknown_downsample_method_raises(): + data = _make_timeseries(10, 1800) + # An invalid pandas aggregation raises. Different pandas versions raise different + # exception types (AttributeError vs ValueError) with different messages, so match + # on the offending method name that is common to all versions. + with pytest.raises((AttributeError, ValueError), match="not_a_method"): + resample_resource_data_to_dt(data, 3600, downsample_method="not_a_method") diff --git a/h2integrate/resource/utilities/time_tools.py b/h2integrate/resource/utilities/time_tools.py index c44b4a7b1..e056520b7 100644 --- a/h2integrate/resource/utilities/time_tools.py +++ b/h2integrate/resource/utilities/time_tools.py @@ -1,28 +1,33 @@ +from calendar import isleap from datetime import timezone, timedelta +import numpy as np import pandas as pd -def process_leap_day(data: dict, include_leap_day: bool, n_timesteps: int): - """Process leap day data by optionally removing it and validating data length. +def process_leap_day(data: dict, include_leap_day: bool): + """Add or remove leap-day handling based on ``include_leap_day`` and the data. - Checks whether the provided resource data contains a leap day (February 29th). - If ``include_leap_day`` is set to False in the config and the data contains a - leap day, the leap day entries are removed. After processing, validates that - the length of the data matches the expected number of timesteps. + Behavior: + + - ``include_leap_day=False``: if the data contains a leap day (February 29) it is + removed; if the data has no leap day the data is returned unchanged. + - ``include_leap_day=True``: if the data contains a leap day it is kept unchanged; if + the data is for a leap year but does not contain a leap day a ``ValueError`` is + raised (a leap day cannot be added when it is not present in the source data). For + a non-leap year there is no leap day and the data is returned unchanged. Args: - data (dict): DataFrame-like dictionary of resource data containing - "Month" and "Day" columns. - include_leap_day (bool): Whether to include leap day in the resource data. - n_timesteps (int): Number of timesteps in the simulation. + data (dict): DataFrame-like dictionary of resource data containing year, month, + and day columns. + include_leap_day (bool): Whether the leap day should be included. Returns: - dict: Processed resource data with leap day handled according to configuration. + dict: Resource data with leap-day handling applied. Raises: - ValueError: If the length of the data does not match ``n_timesteps`` - after leap day processing. + ValueError: If ``include_leap_day`` is True for a leap year whose data does not + contain a leap day. """ convert_to_dict = False @@ -31,43 +36,30 @@ def process_leap_day(data: dict, include_leap_day: bool, n_timesteps: int): convert_to_dict = True case_of_time_cols = "lower" if "month" in data.columns.to_list() else "upper" - data = data.rename(columns={"month": "Month", "day": "Day"}) - - # Check if data includes leap day - data_has_leap_day = int(data[data["Month"] == 2]["Day"].max()) == 29 - - # Remove leap day if needed - if not include_leap_day and data_has_leap_day: - # Get index of dataframe that includes leap day - leap_day_index = ( - data.reset_index(drop=False) - .set_index(keys=["Month", "Day"], drop=True) - .loc[(2, 29)]["index"] - .to_list() - ) - # Drop the leap day data from the dataframe - data = data.drop(index=leap_day_index) - - # Check if data is the same length as the number of timesteps - if len(data) != n_timesteps: - leap_day_msg = "" - if data_has_leap_day and len(data) > n_timesteps: - # Add extra detail to error message if error may be due to leap day - leap_day_msg = ( - "This may be because the resource data includes a leap day. ", - "To remove data from a leap day from resource data, please set " - "`include_leap_day` to False.", - ) + data = data.rename(columns={"year": "Year", "month": "Month", "day": "Day"}) - msg = ( - f"Resource data is not the same length as n_timesteps. " - f"Resource data has length {len(data)}, n_timesteps is {n_timesteps}. " - f"{leap_day_msg}" - ) - raise ValueError(msg) + february = data[data["Month"] == 2] + data_has_leap_day = (not february.empty) and int(february["Day"].max()) == 29 + + if include_leap_day: + # Keep the leap day when present; error only if a leap year is missing it + if not data_has_leap_day and "Year" in data.columns: + year = int(data["Year"].iloc[0]) + if isleap(year): + msg = ( + f"include_leap_day is True but the resource data for leap year {year} " + "does not contain a leap day (February 29). A leap day cannot be added " + "when it is not present in the source data; either provide data that " + "includes the leap day or set include_leap_day to False." + ) + raise ValueError(msg) + elif data_has_leap_day: + # Remove the leap day when it is present but not wanted + leap_day_rows = data[(data["Month"] == 2) & (data["Day"] == 29)] + data = data.drop(index=leap_day_rows.index) if case_of_time_cols == "lower": - data = data.rename(columns={"Month": "month", "Day": "day"}) + data = data.rename(columns={"Year": "year", "Month": "month", "Day": "day"}) if convert_to_dict: data_out = {k: data[k].values for k in data.columns.to_list()} @@ -125,3 +117,292 @@ def add_resource_start_end_times(data: dict): data.update(time_start_end_info) return data + + +TIME_COLUMN_KEYS = ["year", "month", "day", "hour", "minute", "second"] + + +def _is_timeseries_value(value): + """Return True if ``value`` is an array-like of numeric timeseries data.""" + return isinstance(value, np.ndarray | list | tuple) and not isinstance(value, str | bytes) + + +def _infer_native_length(data: dict, present_time_keys: list): + """Infer the native (per-timestep) length of the resource timeseries data. + + The length is taken from the time columns when present, otherwise from the + most common array length among the dictionary values. + + Args: + data (dict): resource data dictionary. + present_time_keys (list): time-column keys that exist in ``data``. + + Returns: + int | None: the inferred native length, or None if it cannot be determined. + """ + if present_time_keys: + return len(np.asarray(data[present_time_keys[0]])) + + lengths = [len(v) for v in data.values() if _is_timeseries_value(v)] + if not lengths: + return None + # Use the most common array length as the native timeseries length + return max(set(lengths), key=lengths.count) + + +def conform_resource_data_to_n_timesteps(data: dict, n_timesteps: int): + """Slice resource timeseries data to match the simulation horizon. + + Resource data is expected to contain at least ``n_timesteps`` of data. When + the native resource length is longer than ``n_timesteps`` (for example a full + year of data for a sub-annual simulation, or the trailing year of a multi-year + download), the timeseries are sliced to ``n_timesteps``. Scalar metadata (site + info, units, etc.) is left unchanged. + + Args: + data (dict): resource data dictionary with timeseries arrays and metadata. + n_timesteps (int): target number of timesteps for the simulation. + + Returns: + dict: resource data with timeseries sliced to ``n_timesteps``. + + Raises: + ValueError: if the resource data contains fewer than ``n_timesteps`` of data. + """ + if not isinstance(data, dict) or n_timesteps is None: + return data + + present_time_keys = [k for k in TIME_COLUMN_KEYS if k in data] + native_len = _infer_native_length(data, present_time_keys) + + # Nothing to do if the native length is unknown or already matches the horizon + if not native_len or native_len == n_timesteps: + return data + + if native_len < n_timesteps: + msg = ( + f"Not enough resource data to cover the simulation horizon. The resource " + f"data provides {native_len} timesteps, but the simulation requires " + f"{n_timesteps} timesteps. Provide additional years of resource data or " + f"shorten the simulation horizon." + ) + raise ValueError(msg) + + conformed = {} + for key, value in data.items(): + if _is_timeseries_value(value) and len(value) == native_len: + conformed[key] = np.asarray(value)[:n_timesteps] + else: + conformed[key] = value + + return conformed + + +def concatenate_resource_years(yearly_data: list): + """Concatenate multiple years of resource data into a single continuous series. + + The timeseries arrays from each year -- including the calendar time columns -- + are concatenated in order, so the combined series preserves the real timestamps + of the source data (for example a removed leap day leaves a February 29 gap while + every other timestamp still matches the downloaded data). Scalar metadata (site + info, units, etc.) is taken from the first year. + + Args: + yearly_data (list): list of resource data dictionaries, one per year, in + chronological order. + + Returns: + dict: a single resource data dictionary spanning all provided years. + """ + if not yearly_data: + return {} + if len(yearly_data) == 1: + return yearly_data[0] + + base = yearly_data[0] + native_len = _infer_native_length(base, [k for k in TIME_COLUMN_KEYS if k in base]) + + combined = dict(base) + for key, value in base.items(): + if _is_timeseries_value(value) and len(value) == native_len: + combined[key] = np.concatenate( + [np.asarray(year_data[key]) for year_data in yearly_data] + ) + + return combined + + +def _regenerate_time_columns_at_dt( + data, start_timestamp, dt_seconds, n, present_time_keys, exclude_leap_day=False +): + """Regenerate time columns as ``n`` steps of ``dt_seconds`` starting at a timestamp. + + When ``exclude_leap_day`` is True, February 29 is skipped so the regenerated + calendar keeps the leap day excluded even at the new timestep (extra steps are + generated to make up for the skipped timestamps). + """ + freq = pd.Timedelta(seconds=dt_seconds) + if exclude_leap_day: + periods = n + new_index = pd.date_range(start=start_timestamp, periods=periods, freq=freq) + new_index = new_index[~((new_index.month == 2) & (new_index.day == 29))] + # Generate additional steps until enough non-leap-day timestamps are available + while len(new_index) < n: + periods += (n - len(new_index)) + 1 + new_index = pd.date_range(start=start_timestamp, periods=periods, freq=freq) + new_index = new_index[~((new_index.month == 2) & (new_index.day == 29))] + new_index = new_index[:n] + else: + new_index = pd.date_range(start=start_timestamp, periods=n, freq=freq) + field_map = { + "year": new_index.year, + "month": new_index.month, + "day": new_index.day, + "hour": new_index.hour, + "minute": new_index.minute, + "second": new_index.second, + } + for k in present_time_keys: + data[k] = np.asarray(field_map[k], dtype=float) + return data + + +def resample_resource_data_to_dt( + data: dict, + target_dt, + upsample_method: str = "time", + downsample_method: str = "mean", +): + """Resample resource timeseries from its native timestep to ``target_dt``. + + Resampling is driven by the actual time span of the data (its native timestep, + inferred from the time columns), not by the number of timesteps. When the + simulation timestep is smaller than the native timestep the data is upsampled + (finer resolution) using :meth:`pandas.DataFrame.interpolate`; when it is larger + the data is downsampled (coarser resolution) using + :meth:`pandas.core.resample.Resampler.agg`. Time columns are regenerated at + ``target_dt`` and scalar metadata is left unchanged. + + Resampling operates on the samples as an evenly spaced sequence at the native + timestep. Resource data may have non-contiguous calendar timestamps (for example + when a leap day is removed to keep a clean annual length), so a contiguous + synthetic time axis at the native timestep is used for the resampling itself and + the calendar time columns are regenerated afterward. + + Args: + data (dict): resource data dictionary with timeseries arrays and time columns. + target_dt (int | float): desired simulation timestep in seconds. + upsample_method (str): interpolation method passed to + :meth:`pandas.DataFrame.interpolate` when upsampling. Defaults to ``"time"`` + (linear interpolation that respects the sample spacing). + downsample_method (str): aggregation passed to + :meth:`pandas.core.resample.Resampler.agg` when downsampling. Defaults to + ``"mean"``. + + Returns: + dict: resource data resampled to ``target_dt``. + """ + if not isinstance(data, dict) or not target_dt: + return data + + present_time_keys = [k for k in TIME_COLUMN_KEYS if k in data] + if not present_time_keys: + # Without time columns the native timestep cannot be determined, so the data + # cannot be resampled to the requested timestep. + msg = ( + "Cannot resample resource data to the simulation timestep because the data " + "has no time columns (year/month/day/hour/minute) to determine its native " + "timestep. Provide resource data that includes time information." + ) + raise ValueError(msg) + + assembly = {k: np.asarray(data[k]).astype(int) for k in present_time_keys} + calendar_index = pd.DatetimeIndex(pd.to_datetime(assembly)) + if len(calendar_index) < 2: + return data + + # Native timestep is taken from the first two samples so that a calendar gap + # (such as a removed leap day) does not distort it. + native_dt = (calendar_index[1] - calendar_index[0]).total_seconds() + if native_dt <= 0: + msg = ( + "Cannot resample resource data: the native timestep derived from the data's " + "time columns is non-positive. Ensure the resource data has valid, strictly " + "increasing timestamps." + ) + raise ValueError(msg) + + # Nothing to do if the native timestep already matches the target + if abs(native_dt - float(target_dt)) < 1e-6: + return data + + # Detect whether the source data has a leap day removed so the regenerated + # calendar keeps February 29 excluded at the new timestep. + has_feb29 = bool(((calendar_index.month == 2) & (calendar_index.day == 29)).any()) + spans_leap_year = any(isleap(int(y)) for y in np.unique(calendar_index.year.to_numpy())) + exclude_leap_day = spans_leap_year and not has_feb29 + + native_len = len(calendar_index) + target_n = int(round(native_len * native_dt / float(target_dt))) + if target_n < 1: + msg = ( + f"Cannot resample resource data to a timestep of {target_dt} s: it is larger " + f"than the total time span of the data ({native_len} samples at {native_dt} s " + f"= {native_len * native_dt} s), so resampling would produce no timesteps. Use " + "a smaller timestep or provide more resource data." + ) + raise ValueError(msg) + + target_freq = pd.Timedelta(seconds=float(target_dt)) + # Use a contiguous synthetic axis anchored at the data's first timestamp so that + # resampling is based on the evenly spaced sample sequence rather than the + # (possibly gapped) calendar timestamps. ``pd.date_range`` is always contiguous, + # so this avoids the gaps a removed leap day would otherwise introduce. + start = calendar_index[0] + native_index = pd.date_range( + start=start, periods=native_len, freq=pd.Timedelta(seconds=native_dt) + ) + target_index = pd.date_range(start=start, periods=target_n, freq=target_freq) + + # Only the numeric timeseries columns are resampled; time columns are regenerated + data_keys = [ + k + for k in data + if k not in present_time_keys + and _is_timeseries_value(data[k]) + and len(data[k]) == native_len + ] + frame = pd.DataFrame( + {k: np.asarray(data[k], dtype=float) for k in data_keys}, index=native_index + ) + + if native_dt > float(target_dt): + # Upsample: interpolate onto the (finer) target grid + union_index = frame.index.union(target_index) + resampled_frame = ( + frame.reindex(union_index).interpolate(method=upsample_method).reindex(target_index) + ) + else: + # Downsample: aggregate native samples within each (coarser) target interval + resampled_frame = ( + frame.resample(target_freq, origin="start").agg(downsample_method).reindex(target_index) + ) + + # Fill any residual NaNs at the grid edges introduced by reindexing + resampled_frame = resampled_frame.ffill().bfill() + + resampled = dict(data) + for k in data_keys: + resampled[k] = resampled_frame[k].to_numpy() + + # Regenerate calendar time columns at the target timestep starting from the + # original first timestamp, keeping any removed leap day excluded. + resampled = _regenerate_time_columns_at_dt( + resampled, + calendar_index[0], + float(target_dt), + target_n, + present_time_keys, + exclude_leap_day, + ) + return resampled diff --git a/h2integrate/resource/wind/nlr_developer_wtk_api_base.py b/h2integrate/resource/wind/nlr_developer_wtk_api_base.py index 50639464d..f044c0736 100644 --- a/h2integrate/resource/wind/nlr_developer_wtk_api_base.py +++ b/h2integrate/resource/wind/nlr_developer_wtk_api_base.py @@ -64,7 +64,7 @@ def setup(self): # add resource data dictionary as an output self.add_discrete_output("wind_resource_data", val=data, desc="Dict of wind resource data") - def create_filename(self, latitude, longitude): + def create_filename(self, latitude, longitude, resource_year=None): """Create default filename to save downloaded data to. Filename is formatted as "{latitude}_{longitude}_{resource_year}_wtk_v2_{interval}min_{tz_desc}_tz.csv" where "tz_desc" is "utc" if the timezone is zero, or "local" otherwise. @@ -72,35 +72,40 @@ def create_filename(self, latitude, longitude): Args: latitude (float): latitude corresponding to location for resource data longitude (float): longitude corresponding to location for resource data + resource_year (int | str | None): resource year to build the filename for. When + None, ``self.config.resource_year`` is used. Returns: str: filename for resource data to be saved to or loaded from. """ - # TODO: update to handle multiple years - # TODO: update to handle nonstandard time intervals + + resource_year = self.config.resource_year if resource_year is None else resource_year if self.utc: tz_desc = "utc" else: tz_desc = "local" filename = ( - f"{latitude}_{longitude}_{self.config.resource_year}_" + f"{latitude}_{longitude}_{resource_year}_" f"{self.config.dataset_desc}_{self.interval}min_{tz_desc}_tz.csv" ) return filename - def create_url(self, latitude, longitude): + def create_url(self, latitude, longitude, resource_year=None): """Create url for data download. Args: latitude (float): latitude corresponding to location for resource data longitude (float): longitude corresponding to location for resource data + resource_year (int | str | None): resource year to build the url for. When None, + ``self.config.resource_year`` is used. Returns: str: url to use for API call. """ + resource_year = self.config.resource_year if resource_year is None else resource_year input_data = { "wkt": f"POINT({longitude} {latitude})", - "names": [str(self.config.resource_year)], # TODO: update to handle multiple years + "names": [str(resource_year)], "interval": str(self.interval), "utc": str(self.utc).lower(), "api_key": get_nlr_developer_api_key(), @@ -109,7 +114,7 @@ def create_url(self, latitude, longitude): url = self.base_url + urllib.parse.urlencode(input_data, True) return url - def load_data(self, fpath): + def load_data(self, fpath, resource_year=None): """Load data from a file and format as a dictionary that: 1) follows naming convention described in WindResourceBase. diff --git a/h2integrate/resource/wind/nlr_developer_wtk_api_models.py b/h2integrate/resource/wind/nlr_developer_wtk_api_models.py index 4674452ba..1c68d147b 100644 --- a/h2integrate/resource/wind/nlr_developer_wtk_api_models.py +++ b/h2integrate/resource/wind/nlr_developer_wtk_api_models.py @@ -36,7 +36,7 @@ class WTKNLRDeveloperAPIConfig(ResourceBaseAPIConfig): resource_type: str = "wind" valid_intervals: list[int] = field(factory=lambda: [5, 15, 30, 60]) resource_data: dict | object = field(default={}) - resource_filename: Path | str = field(default="") + resource_filename: Path | str | list = field(default="") resource_dir: Path | str | None = field(default=None) @@ -86,7 +86,7 @@ class WTKHRRRMETAPIConfig(ResourceBaseAPIConfig): resource_type: str = "wind" valid_intervals: list[int] = field(factory=lambda: [60]) resource_data: dict | object = field(default={}) - resource_filename: Path | str = field(default="") + resource_filename: Path | str | list = field(default="") resource_dir: Path | str | None = field(default=None) diff --git a/h2integrate/resource/wind/openmeteo_wind.py b/h2integrate/resource/wind/openmeteo_wind.py index d02f4d649..b52308b8b 100644 --- a/h2integrate/resource/wind/openmeteo_wind.py +++ b/h2integrate/resource/wind/openmeteo_wind.py @@ -96,7 +96,7 @@ def setup(self): # add resource data dictionary as an out self.add_discrete_output("wind_resource_data", val=data, desc="Dict of wind resource data") - def create_filename(self, latitude, longitude): + def create_filename(self, latitude, longitude, resource_year=None): """Create default filename to save downloaded data to. Filename is formatted as "{latitude}_{longitude}_{resource_year}_openmeteo_archive_{interval}min_{tz_desc}_tz.csv" where "tz_desc" is "utc" if the timezone is zero, or "local" otherwise. @@ -104,34 +104,39 @@ def create_filename(self, latitude, longitude): Args: latitude (float): latitude corresponding to location for resource data longitude (float): longitude corresponding to location for resource data + resource_year (int | str | None): resource year to build the filename for. When + None, ``self.config.resource_year`` is used. Returns: str: filename for resource data to be saved to or loaded from. """ - # TODO: update to handle multiple years - # TODO: update to handle nonstandard time intervals + + resource_year = self.config.resource_year if resource_year is None else resource_year if self.utc: tz_desc = "utc" else: tz_desc = "local" filename = ( - f"{latitude}_{longitude}_{self.config.resource_year}_" + f"{latitude}_{longitude}_{resource_year}_" f"{self.config.dataset_desc}_{self.interval}min_{tz_desc}_tz.csv" ) return filename - def create_url(self, latitude, longitude): + def create_url(self, latitude, longitude, resource_year=None): """Create url for data download. Args: latitude (float): latitude corresponding to location for resource data longitude (float): longitude corresponding to location for resource data + resource_year (int | str | None): resource year to build the url for. When None, + ``self.config.resource_year`` is used. Returns: str: url to use for API call. """ - start_year = int(self.config.resource_year - 1) - end_year = int(self.config.resource_year + 1) + resource_year = self.config.resource_year if resource_year is None else resource_year + start_year = int(resource_year - 1) + end_year = int(resource_year + 1) input_data = { "latitude": latitude, @@ -237,7 +242,7 @@ def download_data(self, url, fpath): return success - def load_data(self, fpath): + def load_data(self, fpath, resource_year=None): """Load data from a file and format as a dictionary that: 1) follows naming convention described in WindResourceBase. @@ -253,11 +258,14 @@ def load_data(self, fpath): Args: fpath (str | Path): filepath to file containing the data + resource_year (int | None): resource year to select from the downloaded file. + When None, ``self.config.resource_year`` is used. Returns: dict: dictionary of data in standardized units and naming convention. Time information is found in the 'time' key. """ + resource_year = self.config.resource_year if resource_year is None else resource_year header = pd.read_csv(fpath, nrows=2, header=None) header_dict = dict(zip(header.iloc[0].to_list(), header.iloc[1].to_list())) @@ -292,11 +300,15 @@ def load_data(self, fpath): data["Hour"] = time.hour data["Minute"] = time.minute - data = data[data["Year"] == self.config.resource_year] + data = data[data["Year"] == resource_year] data = data.reset_index(drop=True) - data = process_leap_day(data, self.config.include_leap_day, self.n_timesteps) + # Handle the leap day according to include_leap_day: remove it when present but + # not wanted, keep it when present and wanted, and error if a leap year's data is + # missing it. The resource base then resamples this native data to the simulation + # timestep and slices it to the requested horizon. + data = process_leap_day(data, self.config.include_leap_day) data, data_units = self.format_timeseries_data(data) # make units for data in openmdao-compatible units From d439b06e8833a29716d2f4131651feaa17cb0539 Mon Sep 17 00:00:00 2001 From: Jared Thomas Date: Thu, 8 Oct 2026 11:26:07 -0600 Subject: [PATCH 2/4] restore annual simulation only enforment --- h2integrate/core/inputs/validation.py | 32 ++++++--------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/h2integrate/core/inputs/validation.py b/h2integrate/core/inputs/validation.py index 78091bc69..60cbdbacd 100644 --- a/h2integrate/core/inputs/validation.py +++ b/h2integrate/core/inputs/validation.py @@ -152,33 +152,15 @@ def load_tech_yaml(finput): def load_plant_yaml(finput): plant_config = _validate(finput, fschema_plant) - n_timesteps = int(plant_config["plant"]["simulation"]["n_timesteps"]) - dt = int(plant_config["plant"]["simulation"]["dt"]) - plant_life = int(plant_config["plant"]["plant_life"]) - - seconds_per_year = 31_536_000 # 8760 h/year * 3600 s/h - seconds_simulated = n_timesteps * dt - years_simulated = seconds_simulated / seconds_per_year - - # The simulation may cover any positive duration (a fraction of a year, a single - # year, or multiple years). Performance/cost/finance models annualize their results - # using ``fraction_of_year_simulated`` (see the model base classes), so an arbitrary - # horizon is supported as long as it is positive and does not exceed the plant life. - if seconds_simulated <= 0: - msg = ( - "The simulation horizon must be positive. Please ensure that " - "plant_config['plant']['simulation']['n_timesteps'] times " - "plant_config['plant']['simulation']['dt'] is greater than 0 (s)." - ) - raise ValueError(msg) + n_timesteps = plant_config["plant"]["simulation"]["n_timesteps"] + dt = plant_config["plant"]["simulation"]["dt"] - if years_simulated > plant_life: + if int(n_timesteps) * int(dt) != 31_536_000: msg = ( - "H2Integrate does not support simulations that are longer than the plant " - f"life. The configured simulation covers {years_simulated:.4g} years " - f"(n_timesteps={n_timesteps} * dt={dt} s = {seconds_simulated} s), but " - f"plant_config['plant']['plant_life'] is {plant_life} years. Either shorten " - "the simulation horizon or increase plant_life." + "H2Integrate does not currently support simulations that are less than or " + "greater than 1-year. Please ensure that " + "plant_config['plant']['simulation']['n_timesteps'] times " + "plant_config['plant']['simulation']['dt'] equals 31536000 (s)." ) raise ValueError(msg) From 5c874592e06783dbcbb8684c888567a6b79fe090 Mon Sep 17 00:00:00 2001 From: Jared Thomas Date: Thu, 8 Oct 2026 12:46:19 -0600 Subject: [PATCH 3/4] update docs --- docs/_static/class_hierarchy.html | 4 ++-- docs/resource/resource_index.md | 6 ++++++ h2integrate/resource/resource_baseclass.py | 8 ++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/_static/class_hierarchy.html b/docs/_static/class_hierarchy.html index a33861c95..2e1b7c134 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.227272727272727, "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": 478.26406871192853, "y": 424.26406871192853}, {"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": 53.999999999999886, "y": -600.0}, {"borderWidth": 5.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "OpenLoopControlBase", "label": "OpenLoopControlBase", "shape": "hexagon", "size": 20.454545454545453, "title": "OpenLoopControlBase\ncontrol/control_strategies/openloop_control_baseclass.py\n[Control / General]", "x": 681.5146849936184, "y": 83.93066263524416}, {"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.84090909090909, "title": "PyomoStorageControllerBaseClass\ncontrol/control_strategies/pyomo_storage_controller_baseclass.py\n[Storage / General]", "x": 81.51468499361823, "y": -516.0693373647558}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PLMHeuristicOpenLoopConverterController", "label": "PLMHeuristicOpenLoopConverterController", "shape": "dot", "size": 18.0, "title": "PLMHeuristicOpenLoopConverterController\ncontrol/control_strategies/converters/plm_openloop_converter_controller.py\n[Converter / Other]", "x": 505.7787537055469, "y": 508.1947313471727}, {"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": -4.029534077577966, "y": -462.0588427802723}, {"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": -109.50296675787511, "y": -499.69371768815415}, {"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": -154.5360312930279, "y": -609.0363174025894}, {"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": -103.92933571731442, "y": -720.3315967539606}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PeakLoadManagementOptimizedStorageController", "label": "PeakLoadManagementOptimizedStorageController", "shape": "diamond", "size": 18.0, "title": "PeakLoadManagementOptimizedStorageController\ncontrol/control_strategies/storage/plm_optimized_storage_controller.py\n[Storage / General]", "x": 14.174835317190345, "y": -761.3786666314061}, {"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": 127.38669554530846, "y": -703.6855452705287}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "CostMinimizationControl", "label": "CostMinimizationControl", "shape": "hexagon", "size": 18.0, "title": "CostMinimizationControl\ncontrol/control_strategies/system_level/cost_minimization_control.py\n[Control / General]", "x": 595.9704659224221, "y": 137.9411572197277}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DemandFollowingControl", "label": "DemandFollowingControl", "shape": "hexagon", "size": 18.0, "title": "DemandFollowingControl\ncontrol/control_strategies/system_level/demand_following_control.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": "ProfitMaximizationControl", "label": "ProfitMaximizationControl", "shape": "hexagon", "size": 18.0, "title": "ProfitMaximizationControl\ncontrol/control_strategies/system_level/profit_maximization_control.py\n[Control / General]", "x": 445.4639687069722, "y": -9.036317402589397}, {"borderWidth": 5.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SystemLevelControlBase", "label": "SystemLevelControlBase", "shape": "hexagon", "size": 19.84090909090909, "title": "SystemLevelControlBase\ncontrol/control_strategies/system_level/system_level_control_baseclass.py\n[Control / General]", "x": 496.0706642826857, "y": -120.33159675396058}, {"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": 420.23453463435067, "y": 562.2052259316563}, {"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_cost.py\n[Converter / Ammonia]", "x": 314.7611019540535, "y": 524.5703510237744}, {"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_performance.py\n[Converter / Ammonia]", "x": 269.7280374189007, "y": 415.2277513093391}, {"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": 320.3347329946142, "y": 303.93247195796795}, {"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": 438.438904029119, "y": 262.8854020805224}, {"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": 551.6507642572371, "y": 320.57852344139985}, {"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": 589.1327652257885, "y": 443.6112367171105}, {"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": 526.1020787646279, "y": 557.1240691202768}, {"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": 399.74069937727677, "y": 591.0159040973604}, {"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": 286.78380282847786, "y": 523.4075329070821}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleCycleTurbinePerformanceModel", "label": "SimpleCycleTurbinePerformanceModel", "shape": "dot", "size": 18.0, "title": "SimpleCycleTurbinePerformanceModel\nconverters/combustion_machines/turbine_simple_cycle.py\n[Converter / Other]", "x": 256.5641523482211, "y": 394.5748768944146}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DieselGeneratorPerformanceModel", "label": "DieselGeneratorPerformanceModel", "shape": "dot", "size": 18.0, "title": "DieselGeneratorPerformanceModel\nconverters/diesel/diesel_generator.py\n[Converter / Other]", "x": 328.3357606918724, "y": 282.7057424982818}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DieselGeneratorCostModel", "label": "DieselGeneratorCostModel", "shape": "dot", "size": 18.0, "title": "DieselGeneratorCostModel\nconverters/diesel/diesel_generator.py\n[Converter / Other]", "x": 459.0997033432264, "y": 256.23717038956255}, {"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": 569.5124313587587, "y": 331.90645520110365}, {"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": 592.1635244622977, "y": 464.21994087945296}, {"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": 512.788859406323, "y": 572.8990626821025}, {"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": 379.21994390308055, "y": 591.6786327094717}, {"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": 19.227272727272727, "title": "ElectrolyzerPerformanceBaseClass\nconverters/hydrogen/electrolyzer_baseclass.py\n[Converter / Hydrogen]", "x": 272.49595476290375, "y": 508.75152096583327}, {"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.454545454545453, "title": "ElectrolyzerCostBaseClass\nconverters/hydrogen/electrolyzer_baseclass.py\n[Converter / Hydrogen]", "x": 257.6313191356596, "y": 374.1688916122072}, {"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": 343.9803548876029, "y": 269.5853411323154}, {"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": 479.36821794000866, "y": 258.67007677091334}, {"borderWidth": 2.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HTSEPerformanceModel", "label": "HTSEPerformanceModel", "shape": "dot", "size": 18.0, "title": "HTSEPerformanceModel\nconverters/hydrogen/htse_electrolyzer.py\n[Converter / Hydrogen]", "x": 581.650938836167, "y": 348.32372625486516}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HTSECostModel", "label": "HTSECostModel", "shape": "dot", "size": 18.0, "title": "HTSECostModel\nconverters/hydrogen/htse_electrolyzer.py\n[Converter / Hydrogen]", "x": 588.5900605700128, "y": 484.33044865825804}, {"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.613636363636363, "title": "ECOElectrolyzerPerformanceModel\nconverters/hydrogen/pem_electrolyzer.py\n[Converter / Hydrogen]", "x": 495.7412113849357, "y": 584.1699407047599}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PEMH2FuelCellPerformanceModel", "label": "PEMH2FuelCellPerformanceModel", "shape": "dot", "size": 18.0, "title": "PEMH2FuelCellPerformanceModel\nconverters/hydrogen/PEM_h2_fuel_cell.py\n[Converter / Hydrogen]", "x": 359.2867055817287, "y": 587.1128330868047}, {"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": 262.01927221450023, "y": 491.1735299973705}, {"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": 263.08673804264254, "y": 354.4313566491494}, {"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": 362.0143798328492, "y": 259.8541656207284}, {"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": 498.89220761566986, "y": 264.9407014092148}, {"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": 590.6696311091409, "y": 366.75587182211586}, {"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": 581.5603238588848, "y": 503.6235115942653}, {"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.227272727272727, "title": "GeoH2SubsurfacePerformanceBaseClass\nconverters/hydrogen/geologic/h2_well_subsurface_baseclass.py\n[Converter / Hydrogen]", "x": 476.95801138100563, "y": 592.4989174292589}, {"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.613636363636363, "title": "GeoH2SubsurfaceCostBaseClass\nconverters/hydrogen/geologic/h2_well_subsurface_baseclass.py\n[Converter / Hydrogen]", "x": 340.2415650067993, "y": 579.367836787116}, {"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.613636363636363, "title": "GeoH2SurfacePerformanceBaseClass\nconverters/hydrogen/geologic/h2_well_surface_baseclass.py\n[Converter / Hydrogen]", "x": 254.36413330758404, "y": 472.0789403482674}, {"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.613636363636363, "title": "GeoH2SurfaceCostBaseClass\nconverters/hydrogen/geologic/h2_well_surface_baseclass.py\n[Converter / Hydrogen]", "x": 271.51153102687147, "y": 335.6507905262953}, {"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": 381.3858774467768, "y": 252.86170302482213}, {"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": 517.3918676313194, "y": 274.01569429418305}, {"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": 597.007311214114, "y": 386.3735069606014}, {"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": 571.8605627878852, "y": 521.8262429979584}, {"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": 457.12229983971235, "y": 598.1874330053814}, {"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.227272727272727, "title": "IronReductionPlantBasePerformanceComponent\nconverters/iron/iron_dri_baseclass.py\n[Converter / Iron]", "x": 322.3514793857651, "y": 569.0657492227268}, {"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.227272727272727, "title": "IronReductionPlantBaseCostComponent\nconverters/iron/iron_dri_baseclass.py\n[Converter / Iron]", "x": 249.3207447753322, "y": 452.0512012075492}, {"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": 282.3956644041949, "y": 318.0887615111607}, {"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": 401.5811138158012, "y": 248.46048673420754}, {"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": 534.6107397512467, "y": 285.4631581602026}, {"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": 600.768590357241, "y": 406.71287320394595}, {"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": 559.8673531932255, "y": 538.6871747572902}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NRRIIronMinePerformanceComponent", "label": "NRRIIronMinePerformanceComponent", "shape": "dot", "size": 18.0, "title": "NRRIIronMinePerformanceComponent\nconverters/iron/nrri_iron_mine.py\n[Converter / Iron]", "x": 436.6612671078575, "y": 601.3105594196858}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NRRIIronMineCostComponent", "label": "NRRIIronMineCostComponent", "shape": "dot", "size": 18.0, "title": "NRRIIronMineCostComponent\nconverters/iron/nrri_iron_mine.py\n[Converter / Iron]", "x": 305.86294694330934, "y": 556.5435697899682}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleIronMineCostComponent", "label": "SimpleIronMineCostComponent", "shape": "dot", "size": 18.0, "title": "SimpleIronMineCostComponent\nconverters/iron/simple_mine_cost_model.py\n[Converter / Iron]", "x": 246.83423502407024, "y": 431.49025214209365}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleIronMinePerformanceComponent", "label": "SimpleIronMinePerformanceComponent", "shape": "dot", "size": 18.0, "title": "SimpleIronMinePerformanceComponent\nconverters/iron/simple_mine_perf_model.py\n[Converter / Iron]", "x": 295.43060589853144, "y": 301.9867542325129}, {"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": 422.22080078765276, "y": 246.6091518066144}, {"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": 550.3124383111342, "y": 298.9950401648947}, {"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": 601.9862168240556, "y": 427.4105858994158}, {"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.227272727272727, "title": "MethanolPerformanceBaseClass\nconverters/methanol/methanol_baseclass.py\n[Converter / Methanol]", "x": 545.8541044431593, "y": 553.9751328652483}, {"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.227272727272727, "title": "MethanolCostBaseClass\nconverters/methanol/methanol_baseclass.py\n[Converter / Methanol]", "x": 415.92585339776986, "y": 601.8960607109656}, {"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.227272727272727, "title": "MethanolFinanceBaseClass\nconverters/methanol/methanol_baseclass.py\n[Converter / Methanol]", "x": 291.00179949131797, "y": 542.0643862040909}, {"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": 246.8790862016323, "y": 410.7371333671474}, {"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": 310.360352964409, "y": 287.56511635115675}, {"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": 442.9719139504812, "y": 247.2823360496412}, {"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": 564.2822487163891, "y": 314.35997748581235}, {"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": 600.6870110996332, "y": 448.1402362399707}, {"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": 530.0693997546277, "y": 567.4811885064375}, {"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": 395.23689238658426, "y": 599.9734735125932}, {"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": 277.9710258430458, "y": 525.8754230377256}, {"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": 249.42205752670833, "y": 390.1078731951356}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SONGFuelCellPerformanceModel", "label": "SONGFuelCellPerformanceModel", "shape": "dot", "size": 18.0, "title": "SONGFuelCellPerformanceModel\nconverters/natural_gas/SO_NG_fuel_cell.py\n[Converter / Natural Gas]", "x": 326.93795243026625, "y": 275.02074263416614}, {"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": 463.522666614879, "y": 250.44231377114315}, {"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": 576.3295228404015, "y": 331.3104577202696}, {"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": 596.9138033781854, "y": 468.59387380591784}, {"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": 512.7619418878196, "y": 579.0210903649258}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleThermalNuclearReactorPerformanceModel", "label": "SimpleThermalNuclearReactorPerformanceModel", "shape": "dot", "size": 18.0, "title": "SimpleThermalNuclearReactorPerformanceModel\nconverters/nuclear/nuclear_plant_thermal.py\n[Converter / Nuclear]", "x": 374.898780701012, "y": 595.5912252374487}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleThermalNuclearReactorCostModel", "label": "SimpleThermalNuclearReactorCostModel", "shape": "dot", "size": 18.0, "title": "SimpleThermalNuclearReactorCostModel\nconverters/nuclear/nuclear_plant_thermal.py\n[Converter / Nuclear]", "x": 266.94833351832864, "y": 508.22704781877746}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PaperMillPerformanceModel", "label": "PaperMillPerformanceModel", "shape": "dot", "size": 18.0, "title": "PaperMillPerformanceModel\nconverters/paper_mill/paper_mill.py\n[Converter / Other]", "x": 254.4087327047308, "y": 369.9035008379885}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PaperMillCostModel", "label": "PaperMillCostModel", "shape": "dot", "size": 18.0, "title": "PaperMillCostModel\nconverters/paper_mill/paper_mill.py\n[Converter / Other]", "x": 344.91102189269037, "y": 264.52465360919535}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SAFPerformanceModel", "label": "SAFPerformanceModel", "shape": "dot", "size": 18.0, "title": "SAFPerformanceModel\nconverters/saf/saf.py\n[Converter / Other]", "x": 483.5752862949212, "y": 256.0283695433512}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SAFCostModel", "label": "SAFCostModel", "shape": "dot", "size": 18.0, "title": "SAFCostModel\nconverters/saf/saf.py\n[Converter / Other]", "x": 586.2900660658137, "y": 349.5918329787311}, {"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": 590.7338521478475, "y": 488.47693212571994}, {"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": 494.18881449746823, "y": 588.4376039723782}, {"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.613636363636363, "title": "SolarPerformanceBaseClass\nconverters/solar/solar_baseclass.py\n[Converter / Solar]", "x": 355.20288174471574, "y": 588.82330673143}, {"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": 258.08386761188416, "y": 489.37888485792956}, {"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": 261.75824472099976, "y": 350.4121413909081}, {"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": 364.01734513031863, "y": 256.2197810699507}, {"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": 502.8449520538379, "y": 263.95265465026137}, {"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": 594.028278287768, "y": 368.93928819073443}, {"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": 582.2420608068893, "y": 507.50798483494464}, {"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.613636363636363, "title": "SteelPerformanceBaseClass\nconverters/steel/steel_baseclass.py\n[Converter / Steel]", "x": 474.61740157528226, "y": 595.6025735513}, {"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.613636363636363, "title": "SteelCostBaseClass\nconverters/steel/steel_baseclass.py\n[Converter / Steel]", "x": 336.4271171942978, "y": 579.7717210898401}, {"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.227272727272727, "title": "ElectricArcFurnacePlantBasePerformanceComponent\nconverters/steel/steel_eaf_baseclass.py\n[Converter / Steel]", "x": 251.498232867932, "y": 469.6008262760399}, {"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.227272727272727, "title": "ElectricArcFurnacePlantBaseCostComponent\nconverters/steel/steel_eaf_baseclass.py\n[Converter / Steel]", "x": 271.3614700358236, "y": 331.9080859673451}, {"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": 383.98460867715687, "y": 250.2190781153802}, {"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": 521.0611417563041, "y": 274.0989256221985}, {"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": 599.4389524278133, "y": 389.07819733591873}, {"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": 571.5617734392102, "y": 525.4204270801721}, {"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": 454.32451418225986, "y": 600.4186260551784}, {"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": 318.8340186270915, "y": 568.5668769225663}, {"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.613636363636363, "title": "DesalinationPerformanceBaseClass\nconverters/water/desal/desalination_baseclass.py\n[Converter / Water]", "x": 247.28088676658444, "y": 449.1717246640683}, {"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.613636363636363, "title": "DesalinationCostBaseClass\nconverters/water/desal/desalination_baseclass.py\n[Converter / Water]", "x": 283.08098668844366, "y": 314.6496310207652}, {"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": 404.5320756399467, "y": 246.6040117026947}, {"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": 537.9699602282101, "y": 286.3228119984574}, {"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": 602.4486803402477, "y": 409.7261082423225}, {"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": 558.8442316692301, "y": 541.9649341194356}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PySAMWavePerformanceModel", "label": "PySAMWavePerformanceModel", "shape": "dot", "size": 18.0, "title": "PySAMWavePerformanceModel\nconverters/water_power/wave_pysam.py\n[Converter / Water Power]", "x": 433.5941405805959, "y": 602.8204736906509}, {"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": 302.668169321717, "y": 555.3667982088076}, {"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": 245.48894202636336, "y": 428.376916416411}, {"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": 296.75208775809443, "y": 298.87644641873663}, {"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": 425.37325701679254, "y": 245.42347163321477}, {"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.227272727272727, "title": "WindPerformanceBaseClass\nconverters/wind/wind_plant_baseclass.py\n[Converter / Wind]", "x": 553.3368230444287, "y": 300.45303318288484}, {"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": 603.0168360496132, "y": 430.59558134683033}, {"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": 41.93181818181818, "title": "PerformanceModelBaseClass\ncore/model_baseclass.py\n[Core / General]", "x": 54.000000000000036, "y": 600.0}, {"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_baseclass.py\n[Core / General]", "x": 81.51468499361839, "y": 683.9306626352442}, {"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.227272727272727, "title": "ResizeablePerformanceModelBaseClass\ncore/model_baseclass.py\n[Core / General]", "x": -4.02953407757782, "y": 737.9411572197278}, {"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": 18.613636363636363, "title": "CacheBaseClass\ncore/model_baseclass.py\n[Core / General]", "x": -109.50296675787496, "y": 700.3062823118458}, {"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.613636363636363, "title": "SiteBaseComponent\ncore/sites.py\n[Core / General]", "x": -154.53603129302775, "y": 590.9636825974106}, {"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": -103.92933571731426, "y": 479.6684032460394}, {"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.227272727272727, "title": "DemandComponentBase\ndemand/demand_baseclass.py\n[Other / Other]", "x": -546.0, "y": 7.347880794884119e-14}, {"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": -518.4853150063816, "y": 83.93066263524423}, {"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": -604.0295340775779, "y": 137.94115721972778}, {"borderWidth": 3.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_price.py\n[Other / Other]", "x": -709.502966757875, "y": 100.30628231184593}, {"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.613636363636363, "title": "FeedstockCostModel\nfeedstocks/feedstocks.py\n[Other / Other]", "x": -754.5360312930278, "y": -9.036317402589324}, {"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.227272727272727, "title": "ProFastBase\nfinances/profast_baseclass.py\n[Finance / General]", "x": -370.2640687119285, "y": 424.26406871192853}, {"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": -342.7493837183101, "y": 508.1947313471727}, {"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": -428.29360278950634, "y": 562.2052259316563}, {"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": 20.454545454545453, "title": "ResourceBaseAPIModel\nresource/resource_baseclass.py\n[Resource / General]", "x": -370.2640687119286, "y": -424.2640687119285}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ResourceBaseH5Model", "label": "ResourceBaseH5Model", "shape": "triangle", "size": 19.227272727272727, "title": "ResourceBaseH5Model\nresource/resource_hpc_baseclass.py\n[Resource / General]", "x": -342.74938371831024, "y": -340.3334060766843}, {"borderWidth": 4.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": 23.522727272727273, "title": "NLRDeveloperAPISolarResourceBase\nresource/solar/nlr_developer_api_baseclass.py\n[Resource / General]", "x": -428.29360278950645, "y": -286.3229114922008}, {"borderWidth": 3.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": -533.7670354698035, "y": -323.9577864000826}, {"borderWidth": 3.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": -578.8001000049563, "y": -433.3003861145179}, {"borderWidth": 3.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": -528.193404429243, "y": -544.595665465889}, {"borderWidth": 3.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": -410.08923339473813, "y": -585.6427353433346}, {"borderWidth": 3.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": -296.87737316662003, "y": -527.9496139824571}, {"borderWidth": 3.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": -259.3953721980687, "y": -404.91690070674656}, {"borderWidth": 3.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": -322.42605865922917, "y": -291.4040683035803}, {"borderWidth": 3.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": -448.78743804658035, "y": -257.5122333264967}, {"borderWidth": 3.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": -561.7443345953793, "y": -325.12060451677485}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NSRDBDatasetH5", "label": "NSRDBDatasetH5", "shape": "triangle", "size": 18.0, "title": "NSRDBDatasetH5\nresource/solar/nlr_nsrdb_dataset_model.py\n[Resource / General]", "x": -591.963985075636, "y": -453.9532605294424}, {"borderWidth": 4.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": -520.1923767319847, "y": -565.8223949255753}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SolarResourceBase", "label": "SolarResourceBase", "shape": "triangle", "size": 19.84090909090909, "title": "SolarResourceBase\nresource/solar/solar_resource_baseclass.py\n[Resource / General]", "x": -389.4284340806307, "y": -592.2909670342945}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NLRDeveloperAPIWindResourceBase", "label": "NLRDeveloperAPIWindResourceBase", "shape": "triangle", "size": 19.227272727272727, "title": "NLRDeveloperAPIWindResourceBase\nresource/wind/nlr_developer_wtk_api_baseclass.py\n[Resource / General]", "x": -279.01570606509836, "y": -516.6216822227533}, {"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_models.py\n[Resource / General]", "x": -256.3646129615595, "y": -384.30819654440404}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HRRRMETToolkitWindAPI", "label": "HRRRMETToolkitWindAPI", "shape": "triangle", "size": 18.0, "title": "HRRRMETToolkitWindAPI\nresource/wind/nlr_developer_wtk_api_models.py\n[Resource / General]", "x": -335.7392780175341, "y": -275.62907474175455}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WTKHRRRMETDatasetH5", "label": "WTKHRRRMETDatasetH5", "shape": "triangle", "size": 18.0, "title": "WTKHRRRMETDatasetH5\nresource/wind/nlr_hrrr_met_toolkit_dataset_model.py\n[Resource / General]", "x": -469.30819352077657, "y": -256.8495047143854}, {"borderWidth": 4.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": -576.0321826609534, "y": -339.77661645802374}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WindResourceBase", "label": "WindResourceBase", "shape": "triangle", "size": 19.84090909090909, "title": "WindResourceBase\nresource/wind/wind_resource_baseclass.py\n[Resource / General]", "x": -590.8968182881974, "y": -474.3592458116498}, {"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": 164.86869651385982, "y": -580.652831994818}, {"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": 101.83801005269929, "y": -467.13999959165176}, {"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": 19.84090909090909, "title": "StoragePerformanceBase\nstorage/storage_baseclass.py\n[Storage / General]", "x": -24.523369334651882, "y": -433.24816461456817}, {"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": -137.4802658834508, "y": -500.8565358048464}, {"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": -167.6999163637075, "y": -629.6891918175139}, {"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": -95.92830802005624, "y": -741.5583262136468}, {"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.454545454545453, "title": "HydrogenStorageBaseCostModel\nstorage/hydrogen/h2_storage_cost.py\n[Storage / General]", "x": 34.83563463129775, "y": -768.0268983223659}, {"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": 145.2483626468301, "y": -692.3576135108249}, {"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": 167.89945575036901, "y": -560.0441278324756}, {"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": 88.52479069439438, "y": -451.3650060298261}, {"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": -45.044124808848096, "y": -432.58543600245685}, {"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": -151.7681139490249, "y": -515.5125477460953}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "LinearMassTransportCostModel", "label": "LinearMassTransportCostModel", "shape": "square", "size": 18.0, "title": "LinearMassTransportCostModel\ntransporters/linear_mass_transport_cost.py\n[Transporter / General]", "x": 478.2640687119284, "y": -424.2640687119286}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "LinearDistanceCostModel", "label": "LinearDistanceCostModel", "shape": "square", "size": 18.0, "title": "LinearDistanceCostModel\ntransporters/linear_transport_cost.py\n[Transporter / General]", "x": 505.77875370554676, "y": -340.3334060766844}]); - edges = new vis.DataSet([{"arrows": "to", "from": "PyomoRuleBaseClass", "to": "PyomoDispatchGenericConverter"}, {"arrows": "to", "from": "PyomoRuleBaseClass", "to": "PyomoRuleStorageBaseclass"}, {"arrows": "to", "from": "OpenLoopControlBase", "to": "PLMHeuristicOpenLoopConverterController"}, {"arrows": "to", "from": "OpenLoopControlBase", "to": "DemandOpenLoopStorageController"}, {"arrows": "to", "from": "OpenLoopControlBase", "to": "PeakLoadManagementHeuristicOpenLoopStorageController"}, {"arrows": "to", "from": "OpenLoopControlBase", "to": "SimpleStorageOpenLoopController"}, {"arrows": "to", "from": "PyomoStorageControllerBaseClass", "to": "HeuristicLoadFollowingStorageController"}, {"arrows": "to", "from": "PyomoStorageControllerBaseClass", "to": "OptimizedDispatchStorageController"}, {"arrows": "to", "from": "PyomoStorageControllerBaseClass", "to": "PeakLoadManagementOptimizedStorageController"}, {"arrows": "to", "from": "SystemLevelControlBase", "to": "CostMinimizationControl"}, {"arrows": "to", "from": "SystemLevelControlBase", "to": "DemandFollowingControl"}, {"arrows": "to", "from": "SystemLevelControlBase", "to": "ProfitMaximizationControl"}, {"arrows": "to", "from": "ElectrolyzerPerformanceBaseClass", "to": "HTSEPerformanceModel"}, {"arrows": "to", "from": "ElectrolyzerPerformanceBaseClass", "to": "ECOElectrolyzerPerformanceModel"}, {"arrows": "to", "from": "ElectrolyzerCostBaseClass", "to": "BasicElectrolyzerCostModel"}, {"arrows": "to", "from": "ElectrolyzerCostBaseClass", "to": "CustomElectrolyzerCostModel"}, {"arrows": "to", "from": "ElectrolyzerCostBaseClass", "to": "HTSECostModel"}, {"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": "SimpleCycleTurbinePerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "DieselGeneratorPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "GridPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "LinearH2FuelCellPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "PEMH2FuelCellPerformanceModel"}, {"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": "NRRIIronMinePerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SimpleIronMinePerformanceComponent"}, {"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": "SONGFuelCellPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SimpleASUPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "QuinnNuclearPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SimpleThermalNuclearReactorPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "PaperMillPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SAFPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SolarPerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "CMUElectricArcFurnaceDRIPerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "CMUElectricArcFurnaceScrapOnlyPerformanceComponent"}, {"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": "PySAMWavePerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "WindArdPerformanceCompatibilityComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "WindPerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "ResizeablePerformanceModelBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "DemandComponentBase"}, {"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": "DieselGeneratorCostModel"}, {"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": "NRRIIronMineCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SimpleIronMineCostComponent"}, {"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": "SimpleThermalNuclearReactorCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "PaperMillCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SAFCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "ATBResComPVCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "ATBUtilityPVCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "CMUElectricArcFurnaceCostModel"}, {"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": "CostModelBaseClass", "to": "LinearMassTransportCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "LinearDistanceCostModel"}, {"arrows": "to", "from": "ResizeablePerformanceModelBaseClass", "to": "AmmoniaSynLoopPerformanceModel"}, {"arrows": "to", "from": "ResizeablePerformanceModelBaseClass", "to": "ElectrolyzerPerformanceBaseClass"}, {"arrows": "to", "from": "CacheBaseClass", "to": "FlorisWindPlantPerformanceModel"}, {"arrows": "to", "from": "SiteBaseComponent", "to": "SiteLocationComponent"}, {"arrows": "to", "from": "DemandComponentBase", "to": "FlexibleDemandComponent"}, {"arrows": "to", "from": "DemandComponentBase", "to": "GenericDemandComponent"}, {"arrows": "to", "from": "FeedstockCostModel", "to": "EIANaturalGasFeedstockCostModel"}, {"arrows": "to", "from": "ProFastBase", "to": "ProFastLCO"}, {"arrows": "to", "from": "ProFastBase", "to": "ProFastNPV"}, {"arrows": "to", "from": "ResourceBaseAPIModel", "to": "NLRDeveloperAPISolarResourceBase"}, {"arrows": "to", "from": "ResourceBaseAPIModel", "to": "OpenMeteoHistoricalSolarResource"}, {"arrows": "to", "from": "ResourceBaseAPIModel", "to": "NLRDeveloperAPIWindResourceBase"}, {"arrows": "to", "from": "ResourceBaseAPIModel", "to": "OpenMeteoHistoricalWindResource"}, {"arrows": "to", "from": "ResourceBaseH5Model", "to": "NSRDBDatasetH5"}, {"arrows": "to", "from": "ResourceBaseH5Model", "to": "WTKHRRRMETDatasetH5"}, {"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": "SolarResourceBase", "to": "NLRDeveloperAPISolarResourceBase"}, {"arrows": "to", "from": "SolarResourceBase", "to": "NSRDBDatasetH5"}, {"arrows": "to", "from": "SolarResourceBase", "to": "OpenMeteoHistoricalSolarResource"}, {"arrows": "to", "from": "NLRDeveloperAPIWindResourceBase", "to": "WTKNLRDeveloperAPIWindResource"}, {"arrows": "to", "from": "NLRDeveloperAPIWindResourceBase", "to": "HRRRMETToolkitWindAPI"}, {"arrows": "to", "from": "WindResourceBase", "to": "NLRDeveloperAPIWindResourceBase"}, {"arrows": "to", "from": "WindResourceBase", "to": "WTKHRRRMETDatasetH5"}, {"arrows": "to", "from": "WindResourceBase", "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"}, {"arrows": "to", "from": "HydrogenStorageBaseCostModel", "to": "CompressedGasStorageCostModel"}]); + nodes = new vis.DataSet([{"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "LinearMassTransportCostModel", "label": "LinearMassTransportCostModel", "shape": "square", "size": 18.0, "title": "LinearMassTransportCostModel\ntransporters/linear_mass_transport_cost.py\n[Transporter / General]", "x": 513.6266658713866, "y": -385.67256581192373}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "LinearDistanceCostModel", "label": "LinearDistanceCostModel", "shape": "square", "size": 18.0, "title": "LinearDistanceCostModel\ntransporters/linear_transport_cost.py\n[Transporter / General]", "x": 541.141350865005, "y": -301.74190317667956}, {"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.613636363636363, "title": "SiteBaseComponent\ncore/sites.py\n[Core / General]", "x": 158.18890660015825, "y": 590.8846518073248}, {"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": 185.7035915937766, "y": 674.8153144425689}, {"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": 41.93181818181818, "title": "PerformanceModelBaseClass\ncore/model_baseclass.py\n[Core / General]", "x": 100.1593725225804, "y": 728.8258090270524}, {"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_baseclass.py\n[Core / General]", "x": -5.314060157716753, "y": 691.1909341191706}, {"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.227272727272727, "title": "ResizeablePerformanceModelBaseClass\ncore/model_baseclass.py\n[Core / General]", "x": -50.34712469286953, "y": 581.8483344047354}, {"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": 18.613636363636363, "title": "CacheBaseClass\ncore/model_baseclass.py\n[Core / General]", "x": 0.2595708828439456, "y": 470.5530550533642}, {"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": 513.6266658713869, "y": 385.67256581192356}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleCycleTurbinePerformanceModel", "label": "SimpleCycleTurbinePerformanceModel", "shape": "dot", "size": 18.0, "title": "SimpleCycleTurbinePerformanceModel\nconverters/combustion_machines/turbine_simple_cycle.py\n[Converter / Other]", "x": 541.1413508650052, "y": 469.60322844716774}, {"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.59713179380896, "y": 523.6137230316513}, {"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": 350.1236991135118, "y": 485.9788481237694}, {"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": 305.0906345783591, "y": 376.63624840933414}, {"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.613636363636363, "title": "SolarPerformanceBaseClass\nconverters/solar/solar_baseclass.py\n[Converter / Solar]", "x": 355.6973301540725, "y": 265.340969057963}, {"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": 19.227272727272727, "title": "ElectrolyzerPerformanceBaseClass\nconverters/hydrogen/electrolyzer_baseclass.py\n[Converter / Hydrogen]", "x": 473.8015011885773, "y": 224.29389918051743}, {"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.454545454545453, "title": "ElectrolyzerCostBaseClass\nconverters/hydrogen/electrolyzer_baseclass.py\n[Converter / Hydrogen]", "x": 587.0133614166954, "y": 281.9870205413949}, {"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": 624.4953623852467, "y": 405.0197338171055}, {"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": 561.4646759240862, "y": 518.5325662202717}, {"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": 435.10329653673506, "y": 552.4244011973553}, {"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": 322.1463999879361, "y": 484.8160300070772}, {"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": 291.92674950767946, "y": 355.98337399440965}, {"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": 363.6983578513307, "y": 244.11423959827678}, {"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": 494.4623005026847, "y": 217.64566748955758}, {"borderWidth": 4.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PEMH2FuelCellPerformanceModel", "label": "PEMH2FuelCellPerformanceModel", "shape": "dot", "size": 18.0, "title": "PEMH2FuelCellPerformanceModel\nconverters/hydrogen/PEM_h2_fuel_cell.py\n[Converter / Hydrogen]", "x": 604.875028518217, "y": 293.3149523010987}, {"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.613636363636363, "title": "ECOElectrolyzerPerformanceModel\nconverters/hydrogen/pem_electrolyzer.py\n[Converter / Hydrogen]", "x": 627.5261216217559, "y": 425.628437979448}, {"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": 548.1514565657814, "y": 534.3075597820974}, {"borderWidth": 2.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HTSEPerformanceModel", "label": "HTSEPerformanceModel", "shape": "dot", "size": 18.0, "title": "HTSEPerformanceModel\nconverters/hydrogen/htse_electrolyzer.py\n[Converter / Hydrogen]", "x": 414.58254106253884, "y": 553.0871298094667}, {"borderWidth": 3.0, "color": {"background": "#2E7D32", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HTSECostModel", "label": "HTSECostModel", "shape": "dot", "size": 18.0, "title": "HTSECostModel\nconverters/hydrogen/htse_electrolyzer.py\n[Converter / Hydrogen]", "x": 307.85855192236204, "y": 470.1600180658283}, {"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": 292.9939162951179, "y": 335.5773887122022}, {"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.227272727272727, "title": "GeoH2SubsurfacePerformanceBaseClass\nconverters/hydrogen/geologic/h2_well_subsurface_baseclass.py\n[Converter / Hydrogen]", "x": 379.3429520470612, "y": 230.99383823231045}, {"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.613636363636363, "title": "GeoH2SubsurfaceCostBaseClass\nconverters/hydrogen/geologic/h2_well_subsurface_baseclass.py\n[Converter / Hydrogen]", "x": 514.7308150994669, "y": 220.07857387090837}, {"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": 617.0135359956254, "y": 309.7322233548602}, {"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": 623.9526577294712, "y": 445.73894575825307}, {"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": 531.103808544394, "y": 545.5784378047549}, {"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": 394.649302741187, "y": 548.5213301867998}, {"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.613636363636363, "title": "GeoH2SurfacePerformanceBaseClass\nconverters/hydrogen/geologic/h2_well_surface_baseclass.py\n[Converter / Hydrogen]", "x": 297.38186937395847, "y": 452.58202709736554}, {"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.613636363636363, "title": "GeoH2SurfaceCostBaseClass\nconverters/hydrogen/geologic/h2_well_surface_baseclass.py\n[Converter / Hydrogen]", "x": 298.4493352021008, "y": 315.83985374914437}, {"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": 397.3769769923075, "y": 221.2626627207234}, {"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": 534.2548047751282, "y": 226.34919850920983}, {"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.613636363636363, "title": "DesalinationPerformanceBaseClass\nconverters/water/desal/desalination_baseclass.py\n[Converter / Water]", "x": 626.0322282685992, "y": 328.1643689221109}, {"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.613636363636363, "title": "DesalinationCostBaseClass\nconverters/water/desal/desalination_baseclass.py\n[Converter / Water]", "x": 616.9229210183431, "y": 465.0320086942603}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleThermalNuclearReactorPerformanceModel", "label": "SimpleThermalNuclearReactorPerformanceModel", "shape": "dot", "size": 18.0, "title": "SimpleThermalNuclearReactorPerformanceModel\nconverters/nuclear/nuclear_plant_thermal.py\n[Converter / Nuclear]", "x": 512.3206085404639, "y": 553.9074145292539}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleThermalNuclearReactorCostModel", "label": "SimpleThermalNuclearReactorCostModel", "shape": "dot", "size": 18.0, "title": "SimpleThermalNuclearReactorCostModel\nconverters/nuclear/nuclear_plant_thermal.py\n[Converter / Nuclear]", "x": 375.6041621662576, "y": 540.776333887111}, {"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": 289.72673046704233, "y": 433.4874374482624}, {"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": 306.8741281863297, "y": 297.05928762629026}, {"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": 416.74847460623505, "y": 214.27020012481717}, {"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.227272727272727, "title": "ElectricArcFurnacePlantBasePerformanceComponent\nconverters/steel/steel_eaf_baseclass.py\n[Converter / Steel]", "x": 552.7544647907778, "y": 235.42419139417808}, {"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.227272727272727, "title": "ElectricArcFurnacePlantBaseCostComponent\nconverters/steel/steel_eaf_baseclass.py\n[Converter / Steel]", "x": 632.3699083735723, "y": 347.7820040605964}, {"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": 607.2231599473436, "y": 483.2347400979534}, {"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": 492.48489699917064, "y": 559.5959301053765}, {"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": 357.7140765452234, "y": 530.4742463227219}, {"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": 284.6833419347905, "y": 413.4596983075442}, {"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": 317.75826156365315, "y": 279.49725861115576}, {"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": 436.9437109752595, "y": 209.86898383420257}, {"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.613636363636363, "title": "SteelPerformanceBaseClass\nconverters/steel/steel_baseclass.py\n[Converter / Steel]", "x": 569.973336910705, "y": 246.87165526019766}, {"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.613636363636363, "title": "SteelCostBaseClass\nconverters/steel/steel_baseclass.py\n[Converter / Steel]", "x": 636.1311875166992, "y": 368.121370303941}, {"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": 595.2299503526839, "y": 500.09567185728525}, {"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": 472.0238642673158, "y": 562.7190565196809}, {"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": 341.2255441027676, "y": 517.9520668899632}, {"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": 282.1968321835285, "y": 392.8987492420887}, {"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": 330.7932030579897, "y": 263.39525133250794}, {"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": 457.58339794711105, "y": 208.01764890660942}, {"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.227272727272727, "title": "WindPerformanceBaseClass\nconverters/wind/wind_plant_baseclass.py\n[Converter / Wind]", "x": 585.6750354705925, "y": 260.40353726488974}, {"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": 637.3488139835139, "y": 388.81908299941085}, {"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": 581.2167016026175, "y": 515.3836299652434}, {"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": 451.28845055722815, "y": 563.3045578109605}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DieselGeneratorPerformanceModel", "label": "DieselGeneratorPerformanceModel", "shape": "dot", "size": 18.0, "title": "DieselGeneratorPerformanceModel\nconverters/diesel/diesel_generator.py\n[Converter / Other]", "x": 326.36439665077626, "y": 503.4728833040859}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DieselGeneratorCostModel", "label": "DieselGeneratorCostModel", "shape": "dot", "size": 18.0, "title": "DieselGeneratorCostModel\nconverters/diesel/diesel_generator.py\n[Converter / Other]", "x": 282.2416833610906, "y": 372.14563046714244}, {"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": 345.72295012386724, "y": 248.97361345115178}, {"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": 478.3345111099395, "y": 208.69083314963623}, {"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": 599.6448458758474, "y": 275.7684745858074}, {"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.227272727272727, "title": "MethanolPerformanceBaseClass\nconverters/methanol/methanol_baseclass.py\n[Converter / Methanol]", "x": 636.0496082590914, "y": 409.54873333996574}, {"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.227272727272727, "title": "MethanolCostBaseClass\nconverters/methanol/methanol_baseclass.py\n[Converter / Methanol]", "x": 565.4319969140861, "y": 528.8896856064326}, {"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.227272727272727, "title": "MethanolFinanceBaseClass\nconverters/methanol/methanol_baseclass.py\n[Converter / Methanol]", "x": 430.59948954604255, "y": 561.3819706125881}, {"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": 313.33362300250417, "y": 487.2839201377206}, {"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": 284.7846546861666, "y": 351.5163702951306}, {"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": 362.30054958972454, "y": 236.42923973416117}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SAFPerformanceModel", "label": "SAFPerformanceModel", "shape": "dot", "size": 18.0, "title": "SAFPerformanceModel\nconverters/saf/saf.py\n[Converter / Other]", "x": 498.88526377433726, "y": 211.85081087113818}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SAFCostModel", "label": "SAFCostModel", "shape": "dot", "size": 18.0, "title": "SAFCostModel\nconverters/saf/saf.py\n[Converter / Other]", "x": 611.6921199998598, "y": 292.7189548202646}, {"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": 632.2764005376438, "y": 430.00237090591287}, {"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": 548.124539047278, "y": 540.4295874649208}, {"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": 410.2613778604703, "y": 556.9997223374437}, {"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": 302.3109306777869, "y": 469.6355449187725}, {"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": 289.77132986418906, "y": 331.3119979379835}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NRRIIronMinePerformanceComponent", "label": "NRRIIronMinePerformanceComponent", "shape": "dot", "size": 18.0, "title": "NRRIIronMinePerformanceComponent\nconverters/iron/nrri_iron_mine.py\n[Converter / Iron]", "x": 380.27361905214866, "y": 225.9331507091904}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NRRIIronMineCostComponent", "label": "NRRIIronMineCostComponent", "shape": "dot", "size": 18.0, "title": "NRRIIronMineCostComponent\nconverters/iron/nrri_iron_mine.py\n[Converter / Iron]", "x": 518.9378834543795, "y": 217.4368666433462}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleIronMineCostComponent", "label": "SimpleIronMineCostComponent", "shape": "dot", "size": 18.0, "title": "SimpleIronMineCostComponent\nconverters/iron/simple_mine_cost_model.py\n[Converter / Iron]", "x": 621.6526632252719, "y": 311.0003300787261}, {"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": 626.0964493073058, "y": 449.885429225715}, {"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.227272727272727, "title": "IronReductionPlantBasePerformanceComponent\nconverters/iron/iron_dri_baseclass.py\n[Converter / Iron]", "x": 529.5514116569266, "y": 549.8461010723731}, {"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.227272727272727, "title": "IronReductionPlantBaseCostComponent\nconverters/iron/iron_dri_baseclass.py\n[Converter / Iron]", "x": 390.56547890417403, "y": 550.2318038314249}, {"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": 293.44646477134245, "y": 450.7873819579246}, {"borderWidth": 4.0, "color": {"background": "#D84315", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SimpleIronMinePerformanceComponent", "label": "SimpleIronMinePerformanceComponent", "shape": "dot", "size": 18.0, "title": "SimpleIronMinePerformanceComponent\nconverters/iron/simple_mine_perf_model.py\n[Converter / Iron]", "x": 297.12084188045804, "y": 311.82063849090315}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PaperMillPerformanceModel", "label": "PaperMillPerformanceModel", "shape": "dot", "size": 18.0, "title": "PaperMillPerformanceModel\nconverters/paper_mill/paper_mill.py\n[Converter / Other]", "x": 399.3799422897769, "y": 217.62827816994576}, {"borderWidth": 4.0, "color": {"background": "#F5C542", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PaperMillCostModel", "label": "PaperMillCostModel", "shape": "dot", "size": 18.0, "title": "PaperMillCostModel\nconverters/paper_mill/paper_mill.py\n[Converter / Other]", "x": 538.2075492132963, "y": 225.36115175025637}, {"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": 629.3908754472262, "y": 330.34778529072946}, {"borderWidth": 4.0, "color": {"background": "#4A90D9", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PySAMWavePerformanceModel", "label": "PySAMWavePerformanceModel", "shape": "dot", "size": 18.0, "title": "PySAMWavePerformanceModel\nconverters/water_power/wave_pysam.py\n[Converter / Water Power]", "x": 617.6046579663476, "y": 468.9164819349397}, {"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": 509.97999873474055, "y": 557.011070651295}, {"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": 371.7897143537561, "y": 541.1802181898352}, {"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": 286.8608300273903, "y": 431.00932337603496}, {"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_performance.py\n[Converter / Ammonia]", "x": 306.7240671952819, "y": 293.31658306734005}, {"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_cost.py\n[Converter / Ammonia]", "x": 419.3472058366151, "y": 211.62757521537523}, {"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": 556.4237389157623, "y": 235.50742272219358}, {"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": 634.8015495872717, "y": 350.48669443591376}, {"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": 606.9243705986685, "y": 486.8289241801672}, {"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": 489.68711134171815, "y": 561.8271231551735}, {"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": 354.19661578654984, "y": 529.9753740225614}, {"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": 282.64348392604273, "y": 410.5802217640633}, {"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": 318.44358384790195, "y": 276.0581281207602}, {"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": 439.894672799405, "y": 208.01250880268972}, {"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": 573.3325573876684, "y": 247.73130909845239}, {"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": 637.811277499706, "y": 371.1346053423175}, {"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": 594.2068288286885, "y": 503.3734312194306}, {"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": 468.95673774005417, "y": 564.2289707906459}, {"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": 338.0307664811753, "y": 516.7752953088027}, {"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": 280.85153918582165, "y": 389.78541351640604}, {"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": 332.1146849175527, "y": 260.28494351873167}, {"borderWidth": 4.0, "color": {"background": "#1B3A5C", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SONGFuelCellPerformanceModel", "label": "SONGFuelCellPerformanceModel", "shape": "dot", "size": 18.0, "title": "SONGFuelCellPerformanceModel\nconverters/natural_gas/SO_NG_fuel_cell.py\n[Converter / Natural Gas]", "x": 460.7358541762508, "y": 206.8319687332098}, {"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": 158.18890660015796, "y": -590.8846518073249}, {"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": 19.84090909090909, "title": "StoragePerformanceBase\nstorage/storage_baseclass.py\n[Storage / General]", "x": 185.7035915937763, "y": -506.9539891720807}, {"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": 100.15937252258013, "y": -452.9434945875972}, {"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": -5.314060157717023, "y": -490.578369495479}, {"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": -50.3471246928698, "y": -599.9209692099142}, {"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.454545454545453, "title": "HydrogenStorageBaseCostModel\nstorage/hydrogen/h2_storage_cost.py\n[Storage / General]", "x": 0.2595708828436756, "y": -711.2162485612855}, {"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": 118.36374191734843, "y": -752.263318438731}, {"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": 231.57560214546655, "y": -694.5701970778535}, {"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": 269.0576031140179, "y": -571.5374838021429}, {"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": 206.02691665285738, "y": -458.02465139897663}, {"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": 79.6655372655062, "y": -424.13281642189304}, {"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": -33.29135928329272, "y": -491.74118761217125}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "BaseDowntime", "label": "BaseDowntime", "shape": "box", "size": 19.84090909090909, "title": "BaseDowntime\nreliability/models.py\n[Reliability / General]", "x": -509.815572471545, "y": -205.2120859954012}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "BaseReliability", "label": "BaseReliability", "shape": "box", "size": 19.227272727272727, "title": "BaseReliability\nreliability/models.py\n[Reliability / General]", "x": -482.30088747792666, "y": -121.28142336015704}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "FixedDowntime", "label": "FixedDowntime", "shape": "box", "size": 18.0, "title": "FixedDowntime\nreliability/models.py\n[Reliability / General]", "x": -567.8451065491229, "y": -67.2709287756735}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "UniformDowntime", "label": "UniformDowntime", "shape": "box", "size": 18.0, "title": "UniformDowntime\nreliability/models.py\n[Reliability / General]", "x": -673.31853922942, "y": -104.90580368355533}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "LogNormalDowntime", "label": "LogNormalDowntime", "shape": "box", "size": 18.0, "title": "LogNormalDowntime\nreliability/models.py\n[Reliability / General]", "x": -718.3516037645728, "y": -214.2484033979906}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WeibullReliability", "label": "WeibullReliability", "shape": "box", "size": 18.0, "title": "WeibullReliability\nreliability/models.py\n[Reliability / General]", "x": -667.7449081888593, "y": -325.5436827493618}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "FixedIntervalReliability", "label": "FixedIntervalReliability", "shape": "box", "size": 18.0, "title": "FixedIntervalReliability\nreliability/models.py\n[Reliability / General]", "x": -549.6407371543546, "y": -366.59075262680733}, {"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.613636363636363, "title": "FeedstockCostModel\nfeedstocks/feedstocks.py\n[Other / Other]", "x": -509.815572471545, "y": 205.21208599540134}, {"borderWidth": 3.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_price.py\n[Other / Other]", "x": -482.30088747792666, "y": 289.1427486306455}, {"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": -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": "ProFastBase", "label": "ProFastBase", "shape": "star", "size": 19.227272727272727, "title": "ProFastBase\nfinances/profast_baseclass.py\n[Finance / General]", "x": -218.48531500638154, "y": 603.5459049059074}, {"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": -304.02953407757775, "y": 657.556399490391}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "ResourceBaseH5Model", "label": "ResourceBaseH5Model", "shape": "triangle", "size": 19.227272727272727, "title": "ResourceBaseH5Model\nresource/resource_hpc_baseclass.py\n[Resource / General]", "x": -246.00000000000028, "y": -519.615242270663}, {"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": 20.454545454545453, "title": "ResourceBaseAPIModel\nresource/resource_baseclass.py\n[Resource / General]", "x": -218.48531500638194, "y": -435.6845796354188}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SolarResourceBase", "label": "SolarResourceBase", "shape": "triangle", "size": 19.84090909090909, "title": "SolarResourceBase\nresource/solar/solar_resource_baseclass.py\n[Resource / General]", "x": -304.02953407757815, "y": -381.6740850509353}, {"borderWidth": 3.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": -409.5029667578753, "y": -419.3089599588171}, {"borderWidth": 3.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": -454.53603129302803, "y": -528.6515596732523}, {"borderWidth": 4.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": -403.9293357173146, "y": -639.9468390246236}, {"borderWidth": 4.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": 23.522727272727273, "title": "NLRDeveloperAPISolarResourceBase\nresource/solar/nlr_developer_api_baseclass.py\n[Resource / General]", "x": -285.8251646828098, "y": -680.993908902069}, {"borderWidth": 3.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": -172.61330445469173, "y": -623.3007875411917}, {"borderWidth": 3.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": -135.13130348614035, "y": -500.268074265481}, {"borderWidth": 3.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": -198.16198994730087, "y": -386.75524186231473}, {"borderWidth": 3.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": -324.52336933465205, "y": -352.86340688523114}, {"borderWidth": 3.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": -437.480265883451, "y": -420.47177807550935}, {"borderWidth": 3.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": -467.69991636370764, "y": -549.3044340881769}, {"borderWidth": 3.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": -395.9283080200564, "y": -661.1735684843097}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NSRDBDatasetH5", "label": "NSRDBDatasetH5", "shape": "triangle", "size": 18.0, "title": "NSRDBDatasetH5\nresource/solar/nlr_nsrdb_dataset_model.py\n[Resource / General]", "x": -265.1643653687024, "y": -687.6421405930289}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WTKHRRRMETDatasetH5", "label": "WTKHRRRMETDatasetH5", "shape": "triangle", "size": 18.0, "title": "WTKHRRRMETDatasetH5\nresource/wind/nlr_hrrr_met_toolkit_dataset_model.py\n[Resource / General]", "x": -154.75163735317008, "y": -611.9728557814879}, {"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_models.py\n[Resource / General]", "x": -132.10054424963116, "y": -479.65937010313854}, {"borderWidth": 3.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "HRRRMETToolkitWindAPI", "label": "HRRRMETToolkitWindAPI", "shape": "triangle", "size": 18.0, "title": "HRRRMETToolkitWindAPI\nresource/wind/nlr_developer_wtk_api_models.py\n[Resource / General]", "x": -211.4752093056058, "y": -370.98024830048905}, {"borderWidth": 4.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": -345.04412480884827, "y": -352.2006782731198}, {"borderWidth": 4.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "NLRDeveloperAPIWindResourceBase", "label": "NLRDeveloperAPIWindResourceBase", "shape": "triangle", "size": 19.227272727272727, "title": "NLRDeveloperAPIWindResourceBase\nresource/wind/nlr_developer_wtk_api_baseclass.py\n[Resource / General]", "x": -451.76811394902506, "y": -435.12779001675824}, {"borderWidth": 5.0, "color": {"background": "#555555", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "WindResourceBase", "label": "WindResourceBase", "shape": "triangle", "size": 19.84090909090909, "title": "WindResourceBase\nresource/wind/wind_resource_baseclass.py\n[Resource / General]", "x": -466.6327495762692, "y": -569.7104193703843}, {"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": -567.8451065491229, "y": 343.153243215129}, {"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.227272727272727, "title": "DemandComponentBase\ndemand/demand_baseclass.py\n[Other / Other]", "x": -673.31853922942, "y": 305.5183683072472}, {"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": -718.3516037645728, "y": 196.17576859281195}, {"borderWidth": 5.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "OpenLoopControlBase", "label": "OpenLoopControlBase", "shape": "hexagon", "size": 20.454545454545453, "title": "OpenLoopControlBase\ncontrol/control_strategies/openloop_control_baseclass.py\n[Control / General]", "x": 654.0, "y": 0.0}, {"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.84090909090909, "title": "PyomoStorageControllerBaseClass\ncontrol/control_strategies/pyomo_storage_controller_baseclass.py\n[Storage / General]", "x": -63.51100976354941, "y": -620.5738436248388}, {"borderWidth": 5.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "SystemLevelControlBase", "label": "SystemLevelControlBase", "shape": "hexagon", "size": 19.84090909090909, "title": "SystemLevelControlBase\ncontrol/control_strategies/system_level/system_level_control_baseclass.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": "ProfitMaximizationControl", "label": "ProfitMaximizationControl", "shape": "hexagon", "size": 18.0, "title": "ProfitMaximizationControl\ncontrol/control_strategies/system_level/profit_maximization_control.py\n[Control / General]", "x": 595.9704659224221, "y": 137.9411572197277}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "DemandFollowingControl", "label": "DemandFollowingControl", "shape": "hexagon", "size": 18.0, "title": "DemandFollowingControl\ncontrol/control_strategies/system_level/demand_following_control.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": "CostMinimizationControl", "label": "CostMinimizationControl", "shape": "hexagon", "size": 18.0, "title": "CostMinimizationControl\ncontrol/control_strategies/system_level/cost_minimization_control.py\n[Control / General]", "x": 445.4639687069722, "y": -9.036317402589397}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PLMHeuristicOpenLoopConverterController", "label": "PLMHeuristicOpenLoopConverterController", "shape": "dot", "size": 18.0, "title": "PLMHeuristicOpenLoopConverterController\ncontrol/control_strategies/converters/plm_openloop_converter_controller.py\n[Converter / Other]", "x": 588.699420203887, "y": 261.8615302828799}, {"borderWidth": 4.0, "color": {"background": "#00ACC1", "border": "#555555", "highlight": {"background": "#FF6B6B", "border": "#FF0000"}, "hover": {"background": "#FFD700", "border": "#FF8C00"}}, "font": {"color": "#333333"}, "id": "PeakLoadManagementOptimizedStorageController", "label": "PeakLoadManagementOptimizedStorageController", "shape": "diamond", "size": 18.0, "title": "PeakLoadManagementOptimizedStorageController\ncontrol/control_strategies/storage/plm_optimized_storage_controller.py\n[Storage / General]", "x": 8.260598580101856, "y": -732.4429780209716}, {"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": 139.02454123145583, "y": -758.9115501296908}, {"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": 249.4372692469882, "y": -683.2422653181497}, {"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": 272.0883623505271, "y": -550.9287796398005}, {"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": 192.71369729455247, "y": -442.24965783715095}, {"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": 59.144781791309995, "y": -423.4700878097817}, {"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.227272727272727, "title": "PyomoRuleBaseClass\ncontrol/control_rules/pyomo_rule_baseclass.py\n[Control / General]", "x": 496.0706642826857, "y": -120.3315967539606}, {"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": 638.3794332090715, "y": 392.00407844682536}, {"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": -47.5792073488668, "y": -506.39719955342014}]); + edges = new vis.DataSet([{"arrows": "to", "from": "SiteBaseComponent", "to": "SiteLocationComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "ResizeablePerformanceModelBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SimpleCycleTurbinePerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SolarPerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "LinearH2FuelCellPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SteamMethaneReformerPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "PEMH2FuelCellPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "GeoH2SubsurfacePerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "GeoH2SurfacePerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "DesalinationPerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SimpleThermalNuclearReactorPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "QuinnNuclearPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "ElectricArcFurnacePlantBasePerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "CMUElectricArcFurnaceScrapOnlyPerformanceComponent"}, {"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": "DieselGeneratorPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "MethanolPerformanceBaseClass"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SAFPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "HumbertEwinPerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "NRRIIronMinePerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "IronReductionPlantBasePerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "SimpleIronMinePerformanceComponent"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "PaperMillPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "PySAMTidalPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "PySAMWavePerformanceModel"}, {"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": "SONGFuelCellPerformanceModel"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "StoragePerformanceBase"}, {"arrows": "to", "from": "PerformanceModelBaseClass", "to": "DemandComponentBase"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "LinearMassTransportCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "LinearDistanceCostModel"}, {"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": "SimpleThermalNuclearReactorCostModel"}, {"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": "DieselGeneratorCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "MethanolCostBaseClass"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SAFCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "NRRIIronMineCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "SimpleIronMineCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "IronTransportCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "IronReductionPlantBaseCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "HumbertStinnEwinCostComponent"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "PaperMillCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "PySAMMarineCostModel"}, {"arrows": "to", "from": "CostModelBaseClass", "to": "RunOfRiverHydroCostModel"}, {"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": "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": "ResizeablePerformanceModelBaseClass", "to": "ElectrolyzerPerformanceBaseClass"}, {"arrows": "to", "from": "ResizeablePerformanceModelBaseClass", "to": "AmmoniaSynLoopPerformanceModel"}, {"arrows": "to", "from": "CacheBaseClass", "to": "FlorisWindPlantPerformanceModel"}, {"arrows": "to", "from": "SolarPerformanceBaseClass", "to": "PYSAMSolarPlantPerformanceModel"}, {"arrows": "to", "from": "ElectrolyzerPerformanceBaseClass", "to": "ECOElectrolyzerPerformanceModel"}, {"arrows": "to", "from": "ElectrolyzerPerformanceBaseClass", "to": "HTSEPerformanceModel"}, {"arrows": "to", "from": "ElectrolyzerCostBaseClass", "to": "SingliticoCostModel"}, {"arrows": "to", "from": "ElectrolyzerCostBaseClass", "to": "BasicElectrolyzerCostModel"}, {"arrows": "to", "from": "ElectrolyzerCostBaseClass", "to": "CustomElectrolyzerCostModel"}, {"arrows": "to", "from": "ElectrolyzerCostBaseClass", "to": "HTSECostModel"}, {"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": "BaseDowntime", "to": "FixedDowntime"}, {"arrows": "to", "from": "BaseDowntime", "to": "UniformDowntime"}, {"arrows": "to", "from": "BaseDowntime", "to": "LogNormalDowntime"}, {"arrows": "to", "from": "BaseReliability", "to": "WeibullReliability"}, {"arrows": "to", "from": "BaseReliability", "to": "FixedIntervalReliability"}, {"arrows": "to", "from": "FeedstockCostModel", "to": "EIANaturalGasFeedstockCostModel"}, {"arrows": "to", "from": "ProFastBase", "to": "ProFastLCO"}, {"arrows": "to", "from": "ProFastBase", "to": "ProFastNPV"}, {"arrows": "to", "from": "ResourceBaseH5Model", "to": "NSRDBDatasetH5"}, {"arrows": "to", "from": "ResourceBaseH5Model", "to": "WTKHRRRMETDatasetH5"}, {"arrows": "to", "from": "ResourceBaseAPIModel", "to": "OpenMeteoHistoricalSolarResource"}, {"arrows": "to", "from": "ResourceBaseAPIModel", "to": "NLRDeveloperAPISolarResourceBase"}, {"arrows": "to", "from": "ResourceBaseAPIModel", "to": "OpenMeteoHistoricalWindResource"}, {"arrows": "to", "from": "ResourceBaseAPIModel", "to": "NLRDeveloperAPIWindResourceBase"}, {"arrows": "to", "from": "SolarResourceBase", "to": "OpenMeteoHistoricalSolarResource"}, {"arrows": "to", "from": "SolarResourceBase", "to": "NLRDeveloperAPISolarResourceBase"}, {"arrows": "to", "from": "SolarResourceBase", "to": "NSRDBDatasetH5"}, {"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": "NLRDeveloperAPIWindResourceBase", "to": "WTKNLRDeveloperAPIWindResource"}, {"arrows": "to", "from": "NLRDeveloperAPIWindResourceBase", "to": "HRRRMETToolkitWindAPI"}, {"arrows": "to", "from": "WindResourceBase", "to": "WTKHRRRMETDatasetH5"}, {"arrows": "to", "from": "WindResourceBase", "to": "OpenMeteoHistoricalWindResource"}, {"arrows": "to", "from": "WindResourceBase", "to": "NLRDeveloperAPIWindResourceBase"}, {"arrows": "to", "from": "DemandComponentBase", "to": "GenericDemandComponent"}, {"arrows": "to", "from": "DemandComponentBase", "to": "FlexibleDemandComponent"}, {"arrows": "to", "from": "OpenLoopControlBase", "to": "PLMHeuristicOpenLoopConverterController"}, {"arrows": "to", "from": "OpenLoopControlBase", "to": "DemandOpenLoopStorageController"}, {"arrows": "to", "from": "OpenLoopControlBase", "to": "SimpleStorageOpenLoopController"}, {"arrows": "to", "from": "OpenLoopControlBase", "to": "PeakLoadManagementHeuristicOpenLoopStorageController"}, {"arrows": "to", "from": "PyomoStorageControllerBaseClass", "to": "PeakLoadManagementOptimizedStorageController"}, {"arrows": "to", "from": "PyomoStorageControllerBaseClass", "to": "HeuristicLoadFollowingStorageController"}, {"arrows": "to", "from": "PyomoStorageControllerBaseClass", "to": "OptimizedDispatchStorageController"}, {"arrows": "to", "from": "SystemLevelControlBase", "to": "ProfitMaximizationControl"}, {"arrows": "to", "from": "SystemLevelControlBase", "to": "DemandFollowingControl"}, {"arrows": "to", "from": "SystemLevelControlBase", "to": "CostMinimizationControl"}, {"arrows": "to", "from": "PyomoRuleBaseClass", "to": "PyomoDispatchGenericConverter"}, {"arrows": "to", "from": "PyomoRuleBaseClass", "to": "PyomoRuleStorageBaseclass"}]); nodeColors = {}; allNodes = nodes.get({ returnType: "Object" }); diff --git a/docs/resource/resource_index.md b/docs/resource/resource_index.md index 3ccf0b297..bb81e4edf 100644 --- a/docs/resource/resource_index.md +++ b/docs/resource/resource_index.md @@ -27,6 +27,12 @@ If running a simulation that is longer than 1 year, then there are a few options 2. Use resource data from a list of filenames. This option is enabled when `resource_filename` is a list. The list must be the same length as the number of years needed for the simulation. The filenames provided as `resource_filename` are **not used if the site changes from the site defined in the configuration file**. This can be used with `resource_year_order`, where `resource_year_order` is used if the site changes. If `resource_year_order` is not specified, the year order is inferred from the data in the provided files under the 'year' column (unless using a TMY dataset, in which case the year order is inferred from the filename). Supplying `resource_year_order` alongside `resource_filename` avoids inferring the years and makes the replacement years explicit. The inferred or provided `resource_year_order` is used instead of `resource_year`. 3. Use a specified order of resource years. This option is enabled when `resource_year_order` is a list of resource years and `resource_filename` is an empty string (or not provided). The list must be the same length as the number of years needed for the simulation. The `resource_year_order` is used instead of `resource_year`. As mentioned above, this can be used with the `resource_filename` option. +## Resampling resource data + +API resource data may have a different native timestep from the simulation, but H2I does not resample it by default. You may explicitly choose a pandas interpolation method for upsampling or a pandas aggregation method for downsampling in the resource's `resource_parameters`. + +Set `upsample_method` or `downsample_method` in `resource_parameters` to explicitly select the pandas operation. If the data timestep and simulation timestep already match, neither setting is needed. When resampling is requested, H2I emits a warning naming the source timestep, target timestep, direction, and method. If a mismatch requires resampling but no corresponding method is set, H2I raises an error. See {py:func}`h2integrate.resource.utilities.time_tools.resample_resource_data_to_dt` for method details. + ## Setting resource data for a technology diff --git a/h2integrate/resource/resource_baseclass.py b/h2integrate/resource/resource_baseclass.py index bdd905b97..2f3ef6b3b 100644 --- a/h2integrate/resource/resource_baseclass.py +++ b/h2integrate/resource/resource_baseclass.py @@ -41,6 +41,8 @@ class ResourceBaseAPIConfig(BaseConfig): load resource files from. Defaults to "". - **resource_filename** (*str*, optional): Filename to save resource data to or load resource data from. Defaults to None. + - **resource_year_setting** (*str*, optional): How resource years are selected for + API datasets. Options include ``start_year``, ``year_order``, and ``filenames``. - **valid_intervals** (*list[int]*): time interval(s) in minutes that resource data can be downloaded in. @@ -66,6 +68,12 @@ class ResourceBaseAPIConfig(BaseConfig): resource_year_order (list, optional): Only used running a simulation requiring multiple resource years. List of resource years in-order, such as [2012, 2011, 2013]. Defaults to None. + upsample_method (str | None, optional): Pandas interpolation method to use when the + simulation timestep is finer than the resource data timestep. Required only when + upsampling is needed; otherwise defaults to None and no automatic resampling occurs. + downsample_method (str | None, optional): Pandas resampling aggregation to use when the + simulation timestep is coarser than the resource data timestep. Required only when + downsampling is needed; otherwise defaults to None and no automatic resampling occurs. Attributes: dataset_desc (str): description of the dataset, used in file naming. From ec554c3398c0cda2a08b69427d5c0fc77bebad5d Mon Sep 17 00:00:00 2001 From: Jared Thomas Date: Thu, 8 Oct 2026 13:04:11 -0600 Subject: [PATCH 4/4] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 584748c65..884ff0684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ - Updates to all GH Actions, pre-commit, isort, and ruff versioning. [PR 904](https://github.com/NatLabRockies/H2Integrate/pull/904) - Update API resource models to be able to be able to handle nonannual simulations. [PR 897](https://github.com/NatLabRockies/H2Integrate/pull/897) - Move PySAM model instantiation for wind and solar performance models to the `compute()` method, and validate recalculated wind power curves. [PR 909](https://github.com/NatLabRockies/H2Integrate/pull/909) +- Add data resampling capabilities via pandas methods. [PR 893](https://github.com/NatLabRockies/H2Integrate/pull/893) ## 0.9 [August 10, 2026]