from typing import Union
from abc import ABC, abstractmethod
[docs]class ReprMixin(ABC):
"""Adds a useful string representation to subclasses"""
def __init__(self):
self._to_repr = {}
# TODO: replace all _to_repr[name] = attr lines with these shortcuts
def _add_repr(self, name, attr=None, check=False):
if attr is None:
attr = name
if (not check) or getattr(self, attr):
self._to_repr[name] = attr
def _add_reprs(self, names, check=False):
for name in names:
self._add_repr(name, check=check)
def __repr__(self):
attr_list = [f'{attr_name}={repr(getattr(self, attr))}'
for attr_name, attr in self._to_repr.items()]
return f'{self.__class__.__name__}({", ".join(attr_list)})'
[docs]class IOBase(ReprMixin, ABC):
"""IOBase(name)
Base class for inputs, objectives and constraints.
:param str name: For generating DataFrame column titles."""
def __init__(self, name=''):
super().__init__()
self._name = name
if name:
self._to_repr['name'] = '_name'
@property
def name(self):
return self._name or self._default_name
@name.setter
def name(self, value):
self._name = value
@property
def _default_name(self):
return ''
def get_name(x: Union[str, int, IOBase]):
if isinstance(x, str):
return x
if isinstance(x, int):
return str(x)
return x.name
[docs]class Descriptor(ReprMixin, ABC):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.platypus_type = NotImplemented
[docs] @abstractmethod
def validate(self, value) -> bool:
"""Checks if value is a valid value for this parameter.
:param value: The value to check
:return: True if the value is valid False otherwise
"""
# consider a pandas type to improve DataFrame generation
[docs] @abstractmethod
def sample(self, value):
"""Takes a value in the range 0-1 and returns a valid value for this parameter
:param value:
"""
pass
[docs]class AnyValue(Descriptor):
"""A descriptor that can take on any possible value.
Intended to be used as a placeholder."""
[docs] def validate(self, value):
return True
[docs] def sample(self, value):
raise NotImplementedError
def __bool__(self):
"""Marks this as a 'missing' piece"""
# ReprMixin will detect this as something to not include when checking
return False
[docs]class Selector(ReprMixin, ABC):
"""Base Class for Selectors, which describe what attribute of the building is read or modified"""
@abstractmethod
def get(self, building):
pass
@abstractmethod
def set(self, building, value):
pass
[docs] def setup(self, building) -> None:
"""Modifies the building so that it is ready for this selector"""
pass
[docs]class DummySelector(Selector):
"""A selector that does not modify the building.
Intended to be used as a placeholder."""
def get(self, building):
raise NotImplementedError
def set(self, building, value):
pass
def __bool__(self):
"""Marks this as a 'missing' piece"""
# ReprMixin will detect this as something to not include when checking
return False
[docs]class Objective(IOBase):
"""Objective(name)
Base class for objectives."""
def __call__(self, *args, **kwargs) -> float:
"""Return the objective's value on the instance represented by *args and **kwargs"""
raise NotImplementedError