flixopt 2.1.6__py3-none-any.whl → 2.1.8__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 +1 -1
- docs/examples/01-Basic Example.md +1 -1
- docs/examples/02-Complex Example.md +1 -1
- docs/examples/index.md +1 -1
- docs/faq/contribute.md +26 -14
- docs/faq/index.md +1 -1
- docs/javascripts/mathjax.js +1 -1
- docs/user-guide/Mathematical Notation/Bus.md +1 -1
- docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +21 -21
- 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 +5 -5
- docs/user-guide/Mathematical Notation/OnOffParameters.md +3 -0
- docs/user-guide/Mathematical Notation/Piecewise.md +1 -1
- docs/user-guide/Mathematical Notation/Storage.md +2 -2
- docs/user-guide/Mathematical Notation/index.md +1 -1
- docs/user-guide/Mathematical Notation/others.md +1 -1
- docs/user-guide/index.md +2 -2
- flixopt/__init__.py +4 -0
- flixopt/aggregation.py +33 -32
- flixopt/calculation.py +161 -65
- flixopt/components.py +687 -154
- flixopt/config.py +17 -8
- flixopt/core.py +69 -60
- flixopt/effects.py +146 -64
- flixopt/elements.py +297 -110
- flixopt/features.py +78 -71
- flixopt/flow_system.py +72 -50
- flixopt/interface.py +952 -113
- flixopt/io.py +15 -10
- flixopt/linear_converters.py +373 -81
- flixopt/network_app.py +445 -266
- flixopt/plotting.py +215 -87
- flixopt/results.py +382 -209
- flixopt/solvers.py +25 -21
- flixopt/structure.py +41 -39
- flixopt/utils.py +10 -7
- {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/METADATA +64 -53
- flixopt-2.1.8.dist-info/RECORD +56 -0
- scripts/extract_release_notes.py +5 -5
- scripts/gen_ref_pages.py +1 -1
- flixopt-2.1.6.dist-info/RECORD +0 -54
- {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/WHEEL +0 -0
- {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/licenses/LICENSE +0 -0
- {flixopt-2.1.6.dist-info → flixopt-2.1.8.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,26 +163,27 @@ 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
|
)
|
|
96
177
|
self.maximum_operation_per_hour = flow_system.create_time_series(
|
|
97
|
-
f'{self.label_full}|maximum_operation_per_hour',
|
|
178
|
+
f'{self.label_full}|maximum_operation_per_hour',
|
|
179
|
+
self.maximum_operation_per_hour,
|
|
98
180
|
)
|
|
99
181
|
|
|
100
182
|
self.specific_share_to_other_effects_operation = flow_system.create_effect_time_series(
|
|
101
183
|
f'{self.label_full}|operation->', self.specific_share_to_other_effects_operation, 'operation'
|
|
102
184
|
)
|
|
103
185
|
|
|
104
|
-
def create_model(self, model: SystemModel) ->
|
|
186
|
+
def create_model(self, model: SystemModel) -> EffectModel:
|
|
105
187
|
self._plausibility_checks()
|
|
106
188
|
self.model = EffectModel(model, self)
|
|
107
189
|
return self.model
|
|
@@ -115,7 +197,7 @@ class EffectModel(ElementModel):
|
|
|
115
197
|
def __init__(self, model: SystemModel, element: Effect):
|
|
116
198
|
super().__init__(model, element)
|
|
117
199
|
self.element: Effect = element
|
|
118
|
-
self.total:
|
|
200
|
+
self.total: linopy.Variable | None = None
|
|
119
201
|
self.invest: ShareAllocationModel = self.add(
|
|
120
202
|
ShareAllocationModel(
|
|
121
203
|
self._model,
|
|
@@ -168,13 +250,13 @@ class EffectModel(ElementModel):
|
|
|
168
250
|
)
|
|
169
251
|
|
|
170
252
|
|
|
171
|
-
EffectValuesExpr =
|
|
172
|
-
EffectTimeSeries =
|
|
173
|
-
EffectValuesDict =
|
|
174
|
-
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
|
|
175
257
|
""" This datatype is used to define the share to an effect by a certain attribute. """
|
|
176
258
|
|
|
177
|
-
EffectValuesUserScalar =
|
|
259
|
+
EffectValuesUserScalar = Scalar | dict[str, Scalar] # User-specified Shares to Effects
|
|
178
260
|
""" This datatype is used to define the share to an effect by a certain attribute. Only scalars are allowed. """
|
|
179
261
|
|
|
180
262
|
|
|
@@ -183,15 +265,15 @@ class EffectCollection:
|
|
|
183
265
|
Handling all Effects
|
|
184
266
|
"""
|
|
185
267
|
|
|
186
|
-
def __init__(self, *effects:
|
|
268
|
+
def __init__(self, *effects: Effect):
|
|
187
269
|
self._effects = {}
|
|
188
|
-
self._standard_effect:
|
|
189
|
-
self._objective_effect:
|
|
270
|
+
self._standard_effect: Effect | None = None
|
|
271
|
+
self._objective_effect: Effect | None = None
|
|
190
272
|
|
|
191
|
-
self.model:
|
|
273
|
+
self.model: EffectCollectionModel | None = None
|
|
192
274
|
self.add_effects(*effects)
|
|
193
275
|
|
|
194
|
-
def create_model(self, model: SystemModel) ->
|
|
276
|
+
def create_model(self, model: SystemModel) -> EffectCollectionModel:
|
|
195
277
|
self._plausibility_checks()
|
|
196
278
|
self.model = EffectCollectionModel(model, self)
|
|
197
279
|
return self.model
|
|
@@ -207,7 +289,7 @@ class EffectCollection:
|
|
|
207
289
|
self._effects[effect.label] = effect
|
|
208
290
|
logger.info(f'Registered new Effect: {effect.label}')
|
|
209
291
|
|
|
210
|
-
def create_effect_values_dict(self, effect_values_user: EffectValuesUser) ->
|
|
292
|
+
def create_effect_values_dict(self, effect_values_user: EffectValuesUser) -> EffectValuesDict | None:
|
|
211
293
|
"""
|
|
212
294
|
Converts effect values into a dictionary. If a scalar is provided, it is associated with a default effect type.
|
|
213
295
|
|
|
@@ -223,7 +305,7 @@ class EffectCollection:
|
|
|
223
305
|
A dictionary with None or Effect as the key, or None if input is None.
|
|
224
306
|
"""
|
|
225
307
|
|
|
226
|
-
def get_effect_label(eff:
|
|
308
|
+
def get_effect_label(eff: Effect | str) -> str:
|
|
227
309
|
"""Temporary function to get the label of an effect and warn for deprecation"""
|
|
228
310
|
if isinstance(eff, Effect):
|
|
229
311
|
warnings.warn(
|
|
@@ -232,7 +314,7 @@ class EffectCollection:
|
|
|
232
314
|
UserWarning,
|
|
233
315
|
stacklevel=2,
|
|
234
316
|
)
|
|
235
|
-
return eff.
|
|
317
|
+
return eff.label
|
|
236
318
|
else:
|
|
237
319
|
return eff
|
|
238
320
|
|
|
@@ -240,7 +322,7 @@ class EffectCollection:
|
|
|
240
322
|
return None
|
|
241
323
|
if isinstance(effect_values_user, dict):
|
|
242
324
|
return {get_effect_label(effect): value for effect, value in effect_values_user.items()}
|
|
243
|
-
return {self.standard_effect.
|
|
325
|
+
return {self.standard_effect.label: effect_values_user}
|
|
244
326
|
|
|
245
327
|
def _plausibility_checks(self) -> None:
|
|
246
328
|
# Check circular loops in effects:
|
|
@@ -257,15 +339,15 @@ class EffectCollection:
|
|
|
257
339
|
# operation:
|
|
258
340
|
for target_effect in effect.specific_share_to_other_effects_operation.keys():
|
|
259
341
|
assert effect not in self[target_effect].specific_share_to_other_effects_operation.keys(), (
|
|
260
|
-
f'Error: circular operation-shares \n{error_str(
|
|
342
|
+
f'Error: circular operation-shares \n{error_str(effect.label, self[target_effect].label)}'
|
|
261
343
|
)
|
|
262
344
|
# invest:
|
|
263
345
|
for target_effect in effect.specific_share_to_other_effects_invest.keys():
|
|
264
346
|
assert effect not in self[target_effect].specific_share_to_other_effects_invest.keys(), (
|
|
265
|
-
f'Error: circular invest-shares \n{error_str(
|
|
347
|
+
f'Error: circular invest-shares \n{error_str(effect.label, self[target_effect].label)}'
|
|
266
348
|
)
|
|
267
349
|
|
|
268
|
-
def __getitem__(self, effect:
|
|
350
|
+
def __getitem__(self, effect: str | Effect | None) -> Effect:
|
|
269
351
|
"""
|
|
270
352
|
Get an effect by label, or return the standard effect if None is passed
|
|
271
353
|
|
|
@@ -291,7 +373,7 @@ class EffectCollection:
|
|
|
291
373
|
def __len__(self) -> int:
|
|
292
374
|
return len(self._effects)
|
|
293
375
|
|
|
294
|
-
def __contains__(self, item:
|
|
376
|
+
def __contains__(self, item: str | Effect) -> bool:
|
|
295
377
|
"""Check if the effect exists. Checks for label or object"""
|
|
296
378
|
if isinstance(item, str):
|
|
297
379
|
return item in self.effects # Check if the label exists
|
|
@@ -300,7 +382,7 @@ class EffectCollection:
|
|
|
300
382
|
return False
|
|
301
383
|
|
|
302
384
|
@property
|
|
303
|
-
def effects(self) ->
|
|
385
|
+
def effects(self) -> dict[str, Effect]:
|
|
304
386
|
return self._effects
|
|
305
387
|
|
|
306
388
|
@property
|
|
@@ -336,7 +418,7 @@ class EffectCollectionModel(Model):
|
|
|
336
418
|
def __init__(self, model: SystemModel, effects: EffectCollection):
|
|
337
419
|
super().__init__(model, label_of_element='Effects')
|
|
338
420
|
self.effects = effects
|
|
339
|
-
self.penalty:
|
|
421
|
+
self.penalty: ShareAllocationModel | None = None
|
|
340
422
|
|
|
341
423
|
def add_share_to_effects(
|
|
342
424
|
self,
|