flixopt 3.3.1__py3-none-any.whl → 3.4.0__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.
- flixopt/calculation.py +104 -38
- flixopt/components.py +6 -0
- flixopt/config.py +120 -0
- flixopt/effects.py +5 -1
- flixopt/elements.py +4 -0
- flixopt/flow_system.py +2 -0
- flixopt/io.py +139 -21
- flixopt/results.py +4 -2
- flixopt/solvers.py +12 -4
- flixopt/structure.py +3 -1
- {flixopt-3.3.1.dist-info → flixopt-3.4.0.dist-info}/METADATA +3 -2
- flixopt-3.4.0.dist-info/RECORD +26 -0
- flixopt-3.3.1.dist-info/RECORD +0 -26
- {flixopt-3.3.1.dist-info → flixopt-3.4.0.dist-info}/WHEEL +0 -0
- {flixopt-3.3.1.dist-info → flixopt-3.4.0.dist-info}/licenses/LICENSE +0 -0
- {flixopt-3.3.1.dist-info → flixopt-3.4.0.dist-info}/top_level.txt +0 -0
flixopt/calculation.py
CHANGED
|
@@ -13,13 +13,14 @@ from __future__ import annotations
|
|
|
13
13
|
import logging
|
|
14
14
|
import math
|
|
15
15
|
import pathlib
|
|
16
|
+
import sys
|
|
16
17
|
import timeit
|
|
17
18
|
import warnings
|
|
18
19
|
from collections import Counter
|
|
19
20
|
from typing import TYPE_CHECKING, Annotated, Any
|
|
20
21
|
|
|
21
22
|
import numpy as np
|
|
22
|
-
import
|
|
23
|
+
from tqdm import tqdm
|
|
23
24
|
|
|
24
25
|
from . import io as fx_io
|
|
25
26
|
from .aggregation import Aggregation, AggregationModel, AggregationParameters
|
|
@@ -53,6 +54,8 @@ class Calculation:
|
|
|
53
54
|
active_timesteps: Deprecated. Use FlowSystem.sel(time=...) or FlowSystem.isel(time=...) instead.
|
|
54
55
|
"""
|
|
55
56
|
|
|
57
|
+
model: FlowSystemModel | None
|
|
58
|
+
|
|
56
59
|
def __init__(
|
|
57
60
|
self,
|
|
58
61
|
name: str,
|
|
@@ -87,7 +90,7 @@ class Calculation:
|
|
|
87
90
|
flow_system._used_in_calculation = True
|
|
88
91
|
|
|
89
92
|
self.flow_system = flow_system
|
|
90
|
-
self.model
|
|
93
|
+
self.model = None
|
|
91
94
|
|
|
92
95
|
self.durations = {'modeling': 0.0, 'solving': 0.0, 'saving': 0.0}
|
|
93
96
|
self.folder = pathlib.Path.cwd() / 'results' if folder is None else pathlib.Path(folder)
|
|
@@ -225,7 +228,7 @@ class FullCalculation(Calculation):
|
|
|
225
228
|
return self
|
|
226
229
|
|
|
227
230
|
def solve(
|
|
228
|
-
self, solver: _Solver, log_file: pathlib.Path | None = None, log_main_results: bool =
|
|
231
|
+
self, solver: _Solver, log_file: pathlib.Path | None = None, log_main_results: bool | None = None
|
|
229
232
|
) -> FullCalculation:
|
|
230
233
|
t_start = timeit.default_timer()
|
|
231
234
|
|
|
@@ -235,6 +238,8 @@ class FullCalculation(Calculation):
|
|
|
235
238
|
**solver.options,
|
|
236
239
|
)
|
|
237
240
|
self.durations['solving'] = round(timeit.default_timer() - t_start, 2)
|
|
241
|
+
logger.info(f'Model solved with {solver.name} in {self.durations["solving"]:.2f} seconds.')
|
|
242
|
+
logger.info(f'Model status after solve: {self.model.status}')
|
|
238
243
|
|
|
239
244
|
if self.model.status == 'warning':
|
|
240
245
|
# Save the model and the flow_system to file in case of infeasibility
|
|
@@ -248,15 +253,13 @@ class FullCalculation(Calculation):
|
|
|
248
253
|
)
|
|
249
254
|
|
|
250
255
|
# Log the formatted output
|
|
251
|
-
if log_main_results
|
|
256
|
+
should_log = log_main_results if log_main_results is not None else CONFIG.Solving.log_main_results
|
|
257
|
+
if should_log:
|
|
252
258
|
logger.info(
|
|
253
259
|
f'{" Main Results ":#^80}\n'
|
|
254
|
-
+
|
|
260
|
+
+ fx_io.format_yaml_string(
|
|
255
261
|
self.main_results,
|
|
256
|
-
|
|
257
|
-
sort_keys=False,
|
|
258
|
-
allow_unicode=True,
|
|
259
|
-
indent=4,
|
|
262
|
+
compact_numeric_lists=True,
|
|
260
263
|
)
|
|
261
264
|
)
|
|
262
265
|
|
|
@@ -366,7 +369,7 @@ class AggregatedCalculation(FullCalculation):
|
|
|
366
369
|
)
|
|
367
370
|
|
|
368
371
|
self.aggregation.cluster()
|
|
369
|
-
self.aggregation.plot(show=
|
|
372
|
+
self.aggregation.plot(show=CONFIG.Plotting.default_show, save=self.folder / 'aggregation.html')
|
|
370
373
|
if self.aggregation_parameters.aggregate_data_and_fix_non_binary_vars:
|
|
371
374
|
ds = self.flow_system.to_dataset()
|
|
372
375
|
for name, series in self.aggregation.aggregated_data.items():
|
|
@@ -567,48 +570,111 @@ class SegmentedCalculation(Calculation):
|
|
|
567
570
|
f'({timesteps_of_segment[0]} -> {timesteps_of_segment[-1]}):'
|
|
568
571
|
)
|
|
569
572
|
|
|
573
|
+
def _solve_single_segment(
|
|
574
|
+
self,
|
|
575
|
+
i: int,
|
|
576
|
+
calculation: FullCalculation,
|
|
577
|
+
solver: _Solver,
|
|
578
|
+
log_file: pathlib.Path | None,
|
|
579
|
+
log_main_results: bool,
|
|
580
|
+
suppress_output: bool,
|
|
581
|
+
) -> None:
|
|
582
|
+
"""Solve a single segment calculation."""
|
|
583
|
+
if i > 0 and self.nr_of_previous_values > 0:
|
|
584
|
+
self._transfer_start_values(i)
|
|
585
|
+
|
|
586
|
+
calculation.do_modeling()
|
|
587
|
+
|
|
588
|
+
# Warn about Investments, but only in first run
|
|
589
|
+
if i == 0:
|
|
590
|
+
invest_elements = [
|
|
591
|
+
model.label_full
|
|
592
|
+
for component in calculation.flow_system.components.values()
|
|
593
|
+
for model in component.submodel.all_submodels
|
|
594
|
+
if isinstance(model, InvestmentModel)
|
|
595
|
+
]
|
|
596
|
+
if invest_elements:
|
|
597
|
+
logger.critical(
|
|
598
|
+
f'Investments are not supported in Segmented Calculation! '
|
|
599
|
+
f'Following InvestmentModels were found: {invest_elements}'
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
log_path = pathlib.Path(log_file) if log_file is not None else self.folder / f'{self.name}.log'
|
|
603
|
+
|
|
604
|
+
if suppress_output:
|
|
605
|
+
with fx_io.suppress_output():
|
|
606
|
+
calculation.solve(solver, log_file=log_path, log_main_results=log_main_results)
|
|
607
|
+
else:
|
|
608
|
+
calculation.solve(solver, log_file=log_path, log_main_results=log_main_results)
|
|
609
|
+
|
|
570
610
|
def do_modeling_and_solve(
|
|
571
|
-
self,
|
|
611
|
+
self,
|
|
612
|
+
solver: _Solver,
|
|
613
|
+
log_file: pathlib.Path | None = None,
|
|
614
|
+
log_main_results: bool = False,
|
|
615
|
+
show_individual_solves: bool = False,
|
|
572
616
|
) -> SegmentedCalculation:
|
|
617
|
+
"""Model and solve all segments of the segmented calculation.
|
|
618
|
+
|
|
619
|
+
This method creates sub-calculations for each time segment, then iteratively
|
|
620
|
+
models and solves each segment. It supports two output modes: a progress bar
|
|
621
|
+
for compact output, or detailed individual solve information.
|
|
622
|
+
|
|
623
|
+
Args:
|
|
624
|
+
solver: The solver instance to use for optimization (e.g., Gurobi, HiGHS).
|
|
625
|
+
log_file: Optional path to the solver log file. If None, defaults to
|
|
626
|
+
folder/name.log.
|
|
627
|
+
log_main_results: Whether to log main results (objective, effects, etc.)
|
|
628
|
+
after each segment solve. Defaults to False.
|
|
629
|
+
show_individual_solves: If True, shows detailed output for each segment
|
|
630
|
+
solve with logger messages. If False (default), shows a compact progress
|
|
631
|
+
bar with suppressed solver output for cleaner display.
|
|
632
|
+
|
|
633
|
+
Returns:
|
|
634
|
+
Self, for method chaining.
|
|
635
|
+
|
|
636
|
+
Note:
|
|
637
|
+
The method automatically transfers all start values between segments to ensure
|
|
638
|
+
continuity of storage states and flow rates across segment boundaries.
|
|
639
|
+
"""
|
|
573
640
|
logger.info(f'{"":#^80}')
|
|
574
641
|
logger.info(f'{" Segmented Solving ":#^80}')
|
|
575
642
|
self._create_sub_calculations()
|
|
576
643
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
644
|
+
if show_individual_solves:
|
|
645
|
+
# Path 1: Show individual solves with detailed output
|
|
646
|
+
for i, calculation in enumerate(self.sub_calculations):
|
|
647
|
+
logger.info(
|
|
648
|
+
f'Solving segment {i + 1}/{len(self.sub_calculations)}: '
|
|
649
|
+
f'{calculation.flow_system.timesteps[0]} -> {calculation.flow_system.timesteps[-1]}'
|
|
650
|
+
)
|
|
651
|
+
self._solve_single_segment(i, calculation, solver, log_file, log_main_results, suppress_output=False)
|
|
652
|
+
else:
|
|
653
|
+
# Path 2: Show only progress bar with suppressed output
|
|
654
|
+
progress_bar = tqdm(
|
|
655
|
+
enumerate(self.sub_calculations),
|
|
656
|
+
total=len(self.sub_calculations),
|
|
657
|
+
desc='Solving segments',
|
|
658
|
+
unit='segment',
|
|
659
|
+
file=sys.stdout,
|
|
660
|
+
disable=not CONFIG.Solving.log_to_console,
|
|
581
661
|
)
|
|
582
662
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
# Warn about Investments, but only in fist run
|
|
589
|
-
if i == 0:
|
|
590
|
-
invest_elements = [
|
|
591
|
-
model.label_full
|
|
592
|
-
for component in calculation.flow_system.components.values()
|
|
593
|
-
for model in component.submodel.all_submodels
|
|
594
|
-
if isinstance(model, InvestmentModel)
|
|
595
|
-
]
|
|
596
|
-
if invest_elements:
|
|
597
|
-
logger.critical(
|
|
598
|
-
f'Investments are not supported in Segmented Calculation! '
|
|
599
|
-
f'Following InvestmentModels were found: {invest_elements}'
|
|
663
|
+
try:
|
|
664
|
+
for i, calculation in progress_bar:
|
|
665
|
+
progress_bar.set_description(
|
|
666
|
+
f'Solving ({calculation.flow_system.timesteps[0]} -> {calculation.flow_system.timesteps[-1]})'
|
|
600
667
|
)
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
log_file=pathlib.Path(log_file) if log_file is not None else self.folder / f'{self.name}.log',
|
|
605
|
-
log_main_results=log_main_results,
|
|
606
|
-
)
|
|
668
|
+
self._solve_single_segment(i, calculation, solver, log_file, log_main_results, suppress_output=True)
|
|
669
|
+
finally:
|
|
670
|
+
progress_bar.close()
|
|
607
671
|
|
|
608
672
|
for calc in self.sub_calculations:
|
|
609
673
|
for key, value in calc.durations.items():
|
|
610
674
|
self.durations[key] += value
|
|
611
675
|
|
|
676
|
+
logger.info(f'Model solved with {solver.name} in {self.durations["solving"]:.2f} seconds.')
|
|
677
|
+
|
|
612
678
|
self.results = SegmentedCalculationResults.from_calculation(self)
|
|
613
679
|
|
|
614
680
|
return self
|
flixopt/components.py
CHANGED
|
@@ -161,6 +161,8 @@ class LinearConverter(Component):
|
|
|
161
161
|
|
|
162
162
|
"""
|
|
163
163
|
|
|
164
|
+
submodel: LinearConverterModel | None
|
|
165
|
+
|
|
164
166
|
def __init__(
|
|
165
167
|
self,
|
|
166
168
|
label: str,
|
|
@@ -377,6 +379,8 @@ class Storage(Component):
|
|
|
377
379
|
With flow rates in m3/h, the charge state is therefore in m3.
|
|
378
380
|
"""
|
|
379
381
|
|
|
382
|
+
submodel: StorageModel | None
|
|
383
|
+
|
|
380
384
|
def __init__(
|
|
381
385
|
self,
|
|
382
386
|
label: str,
|
|
@@ -650,6 +654,8 @@ class Transmission(Component):
|
|
|
650
654
|
|
|
651
655
|
"""
|
|
652
656
|
|
|
657
|
+
submodel: TransmissionModel | None
|
|
658
|
+
|
|
653
659
|
def __init__(
|
|
654
660
|
self,
|
|
655
661
|
label: str,
|
flixopt/config.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import os
|
|
4
5
|
import sys
|
|
5
6
|
import warnings
|
|
6
7
|
from logging.handlers import RotatingFileHandler
|
|
@@ -63,6 +64,14 @@ _DEFAULTS = MappingProxyType(
|
|
|
63
64
|
'default_qualitative_colorscale': 'plotly',
|
|
64
65
|
}
|
|
65
66
|
),
|
|
67
|
+
'solving': MappingProxyType(
|
|
68
|
+
{
|
|
69
|
+
'mip_gap': 0.01,
|
|
70
|
+
'time_limit_seconds': 300,
|
|
71
|
+
'log_to_console': True,
|
|
72
|
+
'log_main_results': True,
|
|
73
|
+
}
|
|
74
|
+
),
|
|
66
75
|
}
|
|
67
76
|
)
|
|
68
77
|
|
|
@@ -75,6 +84,8 @@ class CONFIG:
|
|
|
75
84
|
Attributes:
|
|
76
85
|
Logging: Logging configuration.
|
|
77
86
|
Modeling: Optimization modeling parameters.
|
|
87
|
+
Solving: Solver configuration and default parameters.
|
|
88
|
+
Plotting: Plotting configuration.
|
|
78
89
|
config_name: Configuration name.
|
|
79
90
|
|
|
80
91
|
Examples:
|
|
@@ -91,6 +102,9 @@ class CONFIG:
|
|
|
91
102
|
level: DEBUG
|
|
92
103
|
console: true
|
|
93
104
|
file: app.log
|
|
105
|
+
solving:
|
|
106
|
+
mip_gap: 0.001
|
|
107
|
+
time_limit_seconds: 600
|
|
94
108
|
```
|
|
95
109
|
"""
|
|
96
110
|
|
|
@@ -194,6 +208,30 @@ class CONFIG:
|
|
|
194
208
|
epsilon: float = _DEFAULTS['modeling']['epsilon']
|
|
195
209
|
big_binary_bound: int = _DEFAULTS['modeling']['big_binary_bound']
|
|
196
210
|
|
|
211
|
+
class Solving:
|
|
212
|
+
"""Solver configuration and default parameters.
|
|
213
|
+
|
|
214
|
+
Attributes:
|
|
215
|
+
mip_gap: Default MIP gap tolerance for solver convergence.
|
|
216
|
+
time_limit_seconds: Default time limit in seconds for solver runs.
|
|
217
|
+
log_to_console: Whether solver should output to console.
|
|
218
|
+
log_main_results: Whether to log main results after solving.
|
|
219
|
+
|
|
220
|
+
Examples:
|
|
221
|
+
```python
|
|
222
|
+
# Set tighter convergence and longer timeout
|
|
223
|
+
CONFIG.Solving.mip_gap = 0.001
|
|
224
|
+
CONFIG.Solving.time_limit_seconds = 600
|
|
225
|
+
CONFIG.Solving.log_to_console = False
|
|
226
|
+
CONFIG.apply()
|
|
227
|
+
```
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
mip_gap: float = _DEFAULTS['solving']['mip_gap']
|
|
231
|
+
time_limit_seconds: int = _DEFAULTS['solving']['time_limit_seconds']
|
|
232
|
+
log_to_console: bool = _DEFAULTS['solving']['log_to_console']
|
|
233
|
+
log_main_results: bool = _DEFAULTS['solving']['log_main_results']
|
|
234
|
+
|
|
197
235
|
class Plotting:
|
|
198
236
|
"""Plotting configuration.
|
|
199
237
|
|
|
@@ -246,6 +284,12 @@ class CONFIG:
|
|
|
246
284
|
for key, value in _DEFAULTS['modeling'].items():
|
|
247
285
|
setattr(cls.Modeling, key, value)
|
|
248
286
|
|
|
287
|
+
for key, value in _DEFAULTS['solving'].items():
|
|
288
|
+
setattr(cls.Solving, key, value)
|
|
289
|
+
|
|
290
|
+
for key, value in _DEFAULTS['plotting'].items():
|
|
291
|
+
setattr(cls.Plotting, key, value)
|
|
292
|
+
|
|
249
293
|
cls.config_name = _DEFAULTS['config_name']
|
|
250
294
|
cls.apply()
|
|
251
295
|
|
|
@@ -329,6 +373,12 @@ class CONFIG:
|
|
|
329
373
|
elif key == 'modeling' and isinstance(value, dict):
|
|
330
374
|
for nested_key, nested_value in value.items():
|
|
331
375
|
setattr(cls.Modeling, nested_key, nested_value)
|
|
376
|
+
elif key == 'solving' and isinstance(value, dict):
|
|
377
|
+
for nested_key, nested_value in value.items():
|
|
378
|
+
setattr(cls.Solving, nested_key, nested_value)
|
|
379
|
+
elif key == 'plotting' and isinstance(value, dict):
|
|
380
|
+
for nested_key, nested_value in value.items():
|
|
381
|
+
setattr(cls.Plotting, nested_key, nested_value)
|
|
332
382
|
elif hasattr(cls, key):
|
|
333
383
|
setattr(cls, key, value)
|
|
334
384
|
|
|
@@ -366,6 +416,12 @@ class CONFIG:
|
|
|
366
416
|
'epsilon': cls.Modeling.epsilon,
|
|
367
417
|
'big_binary_bound': cls.Modeling.big_binary_bound,
|
|
368
418
|
},
|
|
419
|
+
'solving': {
|
|
420
|
+
'mip_gap': cls.Solving.mip_gap,
|
|
421
|
+
'time_limit_seconds': cls.Solving.time_limit_seconds,
|
|
422
|
+
'log_to_console': cls.Solving.log_to_console,
|
|
423
|
+
'log_main_results': cls.Solving.log_main_results,
|
|
424
|
+
},
|
|
369
425
|
'plotting': {
|
|
370
426
|
'default_show': cls.Plotting.default_show,
|
|
371
427
|
'default_engine': cls.Plotting.default_engine,
|
|
@@ -376,6 +432,70 @@ class CONFIG:
|
|
|
376
432
|
},
|
|
377
433
|
}
|
|
378
434
|
|
|
435
|
+
@classmethod
|
|
436
|
+
def silent(cls) -> type[CONFIG]:
|
|
437
|
+
"""Configure for silent operation.
|
|
438
|
+
|
|
439
|
+
Disables console logging, solver output, and result logging
|
|
440
|
+
for clean production runs. Does not show plots. Automatically calls apply().
|
|
441
|
+
"""
|
|
442
|
+
cls.Logging.console = False
|
|
443
|
+
cls.Plotting.default_show = False
|
|
444
|
+
cls.Logging.file = None
|
|
445
|
+
cls.Solving.log_to_console = False
|
|
446
|
+
cls.Solving.log_main_results = False
|
|
447
|
+
cls.apply()
|
|
448
|
+
return cls
|
|
449
|
+
|
|
450
|
+
@classmethod
|
|
451
|
+
def debug(cls) -> type[CONFIG]:
|
|
452
|
+
"""Configure for debug mode with verbose output.
|
|
453
|
+
|
|
454
|
+
Enables console logging at DEBUG level and all solver output for
|
|
455
|
+
troubleshooting. Automatically calls apply().
|
|
456
|
+
"""
|
|
457
|
+
cls.Logging.console = True
|
|
458
|
+
cls.Logging.level = 'DEBUG'
|
|
459
|
+
cls.Solving.log_to_console = True
|
|
460
|
+
cls.Solving.log_main_results = True
|
|
461
|
+
cls.apply()
|
|
462
|
+
return cls
|
|
463
|
+
|
|
464
|
+
@classmethod
|
|
465
|
+
def exploring(cls) -> type[CONFIG]:
|
|
466
|
+
"""Configure for exploring flixopt
|
|
467
|
+
|
|
468
|
+
Enables console logging at INFO level and all solver output.
|
|
469
|
+
Also enables browser plotting for plotly with showing plots per default
|
|
470
|
+
"""
|
|
471
|
+
cls.Logging.console = True
|
|
472
|
+
cls.Logging.level = 'INFO'
|
|
473
|
+
cls.Solving.log_to_console = True
|
|
474
|
+
cls.Solving.log_main_results = True
|
|
475
|
+
cls.browser_plotting()
|
|
476
|
+
cls.apply()
|
|
477
|
+
return cls
|
|
478
|
+
|
|
479
|
+
@classmethod
|
|
480
|
+
def browser_plotting(cls) -> type[CONFIG]:
|
|
481
|
+
"""Configure for interactive usage with plotly to open plots in browser.
|
|
482
|
+
|
|
483
|
+
Sets plotly.io.renderers.default = 'browser'. Useful for running examples
|
|
484
|
+
and viewing interactive plots. Does NOT modify CONFIG.Plotting settings.
|
|
485
|
+
|
|
486
|
+
Respects FLIXOPT_CI environment variable if set.
|
|
487
|
+
"""
|
|
488
|
+
cls.Plotting.default_show = True
|
|
489
|
+
cls.apply()
|
|
490
|
+
|
|
491
|
+
# Only set to True if environment variable hasn't overridden it
|
|
492
|
+
if 'FLIXOPT_CI' not in os.environ:
|
|
493
|
+
import plotly.io as pio
|
|
494
|
+
|
|
495
|
+
pio.renderers.default = 'browser'
|
|
496
|
+
|
|
497
|
+
return cls
|
|
498
|
+
|
|
379
499
|
|
|
380
500
|
class MultilineFormatter(logging.Formatter):
|
|
381
501
|
"""Formatter that handles multi-line messages with consistent prefixes.
|
flixopt/effects.py
CHANGED
|
@@ -160,6 +160,8 @@ class Effect(Element):
|
|
|
160
160
|
|
|
161
161
|
"""
|
|
162
162
|
|
|
163
|
+
submodel: EffectModel | None
|
|
164
|
+
|
|
163
165
|
def __init__(
|
|
164
166
|
self,
|
|
165
167
|
label: str,
|
|
@@ -454,12 +456,14 @@ class EffectCollection(ElementContainer[Effect]):
|
|
|
454
456
|
Handling all Effects
|
|
455
457
|
"""
|
|
456
458
|
|
|
459
|
+
submodel: EffectCollectionModel | None
|
|
460
|
+
|
|
457
461
|
def __init__(self, *effects: Effect):
|
|
458
462
|
super().__init__(element_type_name='effects')
|
|
459
463
|
self._standard_effect: Effect | None = None
|
|
460
464
|
self._objective_effect: Effect | None = None
|
|
461
465
|
|
|
462
|
-
self.submodel
|
|
466
|
+
self.submodel = None
|
|
463
467
|
self.add_effects(*effects)
|
|
464
468
|
|
|
465
469
|
def create_model(self, model: FlowSystemModel) -> EffectCollectionModel:
|
flixopt/elements.py
CHANGED
|
@@ -223,6 +223,8 @@ class Bus(Element):
|
|
|
223
223
|
by the FlowSystem during system setup.
|
|
224
224
|
"""
|
|
225
225
|
|
|
226
|
+
submodel: BusModel | None
|
|
227
|
+
|
|
226
228
|
def __init__(
|
|
227
229
|
self,
|
|
228
230
|
label: str,
|
|
@@ -411,6 +413,8 @@ class Flow(Element):
|
|
|
411
413
|
|
|
412
414
|
"""
|
|
413
415
|
|
|
416
|
+
submodel: FlowModel | None
|
|
417
|
+
|
|
414
418
|
def __init__(
|
|
415
419
|
self,
|
|
416
420
|
label: str,
|
flixopt/flow_system.py
CHANGED
|
@@ -143,6 +143,8 @@ class FlowSystem(Interface, CompositeContainerMixin[Element]):
|
|
|
143
143
|
connected_and_transformed automatically when trying to solve a calculation.
|
|
144
144
|
"""
|
|
145
145
|
|
|
146
|
+
model: FlowSystemModel | None
|
|
147
|
+
|
|
146
148
|
def __init__(
|
|
147
149
|
self,
|
|
148
150
|
timesteps: pd.DatetimeIndex,
|
flixopt/io.py
CHANGED
|
@@ -3,8 +3,11 @@ from __future__ import annotations
|
|
|
3
3
|
import inspect
|
|
4
4
|
import json
|
|
5
5
|
import logging
|
|
6
|
+
import os
|
|
6
7
|
import pathlib
|
|
7
8
|
import re
|
|
9
|
+
import sys
|
|
10
|
+
from contextlib import contextmanager
|
|
8
11
|
from dataclasses import dataclass
|
|
9
12
|
from typing import TYPE_CHECKING, Any
|
|
10
13
|
|
|
@@ -167,6 +170,35 @@ def _load_yaml_unsafe(path: str | pathlib.Path) -> dict | list:
|
|
|
167
170
|
return yaml.unsafe_load(f) or {}
|
|
168
171
|
|
|
169
172
|
|
|
173
|
+
def _create_compact_dumper():
|
|
174
|
+
"""
|
|
175
|
+
Create a YAML dumper class with custom representer for compact numeric lists.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
A yaml.SafeDumper subclass configured to format numeric lists inline.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
def represent_list(dumper, data):
|
|
182
|
+
"""
|
|
183
|
+
Custom representer for lists to format them inline (flow style)
|
|
184
|
+
but only if they contain only numbers or nested numeric lists.
|
|
185
|
+
"""
|
|
186
|
+
if data and all(
|
|
187
|
+
isinstance(item, (int, float, np.integer, np.floating))
|
|
188
|
+
or (isinstance(item, list) and all(isinstance(x, (int, float, np.integer, np.floating)) for x in item))
|
|
189
|
+
for item in data
|
|
190
|
+
):
|
|
191
|
+
return dumper.represent_sequence('tag:yaml.org,2002:seq', data, flow_style=True)
|
|
192
|
+
return dumper.represent_sequence('tag:yaml.org,2002:seq', data, flow_style=False)
|
|
193
|
+
|
|
194
|
+
# Create custom dumper with the representer
|
|
195
|
+
class CompactDumper(yaml.SafeDumper):
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
CompactDumper.add_representer(list, represent_list)
|
|
199
|
+
return CompactDumper
|
|
200
|
+
|
|
201
|
+
|
|
170
202
|
def save_yaml(
|
|
171
203
|
data: dict | list,
|
|
172
204
|
path: str | pathlib.Path,
|
|
@@ -193,31 +225,11 @@ def save_yaml(
|
|
|
193
225
|
path = pathlib.Path(path)
|
|
194
226
|
|
|
195
227
|
if compact_numeric_lists:
|
|
196
|
-
# Define custom representer for compact numeric lists
|
|
197
|
-
def represent_list(dumper, data):
|
|
198
|
-
"""
|
|
199
|
-
Custom representer for lists to format them inline (flow style)
|
|
200
|
-
but only if they contain only numbers or nested numeric lists.
|
|
201
|
-
"""
|
|
202
|
-
if data and all(
|
|
203
|
-
isinstance(item, (int, float, np.integer, np.floating))
|
|
204
|
-
or (isinstance(item, list) and all(isinstance(x, (int, float, np.integer, np.floating)) for x in item))
|
|
205
|
-
for item in data
|
|
206
|
-
):
|
|
207
|
-
return dumper.represent_sequence('tag:yaml.org,2002:seq', data, flow_style=True)
|
|
208
|
-
return dumper.represent_sequence('tag:yaml.org,2002:seq', data, flow_style=False)
|
|
209
|
-
|
|
210
|
-
# Create custom dumper with the representer
|
|
211
|
-
class CompactDumper(yaml.SafeDumper):
|
|
212
|
-
pass
|
|
213
|
-
|
|
214
|
-
CompactDumper.add_representer(list, represent_list)
|
|
215
|
-
|
|
216
228
|
with open(path, 'w', encoding='utf-8') as f:
|
|
217
229
|
yaml.dump(
|
|
218
230
|
data,
|
|
219
231
|
f,
|
|
220
|
-
Dumper=
|
|
232
|
+
Dumper=_create_compact_dumper(),
|
|
221
233
|
indent=indent,
|
|
222
234
|
width=width,
|
|
223
235
|
allow_unicode=allow_unicode,
|
|
@@ -239,6 +251,56 @@ def save_yaml(
|
|
|
239
251
|
)
|
|
240
252
|
|
|
241
253
|
|
|
254
|
+
def format_yaml_string(
|
|
255
|
+
data: dict | list,
|
|
256
|
+
indent: int = 4,
|
|
257
|
+
width: int = 1000,
|
|
258
|
+
allow_unicode: bool = True,
|
|
259
|
+
sort_keys: bool = False,
|
|
260
|
+
compact_numeric_lists: bool = False,
|
|
261
|
+
**kwargs,
|
|
262
|
+
) -> str:
|
|
263
|
+
"""
|
|
264
|
+
Format data as a YAML string with consistent formatting.
|
|
265
|
+
|
|
266
|
+
This function provides the same formatting as save_yaml() but returns a string
|
|
267
|
+
instead of writing to a file. Useful for logging or displaying YAML data.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
data: Data to format (dict or list).
|
|
271
|
+
indent: Number of spaces for indentation (default: 4).
|
|
272
|
+
width: Maximum line width (default: 1000).
|
|
273
|
+
allow_unicode: If True, allow Unicode characters (default: True).
|
|
274
|
+
sort_keys: If True, sort dictionary keys (default: False).
|
|
275
|
+
compact_numeric_lists: If True, format numeric lists inline for better readability (default: False).
|
|
276
|
+
**kwargs: Additional arguments to pass to yaml.dump().
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Formatted YAML string.
|
|
280
|
+
"""
|
|
281
|
+
if compact_numeric_lists:
|
|
282
|
+
return yaml.dump(
|
|
283
|
+
data,
|
|
284
|
+
Dumper=_create_compact_dumper(),
|
|
285
|
+
indent=indent,
|
|
286
|
+
width=width,
|
|
287
|
+
allow_unicode=allow_unicode,
|
|
288
|
+
sort_keys=sort_keys,
|
|
289
|
+
default_flow_style=False,
|
|
290
|
+
**kwargs,
|
|
291
|
+
)
|
|
292
|
+
else:
|
|
293
|
+
return yaml.safe_dump(
|
|
294
|
+
data,
|
|
295
|
+
indent=indent,
|
|
296
|
+
width=width,
|
|
297
|
+
allow_unicode=allow_unicode,
|
|
298
|
+
sort_keys=sort_keys,
|
|
299
|
+
default_flow_style=False,
|
|
300
|
+
**kwargs,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
242
304
|
def load_config_file(path: str | pathlib.Path) -> dict:
|
|
243
305
|
"""
|
|
244
306
|
Load a configuration file, automatically detecting JSON or YAML format.
|
|
@@ -931,3 +993,59 @@ def build_metadata_info(parts: list[str], prefix: str = ' | ') -> str:
|
|
|
931
993
|
return ''
|
|
932
994
|
info = ' | '.join(parts)
|
|
933
995
|
return prefix + info if prefix else info
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
@contextmanager
|
|
999
|
+
def suppress_output():
|
|
1000
|
+
"""
|
|
1001
|
+
Suppress all console output including C-level output from solvers.
|
|
1002
|
+
|
|
1003
|
+
WARNING: Not thread-safe. Modifies global file descriptors.
|
|
1004
|
+
Use only with sequential execution or multiprocessing.
|
|
1005
|
+
"""
|
|
1006
|
+
# Save original file descriptors
|
|
1007
|
+
old_stdout_fd = os.dup(1)
|
|
1008
|
+
old_stderr_fd = os.dup(2)
|
|
1009
|
+
devnull_fd = None
|
|
1010
|
+
|
|
1011
|
+
try:
|
|
1012
|
+
# Open devnull
|
|
1013
|
+
devnull_fd = os.open(os.devnull, os.O_WRONLY)
|
|
1014
|
+
|
|
1015
|
+
# Flush Python buffers before redirecting
|
|
1016
|
+
sys.stdout.flush()
|
|
1017
|
+
sys.stderr.flush()
|
|
1018
|
+
|
|
1019
|
+
# Redirect file descriptors to devnull
|
|
1020
|
+
os.dup2(devnull_fd, 1)
|
|
1021
|
+
os.dup2(devnull_fd, 2)
|
|
1022
|
+
|
|
1023
|
+
yield
|
|
1024
|
+
|
|
1025
|
+
finally:
|
|
1026
|
+
# Restore original file descriptors with nested try blocks
|
|
1027
|
+
# to ensure all cleanup happens even if one step fails
|
|
1028
|
+
try:
|
|
1029
|
+
# Flush any buffered output in the redirected streams
|
|
1030
|
+
sys.stdout.flush()
|
|
1031
|
+
sys.stderr.flush()
|
|
1032
|
+
except (OSError, ValueError):
|
|
1033
|
+
pass # Stream might be closed or invalid
|
|
1034
|
+
|
|
1035
|
+
try:
|
|
1036
|
+
os.dup2(old_stdout_fd, 1)
|
|
1037
|
+
except OSError:
|
|
1038
|
+
pass # Failed to restore stdout, continue cleanup
|
|
1039
|
+
|
|
1040
|
+
try:
|
|
1041
|
+
os.dup2(old_stderr_fd, 2)
|
|
1042
|
+
except OSError:
|
|
1043
|
+
pass # Failed to restore stderr, continue cleanup
|
|
1044
|
+
|
|
1045
|
+
# Close all file descriptors
|
|
1046
|
+
for fd in [devnull_fd, old_stdout_fd, old_stderr_fd]:
|
|
1047
|
+
if fd is not None:
|
|
1048
|
+
try:
|
|
1049
|
+
os.close(fd)
|
|
1050
|
+
except OSError:
|
|
1051
|
+
pass # FD already closed or invalid
|
flixopt/results.py
CHANGED
|
@@ -148,6 +148,8 @@ class CalculationResults(CompositeContainerMixin['ComponentResults | BusResults
|
|
|
148
148
|
|
|
149
149
|
"""
|
|
150
150
|
|
|
151
|
+
model: linopy.Model | None
|
|
152
|
+
|
|
151
153
|
@classmethod
|
|
152
154
|
def from_file(cls, folder: str | pathlib.Path, name: str) -> CalculationResults:
|
|
153
155
|
"""Load CalculationResults from saved files.
|
|
@@ -1029,14 +1031,14 @@ class CalculationResults(CompositeContainerMixin['ComponentResults | BusResults
|
|
|
1029
1031
|
]
|
|
1030
1032
|
) = True,
|
|
1031
1033
|
path: pathlib.Path | None = None,
|
|
1032
|
-
show: bool =
|
|
1034
|
+
show: bool | None = None,
|
|
1033
1035
|
) -> pyvis.network.Network | None:
|
|
1034
1036
|
"""Plot interactive network visualization of the system.
|
|
1035
1037
|
|
|
1036
1038
|
Args:
|
|
1037
1039
|
controls: Enable/disable interactive controls.
|
|
1038
1040
|
path: Save path for network HTML.
|
|
1039
|
-
show: Whether to display the plot.
|
|
1041
|
+
show: Whether to display the plot. If None, uses CONFIG.Plotting.default_show.
|
|
1040
1042
|
"""
|
|
1041
1043
|
if path is None:
|
|
1042
1044
|
path = self.folder / f'{self.name}--network.html'
|
flixopt/solvers.py
CHANGED
|
@@ -8,6 +8,8 @@ import logging
|
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
9
|
from typing import Any, ClassVar
|
|
10
10
|
|
|
11
|
+
from flixopt.config import CONFIG
|
|
12
|
+
|
|
11
13
|
logger = logging.getLogger('flixopt')
|
|
12
14
|
|
|
13
15
|
|
|
@@ -17,14 +19,16 @@ class _Solver:
|
|
|
17
19
|
Abstract base class for solvers.
|
|
18
20
|
|
|
19
21
|
Args:
|
|
20
|
-
mip_gap: Acceptable relative optimality gap in [0.0, 1.0].
|
|
21
|
-
time_limit_seconds: Time limit in seconds.
|
|
22
|
+
mip_gap: Acceptable relative optimality gap in [0.0, 1.0]. Defaults to CONFIG.Solving.mip_gap.
|
|
23
|
+
time_limit_seconds: Time limit in seconds. Defaults to CONFIG.Solving.time_limit_seconds.
|
|
24
|
+
log_to_console: If False, no output to console. Defaults to CONFIG.Solving.log_to_console.
|
|
22
25
|
extra_options: Additional solver options merged into `options`.
|
|
23
26
|
"""
|
|
24
27
|
|
|
25
28
|
name: ClassVar[str]
|
|
26
|
-
mip_gap: float
|
|
27
|
-
time_limit_seconds: int
|
|
29
|
+
mip_gap: float = field(default_factory=lambda: CONFIG.Solving.mip_gap)
|
|
30
|
+
time_limit_seconds: int = field(default_factory=lambda: CONFIG.Solving.time_limit_seconds)
|
|
31
|
+
log_to_console: bool = field(default_factory=lambda: CONFIG.Solving.log_to_console)
|
|
28
32
|
extra_options: dict[str, Any] = field(default_factory=dict)
|
|
29
33
|
|
|
30
34
|
@property
|
|
@@ -45,6 +49,7 @@ class GurobiSolver(_Solver):
|
|
|
45
49
|
Args:
|
|
46
50
|
mip_gap: Acceptable relative optimality gap in [0.0, 1.0]; mapped to Gurobi `MIPGap`.
|
|
47
51
|
time_limit_seconds: Time limit in seconds; mapped to Gurobi `TimeLimit`.
|
|
52
|
+
log_to_console: If False, no output to console.
|
|
48
53
|
extra_options: Additional solver options merged into `options`.
|
|
49
54
|
"""
|
|
50
55
|
|
|
@@ -55,6 +60,7 @@ class GurobiSolver(_Solver):
|
|
|
55
60
|
return {
|
|
56
61
|
'MIPGap': self.mip_gap,
|
|
57
62
|
'TimeLimit': self.time_limit_seconds,
|
|
63
|
+
'LogToConsole': 1 if self.log_to_console else 0,
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
|
|
@@ -65,6 +71,7 @@ class HighsSolver(_Solver):
|
|
|
65
71
|
Attributes:
|
|
66
72
|
mip_gap: Acceptable relative optimality gap in [0.0, 1.0]; mapped to HiGHS `mip_rel_gap`.
|
|
67
73
|
time_limit_seconds: Time limit in seconds; mapped to HiGHS `time_limit`.
|
|
74
|
+
log_to_console: If False, no output to console.
|
|
68
75
|
extra_options: Additional solver options merged into `options`.
|
|
69
76
|
threads (int | None): Number of threads to use. If None, HiGHS chooses.
|
|
70
77
|
"""
|
|
@@ -78,4 +85,5 @@ class HighsSolver(_Solver):
|
|
|
78
85
|
'mip_rel_gap': self.mip_gap,
|
|
79
86
|
'time_limit': self.time_limit_seconds,
|
|
80
87
|
'threads': self.threads,
|
|
88
|
+
'log_to_console': self.log_to_console,
|
|
81
89
|
}
|
flixopt/structure.py
CHANGED
|
@@ -850,6 +850,8 @@ class Interface:
|
|
|
850
850
|
class Element(Interface):
|
|
851
851
|
"""This class is the basic Element of flixopt. Every Element has a label"""
|
|
852
852
|
|
|
853
|
+
submodel: ElementModel | None
|
|
854
|
+
|
|
853
855
|
def __init__(self, label: str, meta_data: dict | None = None):
|
|
854
856
|
"""
|
|
855
857
|
Args:
|
|
@@ -858,7 +860,7 @@ class Element(Interface):
|
|
|
858
860
|
"""
|
|
859
861
|
self.label = Element._valid_label(label)
|
|
860
862
|
self.meta_data = meta_data if meta_data is not None else {}
|
|
861
|
-
self.submodel
|
|
863
|
+
self.submodel = None
|
|
862
864
|
|
|
863
865
|
def _plausibility_checks(self) -> None:
|
|
864
866
|
"""This function is used to do some basic plausibility checks for each Element during initialization.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flixopt
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
4
4
|
Summary: Vector based energy and material flow optimization framework in Python.
|
|
5
5
|
Author-email: "Chair of Building Energy Systems and Heat Supply, TU Dresden" <peter.stange@tu-dresden.de>, Felix Bumann <felixbumann387@gmail.com>, Felix Panitz <baumbude@googlemail.com>, Peter Stange <peter.stange@tu-dresden.de>
|
|
6
6
|
Maintainer-email: Felix Bumann <felixbumann387@gmail.com>, Peter Stange <peter.stange@tu-dresden.de>
|
|
@@ -27,6 +27,7 @@ Requires-Dist: linopy<0.6,>=0.5.1
|
|
|
27
27
|
Requires-Dist: netcdf4<2,>=1.6.1
|
|
28
28
|
Requires-Dist: pyyaml<7,>=6.0.0
|
|
29
29
|
Requires-Dist: rich<15,>=13.0.0
|
|
30
|
+
Requires-Dist: tqdm<5,>=4.66.0
|
|
30
31
|
Requires-Dist: tomli<3,>=2.0.1; python_version < "3.11"
|
|
31
32
|
Requires-Dist: highspy<2,>=1.5.3
|
|
32
33
|
Requires-Dist: matplotlib<4,>=3.5.2
|
|
@@ -60,7 +61,7 @@ Requires-Dist: pyvis==0.3.2; extra == "dev"
|
|
|
60
61
|
Requires-Dist: tsam==2.3.9; extra == "dev"
|
|
61
62
|
Requires-Dist: scipy==1.15.1; extra == "dev"
|
|
62
63
|
Requires-Dist: gurobipy==12.0.3; extra == "dev"
|
|
63
|
-
Requires-Dist: dash==3.
|
|
64
|
+
Requires-Dist: dash==3.2.0; extra == "dev"
|
|
64
65
|
Requires-Dist: dash-cytoscape==1.0.2; extra == "dev"
|
|
65
66
|
Requires-Dist: dash-daq==0.6.0; extra == "dev"
|
|
66
67
|
Requires-Dist: networkx==3.0.0; extra == "dev"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
flixopt/__init__.py,sha256=_5d7Buc1ugaip5QbDGc9ebMO8LK0WWAjYHQMX2Th8P0,2217
|
|
2
|
+
flixopt/aggregation.py,sha256=ZE0LcUAZ8xNet13YjxvvMw8BAL7Qo4TcJBwBCE2sHqE,16562
|
|
3
|
+
flixopt/calculation.py,sha256=2jKc2Sma7zra1wJruyJmaNfLouquu1fKk0R4NQAqAWw,32681
|
|
4
|
+
flixopt/color_processing.py,sha256=bSq6iAnreiEBFz4Xf0AIUMyENJsWbJ-5xpiqM7_teUc,9027
|
|
5
|
+
flixopt/commons.py,sha256=ZNlUN1z-h9OGHPo-s-n5OLlJaoPZKVGcAdRyGKpMk4M,1256
|
|
6
|
+
flixopt/components.py,sha256=37JR4jJca7aD40ZWPM83Cyr9w7yotXo7gHUx25UUF8Q,58236
|
|
7
|
+
flixopt/config.py,sha256=vl6drczrsMshCA12kd6FXYE0uBBT3HF08GEF02OeU9Y,28958
|
|
8
|
+
flixopt/core.py,sha256=OG789eUaS5Lu0CjJiMIdtaixqnV5ZtMiKfERjCPRTv8,26366
|
|
9
|
+
flixopt/effects.py,sha256=BZE6Dn3krK9JOX2nn0LohA2GhWGiU9HJUwwp0zEGsb0,34355
|
|
10
|
+
flixopt/elements.py,sha256=2jVqtMgQrP6CO08A-S0JMANLKHddxUfA12KY6VfjZu8,38775
|
|
11
|
+
flixopt/features.py,sha256=kd-fMvADv8GXoKkrXObYjRJLN8toBG-5bOHTuh-59kk,25073
|
|
12
|
+
flixopt/flow_system.py,sha256=foZgjRYEY1qcuqs6c98y5T3Bd84KYtp4CNG2ppPJYJw,43792
|
|
13
|
+
flixopt/interface.py,sha256=TEm1tF24cWwCbP_0yBhhH0aVy_j5Fbgl3LI49H5yOIE,58692
|
|
14
|
+
flixopt/io.py,sha256=cxH3KDetLrfp3b9caOvSSv6A-Vis1dr2w8gRoQ0sZnY,36773
|
|
15
|
+
flixopt/linear_converters.py,sha256=tcz5c1SI36hRFbCX-4NXced12ss9VETg5BE7zOdyeo4,22699
|
|
16
|
+
flixopt/modeling.py,sha256=s0zipbblq-LJrSe7angKT3Imxgr3kIbprG98HUvmkzI,31322
|
|
17
|
+
flixopt/network_app.py,sha256=LnVAlAgzL1BgMYLsJ20a62j6nQUmNccF1zo4ACUXzL4,29433
|
|
18
|
+
flixopt/plotting.py,sha256=C_VyBVQIUP1HYt8roXk__Gz9m17cSSPikXZL4jidIpg,65024
|
|
19
|
+
flixopt/results.py,sha256=KboEmzyu7hv42e8lICaTJMQuV6Rjuejuc_ivdTz2WQo,120518
|
|
20
|
+
flixopt/solvers.py,sha256=rTFuL-lBflpbY_NGVGdXeWB2vLw5AdKemTn-Q0KaG7w,3007
|
|
21
|
+
flixopt/structure.py,sha256=7wjpthFkjFJpSo-OmFov4tF5lFoM6SSNhvDF65ecHdE,58155
|
|
22
|
+
flixopt-3.4.0.dist-info/licenses/LICENSE,sha256=HKsZnbrM_3Rvnr_u9cWSG90cBsj5_slaqI_z_qcxnGI,1118
|
|
23
|
+
flixopt-3.4.0.dist-info/METADATA,sha256=p7zVaOUK8x0umDLWYI1G7Qf-ghvO8wtfKpgC36q1PDM,12886
|
|
24
|
+
flixopt-3.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
+
flixopt-3.4.0.dist-info/top_level.txt,sha256=fanTzb9NylIXfv6Ic7spU97fVmRgGDPKvI_91tw4S3E,8
|
|
26
|
+
flixopt-3.4.0.dist-info/RECORD,,
|
flixopt-3.3.1.dist-info/RECORD
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
flixopt/__init__.py,sha256=_5d7Buc1ugaip5QbDGc9ebMO8LK0WWAjYHQMX2Th8P0,2217
|
|
2
|
-
flixopt/aggregation.py,sha256=ZE0LcUAZ8xNet13YjxvvMw8BAL7Qo4TcJBwBCE2sHqE,16562
|
|
3
|
-
flixopt/calculation.py,sha256=uWgWeU-eg7bbLris85oDtm6fyj7cHFI8CQ-U13vy2u0,29658
|
|
4
|
-
flixopt/color_processing.py,sha256=bSq6iAnreiEBFz4Xf0AIUMyENJsWbJ-5xpiqM7_teUc,9027
|
|
5
|
-
flixopt/commons.py,sha256=ZNlUN1z-h9OGHPo-s-n5OLlJaoPZKVGcAdRyGKpMk4M,1256
|
|
6
|
-
flixopt/components.py,sha256=XYNjQF5IWN1buFfvY4HyWrRD1S3q9cq_ApyaOdT6BE4,58118
|
|
7
|
-
flixopt/config.py,sha256=ScqPyYn_URJmOo_aQDViQ-TktF3TZPrcCSoBCQXVpXc,24591
|
|
8
|
-
flixopt/core.py,sha256=OG789eUaS5Lu0CjJiMIdtaixqnV5ZtMiKfERjCPRTv8,26366
|
|
9
|
-
flixopt/effects.py,sha256=YXx0Ou1Pu3xMugB8DvcqvqC2x8Vx7fDWdHuvn_Eh214,34307
|
|
10
|
-
flixopt/elements.py,sha256=N2RG3OMbGRO8qEROYd-_FsH0Vkx4C-ePii3YiHN0mqA,38712
|
|
11
|
-
flixopt/features.py,sha256=kd-fMvADv8GXoKkrXObYjRJLN8toBG-5bOHTuh-59kk,25073
|
|
12
|
-
flixopt/flow_system.py,sha256=7iSWYCItIQhwUqVZ3VUG_cIUUm0O-OeJbC0tQeyp0OU,43757
|
|
13
|
-
flixopt/interface.py,sha256=TEm1tF24cWwCbP_0yBhhH0aVy_j5Fbgl3LI49H5yOIE,58692
|
|
14
|
-
flixopt/io.py,sha256=Oh1pRA6H_HOeRzvFZtQ8zXlAjWv-J-lYOWCoKIB-n2M,33409
|
|
15
|
-
flixopt/linear_converters.py,sha256=tcz5c1SI36hRFbCX-4NXced12ss9VETg5BE7zOdyeo4,22699
|
|
16
|
-
flixopt/modeling.py,sha256=s0zipbblq-LJrSe7angKT3Imxgr3kIbprG98HUvmkzI,31322
|
|
17
|
-
flixopt/network_app.py,sha256=LnVAlAgzL1BgMYLsJ20a62j6nQUmNccF1zo4ACUXzL4,29433
|
|
18
|
-
flixopt/plotting.py,sha256=C_VyBVQIUP1HYt8roXk__Gz9m17cSSPikXZL4jidIpg,65024
|
|
19
|
-
flixopt/results.py,sha256=b35Y8bkduFEbHFn_nHFc2PW7vjQzPWrM4mCC5rz0Njw,120436
|
|
20
|
-
flixopt/solvers.py,sha256=m38Smc22MJfHYMiqfNf1MA3OmvbTRm5OWS9nECkDdQk,2355
|
|
21
|
-
flixopt/structure.py,sha256=ZB36753ei-VhbaONHLLms9ee_SOs_sEnJLkexRdwoa4,58141
|
|
22
|
-
flixopt-3.3.1.dist-info/licenses/LICENSE,sha256=HKsZnbrM_3Rvnr_u9cWSG90cBsj5_slaqI_z_qcxnGI,1118
|
|
23
|
-
flixopt-3.3.1.dist-info/METADATA,sha256=4HxgUkfhMLstct176MJPqpIvtx7Q1yIv_LlmvRlUF9o,12855
|
|
24
|
-
flixopt-3.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
-
flixopt-3.3.1.dist-info/top_level.txt,sha256=fanTzb9NylIXfv6Ic7spU97fVmRgGDPKvI_91tw4S3E,8
|
|
26
|
-
flixopt-3.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|