flixopt 1.0.12__py3-none-any.whl → 2.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flixopt might be problematic. Click here for more details.
- docs/examples/00-Minimal Example.md +5 -0
- docs/examples/01-Basic Example.md +5 -0
- docs/examples/02-Complex Example.md +10 -0
- docs/examples/03-Calculation Modes.md +5 -0
- docs/examples/index.md +5 -0
- docs/faq/contribute.md +49 -0
- docs/faq/index.md +3 -0
- docs/images/architecture_flixOpt-pre2.0.0.png +0 -0
- docs/images/architecture_flixOpt.png +0 -0
- docs/images/flixopt-icon.svg +1 -0
- docs/javascripts/mathjax.js +18 -0
- docs/release-notes/_template.txt +32 -0
- docs/release-notes/index.md +7 -0
- docs/release-notes/v2.0.0.md +93 -0
- docs/release-notes/v2.0.1.md +12 -0
- docs/user-guide/Mathematical Notation/Bus.md +33 -0
- docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +132 -0
- docs/user-guide/Mathematical Notation/Flow.md +26 -0
- docs/user-guide/Mathematical Notation/LinearConverter.md +21 -0
- docs/user-guide/Mathematical Notation/Piecewise.md +49 -0
- docs/user-guide/Mathematical Notation/Storage.md +44 -0
- docs/user-guide/Mathematical Notation/index.md +22 -0
- docs/user-guide/Mathematical Notation/others.md +3 -0
- docs/user-guide/index.md +124 -0
- {flixOpt → flixopt}/__init__.py +5 -2
- {flixOpt → flixopt}/aggregation.py +113 -140
- flixopt/calculation.py +455 -0
- {flixOpt → flixopt}/commons.py +7 -4
- flixopt/components.py +630 -0
- {flixOpt → flixopt}/config.py +9 -8
- {flixOpt → flixopt}/config.yaml +3 -3
- flixopt/core.py +970 -0
- flixopt/effects.py +386 -0
- flixopt/elements.py +534 -0
- flixopt/features.py +1042 -0
- flixopt/flow_system.py +409 -0
- flixopt/interface.py +265 -0
- flixopt/io.py +308 -0
- flixopt/linear_converters.py +331 -0
- flixopt/plotting.py +1340 -0
- flixopt/results.py +898 -0
- flixopt/solvers.py +77 -0
- flixopt/structure.py +630 -0
- flixopt/utils.py +62 -0
- flixopt-2.0.1.dist-info/METADATA +145 -0
- flixopt-2.0.1.dist-info/RECORD +57 -0
- {flixopt-1.0.12.dist-info → flixopt-2.0.1.dist-info}/WHEEL +1 -1
- flixopt-2.0.1.dist-info/top_level.txt +6 -0
- pics/architecture_flixOpt-pre2.0.0.png +0 -0
- pics/architecture_flixOpt.png +0 -0
- pics/flixopt-icon.svg +1 -0
- pics/pics.pptx +0 -0
- scripts/gen_ref_pages.py +54 -0
- site/release-notes/_template.txt +32 -0
- flixOpt/calculation.py +0 -629
- flixOpt/components.py +0 -614
- flixOpt/core.py +0 -182
- flixOpt/effects.py +0 -410
- flixOpt/elements.py +0 -489
- flixOpt/features.py +0 -942
- flixOpt/flow_system.py +0 -351
- flixOpt/interface.py +0 -203
- flixOpt/linear_converters.py +0 -325
- flixOpt/math_modeling.py +0 -1145
- flixOpt/plotting.py +0 -712
- flixOpt/results.py +0 -563
- flixOpt/solvers.py +0 -21
- flixOpt/structure.py +0 -733
- flixOpt/utils.py +0 -134
- flixopt-1.0.12.dist-info/METADATA +0 -174
- flixopt-1.0.12.dist-info/RECORD +0 -29
- flixopt-1.0.12.dist-info/top_level.txt +0 -3
- {flixopt-1.0.12.dist-info → flixopt-2.0.1.dist-info/licenses}/LICENSE +0 -0
flixopt/effects.py
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the effects of the flixopt framework.
|
|
3
|
+
Furthermore, it contains the EffectCollection, which is used to collect all effects of a system.
|
|
4
|
+
Different Datatypes are used to represent the effects with assigned values by the user,
|
|
5
|
+
which are then transformed into the internal data structure.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import warnings
|
|
10
|
+
from typing import TYPE_CHECKING, Dict, Iterator, List, Literal, Optional, Union
|
|
11
|
+
|
|
12
|
+
import linopy
|
|
13
|
+
import numpy as np
|
|
14
|
+
import pandas as pd
|
|
15
|
+
|
|
16
|
+
from .core import NumericData, NumericDataTS, Scalar, TimeSeries, TimeSeriesCollection
|
|
17
|
+
from .features import ShareAllocationModel
|
|
18
|
+
from .structure import Element, ElementModel, Interface, Model, SystemModel, register_class_for_io
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from .flow_system import FlowSystem
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger('flixopt')
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@register_class_for_io
|
|
27
|
+
class Effect(Element):
|
|
28
|
+
"""
|
|
29
|
+
Effect, i.g. costs, CO2 emissions, area, ...
|
|
30
|
+
Components, FLows, and so on can contribute to an Effect. One Effect is chosen as the Objective of the Optimization
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
label: str,
|
|
36
|
+
unit: str,
|
|
37
|
+
description: str,
|
|
38
|
+
meta_data: Optional[Dict] = None,
|
|
39
|
+
is_standard: bool = False,
|
|
40
|
+
is_objective: bool = False,
|
|
41
|
+
specific_share_to_other_effects_operation: Optional['EffectValuesUser'] = None,
|
|
42
|
+
specific_share_to_other_effects_invest: Optional['EffectValuesUser'] = None,
|
|
43
|
+
minimum_operation: Optional[Scalar] = None,
|
|
44
|
+
maximum_operation: Optional[Scalar] = None,
|
|
45
|
+
minimum_invest: Optional[Scalar] = None,
|
|
46
|
+
maximum_invest: Optional[Scalar] = None,
|
|
47
|
+
minimum_operation_per_hour: Optional[NumericDataTS] = None,
|
|
48
|
+
maximum_operation_per_hour: Optional[NumericDataTS] = None,
|
|
49
|
+
minimum_total: Optional[Scalar] = None,
|
|
50
|
+
maximum_total: Optional[Scalar] = None,
|
|
51
|
+
):
|
|
52
|
+
"""
|
|
53
|
+
Args:
|
|
54
|
+
label: The label of the Element. Used to identify it in the FlowSystem
|
|
55
|
+
unit: The unit of effect, i.g. €, kg_CO2, kWh_primaryEnergy
|
|
56
|
+
description: The long name
|
|
57
|
+
is_standard: true, if Standard-Effect (for direct input of value without effect (alternatively to dict)) , else false
|
|
58
|
+
is_objective: true, if optimization target
|
|
59
|
+
specific_share_to_other_effects_operation: {effectType: TS, ...}, i.g. 180 €/t_CO2, input as {costs: 180}, optional
|
|
60
|
+
share to other effects (only operation)
|
|
61
|
+
specific_share_to_other_effects_invest: {effectType: TS, ...}, i.g. 180 €/t_CO2, input as {costs: 180}, optional
|
|
62
|
+
share to other effects (only invest).
|
|
63
|
+
minimum_operation: minimal sum (only operation) of the effect.
|
|
64
|
+
maximum_operation: maximal sum (nur operation) of the effect.
|
|
65
|
+
minimum_operation_per_hour: max. value per hour (only operation) of effect (=sum of all effect-shares) for each timestep!
|
|
66
|
+
maximum_operation_per_hour: min. value per hour (only operation) of effect (=sum of all effect-shares) for each timestep!
|
|
67
|
+
minimum_invest: minimal sum (only invest) of the effect
|
|
68
|
+
maximum_invest: maximal sum (only invest) of the effect
|
|
69
|
+
minimum_total: min sum of effect (invest+operation).
|
|
70
|
+
maximum_total: max sum of effect (invest+operation).
|
|
71
|
+
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
72
|
+
"""
|
|
73
|
+
super().__init__(label, meta_data=meta_data)
|
|
74
|
+
self.label = label
|
|
75
|
+
self.unit = unit
|
|
76
|
+
self.description = description
|
|
77
|
+
self.is_standard = is_standard
|
|
78
|
+
self.is_objective = is_objective
|
|
79
|
+
self.specific_share_to_other_effects_operation: EffectValuesUser = (
|
|
80
|
+
specific_share_to_other_effects_operation or {}
|
|
81
|
+
)
|
|
82
|
+
self.specific_share_to_other_effects_invest: EffectValuesUser = specific_share_to_other_effects_invest or {}
|
|
83
|
+
self.minimum_operation = minimum_operation
|
|
84
|
+
self.maximum_operation = maximum_operation
|
|
85
|
+
self.minimum_operation_per_hour: NumericDataTS = minimum_operation_per_hour
|
|
86
|
+
self.maximum_operation_per_hour: NumericDataTS = maximum_operation_per_hour
|
|
87
|
+
self.minimum_invest = minimum_invest
|
|
88
|
+
self.maximum_invest = maximum_invest
|
|
89
|
+
self.minimum_total = minimum_total
|
|
90
|
+
self.maximum_total = maximum_total
|
|
91
|
+
|
|
92
|
+
def transform_data(self, flow_system: 'FlowSystem'):
|
|
93
|
+
self.minimum_operation_per_hour = flow_system.create_time_series(
|
|
94
|
+
f'{self.label_full}|minimum_operation_per_hour', self.minimum_operation_per_hour
|
|
95
|
+
)
|
|
96
|
+
self.maximum_operation_per_hour = flow_system.create_time_series(
|
|
97
|
+
f'{self.label_full}|maximum_operation_per_hour', self.maximum_operation_per_hour, flow_system
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
self.specific_share_to_other_effects_operation = flow_system.create_effect_time_series(
|
|
101
|
+
f'{self.label_full}|operation->', self.specific_share_to_other_effects_operation, 'operation'
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def create_model(self, model: SystemModel) -> 'EffectModel':
|
|
105
|
+
self._plausibility_checks()
|
|
106
|
+
self.model = EffectModel(model, self)
|
|
107
|
+
return self.model
|
|
108
|
+
|
|
109
|
+
def _plausibility_checks(self) -> None:
|
|
110
|
+
# TODO: Check for plausibility
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class EffectModel(ElementModel):
|
|
115
|
+
def __init__(self, model: SystemModel, element: Effect):
|
|
116
|
+
super().__init__(model, element)
|
|
117
|
+
self.element: Effect = element
|
|
118
|
+
self.total: Optional[linopy.Variable] = None
|
|
119
|
+
self.invest: ShareAllocationModel = self.add(
|
|
120
|
+
ShareAllocationModel(
|
|
121
|
+
self._model,
|
|
122
|
+
False,
|
|
123
|
+
self.label_of_element,
|
|
124
|
+
'invest',
|
|
125
|
+
label_full=f'{self.label_full}(invest)',
|
|
126
|
+
total_max=self.element.maximum_invest,
|
|
127
|
+
total_min=self.element.minimum_invest,
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
self.operation: ShareAllocationModel = self.add(
|
|
132
|
+
ShareAllocationModel(
|
|
133
|
+
self._model,
|
|
134
|
+
True,
|
|
135
|
+
self.label_of_element,
|
|
136
|
+
'operation',
|
|
137
|
+
label_full=f'{self.label_full}(operation)',
|
|
138
|
+
total_max=self.element.maximum_operation,
|
|
139
|
+
total_min=self.element.minimum_operation,
|
|
140
|
+
min_per_hour=self.element.minimum_operation_per_hour.active_data
|
|
141
|
+
if self.element.minimum_operation_per_hour is not None
|
|
142
|
+
else None,
|
|
143
|
+
max_per_hour=self.element.maximum_operation_per_hour.active_data
|
|
144
|
+
if self.element.maximum_operation_per_hour is not None
|
|
145
|
+
else None,
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def do_modeling(self):
|
|
150
|
+
for model in self.sub_models:
|
|
151
|
+
model.do_modeling()
|
|
152
|
+
|
|
153
|
+
self.total = self.add(
|
|
154
|
+
self._model.add_variables(
|
|
155
|
+
lower=self.element.minimum_total if self.element.minimum_total is not None else -np.inf,
|
|
156
|
+
upper=self.element.maximum_total if self.element.maximum_total is not None else np.inf,
|
|
157
|
+
coords=None,
|
|
158
|
+
name=f'{self.label_full}|total',
|
|
159
|
+
),
|
|
160
|
+
'total',
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
self.add(
|
|
164
|
+
self._model.add_constraints(
|
|
165
|
+
self.total == self.operation.total.sum() + self.invest.total.sum(), name=f'{self.label_full}|total'
|
|
166
|
+
),
|
|
167
|
+
'total',
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
EffectValuesExpr = Dict[str, linopy.LinearExpression] # Used to create Shares
|
|
172
|
+
EffectTimeSeries = Dict[str, TimeSeries] # Used internally to index values
|
|
173
|
+
EffectValuesDict = Dict[str, NumericDataTS] # How effect values are stored
|
|
174
|
+
EffectValuesUser = Union[NumericDataTS, Dict[str, NumericDataTS]] # User-specified Shares to Effects
|
|
175
|
+
""" This datatype is used to define the share to an effect by a certain attribute. """
|
|
176
|
+
|
|
177
|
+
EffectValuesUserScalar = Union[Scalar, Dict[str, Scalar]] # User-specified Shares to Effects
|
|
178
|
+
""" This datatype is used to define the share to an effect by a certain attribute. Only scalars are allowed. """
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class EffectCollection:
|
|
182
|
+
"""
|
|
183
|
+
Handling all Effects
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
def __init__(self, *effects: List[Effect]):
|
|
187
|
+
self._effects = {}
|
|
188
|
+
self._standard_effect: Optional[Effect] = None
|
|
189
|
+
self._objective_effect: Optional[Effect] = None
|
|
190
|
+
|
|
191
|
+
self.model: Optional[EffectCollectionModel] = None
|
|
192
|
+
self.add_effects(*effects)
|
|
193
|
+
|
|
194
|
+
def create_model(self, model: SystemModel) -> 'EffectCollectionModel':
|
|
195
|
+
self._plausibility_checks()
|
|
196
|
+
self.model = EffectCollectionModel(model, self)
|
|
197
|
+
return self.model
|
|
198
|
+
|
|
199
|
+
def add_effects(self, *effects: Effect) -> None:
|
|
200
|
+
for effect in list(effects):
|
|
201
|
+
if effect in self:
|
|
202
|
+
raise ValueError(f'Effect with label "{effect.label=}" already added!')
|
|
203
|
+
if effect.is_standard:
|
|
204
|
+
self.standard_effect = effect
|
|
205
|
+
if effect.is_objective:
|
|
206
|
+
self.objective_effect = effect
|
|
207
|
+
self._effects[effect.label] = effect
|
|
208
|
+
logger.info(f'Registered new Effect: {effect.label}')
|
|
209
|
+
|
|
210
|
+
def create_effect_values_dict(self, effect_values_user: EffectValuesUser) -> Optional[EffectValuesDict]:
|
|
211
|
+
"""
|
|
212
|
+
Converts effect values into a dictionary. If a scalar is provided, it is associated with a default effect type.
|
|
213
|
+
|
|
214
|
+
Examples
|
|
215
|
+
--------
|
|
216
|
+
effect_values_user = 20 -> {None: 20}
|
|
217
|
+
effect_values_user = None -> None
|
|
218
|
+
effect_values_user = {effect1: 20, effect2: 0.3} -> {effect1: 20, effect2: 0.3}
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
dict or None
|
|
223
|
+
A dictionary with None or Effect as the key, or None if input is None.
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
def get_effect_label(eff: Union[Effect, str]) -> str:
|
|
227
|
+
"""Temporary function to get the label of an effect and warn for deprecation"""
|
|
228
|
+
if isinstance(eff, Effect):
|
|
229
|
+
warnings.warn(
|
|
230
|
+
f'The use of effect objects when specifying EffectValues is deprecated. '
|
|
231
|
+
f'Use the label of the effect instead. Used effect: {eff.label_full}',
|
|
232
|
+
UserWarning,
|
|
233
|
+
stacklevel=2,
|
|
234
|
+
)
|
|
235
|
+
return eff.label_full
|
|
236
|
+
else:
|
|
237
|
+
return eff
|
|
238
|
+
|
|
239
|
+
if effect_values_user is None:
|
|
240
|
+
return None
|
|
241
|
+
if isinstance(effect_values_user, dict):
|
|
242
|
+
return {get_effect_label(effect): value for effect, value in effect_values_user.items()}
|
|
243
|
+
return {self.standard_effect.label_full: effect_values_user}
|
|
244
|
+
|
|
245
|
+
def _plausibility_checks(self) -> None:
|
|
246
|
+
# Check circular loops in effects:
|
|
247
|
+
# TODO: Improve checks!! Only most basic case covered...
|
|
248
|
+
|
|
249
|
+
def error_str(effect_label: str, share_ffect_label: str):
|
|
250
|
+
return (
|
|
251
|
+
f' {effect_label} -> has share in: {share_ffect_label}\n'
|
|
252
|
+
f' {share_ffect_label} -> has share in: {effect_label}'
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
for effect in self.effects.values():
|
|
256
|
+
# Effekt darf nicht selber als Share in seinen ShareEffekten auftauchen:
|
|
257
|
+
# operation:
|
|
258
|
+
for target_effect in effect.specific_share_to_other_effects_operation.keys():
|
|
259
|
+
assert effect not in self[target_effect].specific_share_to_other_effects_operation.keys(), (
|
|
260
|
+
f'Error: circular operation-shares \n{error_str(target_effect.label, target_effect.label)}'
|
|
261
|
+
)
|
|
262
|
+
# invest:
|
|
263
|
+
for target_effect in effect.specific_share_to_other_effects_invest.keys():
|
|
264
|
+
assert effect not in self[target_effect].specific_share_to_other_effects_invest.keys(), (
|
|
265
|
+
f'Error: circular invest-shares \n{error_str(target_effect.label, target_effect.label)}'
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def __getitem__(self, effect: Union[str, Effect]) -> 'Effect':
|
|
269
|
+
"""
|
|
270
|
+
Get an effect by label, or return the standard effect if None is passed
|
|
271
|
+
|
|
272
|
+
Raises:
|
|
273
|
+
KeyError: If no effect with the given label is found.
|
|
274
|
+
KeyError: If no standard effect is specified.
|
|
275
|
+
"""
|
|
276
|
+
if effect is None:
|
|
277
|
+
return self.standard_effect
|
|
278
|
+
if isinstance(effect, Effect):
|
|
279
|
+
if effect in self:
|
|
280
|
+
return effect
|
|
281
|
+
else:
|
|
282
|
+
raise KeyError(f'Effect {effect} not found!')
|
|
283
|
+
try:
|
|
284
|
+
return self.effects[effect]
|
|
285
|
+
except KeyError as e:
|
|
286
|
+
raise KeyError(f'Effect "{effect}" not found! Add it to the FlowSystem first!') from e
|
|
287
|
+
|
|
288
|
+
def __iter__(self) -> Iterator[Effect]:
|
|
289
|
+
return iter(self._effects.values())
|
|
290
|
+
|
|
291
|
+
def __len__(self) -> int:
|
|
292
|
+
return len(self._effects)
|
|
293
|
+
|
|
294
|
+
def __contains__(self, item: Union[str, 'Effect']) -> bool:
|
|
295
|
+
"""Check if the effect exists. Checks for label or object"""
|
|
296
|
+
if isinstance(item, str):
|
|
297
|
+
return item in self.effects # Check if the label exists
|
|
298
|
+
elif isinstance(item, Effect):
|
|
299
|
+
return item in self.effects.values() # Check if the object exists
|
|
300
|
+
return False
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def effects(self) -> Dict[str, Effect]:
|
|
304
|
+
return self._effects
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def standard_effect(self) -> Effect:
|
|
308
|
+
if self._standard_effect is None:
|
|
309
|
+
raise KeyError('No standard-effect specified!')
|
|
310
|
+
return self._standard_effect
|
|
311
|
+
|
|
312
|
+
@standard_effect.setter
|
|
313
|
+
def standard_effect(self, value: Effect) -> None:
|
|
314
|
+
if self._standard_effect is not None:
|
|
315
|
+
raise ValueError(f'A standard-effect already exists! ({self._standard_effect.label=})')
|
|
316
|
+
self._standard_effect = value
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def objective_effect(self) -> Effect:
|
|
320
|
+
if self._objective_effect is None:
|
|
321
|
+
raise KeyError('No objective-effect specified!')
|
|
322
|
+
return self._objective_effect
|
|
323
|
+
|
|
324
|
+
@objective_effect.setter
|
|
325
|
+
def objective_effect(self, value: Effect) -> None:
|
|
326
|
+
if self._objective_effect is not None:
|
|
327
|
+
raise ValueError(f'An objective-effect already exists! ({self._objective_effect.label=})')
|
|
328
|
+
self._objective_effect = value
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class EffectCollectionModel(Model):
|
|
332
|
+
"""
|
|
333
|
+
Handling all Effects
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
def __init__(self, model: SystemModel, effects: EffectCollection):
|
|
337
|
+
super().__init__(model, label_of_element='Effects')
|
|
338
|
+
self.effects = effects
|
|
339
|
+
self.penalty: Optional[ShareAllocationModel] = None
|
|
340
|
+
|
|
341
|
+
def add_share_to_effects(
|
|
342
|
+
self,
|
|
343
|
+
name: str,
|
|
344
|
+
expressions: EffectValuesExpr,
|
|
345
|
+
target: Literal['operation', 'invest'],
|
|
346
|
+
) -> None:
|
|
347
|
+
for effect, expression in expressions.items():
|
|
348
|
+
if target == 'operation':
|
|
349
|
+
self.effects[effect].model.operation.add_share(name, expression)
|
|
350
|
+
elif target == 'invest':
|
|
351
|
+
self.effects[effect].model.invest.add_share(name, expression)
|
|
352
|
+
else:
|
|
353
|
+
raise ValueError(f'Target {target} not supported!')
|
|
354
|
+
|
|
355
|
+
def add_share_to_penalty(self, name: str, expression: linopy.LinearExpression) -> None:
|
|
356
|
+
if expression.ndim != 0:
|
|
357
|
+
raise TypeError(f'Penalty shares must be scalar expressions! ({expression.ndim=})')
|
|
358
|
+
self.penalty.add_share(name, expression)
|
|
359
|
+
|
|
360
|
+
def do_modeling(self):
|
|
361
|
+
for effect in self.effects:
|
|
362
|
+
effect.create_model(self._model)
|
|
363
|
+
self.penalty = self.add(
|
|
364
|
+
ShareAllocationModel(self._model, shares_are_time_series=False, label_of_element='Penalty')
|
|
365
|
+
)
|
|
366
|
+
for model in [effect.model for effect in self.effects] + [self.penalty]:
|
|
367
|
+
model.do_modeling()
|
|
368
|
+
|
|
369
|
+
self._add_share_between_effects()
|
|
370
|
+
|
|
371
|
+
self._model.add_objective(self.effects.objective_effect.model.total + self.penalty.total)
|
|
372
|
+
|
|
373
|
+
def _add_share_between_effects(self):
|
|
374
|
+
for origin_effect in self.effects:
|
|
375
|
+
# 1. operation: -> hier sind es Zeitreihen (share_TS)
|
|
376
|
+
for target_effect, time_series in origin_effect.specific_share_to_other_effects_operation.items():
|
|
377
|
+
self.effects[target_effect].model.operation.add_share(
|
|
378
|
+
origin_effect.model.operation.label_full,
|
|
379
|
+
origin_effect.model.operation.total_per_timestep * time_series.active_data,
|
|
380
|
+
)
|
|
381
|
+
# 2. invest: -> hier ist es Scalar (share)
|
|
382
|
+
for target_effect, factor in origin_effect.specific_share_to_other_effects_invest.items():
|
|
383
|
+
self.effects[target_effect].model.invest.add_share(
|
|
384
|
+
origin_effect.model.invest.label_full,
|
|
385
|
+
origin_effect.model.invest.total * factor,
|
|
386
|
+
)
|