Standardized naming conventions
Have a standardized and documented naming convention for model inputs and outputs given that a lot of logic relies on strings. This would include naming outputs in a specific way that indicate whether the output is a timeseries profile, an aggregated value, or a lifetime estimated value. Also would include naming conventions for not just the primary flow variables, but also the secondary inputs/outputs that may not be 'primary'. For example, the electrolyzer model has a primary timeseries input of an electricity profile and primary timeseries output of hydrogen. The electrolyzer consumes electricity (although may not consume all the electricity input) and consumes water. The consumption of water is not output from the model and not included in any financial calculations. The electrolyzer model could output results for the plant lifetime but does not at the moment.
Also - have a more standardized/automated method of understanding what technologies are connected to what commodities.
Proposed solution
General nomenclature:
- commodity: commodities are produced or stored. A converter produces some commodity, a storage component stores some commodity
- feedstock: feedstock(s) is/are consumed by a technology. A converter may be input one of many feedstock(s) to produce its commodity. This could also refer to ancillary equipment needs. A storage component may have a feedstock if it requires some input other than the storage commodity to operate.
- resource: resource(s) is/are used by a technology. Such as a natural resource, refers to things that would be in
plant_config['site'].
Standardized naming convention
Standardized model inputs/outputs could look like introducing a base class for different technology types (converters, storage, transporters, etc). At the very least, this should include documentation. Below is a draft of some possible naming conventions:
Converter Performance Models (commodity to commodity)
- inputs:
- outputs:
- timeseries (arrays of length
n_timesteps):
{commodity}_out: timeseries of commodity production
{feedstock_a}_consumption_profile: timeseries of primary feedstock consumed
{feedstock_b}_consumption_profile: if used, timeseries of secondary feedstock consumed (only required for hourly prices for secondary feedstock)
- totals (scalars):
max_{commodity}_production_possible: maximum amount of commodity that could be produced over the simulation if the converter was running at its rated capacity for the entire simulation. Should be representative of the converter's rated capacity.
total_{commodity}_produced: sum of {commodity}_out
capacity_factor: equal to total_{commodity}_produced/max_{commodity}_production_possible. Should be a value between 0 and 1.
total_{primary_feedstock}_consumed: sum of {primary_feedstock}_consumption_profile
total_{secondary_feedstock}_consumed: sum of {secondary_feedstock}_consumption_profile
- lifetime (array of length
plant_life):
annual_{commodity_out}_produced_per_year: can default to [total_{commodity}_produced*(8760*3600)/(n_timesteps*dt)]*plant_life if simulation is not completed for a whole year or [total_{commodity}_produced]*plant_life if simulation is equal to one year.
annual_{primary_feedstock}_consumed_per_year (if relevant)
annual_{secondary_feedstock}_consumed_per_year (if relevant)
time_until_replacement: can default to plant_life. Wouldn't be included in financials unless replacement_cost_percent is included in tech-specific finance parameters under capex.
storage
- inputs:
- possible design variables:
{technology}_charge_rate AND {technology}_{capacity}
- timeseries:
{commodity}_in: available input to use for charging
{commodity}_demand: reference signal
- annual:
- lifetime:
- outputs:
- timeseries:
{commodity}_out
{commodity}_excess
{commodity}_missed_load
{technology}_SOC
{technology}_discharged
{technology}_charged
- annual:
- lifetime:
Standardized method of connecting technologies and commodities or dealing with models with tech-specific finance models
Add the following dictionaries to supported_models.py:
commodity_to_model_kwargs = {
"hydrogen": ["electrolyzer", "geoh2", "h2_storage", "wombat", "hydrogen_tank"],
"steel": ["steel"],
"methanol": ["methanol", "smr"],
"ammonia": ["ammonia"],
"nitrogen": ["air_separator", "asu"],
"co2": ["doc", "direct_ocean_capture"],
"electricity": ["wind", "solar", "pv", "river", "hydro", "hopp"],
"water": ["desal", "desalination"],
}
commodity_to_common_units = {
"hydrogen": "kg",
"steel": "t", # metric ton
"ammonia": "",
"nitrogen": "kg",
"co2": "kg",
"electricity": "kW*h",
"water": "kg",
}
independent_financial_models = [
"steel_cost",
"stimulated_geoh2_financial",
"natural_geoh2_financial",
"methanol_plant_financial",
]
Including some new utility/tool functions like this:
from h2integrate.core.supported_models import commodity_to_model_kwargs
def make_model_kwarg_to_commodity():
model_kwargs_to_commodity = {}
for commodity, tech_kwargs in commodity_to_model_kwargs.items():
model_kwargs_to_commodity.update({tech_kw: commodity for tech_kw in tech_kwargs})
return model_kwargs_to_commodity
def get_commodities_for_tech_keywords(finance_group_tech_config: dict):
model_kwargs_to_commodity = make_model_kwarg_to_commodity()
commodity_types = []
for tech_name in finance_group_tech_config.keys():
commodity = [v for k, v in model_kwargs_to_commodity.items() if tech_name.lower() in k]
if len(commodity) > 0:
commodity_types.append(commodity[0])
else:
msg = (
f"Technology {tech_name} name has unrecognized commodity type."
"Please check `commodity_to_model_kwargs` in "
"h2integrate/core/supported_models.py to find valid technology keywords."
)
raise ValueError(msg)
unique_commodity_types = list(set(commodity_types))
return unique_commodity_types
Then adjusting the logic within h2integrate/core/h2integrate_model.py to not rely on technology names in tech_config (ex: tech_config.keys()) but to instead look at the strings used for the models (ex: tech_config['electrolyzer']['cost_model']['model']). For example, in create_financial_model():
for group_id, tech_configs in financial_groups.items():
commodity_types = get_commodities_for_tech_keywords(tech_configs)
could replace
for group_id, tech_configs in financial_groups.items():
if "steel" in tech_configs:
commodity_types.append("steel")
if "electrolyzer" in tech_configs:
commodity_types.append("hydrogen")
...
We could also replace some hard-coded logic about either individual finance models or coupled cost/finance models like this:
if any(c in commodity_types for c in ("steel", "methanol")):
continue
# GeoH2 provides own financials
if "geoh2" in tech_configs:
continue
to be this:
# Check for individual financial model
coupled_cost_finance = any(
tech_info.get("cost_model", {}).get("model", False) in independent_financial_models
for tech, tech_info in tech_configs.items()
)
individual_finance = any(
tech_info.get("financial_model", {}).get("model", False)
in independent_financial_models
for tech, tech_info in tech_configs.items()
)
if coupled_cost_finance or individual_finance:
continue
Alternatives considered
Additional context
This was partially discussed/addressed in PR #198.
Related to Issue #231
Standardized naming conventions
Have a standardized and documented naming convention for model inputs and outputs given that a lot of logic relies on strings. This would include naming outputs in a specific way that indicate whether the output is a timeseries profile, an aggregated value, or a lifetime estimated value. Also would include naming conventions for not just the primary flow variables, but also the secondary inputs/outputs that may not be 'primary'. For example, the electrolyzer model has a primary timeseries input of an electricity profile and primary timeseries output of hydrogen. The electrolyzer consumes electricity (although may not consume all the electricity input) and consumes water. The consumption of water is not output from the model and not included in any financial calculations. The electrolyzer model could output results for the plant lifetime but does not at the moment.
Also - have a more standardized/automated method of understanding what technologies are connected to what commodities.
Proposed solution
General nomenclature:
plant_config['site'].Standardized naming convention
Standardized model inputs/outputs could look like introducing a base class for different technology types (converters, storage, transporters, etc). At the very least, this should include documentation. Below is a draft of some possible naming conventions:
Converter Performance Models (commodity to commodity)
possible design variables:
{technology}_capacityORn_{technology_modules}size_for_demand?timeseries:
{feedstock_a}_inOR{feedstock_a}_in_profile{feedstock_b}_inOR{feedstock_b}_in_profile{commodity}_demandperhaps? OR{commodity}_demand_profileannual:
lifetime: ?
n_timesteps):{commodity}_out: timeseries of commodity production{feedstock_a}_consumption_profile: timeseries of primary feedstock consumed{feedstock_b}_consumption_profile: if used, timeseries of secondary feedstock consumed (only required for hourly prices for secondary feedstock)max_{commodity}_production_possible: maximum amount of commodity that could be produced over the simulation if the converter was running at its rated capacity for the entire simulation. Should be representative of the converter's rated capacity.total_{commodity}_produced: sum of{commodity}_outcapacity_factor: equal tototal_{commodity}_produced/max_{commodity}_production_possible. Should be a value between 0 and 1.total_{primary_feedstock}_consumed: sum of{primary_feedstock}_consumption_profiletotal_{secondary_feedstock}_consumed: sum of{secondary_feedstock}_consumption_profileplant_life):annual_{commodity_out}_produced_per_year: can default to[total_{commodity}_produced*(8760*3600)/(n_timesteps*dt)]*plant_lifeif simulation is not completed for a whole year or[total_{commodity}_produced]*plant_lifeif simulation is equal to one year.annual_{primary_feedstock}_consumed_per_year(if relevant)annual_{secondary_feedstock}_consumed_per_year(if relevant)time_until_replacement: can default toplant_life. Wouldn't be included in financials unlessreplacement_cost_percentis included in tech-specific finance parameters undercapex.storage
{technology}_charge_rateAND{technology}_{capacity}{commodity}_in: available input to use for charging{commodity}_demand: reference signal{commodity}_out{commodity}_excess{commodity}_missed_load{technology}_SOC{technology}_discharged{technology}_chargedStandardized method of connecting technologies and commodities or dealing with models with tech-specific finance models
Add the following dictionaries to
supported_models.py:Including some new utility/tool functions like this:
Then adjusting the logic within
h2integrate/core/h2integrate_model.pyto not rely on technology names intech_config(ex:tech_config.keys()) but to instead look at the strings used for the models (ex:tech_config['electrolyzer']['cost_model']['model']). For example, increate_financial_model():could replace
We could also replace some hard-coded logic about either individual finance models or coupled cost/finance models like this:
to be this:
Alternatives considered
Additional context
This was partially discussed/addressed in PR #198.
Related to Issue #231