# tessif/model/components.py
"""Tessif component library.
:mod:`~tessif.components` is a :mod:`tessif` module
transforming the abstract data representation of an energy system stored as
`mapping
<https://docs.python.org/3/library/stdtypes.html#mapping-types-dict>`_ into a
'class-instance' like structure.
Mapping like representations are usually returned by utilities part of the
:mod:`~tessif.parse` module.
Note
----
Tessif's energy system model components are deliberately (somewhat) redundant
from a computer science point of view. They are designed to be intuitively used
by engineers. Hence parameters, attributes and namespaces are aggregated in a
way an engineer would classify such components. For more on that see
:mod:`tessif.system_model`.
.. rubric:: Classes
.. autosummary::
:nosignatures:
AbstractEsComponent
Bus
Connector
Source
Sink
Transformer
CHP
Storage
"""
import json
import numpy as np
import tessif.frused.namedtuples as nts
from tessif.frused.defaults import energy_system_nodes as es_defaults
from tessif.serialize import SystemModelEncoder
[docs]class AbstractEsComponent:
r"""
Entities only concerned with their unique hashable identifier.
If it quacks like an AbstractEsComponent, if it walks like an
AbstractEsComponent it is an AbstractEsComponent.
Parameters
----------
name: ~collections.abc.Hashable
Identifier. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.name<tessif.frused.namedtuples.Uid.name>`
Note
----
All following arguments are considered optional parameters and are
provided using \*\*kwargs.
Parameters
----------
latitude: ~numbers.Number
Geospatial latitude in degree. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.latitude<tessif.frused.namedtuples.Uid.latitude>`
longitude: ~numbers.Number
Geospatial longitude in degree. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.longitude<tessif.frused.namedtuples.Uid.longitude>`
region: str
Arbitrary regional categorization string. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.region<tessif.frused.namedtuples.Uid.region>`
sector: str
Arbitrary sector categorization string.Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.sector<tessif.frused.namedtuples.Uid.sector>`
carrier: str
Arbitrary energy carrier categorization string.Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.carrier<tessif.frused.namedtuples.Uid.carrier>`
component: str
String specifying the components type. Used for post-processing.
node_type: str
Arbitrary node type categorization string. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.node_type<tessif.frused.namedtuples.Uid.node_type>`
Warning
-------
The 6 beforneamed arguments together with
:paramref:`~AbstractEsComponent.name` form a
:attr:`tessif.frused.namedtuples.Uid` object. This
:attr:`~tessif.frused.namedtuples.Uid` object as well as its string
representation (str(:attr:`~tessif.frused.namedtuples.Uid`)) must
be unique.
The string representation can be tweaked using
:attr:`~tessif.frused.configurations.node_uid_style`. But in total
the overall combination of these parameters must be unique and will form
the components hashable uid (unique identifier)
"""
def __init__(self, name, *args, **kwargs):
# modify this dict to add additional parameters
kwargs_and_defaults = dict(
[
("name", name),
("latitude", kwargs.get("latitude", es_defaults["latitude"])),
("longitude", kwargs.get("longitude", es_defaults["longitude"])),
("region", kwargs.get("region", es_defaults["region"])),
("sector", kwargs.get("sector", es_defaults["sector"])),
("carrier", kwargs.get("carrier", es_defaults["carrier"])),
("component", kwargs.get("component", es_defaults["component"])),
("node_type", kwargs.get("node_type", es_defaults["node_type"])),
]
)
self._uid = nts.Uid(**kwargs_and_defaults)
# key parsing functionality wrapper to parameterize the component
self._parse_arguments(**kwargs)
[docs] def duplicate(self, prefix="", separator="_", suffix="copy"):
"""Duplicate this component.
Duplicate the energy system component and return it. Potentially
modifying it's :paramref:`~AbstractEsComponent.name`.
Parameters
----------
prefix: str, default=''
String added to the beginning of the component's
:paramref:`~AbstractEsComponent.name`, separated by
:paramref:`~duplicate.separator`.
separator: str, default='_'
String used for adding the :paramref:`~duplicate.prefix` and the
:paramref:`~duplicate.suffix` to the component's
:paramref:`~AbstractEsComponent.name`.
suffix: str, default=''
String added to the beginning of the component's
:paramref:`~AbstractEsComponent.name`, separated by
:paramref:`~duplicate.separator`.
"""
new_attributes = self.attributes
# get the old uid and transform it into a dict
new_uid = new_attributes.pop("uid")._asdict()
# modify the uid's name according to request
if prefix:
new_uid["name"] = separator.join([prefix, new_uid["name"]])
if suffix:
new_uid["name"] = separator.join([new_uid["name"], suffix])
# create a new component instance using the from_attributes method
return self.from_attributes(attributes={**new_uid, **new_attributes})
[docs] @classmethod
def from_attributes(cls, attributes):
"""Create component from attribute dictionary.
Create an :class:`AbstractEsComponent` object from a dictionary
of :attr:`~AbstractEsComponent.attributes`.
"""
return cls(**attributes)
[docs] def serialize(self):
"""Serialize this component."""
dct_repr = self.attributes
for uid_parameter, parameter in self.uid._asdict().items():
dct_repr[uid_parameter] = parameter
dct_repr.pop("uid")
serialized_component = json.dumps(
dct_repr,
cls=SystemModelEncoder,
)
return serialized_component
def _parse_arguments_as_singular_values(self, **arguments):
"""Parse singular value arguments during init.
Utility for parsing key word arguments in to the form of::
parameter = value
Used for parameters like:
- :attr:`Source.initial_status`
- :attr:`Storage.initial_soc`
- ...
Designed to internally set the energy system components
parameters. Called by :meth:`_parse_arguments`.
"""
for parameter, default_value in self._parameters_and_defaults[
"singular_values"
].items():
setattr(self, f"_{parameter}", arguments.get(parameter, default_value))
def _parse_arguments_as_singular_value_mappings(self, **arguments):
"""Parse singular value mappings during init.
Utility for parsing key word arguments in to the form of::
parameter = {input/output_string: value}
Used for parameters like:
- :attr:`Sink.flow_costs`
- :attr:`Transformer.flow_emissions`
- ...
Designed to internally set the energy system components
parameters. Called by :meth:`_parse_arguments`.
"""
for parameter, default_mapping in self._parameters_and_defaults[
"singular_value_mappings"
].items():
mapping = arguments.get(parameter, default_mapping)
# reading in data sets from external data can lead to NaN values
if mapping is np.nan:
mapping = default_mapping
setattr(
self,
f"_{parameter}",
{key: mapping[key] for key in sorted(mapping.keys())},
)
def _parse_arguments_as_namedtuples(self, **arguments):
"""Parse namedtuples arguments during init.
Utility for parsing key word arguments in to the form of::
parameter = namedtuple(*values)
Used for parameters like:
- :attr:`Transformer.status_inertia`
- :attr:'Storage.number_of_status_changes`
- ...
Designed to internally set the energy system components
parameters. Called by :meth:`_parse_arguments`.
"""
for ntple, parameters in self._parameters_and_defaults["namedtuples"].items():
for parameter, default_tuple in parameters.items():
tpl = arguments.get(parameter, default_tuple)
# reading in data sets from external data can lead to NaNs
if tpl is np.nan:
tpl = default_tuple
setattr(self, f"_{parameter}", getattr(nts, ntple)(*tpl))
def _parse_arguments_as_mapped_namedtuples(self, **arguments):
"""Parse mappings of namedtuples during init.
Utility for parsing key word arguments in to the form of::
parameter = {input/output_string: namedtuple(*values)}
Used for parameters like:
- :attr:`Source.flow_gradients`
- :attr:`Sink.gradient_costs`
- :attr:`Transformer.expansion_limits`
- :attr:'Storage.flow_rates`
- :attr:`Sink.accumulated_amounts`
- ...
Designed to internally set the energy system components
parameters. Called by :meth:`_parse_arguments`.
"""
for ntple, parameters in self._parameters_and_defaults[
"mapped_namedtuples"
].items():
for parameter, default_mapping in parameters.items():
mapping = arguments.get(parameter, default_mapping)
# reading in data sets from external data can lead to NaNs
if mapping is np.nan:
mapping = default_mapping
setattr(
self,
f"_{parameter}",
{
key: getattr(nts, ntple)(*mapping[key])
for key in sorted(mapping.keys())
},
)
def _parse_timeseries(self, **arguments):
"""Parse timeseries argument.
Utility for parsing the key word argument timeseries::
timeseries = None
timeseries = {input/output_string: MinMax namedtuple}
"""
timeseries = arguments.get(
"timeseries",
self._parameters_and_defaults.get("timeseries", es_defaults["timeseries"]),
)
# reading in data sets from external data can lead to NaN values
if timeseries is np.nan:
timeseries = self._parameters_and_defaults.get(
"timeseries", es_defaults["timeseries"]
)
# enforce min max tuple:
if timeseries is not None:
for interface, tple in timeseries.copy().items():
timeseries[interface] = nts.MinMax(*tple)
setattr(self, "_{}".format("timeseries"), timeseries)
def _parse_arguments(self, **arguments):
"""Parse arguments wrapper.
Key functionality wrapper for parsing component parameters.
Add new functionalities to expand the support of different kind
of arguments.
Parameters
----------
kwargs:
Key word arguments representing the component's parameters. The
arguments are provided by the user and filtered by the instance
variables
"""
self._parse_arguments_as_singular_values(**arguments)
self._parse_arguments_as_singular_value_mappings(**arguments)
self._parse_arguments_as_namedtuples(**arguments)
self._parse_arguments_as_mapped_namedtuples(**arguments)
self._parse_timeseries(**arguments)
def __repr__(self):
"""Modifiy repr to be meaningful.
Returns
-------
descriptive_string: str
Returns::
tsf.Component(attribute_name=attribute, ....)
"""
return f"{self.__class__!s}(" + ", ".join(
[
*[
"{!r}={!r}".format(k.lstrip("_"), v)
for k, v in sorted(self.__dict__.items())
if k != "_parameters_and_defaults"
],
")",
]
)
def __str__(self):
"""Modifiy str_repr to be meaningful.
Returns
-------
descriptive_string: str
Returns::
tsf.Component(
attribute_name=attribute
....
)
"""
return f"{self.__class__!s}(\n" + ",\n".join(
[
*[
" {!r}={!r}".format(k.lstrip("_"), v)
for k, v in sorted(self.__dict__.items())
if k != "_parameters_and_defaults"
],
")",
]
)
@property
def attributes(self):
"""Dictionairy of component attributes.
:class:`~collections.abc.Mapping` of entity's energy system
component attribute names to its respective attribute values.
"""
return {
k.lstrip("_"): v
for k, v in sorted(self.__dict__.items())
if k != "_parameters_and_defaults"
}
@property
def interfaces(self):
""":class:`frozenset` holding the component's in- and outputs."""
return self._interfaces
@property
def parameters(self):
"""Dictionairy of component parameters (attributes).
:class:`~collections.abc.Mapping` of the entity's parameter names
to its respective values.
Note
----
Redundant to :attr:`~AbstractEsComponent.attributes`.
Interface designed for engineers since they would call these values
``parameters``, whereas in python ``attributes`` would be the more
accurate terminology.
"""
return self.attributes
@property
def uid(self):
"""Component identifier as Uid namedtuples.
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as a `hashable
<https://docs.python.org/3/library/collections.abc.html#collections.abc.Hashable>`_
unique identifier.
"""
return self._uid
[docs]class Bus(AbstractEsComponent):
r"""
Entities only concerned with input and output names.
Parameters
----------
name: ~collections.abc.Hashable
Identifier. Usually a string, aka a name.
inputs: ~collections.abc.Iterable
An iterable of str(
:paramref:`AbstractEsComponent.uid<AbstractEsComponent.uid>`).output/
input strings specifying the bus-entity's inputs.
e.g.::
['node1.electricity', 'node2.electricity',]
Note
----
Take a look at :attr:`tessif.frused.namedtuples.Uid` as well as
:attr:`~tessif.frused.configurations.node_uid_style` and
:attr:`node_uid_styles` for more details on the uid's string
representation. Especially when having issues with non-unique uid
solver issues.
outputs: ~collections.abc.Iterable
An iterable of str(
:paramref:`AbstractEsComponent.uid<AbstractEsComponent.uid>`).output/
input strings specifying the bus-entity's inputs.
e.g.::
['node1.electricity', 'node2.electricity',]
Note
----
Take a look at :attr:`tessif.frused.namedtuples.Uid` as well as
:attr:`~tessif.frused.configurations.node_uid_style` and
:attr:`node_uid_styles` for more details on the uid's string
representation. Especially when having issues with non-unique uid
solver issues.
Note
----
All following arguments are considered optional parameters and are
provided using \*\*kwargs.
Parameters
----------
latitude: ~numbers.Number
Geospatial latitude in degree. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.latitude<tessif.frused.namedtuples.Uid.latitude>`
longitude: ~numbers.Number
Geospatial longitude in degree. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.longitude<tessif.frused.namedtuples.Uid.longitude>`
region: str
Arbitrary regional categorization string. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.region<tessif.frused.namedtuples.Uid.region>`
sector: str
Arbitrary sector categorization string.Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.sector<tessif.frused.namedtuples.Uid.sector>`
carrier: str
Arbitrary energy carrier categorization string.Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.carrier<tessif.frused.namedtuples.Uid.carrier>`
component: str
String specifying the components type. Used for post-processing.
node_type: str
Arbitrary node type categorization string. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.node_type<tessif.frused.namedtuples.Uid.node_type>`
Warning
-------
The 6 beforneamed arguments together with :paramref:`~Bus.name` form an
id as :attr:`tessif.frused.namedtuples.Uid` object. This
:attr:`~tessif.frused.namedtuples.Uid` object as well as its string
representation (str(:attr:`~tessif.frused.namedtuples.Uid`)) must
be unique.
The string representation can be tweaked using
:attr:`~tessif.frused.configurations.node_uid_style`. But in total
the overall combination of these parameters must be unique and will form
the components hashable uid (unique identifier)
"""
def __init__(self, name, inputs, outputs, *args, **kwargs):
self._inputs = frozenset(inputs)
self._outputs = frozenset(outputs)
self._interfaces = self._inputs.union(self._outputs)
self._parameters_and_defaults = {
"singular_values": {},
"singular_value_mappings": {},
"namedtuples": {},
"mapped_namedtuples": {},
"timeseries": es_defaults["timeseries"],
}
super().__init__(name, *args, **kwargs)
@property
def inputs(self):
"""Frozenset of input uids.
:class:`Hashable<collections.abc.Hashable>` container of str(
:paramref:`uid<AbstractEsComponent.uid>`).output/input strings
representing the inputs.
"""
return self._inputs
@property
def outputs(self):
"""Frozenset of output uids.
:class:`Hashable<collections.abc.Hashable>` container of str(
:paramref:`uid<AbstractEsComponent.uid>`).output/input strings
representing the outputs.
"""
return self._outputs
[docs]class Connector(AbstractEsComponent):
r"""
Entities only concerned with connecting 2 busses.
Parameters
----------
name: ~collections.abc.Hashable
Identifier. Usually a string, aka a name.
interfaces: ~collections.abc.Collection
Collection of length 2 specifying the bus entity's interfaces to
connect to. Takes the form of (str(:paramref:`Bus.uid`),
str(:paramref:`Bus.uid`)) e.g.::
('my_bus_001', 'my_bus_002')
['my_bus_001', 'my_bus_002']
Note
----
Take a look at :attr:`tessif.frused.namedtuples.Uid` as well as
:attr:`~tessif.frused.configurations.node_uid_style` and
:attr:`node_uid_styles` for more details on the uid's string
representation. Especially when having issues with non-unique uid
Note
----
All following arguments are considered optional parameters and are
provided using \*\*kwargs.
Parameters
----------
latitude: ~numbers.Number
Geospatial latitude in degree. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.latitude<tessif.frused.namedtuples.Uid.latitude>`
longitude: ~numbers.Number
Geospatial longitude in degree. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.longitude<tessif.frused.namedtuples.Uid.longitude>`
region: str
Arbitrary regional categorization string. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.region<tessif.frused.namedtuples.Uid.region>`
sector: str
Arbitrary sector categorization string.Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.sector<tessif.frused.namedtuples.Uid.sector>`
carrier: str
Arbitrary energy carrier categorization string.Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.carrier<tessif.frused.namedtuples.Uid.carrier>`
component: str
String specifying the components type. Used for post-processing.
node_type: str
Arbitrary node type categorization string. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.node_type<tessif.frused.namedtuples.Uid.node_type>`
Warning
-------
The 6 beforneamed arguments together with :paramref:`~Connector.name` form
an id as :attr:`tessif.frused.namedtuples.Uid` object. This
:attr:`~tessif.frused.namedtuples.Uid` object as well as its string
representation (str(:attr:`~tessif.frused.namedtuples.Uid`)) must
be unique.
The string representation can be tweaked using
:attr:`~tessif.frused.configurations.node_uid_style`. But in total
the overall combination of these parameters must be unique and will form
the components hashable uid (unique identifier)
Parameters
----------
conversions: ~collections.abc.Mapping
Mapping of connector relevant (str(input.uid), str(output.uid)) tuples
to their respective conversion efficiency. With recognized conversion
efficiencies between 0 and 1 (:math:`\left[0, 1\right]`) e.g.::
{('bus1', 'bus2'): 0.4,
('bus2', 'bus1'): 1,}
Is interpreted in a way that for 1 quantity flowing from 'bus1' to
'bus2', 2.5 (1/0.4) quantities are needed. For 1 quantity flowing
from 'bus2' to 'bus1' on the other hand only 1 quantity is nedded.
Example
-------
Default parameterized :class:`Connector` object:
>>> from tessif.components import Connector
>>> connector = Connector(name='my_connector',
... interfaces=('bus_01', 'bus_02'))
>>> print(connector.uid)
my_connector
>>> for k, v in connector.attributes.items():
... print('{} = {}'.format(
... k, sorted(v) if isinstance(v, frozenset) else v))
... # Frozensets are transformed to sorted lists for doctesting consistency
conversions = {('bus_01', 'bus_02'): 1.0, ('bus_02', 'bus_01'): 1.0}
inputs = ['bus_01', 'bus_02']
interfaces = ['bus_01', 'bus_02']
outputs = ['bus_01', 'bus_02']
timeseries = None
uid = my_connector
"""
def __init__(self, name, interfaces, *args, **kwargs):
self._inputs = frozenset(interfaces)
self._outputs = frozenset(interfaces)
self._interfaces = frozenset(interfaces)
_connections = tuple(interfaces)
if kwargs.get("conversions", None) is None:
self._conversions = {
_connections: es_defaults["efficiency"],
tuple(reversed(_connections)): es_defaults["efficiency"],
}
else:
self._conversions = kwargs.get("conversions")
self._parameters_and_defaults = {
"singular_values": {},
"singular_value_mappings": {},
"namedtuples": {},
"mapped_namedtuples": {},
"timeseries": es_defaults["timeseries"],
}
super().__init__(name, *args, **kwargs)
@property
def inputs(self):
"""Frozenset of input uids.
:class:`Hashable<collections.abc.Hashable>` container of str(
:paramref:`uid<Bus.uid>`).output/input strings
representing the inputs.
"""
return self._inputs
@property
def outputs(self):
"""Frozenset of output uids.
:class:`Hashable<collections.abc.Hashable>` container of str(
:paramref:`uid<Bus.uid>`).output/input strings
representing the outputs.
"""
return self._outputs
@property
def conversions(self):
"""Dictionairy of conversion efficiencies.
:class:`~collections.abc.Mapping` of connector relevant (input-name,
output-name) tuples to their respective conversion efficiency. With
recognized conversion efficiencies between 0 and 1.
"""
return self._conversions
[docs]class Source(AbstractEsComponent):
r"""
Entities only concerned with their outputs and related attributes.
Parameters
----------
name: ~collections.abc.Hashable
Identifier. Usually a string, aka a name.
outputs: ~collections.abc.Iterable
An iterable of hashable unique identifiers. Usually strings, aka names
specifying the source-entity's outputs. e.g.::
('fuel',)
Note
----
All following arguments are considered optional parameters and are
provided using \*\*kwargs.
Parameters
----------
latitude: ~numbers.Number
Geospatial latitude in degree. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.latitude<tessif.frused.namedtuples.Uid.latitude>`
longitude: ~numbers.Number
Geospatial longitude in degree. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.longitude<tessif.frused.namedtuples.Uid.longitude>`
region: str
Arbitrary regional categorization string. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.region<tessif.frused.namedtuples.Uid.region>`
sector: str
Arbitrary sector categorization string.Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.sector<tessif.frused.namedtuples.Uid.sector>`
carrier: str
Arbitrary energy carrier categorization string.Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.carrier<tessif.frused.namedtuples.Uid.carrier>`
component: str
String specifying the components type. Used for post-processing.
node_type: str
Arbitrary node type categorization string. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.node_type<tessif.frused.namedtuples.Uid.node_type>`
Warning
-------
The 6 beforneamed arguments together with :paramref:`~Source.name` form an
id as :attr:`tessif.frused.namedtuples.Uid` object. This
:attr:`~tessif.frused.namedtuples.Uid` object as well as its string
representation (str(:attr:`~tessif.frused.namedtuples.Uid`)) must
be unique.
The string representation can be tweaked using
:attr:`~tessif.frused.configurations.node_uid_style`. But in total
the overall combination of these parameters must be unique and will form
the components hashable uid (unique identifier)
Parameters
----------
accumulated_amounts: ~collections.abc.Mapping
Mapping of each :paramref:`output name <Source.outputs>` to a
:class:`~tessif.frused.namedtuples.MinMax`
:class:`~typing.NamedTuple` describing the minimum/maximum quantity
the entity's outflow has available (in total).
Meaning the total sum of a particular outflow happening during the
simulated time period is less equal than
:paramref:`~Source.accumulated_amounts` [output_name].max
and greater equal than :paramref:`~Source.accumulated_amounts`
[output_name].min
**Default**::
{key: MinMax(0, float('+inf')) for key in outputs}
flow_rates: ~collections.abc.Mapping
Mapping of each :paramref:`output name<Source.outputs>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum amount per time.
Meaning each flow going out during one discrete timestep is greater
equal than the minimum amount per time and less equal than the maximum
amount per time mapped to its name.
**Default**::
{key: MinMax(0, float('+inf')) for key in outputs}
flow_costs: ~collections.abc.Mapping
Mapping of each :paramref:`output name<Source.outputs>` to a
:class:`~numbers.Number` specifying its cost.
Meaning for each amount per time that is going out this amount of
cost-unit is taken into account (by the solver).
**Default**::
{key: 0 for key in outputs}
flow_emissions: ~collections.abc.Mapping
Mapping of each :paramref:`output name<Source.outputs>` to a
:class:`~numbers.Number` specifying its emission.
Meaning for each amount per time that is going out this amount of
emission-unit is taken into account (by the solver).
**Default**::
{key: 0 for key in outputs}
Note
----
This unit primarily serves as system wide constrain parameter as in
'All emissions must remain below 100 units'.
flow_gradients: ~collections.abc.Mapping
Mapping of each :paramref:`output name<Source.outputs>` to a
:class:`~tessif.frused.namedtuples.PositiveNegative` tuple describing
the maximum positive or negative change between two following
timesteps.
Meaning each flow amount increase/decrease between two
following discrete timesteps is less equal than the maximum change
mapped to its name.
**Default**::
{k: PositiveNegative(float('+inf'),float('+inf')) for k in outputs}
gradient_costs: ~collections.abc.Mapping
Mapping of each :paramref:`output name<Source.outputs>` to a
:class:`~tessif.frused.namedtuples.PositiveNegative` tuple describing
the costs for the respective
:paramref:`~Source.flow_gradients`.
Meaning for each unit
of change of its mapped :paramref:`~Source.flow_rates` this
amount of cost-unit is taken into account (by the solver).
default::
{k: PositiveNegative(0, 0) for k in outputs}
timeseries: ~collections.abc.Mapping, default=None
Mapping an arbitrary number of :paramref:`output names<Source.outputs>`
to a :class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum :paramref:`flow_rates` respectively. For Example
Setting the maximum :paramref:`flow_rate <flow_rates>`::
import numpy as np
timeseries = {'input_bus': MinMax(
min=0, max=np.array([10, 42]))}
Setting the minimum :paramref:`flow_rate <flow_rates>`::
import numpy as np
timeseries = {'input_bus': MinMax(
min=np.array([1, 2]), max=float('+inf'))}
Fixing the :paramref:`flow_rate <flow_rates>` to a certain timeseries::
import numpy as np
timeseries = {input_bus': MinMax(
min=np.array([1, 2]), max=np.array([1, 2]))}
expandable: ~collections.abc.Mapping
Mapping of each :paramref:`output name<Source.outputs>` to a
boolean variable describing if the mapped
:paramref:`Source.flow_rates` value can be increased by the
solver or not.
**Default**::
{key: False for key in outputs}
expansion_costs: ~collections.abc.Mapping
Mapping of each :paramref:`output name<Source.outputs>` to a
:class:`~numbers.Number` specifying its expansion cost.
Meaning for each unit the maximum of the mapped
:paramref:`amount per time<Source.flow_rates>` is increased
(by the solver) this amount of cost-unit is taken into account
(by the solver).
**Default**::
{key: 0 for key in outputs}
expansion_limits: ~collections.abc.Mapping
Mapping of each :paramref:`output name<Source.outputs>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum expansion limit.
Meaning the actual increase of the mapped
:paramref:`amount per time<Source.flow_rates>` will be somwhere
between the given minimum and maximum.
(:math:`\left[\text{min}, \text{max}\right]`)
**Default**::
{key: MinMax(0, float('+inf')) for key in outputs}
Note
----
Providing non default parameters for the following set of arguments will
cause the optimization problem to most likely turn into a
`Mixed Integer Linear Problem
<https://en.wikipedia.org/wiki/Integer_programming#Variants>`_
Parameters
----------
milp: ~collections.abc.Mapping
Mapping of each :attr:`output name<Source.outputs>` to a boolean
variable describing if the mapped :attr:`Source.flow_rates` parameter
can be subject to mixed integer linear constraints.
**Default**::
{key: False, for key in outputs}
warning
-------
If :paramref:`~Source.milp` evaluates to ``False`` following set of
parameters is most likely ignored during optimization.
initial_status: bool, default=True
Status variable, indicating if the entity is running, operating,
working, doing the things its supposed to do, in the beginning
of the evaluated timeframe.
status_inertia: ~tessif.frused.namedtuples.OnOff
An :class:`~tessif.frused.namedtuples.OnOff`
:class:`~typing.NamedTuple` describing the minimum uptime and downtime.
With up and downtime describing the minimum amount of following
discrete timesteps the entity as to be operating or standing stil
respectively.
**Default**::
OnOff(0, 0)
status_changing_costs: ~tessif.frused.namedtuples.OnOff
An :class:`~tessif.frused.namedtuples.OnOff`
:class:`~typing.NamedTuple` describing the cost for changing status
from ``on`` to ``off`` and from ``off`` to ``on`` respectively
**Default**::
OnOff(0, 0)
number_of_status_changes: ~tessif.frused.namedtuples.OnOff
:class:`~typing.NamedTuple` describing the number of times the entity
can change its status from ``on`` to ``off`` and from ``off`` to ``on``
respectively.
**Default**::
OnOff(float('+inf'), float('+inf'))
costs_for_being_active: :class:`~number.Number`, default = 0
Costs for not being inactive.
Meaning for each discrete time step the entity's boolean status
variable is ``True``, this amount of cost units is taken in to account
(by the solver).
Example
-------
Default parameterized :class:`Source` object:
>>> from tessif.components import Source
>>> source = Source(name='my_source', outputs=('fuel',))
>>> print(source.uid)
my_source
>>> print(source.outputs)
frozenset({'fuel'})
>>> for k, v in source.attributes.items():
... print('{} = {}'.format(
... k, sorted(v) if isinstance(v, frozenset) else v))
... # Frozensets are transformed to sorted lists for doctesting consistency
accumulated_amounts = {'fuel': MinMax(min=0.0, max=inf)}
costs_for_being_active = 0.0
expandable = {'fuel': False}
expansion_costs = {'fuel': 0.0}
expansion_limits = {'fuel': MinMax(min=0.0, max=inf)}
flow_costs = {'fuel': 0.0}
flow_emissions = {'fuel': 0.0}
flow_gradients = {'fuel': PositiveNegative(positive=inf, negative=inf)}
flow_rates = {'fuel': MinMax(min=0.0, max=inf)}
gradient_costs = {'fuel': PositiveNegative(positive=0.0, negative=0.0)}
initial_status = 1
interfaces = ['fuel']
milp = {'fuel': False}
number_of_status_changes = OnOff(on=inf, off=inf)
outputs = ['fuel']
status_changing_costs = OnOff(on=0.0, off=0.0)
status_inertia = OnOff(on=0, off=0)
timeseries = None
uid = my_source
"""
def __init__(self, name, outputs, *args, **kwargs):
self._outputs = frozenset(o for o in outputs)
self._interfaces = self._outputs
self._parameters_and_defaults = {
"singular_values": {
"initial_status": es_defaults["initial_status"],
"costs_for_being_active": es_defaults["costs_for_being_active"],
},
"singular_value_mappings": {
"flow_costs": {
key: es_defaults["flow_costs"] for key in sorted(self._interfaces)
},
"flow_emissions": {
key: es_defaults["emissions"] for key in sorted(self._interfaces)
},
"expandable": {
key: es_defaults["expandable"] for key in sorted(self._interfaces)
},
"expansion_costs": {
key: es_defaults["expansion_costs"]
for key in sorted(self._interfaces)
},
"milp": {key: es_defaults["milp"] for key in sorted(self._interfaces)},
},
"namedtuples": {
"MinMax": {},
"OnOff": {
"status_inertia": nts.OnOff(
es_defaults["minimum_uptime"], es_defaults["minimum_downtime"]
),
"status_changing_costs": nts.OnOff(
es_defaults["startup_costs"], es_defaults["shutdown_costs"]
),
"number_of_status_changes": nts.OnOff(
es_defaults["maximum_startups"],
es_defaults["maximum_shutdowns"],
),
},
},
"mapped_namedtuples": {
"MinMax": {
"expansion_limits": {
key: nts.MinMax(
es_defaults["minimum_expansion"],
es_defaults["maximum_expansion"],
)
for key in sorted(self._interfaces)
},
"flow_rates": {
key: nts.MinMax(
es_defaults["minimum_flow_rate"],
es_defaults["maximum_flow_rate"],
)
for key in sorted(self._interfaces)
},
"accumulated_amounts": {
key: nts.MinMax(
es_defaults["accumulated_minimum"],
es_defaults["accumulated_maximum"],
)
for key in sorted(self._interfaces)
},
},
"PositiveNegative": {
"flow_gradients": {
key: nts.PositiveNegative(
es_defaults["positive_gradient"],
es_defaults["negative_gradient"],
)
for key in sorted(self._interfaces)
},
"gradient_costs": {
key: nts.PositiveNegative(
es_defaults["positive_gradient_costs"],
es_defaults["negative_gradient_costs"],
)
for key in sorted(self._interfaces)
},
},
},
}
super().__init__(name, *args, **kwargs)
@property
def outputs(self):
"""Frozenset of output uids.
:class:`Hashable<collections.abc.Hashable>` container of
hashable unique identifiers. Usually a string aka a name representing
the outputs.
"""
return self._outputs
@property
def accumulated_amounts(self):
"""Dictionairy of accumulated amount constraints.
:class:`~collections.abc.Mapping`
of each :paramref:`output name <Source.outputs>` to a
:class:`~tessif.frused.namedtuples.MinMax`
:class:`~typing.NamedTuple` describing the minimum/maximum quantity
the entity's outflow has available (in total).
Meaning the total sum of a particular outflow happening during the
simulated time period is less equal
than :paramref:`~Source.accumulated_amounts` [output_name].max and
greater equal than :paramref:`~Source.accumulated_amounts`
[output_name].min
"""
return self._accumulated_amounts
@property
def flow_rates(self):
"""Dictionairy of flow rate constraints.
:class:`~collections.abc.Mapping`
of each :attr:`output name<Source.outputs>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum amount per time.
Meaning each flow going out during one discrete timestep is greater
equal than the minimum amount per time and less equal than the maximum
amount per time mapped to its name.
"""
return self._flow_rates
@property
def flow_costs(self):
"""Dictionairy of flow cost constraints.
:class:`~collections.abc.Mapping`
of each :attr:`output name<Source.outputs>` to a
:class:`~numbers.Number` specifying its cost.
Meaning for each amount per time that is going out this amount of
cost-unit is taken into account (by the solver).
"""
return self._flow_costs
@property
def flow_emissions(self):
"""Dictionairy of flow emission constraints.
:class:`~collections.abc.Mapping`
of each :attr:`output name<Source.outputs>` to a
:class:`~numbers.Number` specifying its emissions
Meaning for each amount per time that is going out this amount of
emission-unit is taken into account (by the solver).
Note
----
This unit primarily serves as system wide constrain parameter as in
'All emissions must remain below 100 units'.
"""
return self._flow_emissions
@property
def flow_gradients(self):
"""Dictionairy of flow gradient constraints.
:class:`~collections.abc.Mapping`
of each :attr:`output name<Source.outputs>` to a
:class:`~tessif.frused.namedtuples.PositiveNegative` tuple describing
the maximum positive or negative change between two following
timesteps.
Meaning each flow amount increase/decrease between two
following discrete timesteps is less equal than the maximum change
mapped to its name.
"""
return self._flow_gradients
@property
def gradient_costs(self):
"""Dictionairy of flow gradient costs.
:class:`~collections.abc.Mapping`
Mapping of each :paramref:`output name<Source.outputs>` to a
:class:`~tessif.frused.namedtuples.PositiveNegative` tuple describing
the costs for the respective
:attr:`~Source.flow_gradients`.
Meaning for each unit
of change of its mapped :paramref:`~Source.flow_rates` this
amount of cost-unit is taken into account (by the solver).
"""
return self._gradient_costs
@property
def timeseries(self):
"""Tuple of flow_rates constraining timeserieses.
:class:`~collections.abc.Mapping` of an arbitrary number
of :attr:`output names<Source.outputs>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum :paramref:`flow_rates` respectively.
"""
return self._timeseries
@property
def expandable(self):
"""True, if maximum flow_rate is subject to solver changes.
:class:`~collections.abc.Mapping`
of each :attr:`output name<Source.outputs>` to a boolean variable
describing if the mapped :attr:`Source.flow_rates` value can be
increased by the solver or not.
"""
return self._expandable
@property
def expansion_costs(self):
"""Dictionairy of maximum flow rate expansion costs.
:class:`~collections.abc.Mapping`
of each :attr:`output name<Source.outputs>` to a
:class:`~numbers.Number` specifying its expansion cost.
Meaning for each unit the maximum of the mapped
:attr:`amount per time<Source.flow_rates>` is increased
(by the solver) this amount of cost-unit is taken into account
(by the solver).
"""
return self._expansion_costs
@property
def expansion_limits(self):
r"""Dictionairy of maximum flow rate expansion limits.
:class:`~collections.abc.Mapping`
of each :attr:`output name<Source.outputs>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum expansion limit.
Meaning the actual increase of the mapped
:attr:`amount per time<Source.flow_rates>` will be somewhere
between the given minimum and maximum.
(:math:`\left[\text{min}, \text{max}\right]`)
"""
return self._expansion_limits
@property
def initial_status(self):
"""True, if component is milp constrained and active at first time step.
:class:`Status variable<bool>`, indicating if the entity is
running, operating, working, doing the things its supposed to do in
the beginning of the evaluated timeframe.
"""
return self._initial_status
@property
def status_inertia(self):
"""Tuple of status-inertia (milp) constraints.
:class:`~tessif.frused.namedtuples.OnOff` :class:`~typing.NamedTuple`
describing the minimum uptime and downtime. With up and downtime
describing the minimum amount of following discrete timesteps the
entity as to be operating or standing still respectively.
"""
return self._status_inertia
@property
def status_changing_costs(self):
"""Tuple of status-changing (milp) constraints.
:class:`~tessif.frused.namedtuples.OnOff` :class:`~typing.NamedTuple`
describing the cost for changing status from ``on`` to ``off`` and
from ``off`` to ``on`` respectively.
"""
return self._status_changing_costs
@property
def number_of_status_changes(self):
"""Tuple of allowed milp status changing constraints.
An :class:`~tessif.frused.namedtuples.OnOff`
:class:`~typing.NamedTuple` describing the number of times the entity
can change its status from ``on`` to ``off`` and from ``off`` to ``on``
respectively.
"""
return self._number_of_status_changes
@property
def costs_for_being_active(self):
"""Milp constained activity costs.
A :class:`~number.Number`, default = 0
Describing the costs for not being inactive.
Meaning for each discrete time step the entity's boolean status
variable is ``True``, this amount of cost units is taken in to account
(by the solver).
"""
return self._costs_for_being_active
[docs]class Sink(AbstractEsComponent):
r"""
Entities only concerned with their inputs and related attributes.
Parameters
----------
name: ~collections.abc.Hashable
Identifier. Usually a string, aka a name.
inputs: ~collections.abc.Iterable
An iterable of hashable unique identifiers. Usually strings, aka names
specifying the source-entity's inputs. e.g.::
('fuel',)
Note
----
All following arguments are considered optional parameters and are
provided using \*\*kwargs.
Parameters
----------
latitude: ~numbers.Number
Geospatial latitude in degree. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.latitude<tessif.frused.namedtuples.Uid.latitude>`
longitude: ~numbers.Number
Geospatial longitude in degree. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.longitude<tessif.frused.namedtuples.Uid.longitude>`
region: str
Arbitrary regional categorization string. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.region<tessif.frused.namedtuples.Uid.region>`
sector: str
Arbitrary sector categorization string.Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.sector<tessif.frused.namedtuples.Uid.sector>`
carrier: str
Arbitrary energy carrier categorization string.Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.carrier<tessif.frused.namedtuples.Uid.carrier>`
component: str
String specifying the components type. Used for post-processing.
node_type: str
Arbitrary node type categorization string. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.node_type<tessif.frused.namedtuples.Uid.node_type>`
Warning
-------
The 6 beforneamed arguments together with :paramref:`~Sink.name` form an id
as a :attr:`tessif.frused.namedtuples.Uid` object. This
:attr:`~tessif.frused.namedtuples.Uid` object as well as its string
representation (str(:attr:`~tessif.frused.namedtuples.Uid`)) must
be unique.
The string representation can be tweaked using
:attr:`~tessif.frused.configurations.node_uid_style`. But in total
the overall combination of these parameters must be unique and will form
the components hashable uid (unique identifier)
Parameters
----------
accumulated_amounts: ~collections.abc.Mapping
Mapping of each :paramref:`input name <Sink.inputs>` to a
:class:`~tessif.frused.namedtuples.MinMax`
:class:`~typing.NamedTuple` describing the minimum/maximum quantity
the entity's inflow has available (in total).
Meaning the total sum of a particular inflow happening during the
simulated time period is less equal than
:paramref:`~Sink.accumulated_amounts` [input_name].max
and greater equal than :paramref:`~Sink.accumulated_amounts`
[input_name].min
**Default**::
{key: MinMax(0, float('+inf')) for key in inputs}
flow_rates: ~collections.abc.Mapping
Mapping of each :paramref:`input name<Sink.inputs>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum amount per time.
Meaning each flow going in during one discrete timestep is greater
equal than the minimum amount per time and less equal than the maximum
amount per time mapped to its name.
**Default**::
{key: MinMax(0, float('+inf')) for key in inputs}
flow_costs: ~collections.abc.Mapping
Mapping of each :paramref:`input name<Sink.inputs>` to a
:class:`~numbers.Number` specifying its cost.
Meaning for each amount per time that is going in this amount of
cost-unit is taken into account (by the solver).
**Default**::
{key: 0 for key in inputs}
flow_emissions: ~collections.abc.Mapping
Mapping of each :paramref:`input name<Sink.inputs>` to a
:class:`~numbers.Number` specifying its emission.
Meaning for each amount per time that is going in this amount of
emission-unit is taken into account (by the solver).
**Default**::
{key: 0 for key in inputs}
Note
----
This unit primarily serves as system wide constrain parameter as in
'All emissions must remain below 100 units'.
flow_gradients: ~collections.abc.Mapping
Mapping of each :paramref:`input name<Sink.inputs>` to a
:class:`~tessif.frused.namedtuples.PositiveNegative` tuple describing
the maximum positive or negative change between two following
timesteps.
Meaning each flow amount increase/decrease between two
following discrete timesteps is less equal than the maximum change
mapped to its name.
**Default**::
k: PositiveNegative(float('+inf'),float('+inf')) for k in inputs}
gradient_costs: ~collections.abc.Mapping
Mapping of each :paramref:`input name<Sink.inputs>` to a
:class:`~tessif.frused.namedtuples.PositiveNegative` tuple describing
the costs for the respective
:paramref:`~Sink.flow_gradients`.
Meaning for each unit
of change of its mapped :paramref:`~Sink.flow_rates` this
amount of cost-unit is taken into account (by the solver).
**Default**::
{k: PositiveNegative(0, 0) for k in inputs}
timeseries: ~collections.abc.Mapping, default=None
Mapping an arbitrary number of :paramref:`input names<Sink.inputs>`
to a :class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum :paramref:`flow_rates` respectively. For Example
Setting the maximum :paramref:`flow_rate <flow_rates>`::
import numpy as np
timeseries = {'input_bus': MinMax(
min=0, max=np.array([10, 42]))}
Setting the minimum :paramref:`flow_rate <flow_rates>`::
import numpy as np
timeseries = {'input_bus': MinMax(
min=np.array([1, 2]), max=float('+inf'))}
Fixing the :paramref:`flow_rate <flow_rates>` to a certain timeseries::
import numpy as np
timeseries = {input_bus': MinMax(
min=np.array([1, 2]), max=np.array([1, 2]))}
expandable: ~collections.abc.Mapping
Mapping of each :paramref:`input name<Sink.inputs>` to a
boolean variable describing if the mapped
:paramref:`Sink.flow_rates` value can be increased by the
solver or not.
**Default**::
{key: False for key in inputs}
expansion_costs: ~collections.abc.Mapping
Mapping of each :paramref:`input name<Sink.inputs>` to a
:class:`~numbers.Number` specifying its expansion cost.
Meaning for each unit the maximum of the mapped
:paramref:`amount per time<Sink.flow_rates>` is increased
(by the solver) this amount of cost-unit is taken into account
(by the solver).
**Default**::
{key: 0 for key in inputs}
expansion_limits: ~collections.abc.Mapping
Mapping of each :paramref:`input name<Sink.inputs>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum expansion limit.
Meaning the actual increase of the mapped
:paramref:`amount per time<Sink.flow_rates>` will be somwhere
between the given minimum and maximum.
(:math:`\left[\text{min}, \text{max}\right]`)
**Default**::
{key: MinMax(0, float('+inf')) for key in inputs}
Note
----
Providing non default parameters for the following set of arguments will
cause the optimization problem to most likely turn into a
`Mixed Integer Linear Problem
<https://en.wikipedia.org/wiki/Integer_programming#Variants>`_
Parameters
----------
milp: ~collections.abc.Mapping
Mapping of each :attr:`output name<Sink.inputs>` to a boolean variable
describing if the mapped :attr:`Sink.flow_rates` parameter can be
subject to mixed integer linear constraints.
**Default**::
{key: False for key in inputs}
warning
-------
If :paramref:`~Sink.milp` evaluates to ``False`` following set of
parameters is most likely ignored during optimization.
initial_status: bool, default=True
Status variable, indicating if the entity is running, operating,
working, doing the things its supposed to do, in the beginning
of the evaluated timeframe.
status_inertia: ~tessif.frused.namedtuples.OnOff
An :class:`~tessif.frused.namedtuples.OnOff`
:class:`~typing.NamedTuple` describing the minimum uptime and downtime.
With up and downtime describing the minimum amount of following
discrete timesteps the entity as to be operating or standing stil
respectively.
**Default**::
OnOff(0, 0)
status_changing_costs: ~tessif.frused.namedtuples.OnOff
An :class:`~tessif.frused.namedtuples.OnOff`
:class:`~typing.NamedTuple` describing the cost for changing status
from ``on`` to ``off`` and from ``off`` to ``on`` respectively.
**Default**::
OnOff(0, 0)
number_of_status_changes: ~tessif.frused.namedtuples.OnOff
:class:`~typing.NamedTuple` describing the number of times the entity
can change its status from ``on`` to ``off`` and from ``off`` to ``on``
respectively.
**Default**::
OnOff(float('+inf'), float('+inf'))
costs_for_being_active: :class:`~number.Number`, default = 0
Costs for not being inactive.
Meaning for each discrete time step the entity's boolean status
variable is ``True``, this amount of cost units is taken in to account
(by the solver).
Example
-------
Default parameterized :class:`Sink` object:
>>> from tessif.components import Sink
>>> sink = Sink(name='my_sink', inputs=('electricity',))
>>> print(sink.uid)
my_sink
>>> print(sink.inputs)
frozenset({'electricity'})
Accessing all its :attr:`~Sink.attributes`:
>>> for k, v in sink.attributes.items():
... print('{} = {}'.format(
... k, sorted(v) if isinstance(v, frozenset) else v))
... # frozensets are transformed to sorted lists for doctesting consistency
accumulated_amounts = {'electricity': MinMax(min=0.0, max=inf)}
costs_for_being_active = 0.0
expandable = {'electricity': False}
expansion_costs = {'electricity': 0.0}
expansion_limits = {'electricity': MinMax(min=0.0, max=inf)}
flow_costs = {'electricity': 0.0}
flow_emissions = {'electricity': 0.0}
flow_gradients = {'electricity': PositiveNegative(positive=inf, negative=inf)}
flow_rates = {'electricity': MinMax(min=0.0, max=inf)}
gradient_costs = {'electricity': PositiveNegative(positive=0.0, negative=0.0)}
initial_status = 1
inputs = ['electricity']
interfaces = ['electricity']
milp = {'electricity': False}
number_of_status_changes = OnOff(on=inf, off=inf)
status_changing_costs = OnOff(on=0.0, off=0.0)
status_inertia = OnOff(on=0, off=0)
timeseries = None
uid = my_sink
"""
def __init__(self, name, inputs, *args, **kwargs):
self._inputs = frozenset(inputs)
self._interfaces = self._inputs
# modify this dict for adding additional parameters
self._parameters_and_defaults = {
"singular_values": {
"initial_status": es_defaults["initial_status"],
"costs_for_being_active": es_defaults["costs_for_being_active"],
},
"singular_value_mappings": {
"flow_costs": {
key: es_defaults["flow_costs"] for key in sorted(self._interfaces)
},
"flow_emissions": {
key: es_defaults["emissions"] for key in sorted(self._interfaces)
},
"expandable": {
key: es_defaults["expandable"] for key in sorted(self._interfaces)
},
"expansion_costs": {
key: es_defaults["expansion_costs"]
for key in sorted(self._interfaces)
},
"milp": {key: es_defaults["milp"] for key in sorted(self._interfaces)},
},
"namedtuples": {
"MinMax": {},
"OnOff": {
"status_inertia": nts.OnOff(
es_defaults["minimum_uptime"], es_defaults["minimum_downtime"]
),
"status_changing_costs": nts.OnOff(
es_defaults["startup_costs"], es_defaults["shutdown_costs"]
),
"number_of_status_changes": nts.OnOff(
es_defaults["maximum_startups"],
es_defaults["maximum_shutdowns"],
),
},
},
"mapped_namedtuples": {
"MinMax": {
"expansion_limits": {
key: nts.MinMax(
es_defaults["minimum_expansion"],
es_defaults["maximum_expansion"],
)
for key in sorted(self._interfaces)
},
"flow_rates": {
key: nts.MinMax(
es_defaults["minimum_flow_rate"],
es_defaults["maximum_flow_rate"],
)
for key in sorted(self._interfaces)
},
"accumulated_amounts": {
key: nts.MinMax(
es_defaults["accumulated_minimum"],
es_defaults["accumulated_maximum"],
)
for key in sorted(self._interfaces)
},
},
"PositiveNegative": {
"flow_gradients": {
key: nts.PositiveNegative(
es_defaults["positive_gradient"],
es_defaults["negative_gradient"],
)
for key in sorted(self._interfaces)
},
"gradient_costs": {
key: nts.PositiveNegative(
es_defaults["positive_gradient_costs"],
es_defaults["negative_gradient_costs"],
)
for key in sorted(self._interfaces)
},
},
},
"timeseries": es_defaults["timeseries"],
}
super().__init__(name, *args, **kwargs)
@property
def inputs(self):
"""Frozenset of input uids.
:class:`Hashable<collections.abc.Hashable>` container of
hashable unique identifiers. Usually a string aka a name representing
the inputs.
"""
return self._inputs
@property
def accumulated_amounts(self):
"""Dictionairy of accumulated amount constraints.
:class:`~collections.abc.Mapping` of each
:attr:`input name<Sink.inputs>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum/maximum quantity the entity's inflow has available (in total).
Meaning the total sum of a particular inflow happening during the
simulated time period is less equal than
:paramref:`~Sink.accumulated_amounts` [input_name].max
and greater equal than :paramref:`~Sink.accumulated_amounts`
[input_name].min
"""
return self._accumulated_amounts
@property
def flow_rates(self):
"""Dictionairy of flow rate constraints.
:class:`~collections.abc.Mapping`
of each :attr:`input name<Sink.inputs>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum amount per time.
Meaning each flow going in during one discrete timestep is greater
equal than the minimum amount per time and less equal than the maximum
amount per time mapped to its name.
"""
return self._flow_rates
@property
def flow_costs(self):
"""Dictionairy of flow cost constraints.
:class:`~collections.abc.Mapping`
of each :attr:`input name<Sink.inputs>` to a
:class:`~numbers.Number` specifying its cost.
Meaning for each amount per time that is going in this amount of
cost-unit is taken into account (by the solver).
"""
return self._flow_costs
@property
def flow_emissions(self):
"""Dictionairy of flow emission constraints.
:class:`~collections.abc.Mapping`
of each :attr:`input name<Sink.inputs>` to a
:class:`~numbers.Number` specifying its emissions
Meaning for each amount per time that is going in this amount of
emission-unit is taken into account (by the solver).
Note
----
This unit primarily serves as system wide constrain parameter as in
'All emissions must remain below 100 units'.
"""
return self._flow_emissions
@property
def flow_gradients(self):
"""Dictionairy of flow gradient constraints.
:class:`~collections.abc.Mapping`
of each :attr:`input name<Sink.inputs>` to a
:class:`~tessif.frused.namedtuples.PositiveNegative` tuple describing
the maximum positive or negative change between two following
timesteps.
Meaning each flow amount increase/decrease between two
following discrete timesteps is less equal than the maximum change
mapped to its name.
"""
return self._flow_gradients
@property
def gradient_costs(self):
"""Dictionairy of flow gradient costs.
:class:`~collections.abc.Mapping`
Mapping of each :paramref:`input name<Sink.inputs>` to a
:class:`~tessif.frused.namedtuples.PositiveNegative` tuple describing
the costs for the respective
:attr:`~Sink.flow_gradients`.
Meaning for each unit
of change of its mapped :paramref:`~Sink.flow_rates` this
amount of cost-unit is taken into account (by the solver).
"""
return self._gradient_costs
@property
def timeseries(self):
"""Tuple of flow_rates constraining timeserieses.
:class:`~collections.abc.Mapping` of an arbitrary number
of :attr:`input names<Sink.inputs>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum :paramref:`flow_rates` respectively.
"""
return self._timeseries
@property
def expandable(self):
"""True, if maximum flow_rate is subject to solver changes.
:class:`~collections.abc.Mapping`
of each :attr:`input name<Sink.inputs>` to a boolean variable
describing if the mapped :attr:`Sink.flow_rates` value can be
increased by the solver or not.
"""
return self._expandable
@property
def expansion_costs(self):
"""Dictionairy of maximum flow rate expansion costs.
:class:`~collections.abc.Mapping`
of each :attr:`input name<Sink.inputs>` to a
:class:`~numbers.Number` specifying its expansion cost.
Meaning for each unit the maximum of the mapped
:attr:`amount per time<Sink.flow_rates>` is increased
(by the solver) this amount of cost-unit is taken into account
(by the solver).
"""
return self._expansion_costs
@property
def expansion_limits(self):
r"""Dictionairy of maximum flow rate expansion limits.
:class:`~collections.abc.Mapping`
of each :attr:`input name<Sink.inputs>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum expansion limit.
Meaning the actual increase of the mapped
:attr:`amount per time<Sink.flow_rates>` will be somewhere
between the given minimum and maximum.
(:math:`\left[\text{min}, \text{max}\right]`)
"""
return self._expansion_limits
@property
def initial_status(self):
"""True, if component is milp constrained and active at first time step.
:class:`Status variable<bool>`, indicating if the entity is
running, operating, working, doing the things its supposed to do in
the beginning of the evaluated timeframe.
"""
return self._initial_status
@property
def status_inertia(self):
"""Tuple of status-inertia (milp) constraints.
:class:`~tessif.frused.namedtuples.OnOff` :class:`~typing.NamedTuple`
describing the minimum uptime and downtime. With up and downtime
describing the minimum amount of following discrete timesteps the
entity as to be operating or standing still respectively.
"""
return self._status_inertia
@property
def status_changing_costs(self):
"""Tuple of status-changing (milp) constraints.
:class:`~tessif.frused.namedtuples.OnOff` :class:`~typing.NamedTuple`
describing the cost for changing status from ``on`` to ``off`` and
from ``off`` to ``on`` respectively.
"""
return self._status_changing_costs
@property
def number_of_status_changes(self):
"""Tuple of allowed milp status changing constraints.
An :class:`~tessif.frused.namedtuples.OnOff`
:class:`~typing.NamedTuple` describing the number of times the entity
can change its status from ``on`` to ``off`` and from ``off`` to ``on``
respectively.
"""
return self._number_of_status_changes
@property
def costs_for_being_active(self):
"""Milp constained activity costs.
A :class:`~number.Number`, default = 0
Describing the costs for not being inactive.
Meaning for each discrete time step the entity's boolean status
variable is ``True``, this amount of cost units is taken in to account
(by the solver).
"""
return self._costs_for_being_active
[docs]class CHP(Transformer):
r"""Entities that are like Transformers but with additional constraints.
Parameters
----------
name: ~collections.abc.Hashable
Identifier. Usually a string, aka a name.
inputs: ~collections.abc.Iterable
An iterable of hashable unique identifiers. Usually strings, aka
names specifying the chp-entity's inputs. e.g.::
['fuel_1', 'fuel_2']
outputs: ~collections.abc.Iterable
An iterable of hashable unique identifiers. Usually strings, aka
names specifying the chp-entity's outputs. e.g.::
['electricity', 'heat']
Note
----
All following arguments are considered optional parameters and are
provided using \*\*kwargs.
Parameters
----------
latitude: ~numbers.Number
Geospatial latitude in degree. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.latitude<tessif.frused.namedtuples.Uid.latitude>`
longitude: ~numbers.Number
Geospatial longitude in degree. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.longitude<tessif.frused.namedtuples.Uid.longitude>`
region: str
Arbitrary regional categorization string. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.region<tessif.frused.namedtuples.Uid.region>`
sector: str
Arbitrary sector categorization string.Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.sector<tessif.frused.namedtuples.Uid.sector>`
carrier: str
Arbitrary energy carrier categorization string.Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.carrier<tessif.frused.namedtuples.Uid.carrier>`
component: str
String specifying the components type. Used for post-processing.
node_type: str
Arbitrary node type categorization string. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.node_type<tessif.frused.namedtuples.Uid.node_type>`
Warning
-------
The 6 beforenamed arguments together with :paramref:`~CHP.name`
form an id as a :attr:`tessif.frused.namedtuples.Uid` object. This
:attr:`~tessif.frused.namedtuples.Uid` object as well as its string
representation (str(:attr:`~tessif.frused.namedtuples.Uid`)) must
be unique.
The string representation can be tweaked using
:attr:`~tessif.frused.configurations.node_uid_style`. But in total
the overall combination of these parameters must be unique and will form
the components hashable uid (unique identifier)
Parameters
----------
back_pressure: bool,
Boolean to specify if back-pressure characteristics shall be used.
Set to True and Q_CW_min to zero for back-pressure turbines.
conversion_factor_full_condensation: ~collections.abc.Mapping
:class:`~collections.abc.Mapping` where the
(inflow-name, outflow-name) tuple of the main flow is the only key and
it's conversion efficiency when there is no tapped flow is it's value.
Conversion efficiencies are expected to be between 0 and 1
(:math:`\left[0, 1\right]`).
el_efficiency_wo_dist_heat: ~tessif.frused.namedtuples.MinMax
Electric efficiency at min/max fuel flow without district heating.
Expects a :class:`~tessif.frused.namedtuples.MinMax` where both the min
and the max value are a list where the length is the same as the
number of timesteps and each entry is a float value between 0 and 1
giving the efficiency at that timestep.
enthalpy_loss: ~tessif.frused.namedtuples.MinMax
Share of flue gas enthalpy loss at min/max heat extraction.
Expects a :class:`~tessif.frused.namedtuples.MinMax` where both the min
and the max value are a list where the length is the same as the
number of timesteps and each entry is a float value between 0 and 1
giving the share of flue gas enthalpy loss for that timestep.
power_wo_dist_heat: ~tessif.frused.namedtuples.MinMax
Min/max electric power without district heating.
Expects a :class:`~tessif.frused.namedtuples.MinMax` where both the min
and the max value are a list where the length is the same as the
number of timesteps and each entry is the power at that timestep.
power_loss_index: list
Marginal loss of electric power for each additional unit of heat.
Expects a list where the length is the same as the
number of timesteps and each entry is a float value between 0 and 1
giving the marginal loss of power at that timestep.
min_condenser_load: list
Minimal thermal condenser load to cooling water.
Expects a list where the length is the same as the
number of timesteps and each entry is the minimal condenser load at
that timestep.
conversions: ~collections.abc.Mapping
Mapping of chp relevant (input-name, output-name) tuples to
their respective conversion efficiency. With recognized conversion
efficiencies between 0 and 1 (:math:`\left[0, 1\right]`) e.g.::
{('fuel_1', 'electricity'): 0.4,
('fuel_2', 'electricity'): 0.8,}
Is interpreted in a way that for transforming 1 quantity of
'electricity' 2.5 quantities (1/0.4) of 'fuel_1' and 1.25
quantities (1/0.8) of 'fuel_2' are needed.
In the CHP class 'conversions' is optional because the efficiencies can
also be specified with 'el_efficiency_wo_dist_heat' and
'power_loss_index'. Using both ways to specify the efficiencies at the
same time might lead to unexpected results.
flow_rates: ~collections.abc.Mapping
Mapping of each :paramref:`input/output name<CHP.inputs>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum amount per time.
Meaning each flow going in/out during one discrete timestep is greater
equal than the minimum amount per time and less equal than the maximum
amount per time mapped to its name.
In the CHP Class it is also possible to constrain the flow rates with a
combination of 'power_wo_dist_heat', 'enthalpy_loss',
'min_condenser_load' and 'power_loss_index'. It should be possible
to combine those attributes with 'flow_rates' but if problems appear,
try to leave 'flow_rates' away and change the other attributes in such
a way that you have the same behvior.
**Default**::
{key: MinMax(0, float('+inf')) for key in [*inputs, *outputs]}
flow_costs: ~collections.abc.Mapping
Mapping of each :paramref:`input/output name<CHP.inputs>` to a
:class:`~numbers.Number` specifying its cost.
Meaning for each amount per time that is going in/out this amount of
cost-unit is taken into account (by the solver).
**Default**::
{key: 0 for key in [*inputs, *outputs]}
flow_emissions: ~collections.abc.Mapping
Mapping of each :paramref:`input/output name<CHP.inputs>` to a
:class:`~numbers.Number` specifying its emission.
Meaning for each amount per time that is going in/out this amount of
emission-unit is taken into account (by the solver).
**Default**::
{key: 0 for key in [*inputs, *outputs]}
Note
----
This unit primarily serves as system wide constrain parameter as in
'All emissions must remain below 100 units'.
flow_gradients: ~collections.abc.Mapping
Mapping of each :paramref:`input/output name<CHP.inputs>` to a
:class:`~tessif.frused.namedtuples.PositiveNegative` tuple describing
the maximum positive or negative change between two following
timesteps.
Meaning each flow amount increase/decrease between two
following discrete timesteps is less equal than the maximum change
mapped to its name.
**Default**::
{k: PositiveNegative(float('+inf'),float('+inf'))
for k in [*inputs, *outputs]}
gradient_costs: ~collections.abc.Mapping
Mapping of each :paramref:`input/output name<CHP.inputs>` to a
:class:`~tessif.frused.namedtuples.PositiveNegative` tuple describing
the costs for the respective
:paramref:`~CHP.flow_gradients`.
Meaning for each unit
of change of its mapped :paramref:`~CHP.flow_rates` this
amount of cost-unit is taken into account (by the solver).
**Default**::
{k: PositiveNegative(0, 0) for k in [*inputs, *outputs]}
timeseries: ~collections.abc.Mapping, default=None
Mapping an arbitrary number of :paramref:`output names<
CHP.outputs>` to a :class:`~tessif.frused.namedtuples.MinMax`
tuple describing the minimum and maximum :paramref:`flow_rates`
respectively. For Example
Setting the maximum :paramref:`flow_rate <flow_rates>`::
import numpy as np
timeseries = {'input_bus': MinMax(
min=0, max=np.array([10, 42]))}
Setting the minimum :paramref:`flow_rate <flow_rates>`::
import numpy as np
timeseries = {'input_bus': MinMax(
min=np.array([1, 2]), max=float('+inf'))}
Fixing the :paramref:`flow_rate <flow_rates>` to a certain timeseries::
import numpy as np
timeseries = {input_bus': MinMax(
min=np.array([1, 2]), max=np.array([1, 2]))}
expandable: ~collections.abc.Mapping
Mapping of each :paramref:`input name<CHP.inputs>` to a
boolean variable describing if the mapped
:paramref:`CHP.flow_rates` value can be increased by the
solver or not.
**Default**::
{key: False for key in [*inputs, *outputs]}
expansion_costs: ~collections.abc.Mapping
Mapping of each :paramref:`input/output name<CHP.inputs>` to a
:class:`~numbers.Number` specifying its expansion cost.
Meaning for each unit the maximum of the mapped
:paramref:`amount per time<CHP.flow_rates>` is increased
(by the solver) this amount of cost-unit is taken into account
(by the solver).
**Default**::
{key: 0 for key in [*inputs, *outputs]}
expansion_limits: ~collections.abc.Mapping
Mapping of each :paramref:`input/output name<CHP.inputs>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum expansion limit.
Meaning the actual increase of the mapped
:paramref:`amount per time<CHP.flow_rates>` will be
somewhere between the given minimum and maximum.
(:math:`\left[\text{min}, \text{max}\right]`)
**Default**::
{k: MinMax(0, float('+inf')) for k in [*inputs, *outputs]}
Note
----
Providing non default parameters for the following set of arguments will
cause the optimization problem to most likely turn into a
`Mixed Integer Linear Problem
<https://en.wikipedia.org/wiki/Integer_programming#Variants>`_
Parameters
----------
milp: ~collections.abc.Mapping
Mapping of each :attr:`input/output name <CHP.inputs>` to a
boolean variable describing if the mapped
:attr:`CHP.flow_rates` parameter can be
subject to mixed integer linear constraints.
**Default**::
{key: False for key in [*inputs, *outputs]}
milp: bool, default=False,
Boolean variable indicating if the component's parameters are to be
parsed as mixed integer-linear optimization problem or not.
warning
-------
If :paramref:`~CHP.milp` evaluates to ``False`` following set
of parameters is most likely ignored during optimization.
initial_status: bool, default=True
Status variable, indicating if the entity is running, operating,
working, doing the things its supposed to do, in the beginning
of the evaluated timeframe.
status_inertia: ~tessif.frused.namedtuples.OnOff
An :class:`~tessif.frused.namedtuples.OnOff`
:class:`~typing.NamedTuple` describing the minimum uptime and downtime.
With up and downtime describing the minimum amount of following
discrete timesteps the entity as to be operating or standing stil
respectively.
**Default**::
OnOff(0, 0)
status_changing_costs: ~tessif.frused.namedtuples.OnOff
An :class:`~tessif.frused.namedtuples.OnOff`
:class:`~typing.NamedTuple` describing the cost for changing status
from ``on`` to ``off`` and from ``off`` to ``on`` respectively
**Default**::
OnOff(0, 0)
number_of_status_changes: ~tessif.frused.namedtuples.OnOff
:class:`~typing.NamedTuple` describing the number of times the entity
can change its status from ``on`` to ``off`` and from ``off`` to ``on``
respectively.
**Default**::
OnOff(float('+inf'), float('+inf'))
costs_for_being_active: :class:`~number.Number`, default = 0
Costs for not being inactive.
Meaning for each discrete time step the entity's boolean status
variable is ``True``, this amount of cost units is taken in to account
(by the solver).
Example
-------
Variable efficiency :class:`CHP` object:
>>> import pprint
>>> from tessif.components import CHP
>>> chp = CHP(
... name='my_chp', inputs=('fuel',), outputs=('electricity','heat',),
... conversions={('fuel', 'electricity'): 0.3, ('fuel', 'heat'): 0.5},
... conversion_factor_full_condensation={('fuel', 'electricity'): 0.5})
>>> print(chp.uid)
my_chp
>>> print(chp.inputs)
frozenset({'fuel'})
>>> print(sorted(chp.outputs))
['electricity', 'heat']
>>> print(sorted(chp.interfaces))
['electricity', 'fuel', 'heat']
Accessing all its :attr:`~Transformer.attributes`:
>>> for k, v in chp.attributes.items():
... print('{} = {}'.format(
... k, sorted(v) if isinstance(v, frozenset) else v))
... # frozensets are sorted for consistent doctesting
back_pressure = None
conversion_factor_full_condensation = {('fuel', 'electricity'): 0.5}
conversions = {('fuel', 'electricity'): 0.3, ('fuel', 'heat'): 0.5}
costs_for_being_active = 0.0
el_efficiency_wo_dist_heat = MinMax(min=None, max=None)
enthalpy_loss = MinMax(min=None, max=None)
expandable = {'electricity': False, 'fuel': False, 'heat': False}
expansion_costs = {'electricity': 0.0, 'fuel': 0.0, 'heat': 0.0}
expansion_limits = {'electricity': MinMax(min=0.0, max=inf), 'fuel': MinMax(min=0.0, max=inf), 'heat': MinMax(min=0.0, max=inf)}
flow_costs = {'electricity': 0.0, 'fuel': 0.0, 'heat': 0.0}
flow_emissions = {'electricity': 0.0, 'fuel': 0.0, 'heat': 0.0}
flow_gradients = {'electricity': PositiveNegative(positive=inf, negative=inf), 'fuel': PositiveNegative(positive=inf, negative=inf), 'heat': PositiveNegative(positive=inf, negative=inf)}
flow_rates = {'electricity': MinMax(min=0.0, max=inf), 'fuel': MinMax(min=0.0, max=inf), 'heat': MinMax(min=0.0, max=inf)}
gradient_costs = {'electricity': PositiveNegative(positive=0.0, negative=0.0), 'fuel': PositiveNegative(positive=0.0, negative=0.0), 'heat': PositiveNegative(positive=0.0, negative=0.0)}
initial_status = 1
inputs = ['fuel']
interfaces = ['electricity', 'fuel', 'heat']
milp = {'electricity': False, 'fuel': False, 'heat': False}
min_condenser_load = None
number_of_status_changes = OnOff(on=inf, off=inf)
outputs = ['electricity', 'heat']
power_loss_index = None
power_wo_dist_heat = MinMax(min=None, max=None)
status_changing_costs = OnOff(on=0.0, off=0.0)
status_inertia = OnOff(on=0, off=0)
timeseries = None
uid = my_chp
"""
def __init__(self, name, inputs, outputs, *args, **kwargs):
# The Transformer class requires the positional argument 'conversions'
# the CHP class however doesn't, therefore if a CHP object is created
# without the 'conversions' argument, a default value gets passed to
# super().__init__().
if "conversions" not in (args or kwargs):
kwargs.update({"conversions": es_defaults["chp_efficiency"]})
super().__init__(name, inputs, outputs, *args, **kwargs)
# Add the additional parameters for the chp class, then call
# _parse_arguments again.
self._parameters_and_defaults["singular_values"].update(
{
"back_pressure": es_defaults["chp_back_pressure"],
"min_condenser_load": es_defaults["min_condenser_load"],
"power_loss_index": es_defaults["power_loss_index"],
}
)
self._parameters_and_defaults["singular_value_mappings"].update(
{
"conversion_factor_full_condensation": es_defaults["chp_efficiency"],
"conversions": es_defaults["chp_efficiency"],
}
)
self._parameters_and_defaults["namedtuples"]["MinMax"].update(
{
"el_efficiency_wo_dist_heat": es_defaults["el_efficiency_wo_dist_heat"],
"enthalpy_loss": es_defaults["enthalpy_loss"],
"power_wo_dist_heat": es_defaults["power_wo_dist_heat"],
}
)
self._parse_arguments(**kwargs)
@property
def back_pressure(self):
"""True if characterized as back-pressure.
Boolean to specify if back-pressure characteristics shall be used.
Set to True and Q_CW_min to zero for back-pressure turbines.
"""
return self._back_pressure
@property
def conversion_factor_full_condensation(self):
r"""Dictionairy of efficiencies when fully condensating.
:class:`~collections.abc.Mapping` where the
(inflow-name, outflow-name) tuple of the main flow is the only key and
it's conversion efficiency when there is no tapped flow is it's value.
Conversion efficiencies are expected to be between 0 and 1
(:math:`\left[0, 1\right]`).
"""
return self._conversion_factor_full_condensation
@property
def el_efficiency_wo_dist_heat(self):
"""Electric efficiency at min/max fuel flow without district heating.
Expects a :class:`~tessif.frused.namedtuples.MinMax` where both the min
and the max value are a list where the length is the same as the
number of timesteps and each entry is a float value between 0 and 1
giving the efficiency at that timestep.
"""
return self._el_efficiency_wo_dist_heat
@property
def enthalpy_loss(self):
"""Share of flue gas loss at min/max heat extraction.
Expects a :class:`~tessif.frused.namedtuples.MinMax` where both the min
and the max value are a list where the length is the same as the
number of timesteps and each entry is a float value between 0 and 1
giving the share of flue gas enthalpy loss for that timestep.
"""
return self._enthalpy_loss
@property
def min_condenser_load(self):
"""Minimal thermal condenser load to cooling water.
Expects a list where the length is the same as the
number of timesteps and each entry is the minimal condenser load at
that timestep.
"""
return self._min_condenser_load
@property
def power_loss_index(self):
"""Marginal loss of electric power for each additional unit of heat.
Expects a list where the length is the same as the
number of timesteps and each entry is a float value between 0 and 1
giving the marginal loss of power at that timestep.
"""
return self._power_loss_index
@property
def power_wo_dist_heat(self):
"""Min/max electric power without district heating.
Expects a :class:`~tessif.frused.namedtuples.MinMax` where both the min
and the max value are a list where the length is the same as the
number of timesteps and each entry is the power at that timestep.
"""
return self._power_wo_dist_heat
[docs]class Storage(AbstractEsComponent):
r"""
Entities only concerned with input, output and accumulation.
Parameters
----------
name: ~collections.abc.Hashable
Identifier. Usually a string, aka a name.
input: ~collections.abc.Hashable
Hashable unique identifier. Usually a string specifying the
storage-entity's input e.g::
'electricity'
output: ~collections.abc.Hashable
Hashable unique identifier. Usually a string specifying the
storage-entity's output e.g::
'electricity'
capacity: ~numbers.Number
Maximum number of units the entity is able to accumulate.
Note
----
All following arguments are considered optional parameters and are
provided using \*\*kwargs.
Parameters
----------
latitude: ~numbers.Number
Geospatial latitude in degree. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.latitude<tessif.frused.namedtuples.Uid.latitude>`
longitude: ~numbers.Number
Geospatial longitude in degree. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.longitude<tessif.frused.namedtuples.Uid.longitude>`
region: str
Arbitrary regional categorization string. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.region<tessif.frused.namedtuples.Uid.region>`
sector: str
Arbitrary sector categorization string.Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.sector<tessif.frused.namedtuples.Uid.sector>`
carrier: str
Arbitrary energy carrier categorization string.Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.carrier<tessif.frused.namedtuples.Uid.carrier>`
component: str
String specifying the components type. Used for post-processing.
node_type: str
Arbitrary node type categorization string. Parsed into a
:class:`namedtuples.Uid<tessif.frused.namedtuples.Uid>` instance
as :paramref:`uid.node_type<tessif.frused.namedtuples.Uid.node_type>`
Warning
-------
The 6 beforneamed arguments together with :paramref:`~Storage.name`
form an id as a :attr:`tessif.frused.namedtuples.Uid` object. This
:attr:`~tessif.frused.namedtuples.Uid` object as well as its string
representation (str(:attr:`~tessif.frused.namedtuples.Uid`)) must
be unique.
The string representation can be tweaked using
:attr:`~tessif.frused.configurations.node_uid_style`. But in total
the overall combination of these parameters must be unique and will form
the components hashable uid (unique identifier)
Parameters
----------
initial_soc: ~numbers.Number, default = 0
Amount of stored units at the beginning of the evaluated
timeframe.
Usually something between 0 and :paramref:`~Storage.capacity`
(:math:`\left[0, \text{capacity}\right]`).
final_soc: ~numbers.Number,None, default = None
Amount of stored units at the end of the evaluated
timeframe.
Usually something between 0 and :paramref:`~Storage.capacity`
(:math:`\left[0, \text{capacity}\right]`).
If ``None``, the solver does not constrain the final soc.
However since currently no plugin supports
constraining the final soc, this value is primarily used to force a
state of charge equilibrium at the beginning and the end of the
simulated timeframe. Meaning ``final_soc == initial_soc`` so
the total energy balance of the simulated systems stays 0.
Use ``None`` to ensure, different socs at first and last timestep
are valid.
idle_changes: :class:`~tessif.frused.namedtuples.PositiveNegative`
A :class:`~tessif.frused.namedtuples.PositiveNegative`
:class:`~typing.NamedTuple` describing state of charge changes of two
following discrete timesteps.
flow_rates: ~collections.abc.Mapping
Mapping of each :paramref:`input/output name<Storage.input>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum amount per time.
Meaning each flow going in/out during one discrete timestep is greater
equal than the minimum amount per time and less equal than the maximum
amount per time mapped to its name.
**Default**::
{key: MinMax(0, float('+inf')) for key in [*input, *output]}
flow_efficiencies: ~collections.abc.Mapping
Mapping of each :paramref:`input/output name<Storage.input>` to a
:class:`~numbers.Number` specifying its efficiency.
Meaning for each amount per time that is going in/out the amount
times its respective efficiency is available for storing.
**Default**::
{k: InOut(1, 1) for k in [*input, *output]}
flow_costs: ~collections.abc.Mapping
Mapping of each :paramref:`input/output name<Storage.input>` to a
:class:`~numbers.Number` specifying its cost.
Meaning for each amount per time that is going in/out this amount of
cost-unit is taken into account (by the solver).
**Default**::
{key: 0 for key in [*input, *output]}
flow_emissions: ~collections.abc.Mapping
Mapping of each :paramref:`input/output name<Storage.input>` to a
:class:`~numbers.Number` specifying its emission.
Meaning for each amount per time that is going in/out this amount of
emission-unit is taken into account (by the solver).
**Default**::
{key: 0 for key in [*input, *output]}
Note
----
This unit primarily serves as system wide constrain parameter as in
'All emissions must remain below 100 units'.
flow_gradients: ~collections.abc.Mapping
Mapping of each :paramref:`input/output name<Storage.input>` to a
:class:`~tessif.frused.namedtuples.PositiveNegative` tuple describing
the maximum positive or negative change between two following
timesteps.
Meaning each flow amount increase/decrease between two
following discrete timesteps is less equal than the maximum change
mapped to its name.
**Default**::
{k: PositiveNegative(float('+inf'),float('+inf'))
for k in [*input, *output]}
gradient_costs: ~collections.abc.Mapping
Mapping of each :paramref:`input/output name<Storage.input>` to a
:class:`~tessif.frused.namedtuples.PositiveNegative` tuple describing
the costs for the respective
:paramref:`~Storage.flow_gradients`.
Meaning for each unit
of change of its mapped :paramref:`~Storage.flow_rates` this
amount of cost-unit is taken into account (by the solver).
**Default**::
{k: PositiveNegative(0, 0) for k in [*input, *output]}
timeseries: ~collections.abc.Mapping, default=None
Mapping an arbitrary number of :paramref:`input/output names<
Storage.inputs>` to a :class:`~tessif.frused.namedtuples.MinMax` tuple
describing the minimum and maximum :paramref:`flow_rates` respectively.
For Example
Setting the maximum :paramref:`flow_rate <flow_rates>`::
import numpy as np
timeseries = {'input_bus': MinMax(
min=0, max=np.array([10, 42]))}
Setting the minimum :paramref:`flow_rate <flow_rates>`::
import numpy as np
timeseries = {'input_bus': MinMax(
min=np.array([1, 2]), max=float('+inf'))}
Fixing the :paramref:`flow_rate <flow_rates>` to a certain timeseries::
import numpy as np
timeseries = {input_bus': MinMax(
min=np.array([1, 2]), max=np.array([1, 2]))}
expandable: ~collections.abc.Mapping
Mapping of each :paramref:`input <Storage.input>` and
:paramref:`output <Storage.output>` name or ``capacity``
to a boolean variable describing if the mapped
:paramref:`Storage.flow_rates`/ the capacity value can be increased by
the solver or not.
**Default**::
{key: False for key in [*input, *output, 'capacity']}
For example::
expandable: {'capacity': True, f'{Storage.output}': True}
fixed_expansion_ratios: ~collections.abc.Mapping
Mapping of each :paramref:`input <Storage.input>` and
:paramref:`output <Storage.output>` name to a boolean variable
describing if the mapped :paramref:`flow rate <Storage.flow_rates>`
expansion is fixed in relation to the installed
:paramref:`capacity <Storage.capacity>`.
**Default**::
{key: True for key in [*input, *output]}
For example::
{f'{Storage.input}': True, f'{Storage.output}': True}
expansion_costs: ~collections.abc.Mapping
Mapping of each :paramref:`input/output name <Storage.input>` or
the keyword ``capacity`` to a :class:`~numbers.Number` specifying its
expansion cost.
Meaning for each unit the :paramref:`~Storage.capacity` or the
maximum of the mapped :paramref:`amount per time<Storage.flow_rates>`
is increased (by the solver) this amount of cost-unit is taken into
account (by the solver).
**Default**::
{key: 0 for key in [*input, *output, 'capacity']}
expansion_limits: ~collections.abc.Mapping
Mapping of each :paramref:`input/output name<Storage.input>` or the
keyword capacity to a :class:`~tessif.frused.namedtuples.MinMax` tuple
describing the minimum and maximum expansion limit.
Meaning the actual increase of the :paramref:`~Storage.capacity` or the
mapped :paramref:`amount per time<Storage.flow_rates>` will be
somwhere between the given minimum and maximum.
(:math:`\left[\text{min}, \text{max}\right]`)
**Default**::
{k: MinMax(0, float('+inf'))
for k in [*input, *output, 'capacity']}
Note
----
Providing non default parameters for the following set of arguments will
cause the optimization problem to most likely turn into a
`Mixed Integer Linear Problem
<https://en.wikipedia.org/wiki/Integer_programming#Variants>`_
Parameters
----------
milp: ~collections.abc.Mapping
Mapping of each :attr:`input/output name <Storage.input>` to a
boolean variable describing if the mapped
:attr:`Storage.flow_rates` parameter can be
subject to mixed integer linear constraints.
**Default**::
{key: False for key in [*inputs, *outputs]}
warning
-------
If :paramref:`~Storage.milp` evaluates to ``False`` following set of
parameters is most likely ignored during optimization.
initial_status: bool, default=True
Status variable, indicating if the entity is running, operating,
working, doing the things its supposed to do, in the beginning
of the evaluated timeframe.
status_inertia: ~tessif.frused.namedtuples.OnOff
An :class:`~tessif.frused.namedtuples.OnOff`
:class:`~typing.NamedTuple` describing the minimum uptime and downtime.
With up and downtime describing the minimum amount of following
discrete timesteps the entity as to be operating or standing stil
respectively.
**Default**::
OnOff(0, 0)
status_changing_costs: ~tessif.frused.namedtuples.OnOff
An :class:`~tessif.frused.namedtuples.OnOff`
:class:`~typing.NamedTuple` describing the cost for changing status
from ``on`` to ``off`` and from ``off`` to ``on`` respectively
**Default**::
OnOff(0, 0)
number_of_status_changes: ~tessif.frused.namedtuples.OnOff
:class:`~typing.NamedTuple` describing the number of times the entity
can change its status from ``on`` to ``off`` and from ``off`` to ``on``
respectively.
**Default**::
OnOff(float('+inf'), float('+inf'))
costs_for_being_active: :class:`~number.Number`, default = 0
Costs for not being inactive.
Meaning for each discrete time step the entity's boolean status
variable is ``True``, this amount of cost units is taken in to account
(by the solver).
Example
-------
Default parameterized :class:`Storage` object with no need to seperate
in and outflow:
>>> import pprint
>>> from tessif.components import Storage
>>> storage = Storage(
... name='my_storage', input='electricity', output='electricity',
... capacity=100)
>>> print(storage.uid)
my_storage
>>> print(storage.input)
electricity
>>> print(storage.output)
electricity
>>> print(sorted(storage.interfaces))
['electricity']
Following example transforms the frozensets into sorted lists, to enable
doctesting
Accessing all its :attr:`~Storage.attributes`:
>>> for k, v in storage.attributes.items():
... print('{} = {}'.format(
... k, sorted(v) if isinstance(v, frozenset) else v))
... # frozensets are transformed to sorted list for doctesting consistency
capacity = 100
costs_for_being_active = 0.0
expandable = {'capacity': False, 'electricity': False}
expansion_costs = {'capacity': 0.0, 'electricity': 0.0}
expansion_limits = {'capacity': MinMax(min=0.0, max=inf), 'electricity': MinMax(min=0.0, max=inf)}
final_soc = None
fixed_expansion_ratios = {'electricity': True}
flow_costs = {'electricity': 0.0}
flow_efficiencies = {'electricity': InOut(inflow=1.0, outflow=1.0)}
flow_emissions = {'electricity': 0.0}
flow_gradients = {'electricity': PositiveNegative(positive=inf, negative=inf)}
flow_rates = {'electricity': MinMax(min=0.0, max=inf)}
gradient_costs = {'electricity': PositiveNegative(positive=0.0, negative=0.0)}
idle_changes = PositiveNegative(positive=0.0, negative=0.0)
initial_soc = 0.0
initial_status = 1
input = electricity
interfaces = ['electricity']
milp = {'electricity': False}
number_of_status_changes = OnOff(on=inf, off=inf)
output = electricity
status_changing_costs = OnOff(on=0.0, off=0.0)
status_inertia = OnOff(on=0, off=0)
timeseries = None
uid = my_storage
"""
def __init__(self, name, input, output, capacity, *args, **kwargs):
self._input = input
self._output = output
self._interfaces = frozenset((self._input, self._output))
self._capacity = capacity
# modify this dict for adding additional parameters
self._parameters_and_defaults = {
"singular_values": {
"initial_status": es_defaults["initial_status"],
"costs_for_being_active": es_defaults["costs_for_being_active"],
"initial_soc": es_defaults["initial_soc"],
"final_soc": es_defaults["final_soc"],
},
"singular_value_mappings": {
"flow_costs": {
key: es_defaults["flow_costs"] for key in sorted(self._interfaces)
},
"flow_emissions": {
key: es_defaults["emissions"] for key in sorted(self._interfaces)
},
"expandable": {
key: es_defaults["expandable"]
for key in sorted(self._interfaces) + ["capacity"]
},
"fixed_expansion_ratios": {
key: es_defaults["fixed_expansion_ratios"]
for key in sorted(self._interfaces)
},
"expansion_costs": {
key: es_defaults["expansion_costs"]
for key in sorted(self._interfaces) + ["capacity"]
},
"milp": {key: es_defaults["milp"] for key in sorted(self._interfaces)},
},
"namedtuples": {
"MinMax": {},
"OnOff": {
"status_inertia": nts.OnOff(
es_defaults["minimum_uptime"], es_defaults["minimum_downtime"]
),
"status_changing_costs": nts.OnOff(
es_defaults["startup_costs"], es_defaults["shutdown_costs"]
),
"number_of_status_changes": nts.OnOff(
es_defaults["maximum_startups"],
es_defaults["maximum_shutdowns"],
),
},
"PositiveNegative": {
"idle_changes": nts.PositiveNegative(
es_defaults["gain_rate"], es_defaults["loss_rate"]
),
},
},
"mapped_namedtuples": {
"MinMax": {
"expansion_limits": {
key: nts.MinMax(
es_defaults["minimum_expansion"],
es_defaults["maximum_expansion"],
)
for key in sorted(self._interfaces) + ["capacity"]
},
"flow_rates": {
key: nts.MinMax(
es_defaults["minimum_flow_rate"],
es_defaults["maximum_flow_rate"],
)
for key in sorted(self._interfaces)
},
},
"PositiveNegative": {
"flow_gradients": {
key: nts.PositiveNegative(
es_defaults["positive_gradient"],
es_defaults["negative_gradient"],
)
for key in sorted(self._interfaces)
},
"gradient_costs": {
key: nts.PositiveNegative(
es_defaults["positive_gradient_costs"],
es_defaults["negative_gradient_costs"],
)
for key in sorted(self._interfaces)
},
},
"InOut": {
"flow_efficiencies": {
key: nts.InOut(
es_defaults["efficiency"], es_defaults["efficiency"]
)
for key in sorted(self._interfaces)
},
},
},
"timeseries": None,
}
super().__init__(name, *args, **kwargs)
@property
def input(self):
"""Input uid.
:class:`Hashable<collections.abc.Hashable>` unique identifier. Usually
a string aka name representing the storage-entity's input.
"""
return self._input
@property
def output(self):
"""Output uid.
:class:`Hashable<collections.abc.Hashable>` unique identifier. Usually
a string aka name representing the storage-entity's output.
"""
return self._output
@property
def capacity(self):
r"""Maximum installed capacity.
Maximum :class:`~numbers.Number` of units the entity is able
to accumulate.
"""
return self._capacity
@property
def initial_soc(self):
r"""Initial SOC constraint.
The :class:`~numbers.Number` of units the entity
has accumulated at the beginning of the evaluated timeframe.
Usually something between 0 and :attr:`~Storage.capacity`
(:math:`\left[0, \text{capacity}\right]`).
"""
return self._initial_soc
@property
def final_soc(self):
r"""Final SOC constriant.
The :class:`~numbers.Number` of units the entity
has accumulated at the end of the evaluated timeframe.
Usually something between 0 and :attr:`~Storage.capacity`
(:math:`\left[0, \text{capacity}\right]`).
Can also be ``None`` to not constrain the final soc.
"""
return self._final_soc
@property
def idle_changes(self):
"""Namedtuple of idle change constraints.
A :class:`~tessif.frused.namedtuples.PositiveNegative`
:class:`~typing.NamedTuple` describing state of charge changes of two
following discrete timesteps.
"""
return self._idle_changes
@property
def flow_rates(self):
"""Dictionairy of flow rate constraints.
:class:`~collections.abc.Mapping`
of each :attr:`input/output name<Storage.input>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum amount per time.
Meaning each flow going out during one discrete timestep is greater
equal than the minimum amount per time and less equal than the maximum
amount per time mapped to its name.
"""
return self._flow_rates
@property
def flow_efficiencies(self):
"""Dictionairy of flow efficiency constraints.
:class:`~collections.abc.Mapping`
of each :paramref:`input/output name<Storage.input>` to a
:class:`~numbers.Number` specifying its efficiency.
Meaning for each amount per time that is going in/out the amount
times its respective efficiency is available for storing.
"""
return self._flow_efficiencies
@property
def flow_costs(self):
"""Dictionairy of flow cost constraints.
:class:`~collections.abc.Mapping`
of each :attr:`input/output name<Storage.input>` to a
:class:`~numbers.Number` specifying its cost.
Meaning for each amount per time that is going out this amount of
cost-unit is taken into account (by the solver).
"""
return self._flow_costs
@property
def flow_emissions(self):
"""Dictionairy of flow emission constraints.
:class:`~collections.abc.Mapping`
of each :attr:`input/output name<Storage.input>` to a
:class:`~numbers.Number` specifying its emissions
Meaning for each amount per time that is going out this amount of
emission-unit is taken into account (by the solver).
Note
----
This unit primarily serves as system wide constrain parameter as in
'All emissions must remain below 100 units'.
"""
return self._flow_emissions
@property
def flow_gradients(self):
"""Dictionairy of flow gradient constraints.
:class:`~collections.abc.Mapping`
of each :attr:`input/output name<Storage.input>` to a
:class:`~tessif.frused.namedtuples.PositiveNegative` tuple describing
the maximum positive or negative change between two following
timesteps.
Meaning each flow amount increase/decrease between two
following discrete timesteps is less equal than the maximum change
mapped to its name.
"""
return self._flow_gradients
@property
def gradient_costs(self):
"""Dictionairy of flow gradient costs.
:class:`~collections.abc.Mapping`
Mapping of each :paramref:`input/output name<Storage.input>` to a
:class:`~tessif.frused.namedtuples.PositiveNegative` tuple describing
the costs for the respective
:attr:`~Storage.flow_gradients`.
Meaning for each unit
of change of its mapped :paramref:`~Storage.flow_rates` this
amount of cost-unit is taken into account (by the solver).
"""
return self._gradient_costs
@property
def timeseries(self):
"""Tuple of flow_rates constraining timeserieses.
:class:`~collections.abc.Mapping` of an arbitrary number
of :attr:`input/output names<Storage.input>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum :paramref:`flow_rates` respectively.
"""
return self._timeseries
@property
def expandable(self):
"""True, if maximum flow_rate is subject to solver changes.
:class:`~collections.abc.Mapping`
of each :attr:`input/output name <Storage.input>` to a boolean
variable describing if the mapped :attr:`Storage.flow_rates`
value can be increased by the solver or not.
"""
return self._expandable
@property
def fixed_expansion_ratios(self):
"""Dictionairy of coupled capacity and flow rate expansion constraints.
:class:`~collections.abc.Mapping`
of each :paramref:`input <Storage.input>` and
:paramref:`output <Storage.output>` name to a boolean variable
describing if the mapped :attr:`flow rate <Storage.flow_rates>`
expansion is fixed in relation to the installed
:attr:`capacity <Storage.capacity>`.
"""
return self._fixed_expansion_ratios
@property
def expansion_costs(self):
"""Dictionairy of maximum flow rate expansion costs.
:class:`~collections.abc.Mapping`
of each :attr:`input/output name <Storage.input>` to a
:class:`~numbers.Number` specifying its expansion cost.
Meaning for each unit the maximum of the mapped
:attr:`amount per time<Storage.flow_rates>` is increased
(by the solver) this amount of cost-unit is taken into account
(by the solver).
"""
return self._expansion_costs
@property
def expansion_limits(self):
r"""Dictionairy of maximum flow rate expansion limits.
:class:`~collections.abc.Mapping`
of each :attr:`input/output name <Storage.input>` to a
:class:`~tessif.frused.namedtuples.MinMax` tuple describing the
minimum and maximum expansion limit.
Meaning the actual increase of the mapped
:attr:`amount per time<Storage.flow_rates>` will be somewhere
between the given minimum and maximum.
(:math:`\left[\text{min}, \text{max}\right]`)
"""
return self._expansion_limits
@property
def initial_status(self):
"""True, if component is milp constrained and active at first time step.
:class:`Status variable <bool>`, indicating if the entity is
running, operating, working, doing the things its supposed to do in
the beginning of the evaluated timeframe.
"""
return self._initial_status
@property
def status_inertia(self):
"""Tuple of status-inertia (milp) constraints.
:class:`~tessif.frused.namedtuples.OnOff` :class:`~typing.NamedTuple`
describing the minimum uptime and downtime. With up and downtime
describing the minimum amount of following discrete timesteps the
entity as to be operating or standing still respectively.
"""
return self._status_inertia
@property
def status_changing_costs(self):
"""Tuple of status-changing (milp) constraints.
:class:`~tessif.frused.namedtuples.OnOff` :class:`~typing.NamedTuple`
describing the cost for changing status from ``on`` to ``off`` and
from ``off`` to ``on`` respectively.
"""
return self._status_changing_costs
@property
def number_of_status_changes(self):
"""Tuple of allowed milp status changing constraints.
An :class:`~tessif.frused.namedtuples.OnOff`
:class:`~typing.NamedTuple` describing the number of times the entity
can change its status from ``on`` to ``off`` and from ``off`` to ``on``
respectively.
"""
return self._number_of_status_changes
@property
def costs_for_being_active(self):
"""Milp constained activity costs.
A :class:`~number.Number`, default = 0
Describing the costs for not being inactive.
Meaning for each discrete time step the entity's boolean status
variable is ``True``, this amount of cost units is taken in to account
(by the solver).
"""
return self._costs_for_being_active