flixopt 3.2.1__tar.gz → 3.4.0__tar.gz
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-3.2.1 → flixopt-3.4.0}/CHANGELOG.md +102 -31
- {flixopt-3.2.1/flixopt.egg-info → flixopt-3.4.0}/PKG-INFO +3 -2
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/calculation.py +105 -39
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/components.py +16 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/config.py +120 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/effects.py +28 -28
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/elements.py +58 -1
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/flow_system.py +141 -84
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/interface.py +23 -2
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/io.py +506 -4
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/results.py +52 -24
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/solvers.py +12 -4
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/structure.py +369 -49
- {flixopt-3.2.1 → flixopt-3.4.0/flixopt.egg-info}/PKG-INFO +3 -2
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt.egg-info/requires.txt +2 -1
- {flixopt-3.2.1 → flixopt-3.4.0}/pyproject.toml +2 -1
- {flixopt-3.2.1 → flixopt-3.4.0}/LICENSE +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/MANIFEST.in +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/README.md +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/__init__.py +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/aggregation.py +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/color_processing.py +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/commons.py +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/core.py +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/features.py +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/linear_converters.py +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/modeling.py +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/network_app.py +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/plotting.py +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt.egg-info/SOURCES.txt +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt.egg-info/dependency_links.txt +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/flixopt.egg-info/top_level.txt +0 -0
- {flixopt-3.2.1 → flixopt-3.4.0}/setup.cfg +0 -0
|
@@ -81,6 +81,77 @@ If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOp
|
|
|
81
81
|
|
|
82
82
|
Until here -->
|
|
83
83
|
|
|
84
|
+
## [3.4.0] - 2025-11-01
|
|
85
|
+
|
|
86
|
+
**Summary**: Enhanced solver configuration with new CONFIG.Solving section for centralized solver parameter management.
|
|
87
|
+
|
|
88
|
+
If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOpt/flixOpt/releases/tag/v3.0.0) and [Migration Guide](https://flixopt.github.io/flixopt/latest/user-guide/migration-guide-v3/).
|
|
89
|
+
|
|
90
|
+
### ✨ Added
|
|
91
|
+
|
|
92
|
+
**Solver configuration:**
|
|
93
|
+
- **New `CONFIG.Solving` configuration section** for centralized solver parameter management:
|
|
94
|
+
- `mip_gap`: Default MIP gap tolerance for solver convergence (default: 0.01)
|
|
95
|
+
- `time_limit_seconds`: Default time limit in seconds for solver runs (default: 300)
|
|
96
|
+
- `log_to_console`: Whether solver should output to console (default: True)
|
|
97
|
+
- `log_main_results`: Whether to log main results after solving (default: True)
|
|
98
|
+
- Solvers (`HighsSolver`, `GurobiSolver`) now use `CONFIG.Solving` defaults for parameters, allowing global configuration
|
|
99
|
+
- Solver parameters can still be explicitly overridden when creating solver instances
|
|
100
|
+
- New `log_to_console` parameter in all Solver classes
|
|
101
|
+
|
|
102
|
+
### ♻️ Changed
|
|
103
|
+
- Individual solver output is now hidden in **SegmentedCalculation**. To return to the prior behaviour, set `show_individual_solves=True` in `do_modeling_and_solve()`.
|
|
104
|
+
|
|
105
|
+
### 🐛 Fixed
|
|
106
|
+
- New compacted list representation for periods and scenarios also in results log and console print
|
|
107
|
+
|
|
108
|
+
### 📝 Docs
|
|
109
|
+
- Unified contributing guides in docs and on github
|
|
110
|
+
|
|
111
|
+
### 👷 Development
|
|
112
|
+
- Added type hints for submodel in all Interface classes
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## [3.3.1] - 2025-10-30
|
|
117
|
+
|
|
118
|
+
**Summary**: Small Bugfix and improving readability
|
|
119
|
+
|
|
120
|
+
If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOpt/flixOpt/releases/tag/v3.0.0) and [Migration Guide](https://flixopt.github.io/flixopt/latest/user-guide/migration-guide-v3/).
|
|
121
|
+
|
|
122
|
+
### ♻️ Changed
|
|
123
|
+
- Improved `summary.yaml` to use a compacted list representation for periods and scenarios
|
|
124
|
+
|
|
125
|
+
### 🐛 Fixed
|
|
126
|
+
- Using `switch_on_total_max` with periods or scenarios failed
|
|
127
|
+
|
|
128
|
+
### 📝 Docs
|
|
129
|
+
- Add more comprehensive `CONTRIBUTE.md`
|
|
130
|
+
- Improve logical structure in User Guide
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## [3.3.0] - 2025-10-30
|
|
135
|
+
|
|
136
|
+
**Summary**: Better access to Elements stored in the FLowSystem and better representations (repr)
|
|
137
|
+
|
|
138
|
+
If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOpt/flixOpt/releases/tag/v3.0.0) and [Migration Guide](https://flixopt.github.io/flixopt/latest/user-guide/migration-guide-v3/).
|
|
139
|
+
|
|
140
|
+
### ♻️ Changed
|
|
141
|
+
**Improved repr methods:**
|
|
142
|
+
- **Results classes** (`ComponentResults`, `BusResults`, `FlowResults`, `EffectResults`) now show concise header with key metadata followed by xarray Dataset repr
|
|
143
|
+
- **Element classes** (`Component`, `Bus`, `Flow`, `Effect`, `Storage`) now show one-line summaries with essential information (connections, sizes, capacities, constraints)
|
|
144
|
+
|
|
145
|
+
**Container-based access:**
|
|
146
|
+
- **FlowSystem** now provides dict-like access patterns for all elements
|
|
147
|
+
- Use `flow_system['element_label']`, `flow_system.keys()`, `flow_system.values()`, and `flow_system.items()` for unified element access
|
|
148
|
+
- Specialized containers (`components`, `buses`, `effects`, `flows`) offer type-specific access with helpful error messages
|
|
149
|
+
|
|
150
|
+
### 🗑️ Deprecated
|
|
151
|
+
- **`FlowSystem.all_elements`** property is deprecated in favor of dict-like interface (`flow_system['label']`, `.keys()`, `.values()`, `.items()`). Will be removed in v4.0.0.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
84
155
|
## [3.2.1] - 2025-10-29
|
|
85
156
|
|
|
86
157
|
**Summary**:
|
|
@@ -105,21 +176,21 @@ If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOp
|
|
|
105
176
|
|
|
106
177
|
**Color management:**
|
|
107
178
|
- **`setup_colors()` method** for `CalculationResults` and `SegmentedCalculationResults` to configure consistent colors across all plots
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
179
|
+
- Group components by colorscales: `results.setup_colors({'CHP': 'reds', 'Storage': 'blues', 'Greys': ['Grid', 'Demand']})`
|
|
180
|
+
- Automatically propagates to all segments in segmented calculations
|
|
181
|
+
- Colors persist across all plot calls unless explicitly overridden
|
|
111
182
|
- **Flexible color inputs**: Supports colorscale names (e.g., 'turbo', 'plasma'), color lists, or label-to-color dictionaries
|
|
112
183
|
- **Cross-backend compatibility**: Seamless color handling for both Plotly and Matplotlib
|
|
113
184
|
|
|
114
185
|
**Plotting customization:**
|
|
115
186
|
- **Plotting kwargs support**: Pass additional arguments to plotting backends via `px_kwargs`, `plot_kwargs`, and `backend_kwargs` parameters
|
|
116
187
|
- **New `CONFIG.Plotting` configuration section**:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
188
|
+
- `default_show`: Control default plot visibility
|
|
189
|
+
- `default_engine`: Choose 'plotly' or 'matplotlib'
|
|
190
|
+
- `default_dpi`: Set resolution for saved plots
|
|
191
|
+
- `default_facet_cols`: Configure default faceting columns
|
|
192
|
+
- `default_sequential_colorscale`: Default for heatmaps (now 'turbo')
|
|
193
|
+
- `default_qualitative_colorscale`: Default for categorical plots (now 'plotly')
|
|
123
194
|
|
|
124
195
|
**I/O improvements:**
|
|
125
196
|
- Centralized JSON/YAML I/O with auto-format detection
|
|
@@ -286,12 +357,12 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
|
|
|
286
357
|
**API and Behavior Changes:**
|
|
287
358
|
|
|
288
359
|
- **Effect system redesigned** (no deprecation):
|
|
289
|
-
|
|
290
|
-
|
|
360
|
+
- **Terminology changes**: Effect domains renamed for clarity: `operation` → `temporal`, `invest`/`investment` → `periodic`
|
|
361
|
+
- **Sharing system**: The old `specific_share_to_other_effects_*` parameters were completely replaced with the new `share_from_temporal` and `share_from_periodic` syntax (see 🔥 Removed section)
|
|
291
362
|
- **FlowSystem independence**: FlowSystems cannot be shared across multiple Calculations anymore. A copy of the FlowSystem is created instead, making every Calculation independent. Each Subcalculation in `SegmentedCalculation` now has its own distinct `FlowSystem` object
|
|
292
363
|
- **Bus and Effect object assignment**: Direct assignment of Bus/Effect objects is no longer supported. Use labels (strings) instead:
|
|
293
|
-
|
|
294
|
-
|
|
364
|
+
- `Flow.bus` must receive a string label, not a Bus object
|
|
365
|
+
- Effect shares must use effect labels (strings) in dictionaries, not Effect objects
|
|
295
366
|
- **Logging defaults** (from v2.2.0): Console and file logging are now disabled by default. Enable explicitly with `CONFIG.Logging.console = True` and `CONFIG.apply()`
|
|
296
367
|
|
|
297
368
|
**Class and Method Renaming:**
|
|
@@ -305,14 +376,14 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
|
|
|
305
376
|
|
|
306
377
|
- Investment binary variable: `is_invested` → `invested` in `InvestmentModel`
|
|
307
378
|
- Switch tracking variables in `OnOffModel`:
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
379
|
+
- `switch_on` → `switch|on`
|
|
380
|
+
- `switch_off` → `switch|off`
|
|
381
|
+
- `switch_on_nr` → `switch|count`
|
|
311
382
|
- Effect submodel variables (following terminology changes):
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
383
|
+
- `Effect(invest)|total` → `Effect(periodic)`
|
|
384
|
+
- `Effect(operation)|total` → `Effect(temporal)`
|
|
385
|
+
- `Effect(operation)|total_per_timestep` → `Effect(temporal)|per_timestep`
|
|
386
|
+
- `Effect|total` → `Effect`
|
|
316
387
|
|
|
317
388
|
**Data Structure Changes:**
|
|
318
389
|
|
|
@@ -533,7 +604,7 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
|
|
|
533
604
|
|
|
534
605
|
### ✨ Added
|
|
535
606
|
- **Network Visualization**: Added `FlowSystem.start_network_app()` and `FlowSystem.stop_network_app()` to easily visualize the network structure of a flow system in an interactive Dash web app
|
|
536
|
-
|
|
607
|
+
- *Note: This is still experimental and might change in the future*
|
|
537
608
|
|
|
538
609
|
### ♻️ Changed
|
|
539
610
|
- **Multi-Flow Support**: `Sink`, `Source`, and `SourceAndSink` now accept multiple `flows` as `inputs` and `outputs` instead of just one. This enables modeling more use cases with these classes
|
|
@@ -575,8 +646,8 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
|
|
|
575
646
|
|
|
576
647
|
### 🐛 Fixed
|
|
577
648
|
- Storage losses per hour were not calculated correctly, as mentioned by @brokenwings01. This might have led to issues when modeling large losses and long timesteps.
|
|
578
|
-
|
|
579
|
-
|
|
649
|
+
- Old implementation: $c(\text{t}_{i}) \cdot (1-\dot{\text{c}}_\text{rel,loss}(\text{t}_i)) \cdot \Delta \text{t}_{i}$
|
|
650
|
+
- Correct implementation: $c(\text{t}_{i}) \cdot (1-\dot{\text{c}}_\text{rel,loss}(\text{t}_i)) ^{\Delta \text{t}_{i}}$
|
|
580
651
|
|
|
581
652
|
### 🚧 Known Issues
|
|
582
653
|
- Just to mention: Plotly >= 6 may raise errors if "nbformat" is not installed. We pinned plotly to <6, but this may be fixed in the future.
|
|
@@ -601,10 +672,10 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
|
|
|
601
672
|
|
|
602
673
|
### 💥 Breaking Changes
|
|
603
674
|
- Restructured the modeling of the On/Off state of Flows or Components
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
675
|
+
- Variable renaming: `...|consecutive_on_hours` → `...|ConsecutiveOn|hours`
|
|
676
|
+
- Variable renaming: `...|consecutive_off_hours` → `...|ConsecutiveOff|hours`
|
|
677
|
+
- Constraint renaming: `...|consecutive_on_hours_con1` → `...|ConsecutiveOn|con1`
|
|
678
|
+
- Similar pattern for all consecutive on/off constraints
|
|
608
679
|
|
|
609
680
|
### 🐛 Fixed
|
|
610
681
|
- Fixed the lower bound of `flow_rate` when using optional investments without OnOffParameters
|
|
@@ -650,10 +721,10 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
|
|
|
650
721
|
|
|
651
722
|
**Variable Structure:**
|
|
652
723
|
- Restructured the modeling of the On/Off state of Flows or Components
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
724
|
+
- Variable renaming: `...|consecutive_on_hours` → `...|ConsecutiveOn|hours`
|
|
725
|
+
- Variable renaming: `...|consecutive_off_hours` → `...|ConsecutiveOff|hours`
|
|
726
|
+
- Constraint renaming: `...|consecutive_on_hours_con1` → `...|ConsecutiveOn|con1`
|
|
727
|
+
- Similar pattern for all consecutive on/off constraints
|
|
657
728
|
|
|
658
729
|
### 🔥 Removed
|
|
659
730
|
- **Pyomo dependency** (replaced by linopy)
|
|
@@ -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"
|
|
@@ -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)
|
|
@@ -112,7 +115,7 @@ class Calculation:
|
|
|
112
115
|
'periodic': effect.submodel.periodic.total.solution.values,
|
|
113
116
|
'total': effect.submodel.total.solution.values,
|
|
114
117
|
}
|
|
115
|
-
for effect in self.flow_system.effects
|
|
118
|
+
for effect in sorted(self.flow_system.effects.values(), key=lambda e: e.label_full.upper())
|
|
116
119
|
},
|
|
117
120
|
'Invest-Decisions': {
|
|
118
121
|
'Invested': {
|
|
@@ -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
|
|
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Literal
|
|
|
11
11
|
import numpy as np
|
|
12
12
|
import xarray as xr
|
|
13
13
|
|
|
14
|
+
from . import io as fx_io
|
|
14
15
|
from .core import PeriodicDataUser, PlausibilityError, TemporalData, TemporalDataUser
|
|
15
16
|
from .elements import Component, ComponentModel, Flow
|
|
16
17
|
from .features import InvestmentModel, PiecewiseModel
|
|
@@ -160,6 +161,8 @@ class LinearConverter(Component):
|
|
|
160
161
|
|
|
161
162
|
"""
|
|
162
163
|
|
|
164
|
+
submodel: LinearConverterModel | None
|
|
165
|
+
|
|
163
166
|
def __init__(
|
|
164
167
|
self,
|
|
165
168
|
label: str,
|
|
@@ -376,6 +379,8 @@ class Storage(Component):
|
|
|
376
379
|
With flow rates in m3/h, the charge state is therefore in m3.
|
|
377
380
|
"""
|
|
378
381
|
|
|
382
|
+
submodel: StorageModel | None
|
|
383
|
+
|
|
379
384
|
def __init__(
|
|
380
385
|
self,
|
|
381
386
|
label: str,
|
|
@@ -528,6 +533,15 @@ class Storage(Component):
|
|
|
528
533
|
f'{self.discharging.size.minimum_size=}, {self.discharging.size.maximum_size=}.'
|
|
529
534
|
)
|
|
530
535
|
|
|
536
|
+
def __repr__(self) -> str:
|
|
537
|
+
"""Return string representation."""
|
|
538
|
+
# Use build_repr_from_init directly to exclude charging and discharging
|
|
539
|
+
return fx_io.build_repr_from_init(
|
|
540
|
+
self,
|
|
541
|
+
excluded_params={'self', 'label', 'charging', 'discharging', 'kwargs'},
|
|
542
|
+
skip_default_size=True,
|
|
543
|
+
) + fx_io.format_flow_details(self)
|
|
544
|
+
|
|
531
545
|
|
|
532
546
|
@register_class_for_io
|
|
533
547
|
class Transmission(Component):
|
|
@@ -640,6 +654,8 @@ class Transmission(Component):
|
|
|
640
654
|
|
|
641
655
|
"""
|
|
642
656
|
|
|
657
|
+
submodel: TransmissionModel | None
|
|
658
|
+
|
|
643
659
|
def __init__(
|
|
644
660
|
self,
|
|
645
661
|
label: str,
|
|
@@ -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.
|