Skip to content

Commit

Permalink
Merge pull request #78 from sedos-project/feature/routine-for-decomis…
Browse files Browse the repository at this point in the history
…sioning-of-exisitng-capacity

Feature/routine for decomissioning of exisiting capacity
  • Loading branch information
FelixMau authored Jun 25, 2024
2 parents caf9fa6 + d9e872d commit ffa4845
Show file tree
Hide file tree
Showing 22 changed files with 1,622 additions and 410 deletions.
103 changes: 91 additions & 12 deletions data_adapter_oemof/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import warnings
from typing import Optional, Type, Union

import numpy as np
import pandas as pd
from oemof.tabular import facades
from oemof.tabular._facade import Facade
Expand Down Expand Up @@ -38,6 +37,8 @@ class Adapter:
Field(name="region", type=str),
Field(name="year", type=int),
)
output_parameters = (Field(name="max", type=float), Field(name="min", type=float))
input_parameters = ()
counter: int = itertools.count()

def __init__(
Expand Down Expand Up @@ -75,16 +76,12 @@ def get_default_parameters(self) -> dict:
)
}
)
if "lifetime" in defaults.keys():
# want to move this section including if statements together with decommissioning
# section to a calculations as "default calculations
if not isinstance(defaults["lifetime"], collections.abc.Iterable):
defaults["lifetime"] = int(np.floor(defaults["lifetime"]))
elif all(x == defaults["lifetime"][0] for x in defaults["lifetime"]):
defaults["lifetime"] = int(np.floor(defaults["lifetime"][0]))
else:
warnings.warn("Lifetime cannot change in Multi-period modeling")
defaults["lifetime"] = int(np.floor(defaults["lifetime"][0]))

defaults = self.default_post_mapping_calculations(defaults)
if not defaults["input_parameters"]:
defaults.pop("input_parameters")
if not defaults["output_parameters"]:
defaults.pop("output_parameters")

return defaults

Expand Down Expand Up @@ -271,24 +268,94 @@ def get_busses(self) -> dict:

return bus_dict

def get_output_input_parameter_fields(self):
"""
Getting output and input parameters from data
Parameters must be applicable to respective flows
Parameters to be defined in Adapter
Returns
{"output_parameters": {"min": [10, 20], "max": [20, 30]},
"input_parameters": {"min": [10, 20], "max": [20, 30]}}
-------
"""

def get_io_parameter_dict(parameters):
io_dict = {}
for param in parameters:
if input_parameter_value := self.get(param.name):
io_dict.update({param.name: input_parameter_value})
return io_dict

input_output_parameters = {"output_parameters": {}, "input_parameters": {}}

input_output_parameters["input_parameters"].update(
get_io_parameter_dict(parameters=self.input_parameters)
)
input_output_parameters["output_parameters"].update(
get_io_parameter_dict(parameters=self.output_parameters)
)

return input_output_parameters

def get_default_mappings(self):
"""
:return: Dictionary for all fields that the facade can take and matching data
"""

self.default_pre_mapping_calculations()

mapped_all_class_fields = {
field.name: value
for field in self.get_fields()
if (value := self.get(field.name, field.type)) is not None
}
mapped_all_class_fields.update(self.get_busses())
mapped_all_class_fields.update(self.get_output_input_parameter_fields())
return mapped_all_class_fields

@staticmethod
def is_sequence(field_type: Type):
# TODO: Implement it using typing hints
return "Sequence" in str(field_type)

def default_pre_mapping_calculations(self):
"""
Takes activity bonds and calculates min/max values
Parameters
----------
adapter_dict
Returns
-------
"""
calculations.normalize_activity_bonds(self)

def default_post_mapping_calculations(self, mapped_defaults):
"""
Does default calculations#
I. Decommissioning of existing Capacities
II. Rounding lifetime down to integers
Returns
-------
"""
# I:
if self.process_name[-1] == "0":
mapped_defaults = calculations.decommission(
process_name=self.process_name, adapter_dict=mapped_defaults
)

# II:
if "lifetime" in mapped_defaults.keys():
mapped_defaults = calculations.floor_lifetime(mapped_defaults)

return mapped_defaults


class DispatchableAdapter(Adapter):
"""
Expand Down Expand Up @@ -427,9 +494,21 @@ class MIMOAdapter(Adapter):
Field(name="groups", type=dict),
Field(name="capacity_cost", type=float),
Field(name="capacity", type=float),
Field(name="max", type=float),
Field(name="expandable", type=bool),
Field(name="activity_bound_min", type=float),
Field(name="activity_bound_max", type=float),
Field(name="activity_bound_fix", type=float),
)
output_parameters = ()

def default_pre_mapping_calculations(self):
"""
Mimo adapter specific pre calculations
Returns
-------
"""
pass

def get_default_parameters(self) -> dict:
defaults = super().get_default_parameters()
Expand Down
154 changes: 154 additions & 0 deletions data_adapter_oemof/calculations.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import collections
import logging
import warnings

import numpy as np
from oemof.tools.economics import annuity


Expand Down Expand Up @@ -34,3 +39,152 @@ def get_name(*args, counter=None):
@calculation
def get_capacity_cost(overnight_cost, fixed_cost, lifetime, wacc):
return annuity(overnight_cost, lifetime, wacc) + fixed_cost


def decommission(process_name, adapter_dict: dict) -> dict:
"""
Takes adapter dictionary from adapters.py with mapped values.
I:
Takes largest found capacity and sets this capacity for all years
Each yearly changing capacity value is divided by max capacity and
quotient from `max capacity`/`yearly capacity` is set as max value.
II:
If Max value is already set by another parameter function will issue info
Recalculating max value to
.. math::
max_{new} = \frac{(max_{column} * capacity_{column})}{capacity_{max}}
Overwriting max value in `output_parameters`
Then is setting capacity to the largest found capacity
Supposed to be called when getting default parameters
Non investment objects must be decommissioned in multi period to take end of lifetime
for said objet into account.
Returns
adapter_dictionary with max values in output parameters and a single capacity
-------
"""

def multiply_two_lists(l1, l2):
"""
Multiplies two lists
Lists must be same length
Parameters
----------
l1
l2
Returns divided list
-------
"""
return [i * j for i, j in zip(l1, l2)]

capacity_column = "capacity"
max_column = "max"

# check if capacity column is there and if it has to be decommissioned
if capacity_column not in adapter_dict.keys():
logging.info(
f"Capacity missing for decommissioning " f"of Process `{process_name}`"
)
return adapter_dict

if not isinstance(adapter_dict[capacity_column], list):
logging.info(
f"No capacity fading out that can be decommissioned"
f" for Process `{process_name}`."
)
return adapter_dict

# I:
if max_column not in adapter_dict["output_parameters"].keys():
adapter_dict["output_parameters"][max_column] = adapter_dict[
capacity_column
] / np.max(adapter_dict[capacity_column])
# II:
else:
adapter_dict["output_parameters"][max_column] = multiply_two_lists(
adapter_dict["output_parameters"][max_column], adapter_dict[capacity_column]
) / np.max(adapter_dict[capacity_column])

adapter_dict[capacity_column] = np.max(adapter_dict[capacity_column])
return adapter_dict


def normalize_activity_bonds(adapter):
"""
Normalizes activity bonds in order to be used as min/max values
Parameters
----------
adapter
Returns
-------
"""

def divide_two_lists(dividend, divisor):
"""
Divides two lists returns quotient, returns 0 if divisor is 0
Lists must be same length
Parameters
----------
dividend
divisor
Returns divided list
-------
"""
return [i / j if j != 0 else 0 for i, j in zip(dividend, divisor)]

if "activity_bound_fix" in adapter.data.keys():
adapter.data["activity_bound_fix"] = divide_two_lists(
adapter.data["activity_bound_fix"], adapter.get("capacity")
)
return adapter

if "activity_bound_min" in adapter.data.keys():
adapter.data["activity_bound_min"] = divide_two_lists(
adapter.data["activity_bound_min"], adapter.get("capacity")
)
return adapter

if "activity_bound_max" in adapter.data.keys():
adapter.data["activity_bound_max"] = divide_two_lists(
adapter.data["activity_bound_max"], adapter.get("capacity")
)
return adapter


def floor_lifetime(mapped_defaults):
"""
Parameters
----------
adapter
Returns
-------
"""
if not isinstance(mapped_defaults["lifetime"], collections.abc.Iterable):
mapped_defaults["lifetime"] = int(np.floor(mapped_defaults["lifetime"]))
elif all(x == mapped_defaults["lifetime"][0] for x in mapped_defaults["lifetime"]):
mapped_defaults["lifetime"] = int(np.floor(mapped_defaults["lifetime"][0]))
else:
warnings.warn("Lifetime cannot change in Multi-period modeling")
mapped_defaults["lifetime"] = int(np.floor(mapped_defaults["lifetime"][0]))
return mapped_defaults
1 change: 0 additions & 1 deletion examples/industry/data_adapter_industry.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@
"MIMOAdapter": {
"capacity_cost": "cost_fix_capacity_w",
"capacity": "capacity_w_resid",
"max": "activity_bound_fix",
"expandable": "capacity_w_abs_new_max",
},
"modex_tech_wind_turbine_onshore": {"profile": "onshore"},
Expand Down
Loading

0 comments on commit ffa4845

Please sign in to comment.