Skip to content

Commit

Permalink
Refactor Adapter to not init facade
Browse files Browse the repository at this point in the history
  • Loading branch information
henhuy committed Aug 16, 2023
1 parent 82f5c49 commit 635c79d
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 115 deletions.
147 changes: 35 additions & 112 deletions data_adapter_oemof/adapters.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,28 @@
import dataclasses
import logging

import oemof.solph
import pandas

from oemof.tabular import facades
from oemof.solph import Bus
from oemof.tabular._facade import Facade

from data_adapter_oemof import calculations
from data_adapter_oemof.mappings import Mapper


logger = logging.getLogger()


class Adapter:
type: str = "adapter"
facade: Facade = None
extra_attributes = ("name", "type")

def as_dict(self):
"""
Adds function to return DataFrame from adapter.
This mixin is needed because `pd.DataFrame(dataclass_instance)` creates columns
only for existing dataclass attributes.
But we add custom_attributes (i.e. "name") which would be neglected.
"""
fields = dataclasses.fields(self)
data = {}
for field in fields:
value = getattr(self, field.name)
data[field.name] = value
if isinstance(value, oemof.solph._plumbing._Sequence):
if value.periodic_values:
data[field.name] = value.periodic_values
elif len(value) != 0:
data[field.name] = value.data
else:
data[field.name] = value.default
# data = {field.name: field_value
# for field in fields
# if isinstance((field_value := getattr(self, field.name)), oemof.solph._plumbing._Sequence)
# }
for attr in self.extra_attributes:
data[attr] = getattr(self, attr)
return data

@classmethod
def parametrize_dataclass(
cls,
struct,
mapper: Mapper,
) -> "Adapter":
return cls(**cls.get_default_parameters(struct, mapper))

@classmethod
def get_default_parameters(cls, struct: dict, mapper: Mapper) -> dict:
def as_dict(self) -> dict:
return self.facade_dict

def __init__(self, struct: dict, mapper: Mapper):
self.facade_dict = self.get_default_parameters(struct, mapper)

def get_default_parameters(self, struct: dict, mapper: Mapper) -> dict:
defaults = {
"type": cls.type,
"type": self.type,
}
# Add mapped attributes
mapped_values = mapper.get_default_mappings(struct)
Expand All @@ -75,146 +40,104 @@ def get_default_parameters(cls, struct: dict, mapper: Mapper) -> dict:
return defaults


def facade_adapter(cls):
r"""
Sets type of Busses to str
Sets 'build_solph_components' to False in __init__.
"""
# set type of bus to str
for field in dataclasses.fields(cls):
if field.type == Bus:
field.type = str

original_init = cls.__init__

# do not build solph components
def new_init(self, *args, **kwargs):
custom_attributes = {}
original_fields = tuple(field.name for field in dataclasses.fields(cls))
for key in tuple(kwargs.keys()):
if key not in original_fields:
custom_attributes[key] = kwargs.pop(key)

original_init(self, *args, build_solph_components=False, **kwargs)

for key, value in custom_attributes.items():
setattr(self, key, value)

cls.__init__ = new_init

return cls


@facade_adapter
class DispatchableAdapter(facades.Dispatchable, Adapter):
class DispatchableAdapter(Adapter):
"""
Dispatchable Adapter
"""

type = "dispatchable"
facade = facades.Dispatchable


@facade_adapter
class HeatPumpAdapter(facades.HeatPump, Adapter):
class HeatPumpAdapter(Adapter):
"""
HeatPump Adapter
"""

type = "heat_pump"
facade = facades.HeatPump


@facade_adapter
class LinkAdapter(facades.Link, Adapter):
class LinkAdapter(Adapter):
"""
Link Adapter
"""

type = "link"
facade = facades.Link


@facade_adapter
class ReservoirAdapter(facades.Reservoir, Adapter):
class ReservoirAdapter(Adapter):
"""
Reservoir Adapter
"""

type = "reservoir"
facade = facades.Reservoir


@facade_adapter
class ExcessAdapter(facades.Excess, Adapter):
class ExcessAdapter(Adapter):
"""
Excess Adapter
"""

type = "excess"
facade = facades.Excess


@facade_adapter
class BackpressureTurbineAdapter(facades.BackpressureTurbine, Adapter):
class BackpressureTurbineAdapter(Adapter):
"""
BackpressureTurbine Adapter
"""

type = "backpressure_turbine"
facade = facades.BackpressureTurbine


@facade_adapter
class CommodityAdapter(facades.Commodity, Adapter):
class CommodityAdapter(Adapter):
"""
CommodityAdapter
"""

type = "commodity"
facade = facades.Commodity


@facade_adapter
class ConversionAdapter(facades.Conversion, Adapter):
class ConversionAdapter(Adapter):
"""
ConversionAdapter
To use Conversion, map the inputs and outputs within the structure to avoid deduction failure.
"""

type = "conversion"
facade = facades.Conversion


@facade_adapter
class LoadAdapter(facades.Load, Adapter):
class LoadAdapter(Adapter):
"""
LoadAdapter
"""

type = "load"
facade = facades.Load


@facade_adapter
class StorageAdapter(facades.Storage, Adapter):
class StorageAdapter(Adapter):
"""
StorageAdapter
"""

type = "storage"
facade = facades.Storage


@facade_adapter
class ExtractionTurbineAdapter(facades.ExtractionTurbine, Adapter):
class ExtractionTurbineAdapter(Adapter):
"""
ExtractionTurbineAdapter
"""

type = "extraction_turbine"
facade = facades.ExtractionTurbine


@facade_adapter
class VolatileAdapter(facades.Volatile, Adapter):
class VolatileAdapter(Adapter):
"""
VolatileAdapter
"""

type = "volatile"
facade = facades.Volatile


# Create a dictionary of all adapter classes defined in this module
FACADE_ADAPTERS = {
name: adapter for name, adapter in globals().items() if name.endswith("Adapter")
adapter.type: adapter for name, adapter in globals().items() if name.endswith("Adapter")
}
4 changes: 2 additions & 2 deletions data_adapter_oemof/mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def get_busses(self, struct):
"""
bus_occurrences_in_fields = [
field.name
for field in dataclasses.fields(self.adapter)
for field in dataclasses.fields(self.adapter.facade)
if "bus" in field.name
]
if len(bus_occurrences_in_fields) == 0:
Expand Down Expand Up @@ -213,7 +213,7 @@ def get_default_mappings(self, struct):

mapped_all_class_fields = {
field.name: value
for field in dataclasses.fields(self.adapter)
for field in dataclasses.fields(self.adapter.facade)
if (value := self.get(field.name, field.type)) is not None
}
mapped_all_class_fields.update(self.get_busses(struct))
Expand Down
45 changes: 44 additions & 1 deletion tests/test_data_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,55 @@

import pandas as pd

from data_adapter_oemof.adapters import TYPE_MAP
from data_adapter_oemof.mappings import Mapper
from data_adapter_oemof.adapters import VolatileAdapter, FACADE_ADAPTERS

TEST_DIR = pathlib.Path(__file__).parent
TEMP_DIR = TEST_DIR / "_temp"


def test_simple_adapter():
data = {
"type": "volatile",
"region": "B",
"carrier": "wind",
"bus": "electricity",
"tech": "onshore",
"capacity": 12,
"sedos-marginal_cost": 1,
"sedos-overnight_cost": 1000,
"capital_costs": 100,
"sedos-lifetime": 25,
"sedos-wacc": 0.05,
}
timeseries = pd.DataFrame({"a": [0, 0, 0], "onshore_B": [1, 2, 3]})
struct = {"default": {"inputs": ["onshore"], "outputs": ["electricity"]}}
mapping = {
"modex_tech_wind_turbine_onshore": {"profile": "onshore", "lifetime": "sedos-lifetime"}
}
mapper = Mapper(
adapter=VolatileAdapter,
process_name="modex_tech_wind_turbine_onshore",
data=data,
timeseries=timeseries,
mapping=mapping,
)
adapter = VolatileAdapter(struct, mapper)
expected_dict = {
"bus": "electricity",
"capacity": 12,
"carrier": "wind",
"name": "B-None-onshore",
"profile": "onshore_B",
"tech": "onshore",
"type": "volatile",
"lifetime": 25,
"region": "B",
"year": None
}
assert adapter.as_dict() == expected_dict


def test_adapter():
Component = namedtuple("Component", ["type", "data"])

Expand Down

0 comments on commit 635c79d

Please sign in to comment.