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.

Files changed (73) hide show
  1. docs/examples/00-Minimal Example.md +5 -0
  2. docs/examples/01-Basic Example.md +5 -0
  3. docs/examples/02-Complex Example.md +10 -0
  4. docs/examples/03-Calculation Modes.md +5 -0
  5. docs/examples/index.md +5 -0
  6. docs/faq/contribute.md +49 -0
  7. docs/faq/index.md +3 -0
  8. docs/images/architecture_flixOpt-pre2.0.0.png +0 -0
  9. docs/images/architecture_flixOpt.png +0 -0
  10. docs/images/flixopt-icon.svg +1 -0
  11. docs/javascripts/mathjax.js +18 -0
  12. docs/release-notes/_template.txt +32 -0
  13. docs/release-notes/index.md +7 -0
  14. docs/release-notes/v2.0.0.md +93 -0
  15. docs/release-notes/v2.0.1.md +12 -0
  16. docs/user-guide/Mathematical Notation/Bus.md +33 -0
  17. docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +132 -0
  18. docs/user-guide/Mathematical Notation/Flow.md +26 -0
  19. docs/user-guide/Mathematical Notation/LinearConverter.md +21 -0
  20. docs/user-guide/Mathematical Notation/Piecewise.md +49 -0
  21. docs/user-guide/Mathematical Notation/Storage.md +44 -0
  22. docs/user-guide/Mathematical Notation/index.md +22 -0
  23. docs/user-guide/Mathematical Notation/others.md +3 -0
  24. docs/user-guide/index.md +124 -0
  25. {flixOpt → flixopt}/__init__.py +5 -2
  26. {flixOpt → flixopt}/aggregation.py +113 -140
  27. flixopt/calculation.py +455 -0
  28. {flixOpt → flixopt}/commons.py +7 -4
  29. flixopt/components.py +630 -0
  30. {flixOpt → flixopt}/config.py +9 -8
  31. {flixOpt → flixopt}/config.yaml +3 -3
  32. flixopt/core.py +970 -0
  33. flixopt/effects.py +386 -0
  34. flixopt/elements.py +534 -0
  35. flixopt/features.py +1042 -0
  36. flixopt/flow_system.py +409 -0
  37. flixopt/interface.py +265 -0
  38. flixopt/io.py +308 -0
  39. flixopt/linear_converters.py +331 -0
  40. flixopt/plotting.py +1340 -0
  41. flixopt/results.py +898 -0
  42. flixopt/solvers.py +77 -0
  43. flixopt/structure.py +630 -0
  44. flixopt/utils.py +62 -0
  45. flixopt-2.0.1.dist-info/METADATA +145 -0
  46. flixopt-2.0.1.dist-info/RECORD +57 -0
  47. {flixopt-1.0.12.dist-info → flixopt-2.0.1.dist-info}/WHEEL +1 -1
  48. flixopt-2.0.1.dist-info/top_level.txt +6 -0
  49. pics/architecture_flixOpt-pre2.0.0.png +0 -0
  50. pics/architecture_flixOpt.png +0 -0
  51. pics/flixopt-icon.svg +1 -0
  52. pics/pics.pptx +0 -0
  53. scripts/gen_ref_pages.py +54 -0
  54. site/release-notes/_template.txt +32 -0
  55. flixOpt/calculation.py +0 -629
  56. flixOpt/components.py +0 -614
  57. flixOpt/core.py +0 -182
  58. flixOpt/effects.py +0 -410
  59. flixOpt/elements.py +0 -489
  60. flixOpt/features.py +0 -942
  61. flixOpt/flow_system.py +0 -351
  62. flixOpt/interface.py +0 -203
  63. flixOpt/linear_converters.py +0 -325
  64. flixOpt/math_modeling.py +0 -1145
  65. flixOpt/plotting.py +0 -712
  66. flixOpt/results.py +0 -563
  67. flixOpt/solvers.py +0 -21
  68. flixOpt/structure.py +0 -733
  69. flixOpt/utils.py +0 -134
  70. flixopt-1.0.12.dist-info/METADATA +0 -174
  71. flixopt-1.0.12.dist-info/RECORD +0 -29
  72. flixopt-1.0.12.dist-info/top_level.txt +0 -3
  73. {flixopt-1.0.12.dist-info → flixopt-2.0.1.dist-info/licenses}/LICENSE +0 -0
flixOpt/core.py DELETED
@@ -1,182 +0,0 @@
1
- """
2
- This module contains the core functionality of the flixOpt framework.
3
- It provides Datatypes, logging functionality, and some functions to transform data structures.
4
- """
5
-
6
- import inspect
7
- import logging
8
- from typing import Any, Dict, List, Optional, Union
9
-
10
- import numpy as np
11
-
12
- from . import utils
13
-
14
- logger = logging.getLogger('flixOpt')
15
-
16
- Skalar = Union[int, float] # Datatype
17
- Numeric = Union[int, float, np.ndarray] # Datatype
18
-
19
-
20
- class TimeSeriesData:
21
- # TODO: Move to Interface.py
22
- def __init__(self, data: Numeric, agg_group: Optional[str] = None, agg_weight: Optional[float] = None):
23
- """
24
- timeseries class for transmit timeseries AND special characteristics of timeseries,
25
- i.g. to define weights needed in calculation_type 'aggregated'
26
- EXAMPLE solar:
27
- you have several solar timeseries. These should not be overweighted
28
- compared to the remaining timeseries (i.g. heat load, price)!
29
- fixed_relative_profile_solar1 = TimeSeriesData(sol_array_1, type = 'solar')
30
- fixed_relative_profile_solar2 = TimeSeriesData(sol_array_2, type = 'solar')
31
- fixed_relative_profile_solar3 = TimeSeriesData(sol_array_3, type = 'solar')
32
- --> this 3 series of same type share one weight, i.e. internally assigned each weight = 1/3
33
- (instead of standard weight = 1)
34
-
35
- Parameters
36
- ----------
37
- data : Union[int, float, np.ndarray]
38
- The timeseries data, which can be a scalar, array, or numpy array.
39
- agg_group : str, optional
40
- The group this TimeSeriesData is a part of. agg_weight is split between members of a group. Default is None.
41
- agg_weight : float, optional
42
- The weight for calculation_type 'aggregated', should be between 0 and 1. Default is None.
43
-
44
- Raises
45
- ------
46
- Exception
47
- If both agg_group and agg_weight are set, an exception is raised.
48
- """
49
- self.data = data
50
- self.agg_group = agg_group
51
- self.agg_weight = agg_weight
52
- if (agg_group is not None) and (agg_weight is not None):
53
- raise Exception('Either <agg_group> or explicit <agg_weigth> can be used. Not both!')
54
- self.label: Optional[str] = None
55
-
56
- def __repr__(self):
57
- # Get the constructor arguments and their current values
58
- init_signature = inspect.signature(self.__init__)
59
- init_args = init_signature.parameters
60
-
61
- # Create a dictionary with argument names and their values
62
- args_str = ', '.join(f'{name}={repr(getattr(self, name, None))}' for name in init_args if name != 'self')
63
- return f'{self.__class__.__name__}({args_str})'
64
-
65
- def __str__(self):
66
- return str(self.data)
67
-
68
-
69
- Numeric_TS = Union[
70
- Skalar, np.ndarray, TimeSeriesData
71
- ] # TODO: This is not really correct throughozt the codebase. Sometimes its used for TimeSeries aswell?
72
-
73
-
74
- class TimeSeries:
75
- """
76
- Class for data that applies to time series, stored as vector (np.ndarray) or scalar.
77
-
78
- This class represents a vector or scalar value that makes the handling of time series easier.
79
- It supports various operations such as activation of specific time indices, setting explicit active data, and
80
- aggregation weight management.
81
-
82
- Attributes
83
- ----------
84
- label : str
85
- The label for the time series.
86
- data : Optional[Numeric]
87
- The actual data for the time series. Can be None.
88
- aggregated_data : Optional[Numeric]
89
- aggregated_data to use instead of data if provided.
90
- active_indices : Optional[np.ndarray]
91
- Indices of the time steps to activate.
92
- aggregation_weight : float
93
- Weight for aggregation method, between 0 and 1, normally 1.
94
- aggregation_group : str
95
- Group for calculating the aggregation weigth for aggregation method.
96
- """
97
-
98
- def __init__(self, label: str, data: Optional[Numeric_TS]):
99
- self.label: str = label
100
- if isinstance(data, TimeSeriesData):
101
- self.data = self.make_scalar_if_possible(data.data)
102
- self.aggregation_weight, self.aggregation_group = data.agg_weight, data.agg_group
103
- data.label = self.label # Connecting User_time_series to real Time_series
104
- else:
105
- self.data = self.make_scalar_if_possible(data)
106
- self.aggregation_weight, self.aggregation_group = None, None
107
-
108
- self.active_indices: Optional[Union[range, List[int]]] = None
109
- self.aggregated_data: Optional[Numeric] = None
110
-
111
- def activate_indices(self, indices: Optional[Union[range, List[int]]], aggregated_data: Optional[Numeric] = None):
112
- self.active_indices = indices
113
-
114
- if aggregated_data is not None:
115
- assert len(aggregated_data) == len(self.active_indices) or len(aggregated_data) == 1, (
116
- f'The aggregated_data has the wrong length for TimeSeries {self.label}. '
117
- f'Length should be: {len(self.active_indices)} or 1, but is {len(aggregated_data)}'
118
- )
119
- self.aggregated_data = self.make_scalar_if_possible(aggregated_data)
120
-
121
- def clear_indices_and_aggregated_data(self):
122
- self.active_indices = None
123
- self.aggregated_data = None
124
-
125
- @property
126
- def active_data(self) -> Numeric:
127
- if self.aggregated_data is not None: # Aggregated data is always active, if present
128
- return self.aggregated_data
129
-
130
- indices_not_applicable = np.isscalar(self.data) or (self.data is None) or (self.active_indices is None)
131
- if indices_not_applicable:
132
- return self.data
133
- else:
134
- return self.data[self.active_indices]
135
-
136
- @property
137
- def active_data_vector(self) -> np.ndarray:
138
- # Always returns the active data as a vector.
139
- return utils.as_vector(self.active_data, len(self.active_indices))
140
-
141
- @property
142
- def is_scalar(self) -> bool:
143
- return np.isscalar(self.data)
144
-
145
- @property
146
- def is_array(self) -> bool:
147
- return not self.is_scalar and self.data is not None
148
-
149
- def __repr__(self):
150
- # Retrieve all attributes and their values
151
- attrs = vars(self)
152
- # Format each attribute as 'key=value'
153
- attrs_str = ', '.join(f'{key}={value!r}' for key, value in attrs.items())
154
- # Format the output as 'ClassName(attr1=value1, attr2=value2, ...)'
155
- return f'{self.__class__.__name__}({attrs_str})'
156
-
157
- def __str__(self):
158
- return str(self.active_data)
159
-
160
- @staticmethod
161
- def make_scalar_if_possible(data: Optional[Numeric]) -> Optional[Numeric]:
162
- """
163
- Convert an array to a scalar if all values are equal, or return the array as-is.
164
- Can Return None if the passed data is None
165
-
166
- Parameters
167
- ----------
168
- data : Numeric, None
169
- The data to process.
170
-
171
- Returns
172
- -------
173
- Numeric
174
- A scalar if all values in the array are equal, otherwise the array itself. None, if the passed value is None
175
- """
176
- # TODO: Should this really return None Values?
177
- if np.isscalar(data) or data is None:
178
- return data
179
- data = np.array(data)
180
- if np.all(data == data[0]):
181
- return data[0]
182
- return data
flixOpt/effects.py DELETED
@@ -1,410 +0,0 @@
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
- from typing import Dict, Literal, Optional, Union
10
-
11
- import numpy as np
12
-
13
- from .core import Numeric, Numeric_TS, Skalar, TimeSeries
14
- from .features import ShareAllocationModel
15
- from .math_modeling import Equation, Variable
16
- from .structure import Element, ElementModel, SystemModel, _create_time_series
17
-
18
- logger = logging.getLogger('flixOpt')
19
-
20
-
21
- class Effect(Element):
22
- """
23
- Effect, i.g. costs, CO2 emissions, area, ...
24
- Components, FLows, and so on can contribute to an Effect. One Effect is chosen as the Objective of the Optimization
25
- """
26
-
27
- def __init__(
28
- self,
29
- label: str,
30
- unit: str,
31
- description: str,
32
- meta_data: Optional[Dict] = None,
33
- is_standard: bool = False,
34
- is_objective: bool = False,
35
- specific_share_to_other_effects_operation: 'EffectValues' = None,
36
- specific_share_to_other_effects_invest: 'EffectValuesInvest' = None,
37
- minimum_operation: Optional[Skalar] = None,
38
- maximum_operation: Optional[Skalar] = None,
39
- minimum_invest: Optional[Skalar] = None,
40
- maximum_invest: Optional[Skalar] = None,
41
- minimum_operation_per_hour: Optional[Numeric_TS] = None,
42
- maximum_operation_per_hour: Optional[Numeric_TS] = None,
43
- minimum_total: Optional[Skalar] = None,
44
- maximum_total: Optional[Skalar] = None,
45
- ):
46
- """
47
- Parameters
48
- ----------
49
- label : str
50
- name
51
- unit : str
52
- unit of effect, i.g. €, kg_CO2, kWh_primaryEnergy
53
- description : str
54
- long name
55
- meta_data : Optional[Dict]
56
- used to store more information about the element. Is not used internally, but saved in the results
57
- is_standard : boolean, optional
58
- true, if Standard-Effect (for direct input of value without effect (alternatively to dict)) , else false
59
- is_objective : boolean, optional
60
- true, if optimization target
61
- specific_share_to_other_effects_operation : {effectType: TS, ...}, i.g. 180 €/t_CO2, input as {costs: 180}, optional
62
- share to other effects (only operation)
63
- specific_share_to_other_effects_invest : {effectType: TS, ...}, i.g. 180 €/t_CO2, input as {costs: 180}, optional
64
- share to other effects (only invest).
65
- minimum_operation : scalar, optional
66
- minimal sum (only operation) of the effect
67
- maximum_operation : scalar, optional
68
- maximal sum (nur operation) of the effect.
69
- minimum_operation_per_hour : scalar or TS
70
- maximum value per hour (only operation) of effect (=sum of all effect-shares) for each timestep!
71
- maximum_operation_per_hour : scalar or TS
72
- minimum value per hour (only operation) of effect (=sum of all effect-shares) for each timestep!
73
- minimum_invest : scalar, optional
74
- minimal sum (only invest) of the effect
75
- maximum_invest : scalar, optional
76
- maximal sum (only invest) of the effect
77
- minimum_total : sclalar, optional
78
- min sum of effect (invest+operation).
79
- maximum_total : scalar, optional
80
- max sum of effect (invest+operation).
81
- **kwargs : TYPE
82
- DESCRIPTION.
83
-
84
- Returns
85
- -------
86
- None.
87
-
88
- """
89
- super().__init__(label, meta_data=meta_data)
90
- self.label = label
91
- self.unit = unit
92
- self.description = description
93
- self.is_standard = is_standard
94
- self.is_objective = is_objective
95
- self.specific_share_to_other_effects_operation: Union[EffectValues, EffectTimeSeries] = (
96
- specific_share_to_other_effects_operation or {}
97
- )
98
- self.specific_share_to_other_effects_invest: Union[EffectValuesInvest, EffectDictInvest] = (
99
- specific_share_to_other_effects_invest or {}
100
- )
101
- self.minimum_operation = minimum_operation
102
- self.maximum_operation = maximum_operation
103
- self.minimum_operation_per_hour: Numeric_TS = minimum_operation_per_hour
104
- self.maximum_operation_per_hour: Numeric_TS = maximum_operation_per_hour
105
- self.minimum_invest = minimum_invest
106
- self.maximum_invest = maximum_invest
107
- self.minimum_total = minimum_total
108
- self.maximum_total = maximum_total
109
-
110
- self._plausibility_checks()
111
-
112
- def _plausibility_checks(self) -> None:
113
- # Check circular loops in effects: (Effekte fügen sich gegenseitig Shares hinzu):
114
- # TODO: Improve checks!! Only most basic case covered...
115
-
116
- def error_str(effect_label: str, share_ffect_label: str):
117
- return (
118
- f' {effect_label} -> has share in: {share_ffect_label}\n'
119
- f' {share_ffect_label} -> has share in: {effect_label}'
120
- )
121
-
122
- # Effekt darf nicht selber als Share in seinen ShareEffekten auftauchen:
123
- # operation:
124
- for target_effect in self.specific_share_to_other_effects_operation.keys():
125
- assert self not in target_effect.specific_share_to_other_effects_operation.keys(), (
126
- f'Error: circular operation-shares \n{error_str(target_effect.label, target_effect.label)}'
127
- )
128
- # invest:
129
- for target_effect in self.specific_share_to_other_effects_invest.keys():
130
- assert self not in target_effect.specific_share_to_other_effects_invest.keys(), (
131
- f'Error: circular invest-shares \n{error_str(target_effect.label, target_effect.label)}'
132
- )
133
-
134
- def transform_data(self):
135
- self.minimum_operation_per_hour = _create_time_series(
136
- 'minimum_operation_per_hour', self.minimum_operation_per_hour, self
137
- )
138
- self.maximum_operation_per_hour = _create_time_series(
139
- 'maximum_operation_per_hour', self.maximum_operation_per_hour, self
140
- )
141
-
142
- self.specific_share_to_other_effects_operation = effect_values_to_time_series(
143
- 'specific_share_to_other_effects_operation', self.specific_share_to_other_effects_operation, self
144
- )
145
-
146
- def create_model(self) -> 'EffectModel':
147
- self.model = EffectModel(self)
148
- return self.model
149
-
150
-
151
- class EffectModel(ElementModel):
152
- def __init__(self, element: Effect):
153
- super().__init__(element)
154
- self.element: Effect
155
- self.invest = ShareAllocationModel(
156
- self.element, 'invest', False, total_max=self.element.maximum_invest, total_min=self.element.minimum_invest
157
- )
158
- self.operation = ShareAllocationModel(
159
- self.element,
160
- 'operation',
161
- True,
162
- total_max=self.element.maximum_operation,
163
- total_min=self.element.minimum_operation,
164
- min_per_hour=self.element.minimum_operation_per_hour.active_data
165
- if self.element.minimum_operation_per_hour is not None
166
- else None,
167
- max_per_hour=self.element.maximum_operation_per_hour.active_data
168
- if self.element.maximum_operation_per_hour is not None
169
- else None,
170
- )
171
- self.all = ShareAllocationModel(
172
- self.element, 'all', False, total_max=self.element.maximum_total, total_min=self.element.minimum_total
173
- )
174
- self.sub_models.extend([self.invest, self.operation, self.all])
175
-
176
- def do_modeling(self, system_model: SystemModel):
177
- for model in self.sub_models:
178
- model.do_modeling(system_model)
179
-
180
- self.all.add_share(system_model, 'operation', self.operation.sum, 1)
181
- self.all.add_share(system_model, 'invest', self.invest.sum, 1)
182
-
183
-
184
- EffectDict = Dict[Optional['Effect'], Numeric]
185
- EffectDictInvest = Dict[Optional['Effect'], Skalar]
186
-
187
- EffectValues = Optional[Union[Numeric_TS, EffectDict]] # Datatype for User Input
188
- EffectValuesInvest = Optional[Union[Skalar, EffectDictInvest]] # Datatype for User Input
189
-
190
- EffectTimeSeries = Dict[Optional['Effect'], TimeSeries] # Final Internal Data Structure
191
- ElementTimeSeries = Dict[Optional[Element], TimeSeries] # Final Internal Data Structure
192
-
193
-
194
- def nested_values_to_time_series(
195
- nested_values: Dict[Element, Numeric_TS], label_suffix: str, parent_element: Element
196
- ) -> ElementTimeSeries:
197
- """
198
- Creates TimeSeries from nested values, which are a Dict of Elements to values.
199
- The resulting label of the TimeSeries is the label of the parent_element, followed by the label of the element in
200
- the nested_values and the label_suffix.
201
- """
202
- return {
203
- element: _create_time_series(f'{element.label}_{label_suffix}', value, parent_element)
204
- for element, value in nested_values.items()
205
- if element is not None
206
- }
207
-
208
-
209
- def effect_values_to_time_series(
210
- label_suffix: str, nested_values: EffectValues, parent_element: Element
211
- ) -> Optional[EffectTimeSeries]:
212
- """
213
- Creates TimeSeries from EffectValues. The resulting label of the TimeSeries is the label of the parent_element,
214
- followed by the label of the Effect in the nested_values and the label_suffix.
215
- If the key in the EffectValues is None, the alias 'Standart_Effect' is used
216
- """
217
- nested_values = as_effect_dict(nested_values)
218
- if nested_values is None:
219
- return None
220
- else:
221
- standard_value = nested_values.pop(None, None)
222
- transformed_values = nested_values_to_time_series(nested_values, label_suffix, parent_element)
223
- if standard_value is not None:
224
- transformed_values[None] = _create_time_series(
225
- f'Standard_Effect_{label_suffix}', standard_value, parent_element
226
- )
227
- return transformed_values
228
-
229
-
230
- def as_effect_dict(effect_values: EffectValues) -> Optional[EffectDict]:
231
- """
232
- Converts effect values into a dictionary. If a scalar is provided, it is associated with a default effect type.
233
-
234
- Examples
235
- --------
236
- costs = 20 -> {None: 20}
237
- costs = None -> None
238
- costs = {effect1: 20, effect2: 0.3} -> {effect1: 20, effect2: 0.3}
239
-
240
- Parameters
241
- ----------
242
- effect_values : None, int, float, TimeSeries, or dict
243
- The effect values to convert, either a scalar, TimeSeries, or a dictionary.
244
-
245
- Returns
246
- -------
247
- dict or None
248
- A dictionary with None or Effect as the key, or None if input is None.
249
- """
250
- return (
251
- effect_values
252
- if isinstance(effect_values, dict)
253
- else {None: effect_values}
254
- if effect_values is not None
255
- else None
256
- )
257
-
258
-
259
- def effect_values_from_effect_time_series(effect_time_series: EffectTimeSeries) -> Dict[Optional[Effect], Numeric]:
260
- return {effect: time_series.active_data for effect, time_series in effect_time_series.items()}
261
-
262
-
263
- class EffectCollection:
264
- """
265
- Handling all Effects
266
- """
267
-
268
- def __init__(self, label: str):
269
- self.label = label
270
- self.model: Optional[EffectCollectionModel] = None
271
- self.effects: Dict[str, Effect] = {}
272
-
273
- def create_model(self, system_model: SystemModel) -> 'EffectCollectionModel':
274
- self.model = EffectCollectionModel(self, system_model)
275
- return self.model
276
-
277
- def add_effect(self, effect: 'Effect') -> None:
278
- if effect.is_standard and self.standard_effect is not None:
279
- raise Exception(f'A standard-effect already exists! ({self.standard_effect.label=})')
280
- if effect.is_objective and self.objective_effect is not None:
281
- raise Exception(f'A objective-effect already exists! ({self.objective_effect.label=})')
282
- if effect in self.effects.values():
283
- raise Exception(f'Effect already added! ({effect.label=})')
284
- if effect.label in self.effects:
285
- raise Exception(f'Effect with label "{effect.label=}" already added!')
286
- self.effects[effect.label] = effect
287
-
288
- @property
289
- def standard_effect(self) -> Optional[Effect]:
290
- for effect in self.effects.values():
291
- if effect.is_standard:
292
- return effect
293
-
294
- @property
295
- def objective_effect(self) -> Optional[Effect]:
296
- for effect in self.effects.values():
297
- if effect.is_objective:
298
- return effect
299
-
300
- @property
301
- def label_full(self):
302
- return self.label
303
-
304
-
305
- class EffectCollectionModel(ElementModel):
306
- # TODO: Maybe all EffectModels should be sub_models of this Model? Including Objective and Penalty?
307
- def __init__(self, element: EffectCollection, system_model: SystemModel):
308
- super().__init__(element)
309
- self.element = element
310
- self._system_model = system_model
311
- self._effect_models: Dict[Effect, EffectModel] = {}
312
- self.penalty: Optional[ShareAllocationModel] = None
313
- self.objective: Optional[Equation] = None
314
-
315
- def do_modeling(self, system_model: SystemModel):
316
- self._effect_models = {effect: effect.create_model() for effect in self.element.effects.values()}
317
- self.penalty = ShareAllocationModel(self.element, 'penalty', False)
318
- self.sub_models.extend(list(self._effect_models.values()) + [self.penalty])
319
- for model in self.sub_models:
320
- model.do_modeling(system_model)
321
-
322
- self.add_share_between_effects()
323
-
324
- self.objective = Equation('OBJECTIVE', 'OBJECTIVE', is_objective=True)
325
- self.objective.add_summand(self._objective_effect_model.operation.sum, 1)
326
- self.objective.add_summand(self._objective_effect_model.invest.sum, 1)
327
- self.objective.add_summand(self.penalty.sum, 1)
328
-
329
- @property
330
- def _objective_effect_model(self) -> EffectModel:
331
- return self._effect_models[self.element.objective_effect]
332
-
333
- def _add_share_to_effects(
334
- self,
335
- name: str,
336
- element: Element,
337
- target: Literal['operation', 'invest'],
338
- effect_values: EffectDict,
339
- factor: Numeric,
340
- variable: Optional[Variable] = None,
341
- ) -> None:
342
- # an alle Effects, die einen Wert haben, anhängen:
343
- for effect, value in effect_values.items():
344
- if effect is None: # Falls None, dann Standard-effekt nutzen:
345
- effect = self.element.standard_effect
346
- assert effect in self.element.effects.values(), f'Effect {effect.label} was used but not added to model!'
347
-
348
- if target == 'operation':
349
- model = self._effect_models[effect].operation
350
- elif target == 'invest':
351
- model = self._effect_models[effect].invest
352
- else:
353
- raise ValueError(f'Target {target} not supported!')
354
-
355
- name_of_share = f'{element.label_full}__{name}'
356
- total_factor = np.multiply(value, factor)
357
- model.add_share(self._system_model, name_of_share, variable, total_factor)
358
-
359
- def add_share_to_invest(
360
- self,
361
- name: str,
362
- element: Element,
363
- effect_values: EffectDictInvest,
364
- factor: Numeric,
365
- variable: Optional[Variable] = None,
366
- ) -> None:
367
- # TODO: Add checks
368
- self._add_share_to_effects(name, element, 'invest', effect_values, factor, variable)
369
-
370
- def add_share_to_operation(
371
- self,
372
- name: str,
373
- element: Element,
374
- effect_values: EffectTimeSeries,
375
- factor: Numeric,
376
- variable: Optional[Variable] = None,
377
- ) -> None:
378
- # TODO: Add checks
379
- self._add_share_to_effects(
380
- name, element, 'operation', effect_values_from_effect_time_series(effect_values), factor, variable
381
- )
382
-
383
- def add_share_to_penalty(
384
- self,
385
- name: Optional[str],
386
- variable: Variable,
387
- factor: Numeric,
388
- ) -> None:
389
- assert variable is not None, 'A Variable must be passed to add a share to penalty! Else its a constant Penalty!'
390
- self.penalty.add_share(self._system_model, name, variable, factor, True)
391
-
392
- def add_share_between_effects(self):
393
- for origin_effect in self.element.effects.values():
394
- # 1. operation: -> hier sind es Zeitreihen (share_TS)
395
- for target_effect, time_series in origin_effect.specific_share_to_other_effects_operation.items():
396
- target_model = self._effect_models[target_effect].operation
397
- origin_model = self._effect_models[origin_effect].operation
398
- target_model.add_share(
399
- self._system_model,
400
- f'{origin_effect.label_full}_operation',
401
- origin_model.sum_TS,
402
- time_series.active_data,
403
- )
404
- # 2. invest: -> hier ist es Skalar (share)
405
- for target_effect, factor in origin_effect.specific_share_to_other_effects_invest.items():
406
- target_model = self._effect_models[target_effect].invest
407
- origin_model = self._effect_models[origin_effect].invest
408
- target_model.add_share(
409
- self._system_model, f'{origin_effect.label_full}_invest', origin_model.sum, factor
410
- )