Skip to content

Commit

Permalink
Merge pull request #952 from rl-institut/fix/chp_component
Browse files Browse the repository at this point in the history
Fix/chp component
  • Loading branch information
Bachibouzouk authored Feb 3, 2023
2 parents 6effae2 + 620cfc4 commit 84375e4
Show file tree
Hide file tree
Showing 13 changed files with 683 additions and 32 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Here is a template for new release sections
- `ready_sankey_diagram` in `F2_autoreport.py` to add sankey diagram to output and report (#935)
- "maximum_add_cap" to the keys returned to EPA after simulating got production assets (#939)
- `PARAMETER_DOC` in `utils` can be used to get information about a parameter directly from the csv file `docs/MVS_parameters_list.csv` (#956)
- Now oemof-solph ExtractionTurbine CHP component can be simulated (only tested from the json input) (#952)

### Changed
- `F0_output.parse_simulation_log`, so that `SIMULATION_RESULTS` are not overwritten anymore (#901)
- `input_template/csv_elements`: Added missing parameters and generalized units (#904)
Expand All @@ -52,6 +54,8 @@ Here is a template for new release sections
- The if statement for adapting `MAXIMUM_CAP` for non-dispatchable production assets is now based on the value of `DISPATCHABILITY` and not on the existence of the key `FILENAME` in the `asset_dict` (#939)
- The default values for the constraints are now located in `src/constants.py` under the variable `DEFAULT_CONSTRAINT_VALUES` (#953)
- When a required parameter is missing its default value, defined in `docs/MVS_parameters_list.csv`, is used instead of raising an error (#956)
- If an asset has 2 output busses, the output flow of only one of the busses is provided using its name `asset[FLOW][bus_name]` (#952)

### Removed
- Input timeseries is now not returned to epa in `utils.data_parser.py` (#936)

Expand Down
1 change: 1 addition & 0 deletions docs/MVS_parameters_list.csv
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ None,"Name of a csv file containing the properties of a storage component","stor
None,"The type of the component.","demand","*demand*","str",None,"type_asset","typeasset-label","hidden",
None,"Input the type of OEMOF component. For example, a PV plant would be a source, a solar inverter would be a transformer, etc. The `type_oemof` will later on be determined through the EPA.","sink","*sink* or *source* or one of the other component classes of OEMOF.","str",None,"type_oemof","typeoemof-label","consumption;conversion;production;providers;storage",
None,"Unit associated with the capacity of the component.","Storage could have units like kW or kWh, transformer station could have kVA, and so on.","Appropriate scientific unit","str","NA","unit","unit-label","consumption;conversion;production;providers;storage_csv",
0,Power loss index for CHPs,0.6,Between 0 and 1,numeric,factor,beta,beta-label,conversion,
2 changes: 2 additions & 0 deletions src/multi_vector_simulator/C1_verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
RENEWABLE_SHARE_DSO,
DSO_PEAK_DEMAND_SUFFIX,
DSO_FEEDIN_CAP,
BETA,
)

# Necessary for check_for_label_duplicates()
Expand Down Expand Up @@ -608,6 +609,7 @@ def all_valid_intervals(name, value, title):
DISCOUNTFACTOR: [0, 1],
PROJECT_DURATION: ["largerzero", "any"],
TAX: [0, 1],
BETA: [0, 1],
}

if name in valid_type_int:
Expand Down
12 changes: 11 additions & 1 deletion src/multi_vector_simulator/D0_modelling_and_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@
)
from multi_vector_simulator.utils.constants_json_strings import (
ENERGY_BUSSES,
ENERGY_VECTOR,
OEMOF_ASSET_TYPE,
ACCEPTED_ASSETS_FOR_ASSET_GROUPS,
OEMOF_GEN_STORAGE,
OEMOF_SINK,
OEMOF_SOURCE,
OEMOF_TRANSFORMER,
OEMOF_BUSSES,
OEMOF_ExtractionTurbineCHP,
VALUE,
SIMULATION_SETTINGS,
LABEL,
Expand Down Expand Up @@ -150,6 +152,7 @@ def initialize(dict_values):
OEMOF_SOURCE: {},
OEMOF_TRANSFORMER: {},
OEMOF_GEN_STORAGE: {},
OEMOF_ExtractionTurbineCHP: {},
}

return model, dict_model
Expand All @@ -176,7 +179,12 @@ def adding_assets_to_energysystem_model(dict_values, dict_model, model, **kwargs

# Busses have to be defined first
for bus in dict_values[ENERGY_BUSSES]:
D1.bus(model, dict_values[ENERGY_BUSSES][bus][LABEL], **dict_model)
D1.bus(
model,
dict_values[ENERGY_BUSSES][bus][LABEL],
energy_vector=dict_values[ENERGY_BUSSES][bus][ENERGY_VECTOR],
**dict_model,
)

# Adding step by step all assets defined within the asset groups
for asset_group in ACCEPTED_ASSETS_FOR_ASSET_GROUPS:
Expand All @@ -190,6 +198,8 @@ def adding_assets_to_energysystem_model(dict_values, dict_model, model, **kwargs
D1.transformer(
model, dict_values[asset_group][asset], **dict_model
)
elif type == OEMOF_ExtractionTurbineCHP:
D1.chp(model, dict_values[asset_group][asset], **dict_model)
elif type == OEMOF_SINK:
D1.sink(
model, dict_values[asset_group][asset], **dict_model
Expand Down
236 changes: 235 additions & 1 deletion src/multi_vector_simulator/D1_model_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@

from multi_vector_simulator.utils.constants_json_strings import (
VALUE,
UNIT,
LABEL,
DISPATCH_PRICE,
AVAILABILITY_DISPATCH,
OPTIMIZE_CAP,
INSTALLED_CAP,
INSTALLED_CAP_NORMALIZED,
EFFICIENCY,
ENERGY_VECTOR,
INPUT_POWER,
OUTPUT_POWER,
C_RATE,
Expand All @@ -50,9 +52,15 @@
OEMOF_SOURCE,
OEMOF_TRANSFORMER,
OEMOF_BUSSES,
OEMOF_ExtractionTurbineCHP,
EMISSION_FACTOR,
BETA,
)
from multi_vector_simulator.utils.helpers import get_item_if_list, get_length_if_list
from multi_vector_simulator.utils.exceptions import (
MissingParameterError,
WrongParameterFormatError,
)


def transformer(model, dict_asset, **kwargs):
Expand Down Expand Up @@ -112,6 +120,102 @@ def transformer(model, dict_asset, **kwargs):
)


def chp(model, dict_asset, **kwargs):
r"""
Defines a chp component specified in `dict_asset`.
Depending on the 'value' of 'optimizeCap' in `dict_asset` the chp
is defined with a fixed capacity or a capacity to be optimized.
The chp has single input and multiple output busses.
Parameters
----------
model : oemof.solph.network.EnergySystem object
See the oemof documentation for more information.
dict_asset : dict
Contains information about the chp like (not exhaustive):
efficiency, installed capacity ('installedCap'), information on the
busses the chp is connected to ('inflow_direction',
'outflow_direction'), beta coefficient.
Other Parameters
----------------
busses : dict
sinks : dict, optional
sources : dict, optional
transformers : dict
storages : dict, optional
extractionTurbineCHP: dict, optional
Notes
-----
The transformer has either multiple input or multiple output busses.
The following functions are used for defining the chp:
* :py:func:`~.chp_fix`
* :py:func:`~.chp_optimize` for investment optimization
Tested with:
- test_chp_fix_cap()
- test_chp_optimize_cap()
- test_chp_missing_beta()
- test_chp_wrong_beta_formatting()
- test_chp_wrong_efficiency_formatting()
- test_chp_wrong_outflow_bus_energy_vector()
Returns
-------
Indirectly updated `model` and dict of asset in `kwargs` with chp object.
"""
if BETA in dict_asset:
beta = dict_asset[BETA]
if isinstance(beta, dict) is False:
raise WrongParameterFormatError(
f"For the conversion asset named '{dict_asset[LABEL]}' of type {OEMOF_ExtractionTurbineCHP}, "
f"the {BETA} parameter should have the following format {{ '{VALUE}': ..., '{UNIT}': ... }}"
)
else:
beta = beta[VALUE]
if 0 <= beta <= 1:
pass
else:
raise ValueError("beta should be a number between 0 and 1.")
else:
raise MissingParameterError("No beta for extraction turbine chp specified.")

if isinstance(dict_asset[EFFICIENCY][VALUE], list) is False:
missing_efficiencies = (
f"For the conversion asset named '{dict_asset[LABEL]}' of type {OEMOF_ExtractionTurbineCHP} "
f"you must provide exactly 2 values for the parameter '{EFFICIENCY}'."
)
logging.error(missing_efficiencies)
raise WrongParameterFormatError(missing_efficiencies)

busses_energy_vectors = [
kwargs[OEMOF_BUSSES][b].energy_vector for b in dict_asset[OUTFLOW_DIRECTION]
]
if (
"Heat" not in busses_energy_vectors
or "Electricity" not in busses_energy_vectors
):
mapping_busses = [
f"'{v}' (from '{k}')"
for k, v in zip(dict_asset[OUTFLOW_DIRECTION], busses_energy_vectors)
]
wrong_output_energy_vectors = (
f"For the conversion asset named '{dict_asset[LABEL]}' of type {OEMOF_ExtractionTurbineCHP} "
f"you must provide 1 output bus for energy vector 'Heat' and one for 'Electricity'. You provided "
f"{' and '.join(mapping_busses)}"
)
logging.error(wrong_output_energy_vectors)
raise WrongParameterFormatError(wrong_output_energy_vectors)

check_optimize_cap(
model, dict_asset, func_constant=chp_fix, func_optimize=chp_optimize, **kwargs
)


def storage(model, dict_asset, **kwargs):
r"""
Defines a storage component specified in `dict_asset`.
Expand Down Expand Up @@ -339,6 +443,13 @@ def check_optimize_cap(model, dict_asset, func_constant, func_optimize, **kwargs
)


class CustomBus(solph.Bus):
def __init__(self, *args, **kwargs):
ev = kwargs.pop("energy_vector", None) # change to ENERGY_VECTOR
super(CustomBus, self).__init__(*args, **kwargs)
self.energy_vector = ev


def bus(model, name, **kwargs):
r"""
Adds bus `name` to `model` and to 'busses' in `kwargs`.
Expand All @@ -351,7 +462,8 @@ def bus(model, name, **kwargs):
"""
logging.debug(f"Added: Bus {name}")
bus = solph.Bus(label=name)
energy_vector = kwargs.get("energy_vector", None) # change to ENERGY_VECTOR
bus = CustomBus(label=name, energy_vector=energy_vector)
kwargs[OEMOF_BUSSES].update({name: bus})
model.add(bus)

Expand Down Expand Up @@ -586,6 +698,7 @@ def transformer_constant_efficiency_optimize(model, dict_asset, **kwargs):
logging.error(missing_dispatch_prices_or_efficiencies)
raise ValueError(missing_dispatch_prices_or_efficiencies)

# TODO move the investment in the input bus???
inputs = {kwargs[OEMOF_BUSSES][dict_asset[INFLOW_DIRECTION]]: solph.Flow()}
outputs = {}

Expand Down Expand Up @@ -1108,3 +1221,124 @@ def sink_non_dispatchable(model, dict_asset, **kwargs):
logging.debug(
f"Added: Non-dispatchable sink {dict_asset[LABEL]} to bus {dict_asset[INFLOW_DIRECTION]}"
)


def chp_fix(model, dict_asset, **kwargs):
r"""
Extraction turbine chp from Oemof solph. Extraction turbine must have one input and two outputs
Notes
-----
Tested with:
- test_to_be_written()
Returns
-------
Indirectly updated `model` and dict of asset in `kwargs` with the extraction turbine component.
"""

inputs = {
kwargs[OEMOF_BUSSES][dict_asset[INFLOW_DIRECTION]]: solph.Flow(
nominal_value=dict_asset[INSTALLED_CAP][VALUE]
)
}

busses_energy_vectors = [
kwargs[OEMOF_BUSSES][b].energy_vector for b in dict_asset[OUTFLOW_DIRECTION]
]
idx_el = busses_energy_vectors.index("Electricity")
idx_th = busses_energy_vectors.index("Heat")
el_bus = kwargs[OEMOF_BUSSES][dict_asset[OUTFLOW_DIRECTION][idx_el]]
th_bus = kwargs[OEMOF_BUSSES][dict_asset[OUTFLOW_DIRECTION][idx_th]]

outputs = {
el_bus: solph.Flow(),
th_bus: solph.Flow(),
} # if kW for heat and kW for elect then insert it under nominal_value

beta = dict_asset[BETA][VALUE]

efficiency_el_wo_heat_extraction = dict_asset[EFFICIENCY][VALUE][idx_th]
efficiency_th_max_heat_extraction = dict_asset[EFFICIENCY][VALUE][idx_el]
efficiency_el_max_heat_extraction = (
efficiency_el_wo_heat_extraction - beta * efficiency_th_max_heat_extraction
)
efficiency_full_condensation = {el_bus: efficiency_el_wo_heat_extraction}

efficiencies = {
el_bus: efficiency_el_max_heat_extraction,
th_bus: efficiency_th_max_heat_extraction,
}

ext_turb_chp = solph.components.ExtractionTurbineCHP(
label=dict_asset[LABEL],
inputs=inputs,
outputs=outputs,
conversion_factors=efficiencies,
conversion_factor_full_condensation=efficiency_full_condensation,
)

model.add(ext_turb_chp)
kwargs[OEMOF_ExtractionTurbineCHP].update({dict_asset[LABEL]: ext_turb_chp})


def chp_optimize(model, dict_asset, **kwargs):
r"""
Extraction turbine chp from Oemof solph. Extraction turbine must have one input and two outputs
Notes
-----
Tested with:
- test_to_be_written()
Returns
-------
Indirectly updated `model` and dict of asset in `kwargs` with the extraction turbine component.
"""

inputs = {kwargs[OEMOF_BUSSES][dict_asset[INFLOW_DIRECTION]]: solph.Flow()}

busses_energy_vectors = [
kwargs[OEMOF_BUSSES][b].energy_vector for b in dict_asset[OUTFLOW_DIRECTION]
]

idx_el = busses_energy_vectors.index("Electricity")
idx_th = busses_energy_vectors.index("Heat")
el_bus = kwargs[OEMOF_BUSSES][dict_asset[OUTFLOW_DIRECTION][idx_el]]
th_bus = kwargs[OEMOF_BUSSES][dict_asset[OUTFLOW_DIRECTION][idx_th]]

outputs = {
el_bus: solph.Flow(
investment=solph.Investment(
ep_costs=dict_asset[SIMULATION_ANNUITY][VALUE],
maximum=dict_asset[MAXIMUM_ADD_CAP][VALUE],
existing=dict_asset[INSTALLED_CAP][VALUE],
)
),
th_bus: solph.Flow(),
}

beta = dict_asset[BETA][VALUE]

efficiency_el_wo_heat_extraction = dict_asset[EFFICIENCY][VALUE][idx_el]
efficiency_th_max_heat_extraction = dict_asset[EFFICIENCY][VALUE][idx_th]
efficiency_el_max_heat_extraction = (
efficiency_el_wo_heat_extraction - beta * efficiency_th_max_heat_extraction
)
efficiency_full_condensation = {el_bus: efficiency_el_wo_heat_extraction}

efficiencies = {
el_bus: efficiency_el_max_heat_extraction,
th_bus: efficiency_th_max_heat_extraction,
}

ext_turb_chp = solph.components.ExtractionTurbineCHP(
label=dict_asset[LABEL],
inputs=inputs,
outputs=outputs,
conversion_factors=efficiencies,
conversion_factor_full_condensation=efficiency_full_condensation,
)

model.add(ext_turb_chp)
kwargs[OEMOF_ExtractionTurbineCHP].update({dict_asset[LABEL]: ext_turb_chp})
Loading

0 comments on commit 84375e4

Please sign in to comment.