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.

Files changed (45) hide show
  1. docs/examples/00-Minimal Example.md +1 -1
  2. docs/examples/01-Basic Example.md +1 -1
  3. docs/examples/02-Complex Example.md +1 -1
  4. docs/examples/index.md +1 -1
  5. docs/faq/contribute.md +26 -14
  6. docs/faq/index.md +1 -1
  7. docs/javascripts/mathjax.js +1 -1
  8. docs/user-guide/Mathematical Notation/Bus.md +1 -1
  9. docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +21 -21
  10. docs/user-guide/Mathematical Notation/Flow.md +3 -3
  11. docs/user-guide/Mathematical Notation/InvestParameters.md +3 -0
  12. docs/user-guide/Mathematical Notation/LinearConverter.md +5 -5
  13. docs/user-guide/Mathematical Notation/OnOffParameters.md +3 -0
  14. docs/user-guide/Mathematical Notation/Piecewise.md +1 -1
  15. docs/user-guide/Mathematical Notation/Storage.md +2 -2
  16. docs/user-guide/Mathematical Notation/index.md +1 -1
  17. docs/user-guide/Mathematical Notation/others.md +1 -1
  18. docs/user-guide/index.md +2 -2
  19. flixopt/__init__.py +4 -0
  20. flixopt/aggregation.py +33 -32
  21. flixopt/calculation.py +161 -65
  22. flixopt/components.py +687 -154
  23. flixopt/config.py +17 -8
  24. flixopt/core.py +69 -60
  25. flixopt/effects.py +146 -64
  26. flixopt/elements.py +297 -110
  27. flixopt/features.py +78 -71
  28. flixopt/flow_system.py +72 -50
  29. flixopt/interface.py +952 -113
  30. flixopt/io.py +15 -10
  31. flixopt/linear_converters.py +373 -81
  32. flixopt/network_app.py +445 -266
  33. flixopt/plotting.py +215 -87
  34. flixopt/results.py +382 -209
  35. flixopt/solvers.py +25 -21
  36. flixopt/structure.py +41 -39
  37. flixopt/utils.py +10 -7
  38. {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/METADATA +64 -53
  39. flixopt-2.1.8.dist-info/RECORD +56 -0
  40. scripts/extract_release_notes.py +5 -5
  41. scripts/gen_ref_pages.py +1 -1
  42. flixopt-2.1.6.dist-info/RECORD +0 -54
  43. {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/WHEEL +0 -0
  44. {flixopt-2.1.6.dist-info → flixopt-2.1.8.dist-info}/licenses/LICENSE +0 -0
  45. {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 Any, Dict, List, Optional, Union
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
- from .solvers import _Solver
32
- from .structure import SystemModel, copy_and_convert_datatypes, get_compact_representation
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: Optional[pd.DatetimeIndex] = None,
47
- folder: Optional[pathlib.Path] = None,
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: Optional[SystemModel] = None
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: Optional[CalculationResults] = None
68
+ self.results: CalculationResults | None = None
64
69
 
65
- if not self.folder.exists():
66
- try:
67
- self.folder.mkdir(parents=False)
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) -> Dict[str, Union[Scalar, Dict]]:
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
- class for defined way of solving a flow_system optimization
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: Optional[pathlib.Path] = None, log_main_results: bool = True):
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
- class for defined way of solving a flow_system optimization
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: Optional[List[Component]] = None,
202
- active_timesteps: Optional[pd.DatetimeIndex] = None,
203
- folder: Optional[pathlib.Path] = None,
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: Optional[pathlib.Path] = None,
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: List[FullCalculation] = []
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: List[Dict[str, Any]] = []
449
+ self._transfered_start_values: list[dict[str, Any]] = []
354
450
 
355
451
  def do_modeling_and_solve(
356
- self, solver: _Solver, log_file: Optional[pathlib.Path] = None, log_main_results: bool = False
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) -> List[pd.DatetimeIndex]:
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) -> Dict[int, Dict[str, Any]]:
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()},