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.

Files changed (33) hide show
  1. {flixopt-3.2.1 → flixopt-3.4.0}/CHANGELOG.md +102 -31
  2. {flixopt-3.2.1/flixopt.egg-info → flixopt-3.4.0}/PKG-INFO +3 -2
  3. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/calculation.py +105 -39
  4. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/components.py +16 -0
  5. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/config.py +120 -0
  6. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/effects.py +28 -28
  7. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/elements.py +58 -1
  8. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/flow_system.py +141 -84
  9. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/interface.py +23 -2
  10. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/io.py +506 -4
  11. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/results.py +52 -24
  12. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/solvers.py +12 -4
  13. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/structure.py +369 -49
  14. {flixopt-3.2.1 → flixopt-3.4.0/flixopt.egg-info}/PKG-INFO +3 -2
  15. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt.egg-info/requires.txt +2 -1
  16. {flixopt-3.2.1 → flixopt-3.4.0}/pyproject.toml +2 -1
  17. {flixopt-3.2.1 → flixopt-3.4.0}/LICENSE +0 -0
  18. {flixopt-3.2.1 → flixopt-3.4.0}/MANIFEST.in +0 -0
  19. {flixopt-3.2.1 → flixopt-3.4.0}/README.md +0 -0
  20. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/__init__.py +0 -0
  21. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/aggregation.py +0 -0
  22. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/color_processing.py +0 -0
  23. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/commons.py +0 -0
  24. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/core.py +0 -0
  25. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/features.py +0 -0
  26. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/linear_converters.py +0 -0
  27. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/modeling.py +0 -0
  28. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/network_app.py +0 -0
  29. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt/plotting.py +0 -0
  30. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt.egg-info/SOURCES.txt +0 -0
  31. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt.egg-info/dependency_links.txt +0 -0
  32. {flixopt-3.2.1 → flixopt-3.4.0}/flixopt.egg-info/top_level.txt +0 -0
  33. {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
- - Group components by colorscales: `results.setup_colors({'CHP': 'reds', 'Storage': 'blues', 'Greys': ['Grid', 'Demand']})`
109
- - Automatically propagates to all segments in segmented calculations
110
- - Colors persist across all plot calls unless explicitly overridden
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
- - `default_show`: Control default plot visibility
118
- - `default_engine`: Choose 'plotly' or 'matplotlib'
119
- - `default_dpi`: Set resolution for saved plots
120
- - `default_facet_cols`: Configure default faceting columns
121
- - `default_sequential_colorscale`: Default for heatmaps (now 'turbo')
122
- - `default_qualitative_colorscale`: Default for categorical plots (now 'plotly')
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
- - **Terminology changes**: Effect domains renamed for clarity: `operation` → `temporal`, `invest`/`investment` → `periodic`
290
- - **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)
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
- - `Flow.bus` must receive a string label, not a Bus object
294
- - Effect shares must use effect labels (strings) in dictionaries, not Effect objects
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
- - `switch_on` → `switch|on`
309
- - `switch_off` → `switch|off`
310
- - `switch_on_nr` → `switch|count`
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
- - `Effect(invest)|total` → `Effect(periodic)`
313
- - `Effect(operation)|total` → `Effect(temporal)`
314
- - `Effect(operation)|total_per_timestep` → `Effect(temporal)|per_timestep`
315
- - `Effect|total` → `Effect`
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
- - *Note: This is still experimental and might change in the future*
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
- - Old implementation: $c(\text{t}_{i}) \cdot (1-\dot{\text{c}}_\text{rel,loss}(\text{t}_i)) \cdot \Delta \text{t}_{i}$
579
- - Correct implementation: $c(\text{t}_{i}) \cdot (1-\dot{\text{c}}_\text{rel,loss}(\text{t}_i)) ^{\Delta \text{t}_{i}}$
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
- - Variable renaming: `...|consecutive_on_hours` → `...|ConsecutiveOn|hours`
605
- - Variable renaming: `...|consecutive_off_hours` → `...|ConsecutiveOff|hours`
606
- - Constraint renaming: `...|consecutive_on_hours_con1` → `...|ConsecutiveOn|con1`
607
- - Similar pattern for all consecutive on/off constraints
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
- - Variable renaming: `...|consecutive_on_hours` → `...|ConsecutiveOn|hours`
654
- - Variable renaming: `...|consecutive_off_hours` → `...|ConsecutiveOff|hours`
655
- - Constraint renaming: `...|consecutive_on_hours_con1` → `...|ConsecutiveOn|con1`
656
- - Similar pattern for all consecutive on/off constraints
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.2.1
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.0.0; extra == "dev"
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 yaml
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: FlowSystemModel | None = None
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 = True
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
- + yaml.dump(
260
+ + fx_io.format_yaml_string(
255
261
  self.main_results,
256
- default_flow_style=False,
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=True, save=self.folder / 'aggregation.html')
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, solver: _Solver, log_file: pathlib.Path | None = None, log_main_results: bool = False
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
- for i, calculation in enumerate(self.sub_calculations):
578
- logger.info(
579
- f'{self.segment_names[i]} [{i + 1:>2}/{len(self.segment_names):<2}] '
580
- f'({calculation.flow_system.timesteps[0]} -> {calculation.flow_system.timesteps[-1]}):'
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
- 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 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
- calculation.solve(
603
- solver,
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.