flixopt 2.1.7__py3-none-any.whl → 2.1.9__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/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +8 -8
- docs/user-guide/Mathematical Notation/Flow.md +3 -3
- docs/user-guide/Mathematical Notation/InvestParameters.md +3 -0
- docs/user-guide/Mathematical Notation/LinearConverter.md +3 -3
- docs/user-guide/Mathematical Notation/OnOffParameters.md +3 -0
- docs/user-guide/Mathematical Notation/Storage.md +1 -1
- flixopt/aggregation.py +33 -32
- flixopt/calculation.py +158 -58
- flixopt/components.py +673 -150
- flixopt/config.py +17 -8
- flixopt/core.py +59 -54
- flixopt/effects.py +144 -63
- flixopt/elements.py +292 -107
- flixopt/features.py +61 -58
- flixopt/flow_system.py +69 -48
- flixopt/interface.py +952 -113
- flixopt/io.py +15 -10
- flixopt/linear_converters.py +373 -81
- flixopt/network_app.py +73 -39
- flixopt/plotting.py +215 -87
- flixopt/results.py +382 -209
- flixopt/solvers.py +25 -21
- flixopt/structure.py +41 -37
- flixopt/utils.py +10 -7
- {flixopt-2.1.7.dist-info → flixopt-2.1.9.dist-info}/METADATA +46 -42
- {flixopt-2.1.7.dist-info → flixopt-2.1.9.dist-info}/RECORD +30 -28
- scripts/gen_ref_pages.py +1 -1
- {flixopt-2.1.7.dist-info → flixopt-2.1.9.dist-info}/WHEEL +0 -0
- {flixopt-2.1.7.dist-info → flixopt-2.1.9.dist-info}/licenses/LICENSE +0 -0
- {flixopt-2.1.7.dist-info → flixopt-2.1.9.dist-info}/top_level.txt +0 -0
flixopt/effects.py
CHANGED
|
@@ -5,19 +5,22 @@ Different Datatypes are used to represent the effects with assigned values by th
|
|
|
5
5
|
which are then transformed into the internal data structure.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
8
10
|
import logging
|
|
9
11
|
import warnings
|
|
10
|
-
from typing import TYPE_CHECKING,
|
|
12
|
+
from typing import TYPE_CHECKING, Literal
|
|
11
13
|
|
|
12
14
|
import linopy
|
|
13
15
|
import numpy as np
|
|
14
|
-
import pandas as pd
|
|
15
16
|
|
|
16
|
-
from .core import
|
|
17
|
+
from .core import NumericDataTS, Scalar, TimeSeries
|
|
17
18
|
from .features import ShareAllocationModel
|
|
18
|
-
from .structure import Element, ElementModel,
|
|
19
|
+
from .structure import Element, ElementModel, Model, SystemModel, register_class_for_io
|
|
19
20
|
|
|
20
21
|
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Iterator
|
|
23
|
+
|
|
21
24
|
from .flow_system import FlowSystem
|
|
22
25
|
|
|
23
26
|
logger = logging.getLogger('flixopt')
|
|
@@ -26,8 +29,107 @@ logger = logging.getLogger('flixopt')
|
|
|
26
29
|
@register_class_for_io
|
|
27
30
|
class Effect(Element):
|
|
28
31
|
"""
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
Represents system-wide impacts like costs, emissions, resource consumption, or other effects.
|
|
33
|
+
|
|
34
|
+
Effects capture the broader impacts of system operation and investment decisions beyond
|
|
35
|
+
the primary energy/material flows. Each Effect accumulates contributions from Components,
|
|
36
|
+
Flows, and other system elements. One Effect is typically chosen as the optimization
|
|
37
|
+
objective, while others can serve as constraints or tracking metrics.
|
|
38
|
+
|
|
39
|
+
Effects support comprehensive modeling including operational and investment contributions,
|
|
40
|
+
cross-effect relationships (e.g., carbon pricing), and flexible constraint formulation.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
label: The label of the Element. Used to identify it in the FlowSystem.
|
|
44
|
+
unit: The unit of the effect (e.g., '€', 'kg_CO2', 'kWh_primary', 'm²').
|
|
45
|
+
This is informative only and does not affect optimization calculations.
|
|
46
|
+
description: Descriptive name explaining what this effect represents.
|
|
47
|
+
is_standard: If True, this is a standard effect allowing direct value input
|
|
48
|
+
without effect dictionaries. Used for simplified effect specification (and less boilerplate code).
|
|
49
|
+
is_objective: If True, this effect serves as the optimization objective function.
|
|
50
|
+
Only one effect can be marked as objective per optimization.
|
|
51
|
+
specific_share_to_other_effects_operation: Operational cross-effect contributions.
|
|
52
|
+
Maps this effect's operational values to contributions to other effects
|
|
53
|
+
specific_share_to_other_effects_invest: Investment cross-effect contributions.
|
|
54
|
+
Maps this effect's investment values to contributions to other effects.
|
|
55
|
+
minimum_operation: Minimum allowed total operational contribution across all timesteps.
|
|
56
|
+
maximum_operation: Maximum allowed total operational contribution across all timesteps.
|
|
57
|
+
minimum_operation_per_hour: Minimum allowed operational contribution per timestep.
|
|
58
|
+
maximum_operation_per_hour: Maximum allowed operational contribution per timestep.
|
|
59
|
+
minimum_invest: Minimum allowed total investment contribution.
|
|
60
|
+
maximum_invest: Maximum allowed total investment contribution.
|
|
61
|
+
minimum_total: Minimum allowed total effect (operation + investment combined).
|
|
62
|
+
maximum_total: Maximum allowed total effect (operation + investment combined).
|
|
63
|
+
meta_data: Used to store additional information. Not used internally but saved
|
|
64
|
+
in results. Only use Python native types.
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
Basic cost objective:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
cost_effect = Effect(label='system_costs', unit='€', description='Total system costs', is_objective=True)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
CO2 emissions with carbon pricing:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
co2_effect = Effect(
|
|
77
|
+
label='co2_emissions',
|
|
78
|
+
unit='kg_CO2',
|
|
79
|
+
description='Carbon dioxide emissions',
|
|
80
|
+
specific_share_to_other_effects_operation={'costs': 50}, # €50/t_CO2
|
|
81
|
+
maximum_total=1_000_000, # 1000 t CO2 annual limit
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Land use constraint:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
land_use = Effect(
|
|
89
|
+
label='land_usage',
|
|
90
|
+
unit='m²',
|
|
91
|
+
description='Land area requirement',
|
|
92
|
+
maximum_total=50_000, # Maximum 5 hectares available
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Primary energy tracking:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
primary_energy = Effect(
|
|
100
|
+
label='primary_energy',
|
|
101
|
+
unit='kWh_primary',
|
|
102
|
+
description='Primary energy consumption',
|
|
103
|
+
specific_share_to_other_effects_operation={'costs': 0.08}, # €0.08/kWh
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Water consumption with tiered constraints:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
water_usage = Effect(
|
|
111
|
+
label='water_consumption',
|
|
112
|
+
unit='m³',
|
|
113
|
+
description='Industrial water usage',
|
|
114
|
+
minimum_operation_per_hour=10, # Minimum 10 m³/h for process stability
|
|
115
|
+
maximum_operation_per_hour=500, # Maximum 500 m³/h capacity limit
|
|
116
|
+
maximum_total=100_000, # Annual permit limit: 100,000 m³
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Note:
|
|
121
|
+
Effect bounds can be None to indicate no constraint in that direction.
|
|
122
|
+
|
|
123
|
+
Cross-effect relationships enable sophisticated modeling like carbon pricing,
|
|
124
|
+
resource valuation, or multi-criteria optimization with weighted objectives.
|
|
125
|
+
|
|
126
|
+
The unit field is purely informational - ensure dimensional consistency
|
|
127
|
+
across all contributions to each effect manually.
|
|
128
|
+
|
|
129
|
+
Effects are accumulated as:
|
|
130
|
+
- Total = Σ(operational contributions) + Σ(investment contributions)
|
|
131
|
+
- Cross-effects add to target effects based on specific_share ratios
|
|
132
|
+
|
|
31
133
|
"""
|
|
32
134
|
|
|
33
135
|
def __init__(
|
|
@@ -35,41 +137,20 @@ class Effect(Element):
|
|
|
35
137
|
label: str,
|
|
36
138
|
unit: str,
|
|
37
139
|
description: str,
|
|
38
|
-
meta_data:
|
|
140
|
+
meta_data: dict | None = None,
|
|
39
141
|
is_standard: bool = False,
|
|
40
142
|
is_objective: bool = False,
|
|
41
|
-
specific_share_to_other_effects_operation:
|
|
42
|
-
specific_share_to_other_effects_invest:
|
|
43
|
-
minimum_operation:
|
|
44
|
-
maximum_operation:
|
|
45
|
-
minimum_invest:
|
|
46
|
-
maximum_invest:
|
|
47
|
-
minimum_operation_per_hour:
|
|
48
|
-
maximum_operation_per_hour:
|
|
49
|
-
minimum_total:
|
|
50
|
-
maximum_total:
|
|
143
|
+
specific_share_to_other_effects_operation: EffectValuesUser | None = None,
|
|
144
|
+
specific_share_to_other_effects_invest: EffectValuesUser | None = None,
|
|
145
|
+
minimum_operation: Scalar | None = None,
|
|
146
|
+
maximum_operation: Scalar | None = None,
|
|
147
|
+
minimum_invest: Scalar | None = None,
|
|
148
|
+
maximum_invest: Scalar | None = None,
|
|
149
|
+
minimum_operation_per_hour: NumericDataTS | None = None,
|
|
150
|
+
maximum_operation_per_hour: NumericDataTS | None = None,
|
|
151
|
+
minimum_total: Scalar | None = None,
|
|
152
|
+
maximum_total: Scalar | None = None,
|
|
51
153
|
):
|
|
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
154
|
super().__init__(label, meta_data=meta_data)
|
|
74
155
|
self.label = label
|
|
75
156
|
self.unit = unit
|
|
@@ -82,14 +163,14 @@ class Effect(Element):
|
|
|
82
163
|
self.specific_share_to_other_effects_invest: EffectValuesUser = specific_share_to_other_effects_invest or {}
|
|
83
164
|
self.minimum_operation = minimum_operation
|
|
84
165
|
self.maximum_operation = maximum_operation
|
|
85
|
-
self.minimum_operation_per_hour
|
|
86
|
-
self.maximum_operation_per_hour
|
|
166
|
+
self.minimum_operation_per_hour = minimum_operation_per_hour
|
|
167
|
+
self.maximum_operation_per_hour = maximum_operation_per_hour
|
|
87
168
|
self.minimum_invest = minimum_invest
|
|
88
169
|
self.maximum_invest = maximum_invest
|
|
89
170
|
self.minimum_total = minimum_total
|
|
90
171
|
self.maximum_total = maximum_total
|
|
91
172
|
|
|
92
|
-
def transform_data(self, flow_system:
|
|
173
|
+
def transform_data(self, flow_system: FlowSystem):
|
|
93
174
|
self.minimum_operation_per_hour = flow_system.create_time_series(
|
|
94
175
|
f'{self.label_full}|minimum_operation_per_hour', self.minimum_operation_per_hour
|
|
95
176
|
)
|
|
@@ -102,7 +183,7 @@ class Effect(Element):
|
|
|
102
183
|
f'{self.label_full}|operation->', self.specific_share_to_other_effects_operation, 'operation'
|
|
103
184
|
)
|
|
104
185
|
|
|
105
|
-
def create_model(self, model: SystemModel) ->
|
|
186
|
+
def create_model(self, model: SystemModel) -> EffectModel:
|
|
106
187
|
self._plausibility_checks()
|
|
107
188
|
self.model = EffectModel(model, self)
|
|
108
189
|
return self.model
|
|
@@ -116,7 +197,7 @@ class EffectModel(ElementModel):
|
|
|
116
197
|
def __init__(self, model: SystemModel, element: Effect):
|
|
117
198
|
super().__init__(model, element)
|
|
118
199
|
self.element: Effect = element
|
|
119
|
-
self.total:
|
|
200
|
+
self.total: linopy.Variable | None = None
|
|
120
201
|
self.invest: ShareAllocationModel = self.add(
|
|
121
202
|
ShareAllocationModel(
|
|
122
203
|
self._model,
|
|
@@ -169,13 +250,13 @@ class EffectModel(ElementModel):
|
|
|
169
250
|
)
|
|
170
251
|
|
|
171
252
|
|
|
172
|
-
EffectValuesExpr =
|
|
173
|
-
EffectTimeSeries =
|
|
174
|
-
EffectValuesDict =
|
|
175
|
-
EffectValuesUser =
|
|
253
|
+
EffectValuesExpr = dict[str, linopy.LinearExpression] # Used to create Shares
|
|
254
|
+
EffectTimeSeries = dict[str, TimeSeries] # Used internally to index values
|
|
255
|
+
EffectValuesDict = dict[str, NumericDataTS] # How effect values are stored
|
|
256
|
+
EffectValuesUser = NumericDataTS | dict[str, NumericDataTS] # User-specified Shares to Effects
|
|
176
257
|
""" This datatype is used to define the share to an effect by a certain attribute. """
|
|
177
258
|
|
|
178
|
-
EffectValuesUserScalar =
|
|
259
|
+
EffectValuesUserScalar = Scalar | dict[str, Scalar] # User-specified Shares to Effects
|
|
179
260
|
""" This datatype is used to define the share to an effect by a certain attribute. Only scalars are allowed. """
|
|
180
261
|
|
|
181
262
|
|
|
@@ -184,15 +265,15 @@ class EffectCollection:
|
|
|
184
265
|
Handling all Effects
|
|
185
266
|
"""
|
|
186
267
|
|
|
187
|
-
def __init__(self, *effects:
|
|
268
|
+
def __init__(self, *effects: Effect):
|
|
188
269
|
self._effects = {}
|
|
189
|
-
self._standard_effect:
|
|
190
|
-
self._objective_effect:
|
|
270
|
+
self._standard_effect: Effect | None = None
|
|
271
|
+
self._objective_effect: Effect | None = None
|
|
191
272
|
|
|
192
|
-
self.model:
|
|
273
|
+
self.model: EffectCollectionModel | None = None
|
|
193
274
|
self.add_effects(*effects)
|
|
194
275
|
|
|
195
|
-
def create_model(self, model: SystemModel) ->
|
|
276
|
+
def create_model(self, model: SystemModel) -> EffectCollectionModel:
|
|
196
277
|
self._plausibility_checks()
|
|
197
278
|
self.model = EffectCollectionModel(model, self)
|
|
198
279
|
return self.model
|
|
@@ -208,7 +289,7 @@ class EffectCollection:
|
|
|
208
289
|
self._effects[effect.label] = effect
|
|
209
290
|
logger.info(f'Registered new Effect: {effect.label}')
|
|
210
291
|
|
|
211
|
-
def create_effect_values_dict(self, effect_values_user: EffectValuesUser) ->
|
|
292
|
+
def create_effect_values_dict(self, effect_values_user: EffectValuesUser) -> EffectValuesDict | None:
|
|
212
293
|
"""
|
|
213
294
|
Converts effect values into a dictionary. If a scalar is provided, it is associated with a default effect type.
|
|
214
295
|
|
|
@@ -224,7 +305,7 @@ class EffectCollection:
|
|
|
224
305
|
A dictionary with None or Effect as the key, or None if input is None.
|
|
225
306
|
"""
|
|
226
307
|
|
|
227
|
-
def get_effect_label(eff:
|
|
308
|
+
def get_effect_label(eff: Effect | str) -> str:
|
|
228
309
|
"""Temporary function to get the label of an effect and warn for deprecation"""
|
|
229
310
|
if isinstance(eff, Effect):
|
|
230
311
|
warnings.warn(
|
|
@@ -233,7 +314,7 @@ class EffectCollection:
|
|
|
233
314
|
UserWarning,
|
|
234
315
|
stacklevel=2,
|
|
235
316
|
)
|
|
236
|
-
return eff.
|
|
317
|
+
return eff.label
|
|
237
318
|
else:
|
|
238
319
|
return eff
|
|
239
320
|
|
|
@@ -241,7 +322,7 @@ class EffectCollection:
|
|
|
241
322
|
return None
|
|
242
323
|
if isinstance(effect_values_user, dict):
|
|
243
324
|
return {get_effect_label(effect): value for effect, value in effect_values_user.items()}
|
|
244
|
-
return {self.standard_effect.
|
|
325
|
+
return {self.standard_effect.label: effect_values_user}
|
|
245
326
|
|
|
246
327
|
def _plausibility_checks(self) -> None:
|
|
247
328
|
# Check circular loops in effects:
|
|
@@ -258,15 +339,15 @@ class EffectCollection:
|
|
|
258
339
|
# operation:
|
|
259
340
|
for target_effect in effect.specific_share_to_other_effects_operation.keys():
|
|
260
341
|
assert effect not in self[target_effect].specific_share_to_other_effects_operation.keys(), (
|
|
261
|
-
f'Error: circular operation-shares \n{error_str(
|
|
342
|
+
f'Error: circular operation-shares \n{error_str(effect.label, self[target_effect].label)}'
|
|
262
343
|
)
|
|
263
344
|
# invest:
|
|
264
345
|
for target_effect in effect.specific_share_to_other_effects_invest.keys():
|
|
265
346
|
assert effect not in self[target_effect].specific_share_to_other_effects_invest.keys(), (
|
|
266
|
-
f'Error: circular invest-shares \n{error_str(
|
|
347
|
+
f'Error: circular invest-shares \n{error_str(effect.label, self[target_effect].label)}'
|
|
267
348
|
)
|
|
268
349
|
|
|
269
|
-
def __getitem__(self, effect:
|
|
350
|
+
def __getitem__(self, effect: str | Effect | None) -> Effect:
|
|
270
351
|
"""
|
|
271
352
|
Get an effect by label, or return the standard effect if None is passed
|
|
272
353
|
|
|
@@ -292,7 +373,7 @@ class EffectCollection:
|
|
|
292
373
|
def __len__(self) -> int:
|
|
293
374
|
return len(self._effects)
|
|
294
375
|
|
|
295
|
-
def __contains__(self, item:
|
|
376
|
+
def __contains__(self, item: str | Effect) -> bool:
|
|
296
377
|
"""Check if the effect exists. Checks for label or object"""
|
|
297
378
|
if isinstance(item, str):
|
|
298
379
|
return item in self.effects # Check if the label exists
|
|
@@ -301,7 +382,7 @@ class EffectCollection:
|
|
|
301
382
|
return False
|
|
302
383
|
|
|
303
384
|
@property
|
|
304
|
-
def effects(self) ->
|
|
385
|
+
def effects(self) -> dict[str, Effect]:
|
|
305
386
|
return self._effects
|
|
306
387
|
|
|
307
388
|
@property
|
|
@@ -337,7 +418,7 @@ class EffectCollectionModel(Model):
|
|
|
337
418
|
def __init__(self, model: SystemModel, effects: EffectCollection):
|
|
338
419
|
super().__init__(model, label_of_element='Effects')
|
|
339
420
|
self.effects = effects
|
|
340
|
-
self.penalty:
|
|
421
|
+
self.penalty: ShareAllocationModel | None = None
|
|
341
422
|
|
|
342
423
|
def add_share_to_effects(
|
|
343
424
|
self,
|