Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/routine for decomissioning of exisiting capacity #78

Merged
merged 30 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
47a629e
Structure for steelcast test case
FelixMau Mar 5, 2024
192cb71
Merge Dev
FelixMau Mar 5, 2024
f51db2d
start test writing
FelixMau Mar 6, 2024
b832178
Adapt strukture and Mappings for steel
FelixMau Mar 13, 2024
4da45c7
Merge branch 'feature/add_mimo_adapter' into feature/routine-for-deco…
FelixMau Mar 15, 2024
4cc72fd
Add decomissioning to adapters & calculations
FelixMau Mar 15, 2024
32cf342
Linting
FelixMau Mar 15, 2024
82c57e0
Warn and return single capacity
FelixMau Mar 15, 2024
89ac703
Calc max value from capacity
FelixMau Apr 3, 2024
4e28f88
Commenting & Linting
FelixMau Apr 3, 2024
97a7ad0
Adding empty helper
FelixMau Apr 10, 2024
39192f5
Commenting & Linting
FelixMau Apr 10, 2024
c8b1f35
Loggin info if `max` already set
FelixMau Apr 11, 2024
cf958cb
Loggin info if `max` already set
FelixMau Apr 11, 2024
9583a68
Adding comment
FelixMau Apr 15, 2024
4cbeb22
Seperating calculation functionality
FelixMau Apr 15, 2024
0ee4eca
seperating functions for better overview
FelixMau Apr 16, 2024
1e3d412
Merge branch 'dev' into feature/routine-for-decomissioning-of-existin…
FelixMau Apr 16, 2024
6132c83
Move post mappinig calculations to calculations
FelixMau Apr 16, 2024
8c0c690
Commenting & Linting
FelixMau Apr 16, 2024
4cee6ff
Idea pitch to add in flow parameters
FelixMau Apr 16, 2024
e32c109
Rearranging functions
FelixMau Apr 17, 2024
d96d6f6
Revisting decomissioning
FelixMau Apr 17, 2024
2901bb9
Test fail due to empty parameter dicts
FelixMau Apr 17, 2024
3451937
Update oemof.industry
FelixMau Apr 24, 2024
78600e5
Update oemof.industry
FelixMau Apr 24, 2024
03fbefc
Adjust extra fields for mimo
FelixMau Apr 24, 2024
60c135f
Remove outputparameters in Mimo
FelixMau May 6, 2024
acdb9e9
Update dependencies
FelixMau May 6, 2024
d9e872d
Update documentation & Logging
FelixMau May 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading