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/calculation.py
CHANGED
|
@@ -8,14 +8,15 @@ There are three different Calculation types:
|
|
|
8
8
|
3. SegmentedCalculation: Solves a SystemModel for each individual Segment of the FlowSystem.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
11
13
|
import logging
|
|
12
14
|
import math
|
|
13
15
|
import pathlib
|
|
14
16
|
import timeit
|
|
15
|
-
from typing import
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
16
18
|
|
|
17
19
|
import numpy as np
|
|
18
|
-
import pandas as pd
|
|
19
20
|
import yaml
|
|
20
21
|
|
|
21
22
|
from . import io as fx_io
|
|
@@ -23,13 +24,17 @@ from . import utils as utils
|
|
|
23
24
|
from .aggregation import AggregationModel, AggregationParameters
|
|
24
25
|
from .components import Storage
|
|
25
26
|
from .config import CONFIG
|
|
26
|
-
from .core import Scalar
|
|
27
|
-
from .elements import Component
|
|
28
27
|
from .features import InvestmentModel
|
|
29
|
-
from .flow_system import FlowSystem
|
|
30
28
|
from .results import CalculationResults, SegmentedCalculationResults
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
import pandas as pd
|
|
32
|
+
|
|
33
|
+
from .core import Scalar
|
|
34
|
+
from .elements import Component
|
|
35
|
+
from .flow_system import FlowSystem
|
|
36
|
+
from .solvers import _Solver
|
|
37
|
+
from .structure import SystemModel
|
|
33
38
|
|
|
34
39
|
logger = logging.getLogger('flixopt')
|
|
35
40
|
|
|
@@ -43,8 +48,8 @@ class Calculation:
|
|
|
43
48
|
self,
|
|
44
49
|
name: str,
|
|
45
50
|
flow_system: FlowSystem,
|
|
46
|
-
active_timesteps:
|
|
47
|
-
folder:
|
|
51
|
+
active_timesteps: pd.DatetimeIndex | None = None,
|
|
52
|
+
folder: pathlib.Path | None = None,
|
|
48
53
|
):
|
|
49
54
|
"""
|
|
50
55
|
Args:
|
|
@@ -55,23 +60,19 @@ class Calculation:
|
|
|
55
60
|
"""
|
|
56
61
|
self.name = name
|
|
57
62
|
self.flow_system = flow_system
|
|
58
|
-
self.model:
|
|
63
|
+
self.model: SystemModel | None = None
|
|
59
64
|
self.active_timesteps = active_timesteps
|
|
60
65
|
|
|
61
66
|
self.durations = {'modeling': 0.0, 'solving': 0.0, 'saving': 0.0}
|
|
62
67
|
self.folder = pathlib.Path.cwd() / 'results' if folder is None else pathlib.Path(folder)
|
|
63
|
-
self.results:
|
|
68
|
+
self.results: CalculationResults | None = None
|
|
64
69
|
|
|
65
|
-
if not self.folder.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
except FileNotFoundError as e:
|
|
69
|
-
raise FileNotFoundError(
|
|
70
|
-
f'Folder {self.folder} and its parent do not exist. Please create them first.'
|
|
71
|
-
) from e
|
|
70
|
+
if self.folder.exists() and not self.folder.is_dir():
|
|
71
|
+
raise NotADirectoryError(f'Path {self.folder} exists and is not a directory.')
|
|
72
|
+
self.folder.mkdir(parents=False, exist_ok=True)
|
|
72
73
|
|
|
73
74
|
@property
|
|
74
|
-
def main_results(self) ->
|
|
75
|
+
def main_results(self) -> dict[str, Scalar | dict]:
|
|
75
76
|
from flixopt.features import InvestmentModel
|
|
76
77
|
|
|
77
78
|
return {
|
|
@@ -131,7 +132,10 @@ class Calculation:
|
|
|
131
132
|
|
|
132
133
|
class FullCalculation(Calculation):
|
|
133
134
|
"""
|
|
134
|
-
|
|
135
|
+
FullCalculation solves the complete optimization problem using all time steps.
|
|
136
|
+
|
|
137
|
+
This is the most comprehensive calculation type that considers every time step
|
|
138
|
+
in the optimization, providing the most accurate but computationally intensive solution.
|
|
135
139
|
"""
|
|
136
140
|
|
|
137
141
|
def do_modeling(self) -> SystemModel:
|
|
@@ -144,7 +148,7 @@ class FullCalculation(Calculation):
|
|
|
144
148
|
self.durations['modeling'] = round(timeit.default_timer() - t_start, 2)
|
|
145
149
|
return self.model
|
|
146
150
|
|
|
147
|
-
def solve(self, solver: _Solver, log_file:
|
|
151
|
+
def solve(self, solver: _Solver, log_file: pathlib.Path | None = None, log_main_results: bool = True):
|
|
148
152
|
t_start = timeit.default_timer()
|
|
149
153
|
|
|
150
154
|
self.model.solve(
|
|
@@ -190,7 +194,25 @@ class FullCalculation(Calculation):
|
|
|
190
194
|
|
|
191
195
|
class AggregatedCalculation(FullCalculation):
|
|
192
196
|
"""
|
|
193
|
-
|
|
197
|
+
AggregatedCalculation reduces computational complexity by clustering time series into typical periods.
|
|
198
|
+
|
|
199
|
+
This calculation approach aggregates time series data using clustering techniques (tsam) to identify
|
|
200
|
+
representative time periods, significantly reducing computation time while maintaining solution accuracy.
|
|
201
|
+
|
|
202
|
+
Note:
|
|
203
|
+
The quality of the solution depends on the choice of aggregation parameters.
|
|
204
|
+
The optimal parameters depend on the specific problem and the characteristics of the time series data.
|
|
205
|
+
For more information, refer to the [tsam documentation](https://tsam.readthedocs.io/en/latest/).
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
name: Name of the calculation
|
|
209
|
+
flow_system: FlowSystem to be optimized
|
|
210
|
+
aggregation_parameters: Parameters for aggregation. See AggregationParameters class documentation
|
|
211
|
+
components_to_clusterize: list of Components to perform aggregation on. If None, all components are aggregated.
|
|
212
|
+
This equalizes variables in the components according to the typical periods computed in the aggregation
|
|
213
|
+
active_timesteps: DatetimeIndex of timesteps to use for calculation. If None, all timesteps are used
|
|
214
|
+
folder: Folder where results should be saved. If None, current working directory is used
|
|
215
|
+
aggregation: contains the aggregation model
|
|
194
216
|
"""
|
|
195
217
|
|
|
196
218
|
def __init__(
|
|
@@ -198,25 +220,10 @@ class AggregatedCalculation(FullCalculation):
|
|
|
198
220
|
name: str,
|
|
199
221
|
flow_system: FlowSystem,
|
|
200
222
|
aggregation_parameters: AggregationParameters,
|
|
201
|
-
components_to_clusterize:
|
|
202
|
-
active_timesteps:
|
|
203
|
-
folder:
|
|
223
|
+
components_to_clusterize: list[Component] | None = None,
|
|
224
|
+
active_timesteps: pd.DatetimeIndex | None = None,
|
|
225
|
+
folder: pathlib.Path | None = None,
|
|
204
226
|
):
|
|
205
|
-
"""
|
|
206
|
-
Class for Optimizing the `FlowSystem` including:
|
|
207
|
-
1. Aggregating TimeSeriesData via typical periods using tsam.
|
|
208
|
-
2. Equalizing variables of typical periods.
|
|
209
|
-
Args:
|
|
210
|
-
name: name of calculation
|
|
211
|
-
flow_system: flow_system which should be calculated
|
|
212
|
-
aggregation_parameters: Parameters for aggregation. See documentation of AggregationParameters class.
|
|
213
|
-
components_to_clusterize: List of Components to perform aggregation on. If None, then all components are aggregated.
|
|
214
|
-
This means, teh variables in the components are equalized to each other, according to the typical periods
|
|
215
|
-
computed in the DataAggregation
|
|
216
|
-
active_timesteps: pd.DatetimeIndex or None
|
|
217
|
-
list with indices, which should be used for calculation. If None, then all timesteps are used.
|
|
218
|
-
folder: folder where results should be saved. If None, then the current working directory is used.
|
|
219
|
-
"""
|
|
220
227
|
super().__init__(name, flow_system, active_timesteps, folder=folder)
|
|
221
228
|
self.aggregation_parameters = aggregation_parameters
|
|
222
229
|
self.components_to_clusterize = components_to_clusterize
|
|
@@ -293,6 +300,114 @@ class AggregatedCalculation(FullCalculation):
|
|
|
293
300
|
|
|
294
301
|
|
|
295
302
|
class SegmentedCalculation(Calculation):
|
|
303
|
+
"""Solve large optimization problems by dividing time horizon into (overlapping) segments.
|
|
304
|
+
|
|
305
|
+
This class addresses memory and computational limitations of large-scale optimization
|
|
306
|
+
problems by decomposing the time horizon into smaller overlapping segments that are
|
|
307
|
+
solved sequentially. Each segment uses final values from the previous segment as
|
|
308
|
+
initial conditions, ensuring dynamic continuity across the solution.
|
|
309
|
+
|
|
310
|
+
Key Concepts:
|
|
311
|
+
**Temporal Decomposition**: Divides long time horizons into manageable segments
|
|
312
|
+
**Overlapping Windows**: Segments share timesteps to improve storage dynamics
|
|
313
|
+
**Value Transfer**: Final states of one segment become initial states of the next
|
|
314
|
+
**Sequential Solving**: Each segment solved independently but with coupling
|
|
315
|
+
|
|
316
|
+
Limitations and Constraints:
|
|
317
|
+
**Investment Parameters**: InvestParameters are not supported in segmented calculations
|
|
318
|
+
as investment decisions must be made for the entire time horizon, not per segment.
|
|
319
|
+
|
|
320
|
+
**Global Constraints**: Time-horizon-wide constraints (flow_hours_total_min/max,
|
|
321
|
+
load_factor_min/max) may produce suboptimal results as they cannot be enforced
|
|
322
|
+
globally across segments.
|
|
323
|
+
|
|
324
|
+
**Storage Dynamics**: While overlap helps, storage optimization may be suboptimal
|
|
325
|
+
compared to full-horizon solutions due to limited foresight in each segment.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
name: Unique identifier for the calculation, used in result files and logging.
|
|
329
|
+
flow_system: The FlowSystem to optimize, containing all components, flows, and buses.
|
|
330
|
+
timesteps_per_segment: Number of timesteps in each segment (excluding overlap).
|
|
331
|
+
Must be > 2 to avoid internal side effects. Larger values provide better
|
|
332
|
+
optimization at the cost of memory and computation time.
|
|
333
|
+
overlap_timesteps: Number of additional timesteps added to each segment.
|
|
334
|
+
Improves storage optimization by providing lookahead. Higher values
|
|
335
|
+
improve solution quality but increase computational cost.
|
|
336
|
+
nr_of_previous_values: Number of previous timestep values to transfer between
|
|
337
|
+
segments for initialization. Typically 1 is sufficient.
|
|
338
|
+
folder: Directory for saving results. Defaults to current working directory + 'results'.
|
|
339
|
+
|
|
340
|
+
Examples:
|
|
341
|
+
Annual optimization with monthly segments:
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
# 8760 hours annual data with monthly segments (730 hours) and 48-hour overlap
|
|
345
|
+
segmented_calc = SegmentedCalculation(
|
|
346
|
+
name='annual_energy_system',
|
|
347
|
+
flow_system=energy_system,
|
|
348
|
+
timesteps_per_segment=730, # ~1 month
|
|
349
|
+
overlap_timesteps=48, # 2 days overlap
|
|
350
|
+
folder=Path('results/segmented'),
|
|
351
|
+
)
|
|
352
|
+
segmented_calc.do_modeling_and_solve(solver='gurobi')
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Weekly optimization with daily overlap:
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
# Weekly segments for detailed operational planning
|
|
359
|
+
weekly_calc = SegmentedCalculation(
|
|
360
|
+
name='weekly_operations',
|
|
361
|
+
flow_system=industrial_system,
|
|
362
|
+
timesteps_per_segment=168, # 1 week (hourly data)
|
|
363
|
+
overlap_timesteps=24, # 1 day overlap
|
|
364
|
+
nr_of_previous_values=1,
|
|
365
|
+
)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Large-scale system with minimal overlap:
|
|
369
|
+
|
|
370
|
+
```python
|
|
371
|
+
# Large system with minimal overlap for computational efficiency
|
|
372
|
+
large_calc = SegmentedCalculation(
|
|
373
|
+
name='large_scale_grid',
|
|
374
|
+
flow_system=grid_system,
|
|
375
|
+
timesteps_per_segment=100, # Shorter segments
|
|
376
|
+
overlap_timesteps=5, # Minimal overlap
|
|
377
|
+
)
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Design Considerations:
|
|
381
|
+
**Segment Size**: Balance between solution quality and computational efficiency.
|
|
382
|
+
Larger segments provide better optimization but require more memory and time.
|
|
383
|
+
|
|
384
|
+
**Overlap Duration**: More overlap improves storage dynamics and reduces
|
|
385
|
+
end-effects but increases computational cost. Typically 5-10% of segment length.
|
|
386
|
+
|
|
387
|
+
**Storage Systems**: Systems with large storage components benefit from longer
|
|
388
|
+
overlaps to capture charge/discharge cycles effectively.
|
|
389
|
+
|
|
390
|
+
**Investment Decisions**: Use FullCalculation for problems requiring investment
|
|
391
|
+
optimization, as SegmentedCalculation cannot handle investment parameters.
|
|
392
|
+
|
|
393
|
+
Common Use Cases:
|
|
394
|
+
- **Annual Planning**: Long-term planning with seasonal variations
|
|
395
|
+
- **Large Networks**: Spatially or temporally large energy systems
|
|
396
|
+
- **Memory-Limited Systems**: When full optimization exceeds available memory
|
|
397
|
+
- **Operational Planning**: Detailed short-term optimization with limited foresight
|
|
398
|
+
- **Sensitivity Analysis**: Quick approximate solutions for parameter studies
|
|
399
|
+
|
|
400
|
+
Performance Tips:
|
|
401
|
+
- Start with FullCalculation and use this class if memory issues occur
|
|
402
|
+
- Use longer overlaps for systems with significant storage
|
|
403
|
+
- Monitor solution quality at segment boundaries for discontinuities
|
|
404
|
+
|
|
405
|
+
Warning:
|
|
406
|
+
The evaluation of the solution is a bit more complex than FullCalculation or AggregatedCalculation
|
|
407
|
+
due to the overlapping individual solutions.
|
|
408
|
+
|
|
409
|
+
"""
|
|
410
|
+
|
|
296
411
|
def __init__(
|
|
297
412
|
self,
|
|
298
413
|
name: str,
|
|
@@ -300,32 +415,13 @@ class SegmentedCalculation(Calculation):
|
|
|
300
415
|
timesteps_per_segment: int,
|
|
301
416
|
overlap_timesteps: int,
|
|
302
417
|
nr_of_previous_values: int = 1,
|
|
303
|
-
folder:
|
|
418
|
+
folder: pathlib.Path | None = None,
|
|
304
419
|
):
|
|
305
|
-
"""
|
|
306
|
-
Dividing and Modeling the problem in (overlapping) segments.
|
|
307
|
-
The final values of each Segment are recognized by the following segment, effectively coupling
|
|
308
|
-
charge_states and flow_rates between segments.
|
|
309
|
-
Because of this intersection, both modeling and solving is done in one step
|
|
310
|
-
|
|
311
|
-
Take care:
|
|
312
|
-
Parameters like InvestParameters, sum_of_flow_hours and other restrictions over the total time_series
|
|
313
|
-
don't really work in this Calculation. Lower bounds to such SUMS can lead to weird results.
|
|
314
|
-
This is NOT yet explicitly checked for...
|
|
315
|
-
|
|
316
|
-
Args:
|
|
317
|
-
name: name of calculation
|
|
318
|
-
flow_system: flow_system which should be calculated
|
|
319
|
-
timesteps_per_segment: The number of time_steps per individual segment (without the overlap)
|
|
320
|
-
overlap_timesteps: The number of time_steps that are added to each individual model. Used for better
|
|
321
|
-
results of storages)
|
|
322
|
-
folder: folder where results should be saved. If None, then the current working directory is used.
|
|
323
|
-
"""
|
|
324
420
|
super().__init__(name, flow_system, folder=folder)
|
|
325
421
|
self.timesteps_per_segment = timesteps_per_segment
|
|
326
422
|
self.overlap_timesteps = overlap_timesteps
|
|
327
423
|
self.nr_of_previous_values = nr_of_previous_values
|
|
328
|
-
self.sub_calculations:
|
|
424
|
+
self.sub_calculations: list[FullCalculation] = []
|
|
329
425
|
|
|
330
426
|
self.all_timesteps = self.flow_system.time_series_collection.all_timesteps
|
|
331
427
|
self.all_timesteps_extra = self.flow_system.time_series_collection.all_timesteps_extra
|
|
@@ -350,10 +446,10 @@ class SegmentedCalculation(Calculation):
|
|
|
350
446
|
if isinstance(comp, Storage)
|
|
351
447
|
},
|
|
352
448
|
}
|
|
353
|
-
self._transfered_start_values:
|
|
449
|
+
self._transfered_start_values: list[dict[str, Any]] = []
|
|
354
450
|
|
|
355
451
|
def do_modeling_and_solve(
|
|
356
|
-
self, solver: _Solver, log_file:
|
|
452
|
+
self, solver: _Solver, log_file: pathlib.Path | None = None, log_main_results: bool = False
|
|
357
453
|
):
|
|
358
454
|
logger.info(f'{"":#^80}')
|
|
359
455
|
logger.info(f'{" Segmented Solving ":#^80}')
|
|
@@ -434,7 +530,7 @@ class SegmentedCalculation(Calculation):
|
|
|
434
530
|
if isinstance(comp, Storage):
|
|
435
531
|
comp.initial_charge_state = self._original_start_values[comp.label_full]
|
|
436
532
|
|
|
437
|
-
def _calculate_timesteps_of_segment(self) ->
|
|
533
|
+
def _calculate_timesteps_of_segment(self) -> list[pd.DatetimeIndex]:
|
|
438
534
|
active_timesteps_per_segment = []
|
|
439
535
|
for i, _ in enumerate(self.segment_names):
|
|
440
536
|
start = self.timesteps_per_segment * i
|
|
@@ -447,7 +543,7 @@ class SegmentedCalculation(Calculation):
|
|
|
447
543
|
return self.timesteps_per_segment + self.overlap_timesteps
|
|
448
544
|
|
|
449
545
|
@property
|
|
450
|
-
def start_values_of_segments(self) ->
|
|
546
|
+
def start_values_of_segments(self) -> dict[int, dict[str, Any]]:
|
|
451
547
|
"""Gives an overview of the start values of all Segments"""
|
|
452
548
|
return {
|
|
453
549
|
0: {element.label_full: value for element, value in self._original_start_values.items()},
|