flixopt 3.2.0__tar.gz → 3.3.1__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.0 → flixopt-3.3.1}/CHANGELOG.md +84 -31
- flixopt-3.3.1/CONTRIBUTE.md +168 -0
- {flixopt-3.2.0/flixopt.egg-info → flixopt-3.3.1}/PKG-INFO +2 -2
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/calculation.py +1 -1
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/components.py +10 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/effects.py +23 -27
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/elements.py +54 -1
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/flow_system.py +149 -86
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/interface.py +23 -2
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/io.py +396 -12
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/results.py +48 -22
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/structure.py +366 -48
- {flixopt-3.2.0 → flixopt-3.3.1/flixopt.egg-info}/PKG-INFO +2 -2
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt.egg-info/SOURCES.txt +1 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt.egg-info/requires.txt +1 -1
- {flixopt-3.2.0 → flixopt-3.3.1}/pyproject.toml +1 -1
- {flixopt-3.2.0 → flixopt-3.3.1}/LICENSE +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/MANIFEST.in +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/README.md +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/__init__.py +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/aggregation.py +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/color_processing.py +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/commons.py +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/config.py +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/core.py +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/features.py +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/linear_converters.py +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/modeling.py +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/network_app.py +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/plotting.py +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt/solvers.py +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt.egg-info/dependency_links.txt +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/flixopt.egg-info/top_level.txt +0 -0
- {flixopt-3.2.0 → flixopt-3.3.1}/setup.cfg +0 -0
|
@@ -81,6 +81,59 @@ 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.3.1] - 2025-10-30
|
|
85
|
+
|
|
86
|
+
**Summary**: Small Bugfix and improving readability
|
|
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
|
+
### ♻️ Changed
|
|
91
|
+
- Improved `summary.yaml` to use a compacted list representation for periods and scenarios
|
|
92
|
+
|
|
93
|
+
### 🐛 Fixed
|
|
94
|
+
- Using `switch_on_total_max` with periods or scenarios failed
|
|
95
|
+
|
|
96
|
+
### 📝 Docs
|
|
97
|
+
- Add more comprehensive `CONTRIBUTE.md`
|
|
98
|
+
- Improve logical structure in User Guide
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## [3.3.0] - 2025-10-30
|
|
103
|
+
|
|
104
|
+
**Summary**: Better access to Elements stored in the FLowSystem and better representations (repr)
|
|
105
|
+
|
|
106
|
+
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/).
|
|
107
|
+
|
|
108
|
+
### ♻️ Changed
|
|
109
|
+
**Improved repr methods:**
|
|
110
|
+
- **Results classes** (`ComponentResults`, `BusResults`, `FlowResults`, `EffectResults`) now show concise header with key metadata followed by xarray Dataset repr
|
|
111
|
+
- **Element classes** (`Component`, `Bus`, `Flow`, `Effect`, `Storage`) now show one-line summaries with essential information (connections, sizes, capacities, constraints)
|
|
112
|
+
|
|
113
|
+
**Container-based access:**
|
|
114
|
+
- **FlowSystem** now provides dict-like access patterns for all elements
|
|
115
|
+
- Use `flow_system['element_label']`, `flow_system.keys()`, `flow_system.values()`, and `flow_system.items()` for unified element access
|
|
116
|
+
- Specialized containers (`components`, `buses`, `effects`, `flows`) offer type-specific access with helpful error messages
|
|
117
|
+
|
|
118
|
+
### 🗑️ Deprecated
|
|
119
|
+
- **`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.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## [3.2.1] - 2025-10-29
|
|
124
|
+
|
|
125
|
+
**Summary**:
|
|
126
|
+
|
|
127
|
+
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/).
|
|
128
|
+
|
|
129
|
+
### 🐛 Fixed
|
|
130
|
+
- Fixed resampling of FlowSystem to reset `hours_of_last_timestep` and `hours_of_previous_timesteps` properly
|
|
131
|
+
|
|
132
|
+
### 👷 Development
|
|
133
|
+
- Improved issue templates
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
84
137
|
## [3.2.0] - 2025-10-26
|
|
85
138
|
|
|
86
139
|
**Summary**: Enhanced plotting capabilities with consistent color management, custom plotting kwargs support, and centralized I/O handling.
|
|
@@ -91,21 +144,21 @@ If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOp
|
|
|
91
144
|
|
|
92
145
|
**Color management:**
|
|
93
146
|
- **`setup_colors()` method** for `CalculationResults` and `SegmentedCalculationResults` to configure consistent colors across all plots
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
147
|
+
- Group components by colorscales: `results.setup_colors({'CHP': 'reds', 'Storage': 'blues', 'Greys': ['Grid', 'Demand']})`
|
|
148
|
+
- Automatically propagates to all segments in segmented calculations
|
|
149
|
+
- Colors persist across all plot calls unless explicitly overridden
|
|
97
150
|
- **Flexible color inputs**: Supports colorscale names (e.g., 'turbo', 'plasma'), color lists, or label-to-color dictionaries
|
|
98
151
|
- **Cross-backend compatibility**: Seamless color handling for both Plotly and Matplotlib
|
|
99
152
|
|
|
100
153
|
**Plotting customization:**
|
|
101
154
|
- **Plotting kwargs support**: Pass additional arguments to plotting backends via `px_kwargs`, `plot_kwargs`, and `backend_kwargs` parameters
|
|
102
155
|
- **New `CONFIG.Plotting` configuration section**:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
156
|
+
- `default_show`: Control default plot visibility
|
|
157
|
+
- `default_engine`: Choose 'plotly' or 'matplotlib'
|
|
158
|
+
- `default_dpi`: Set resolution for saved plots
|
|
159
|
+
- `default_facet_cols`: Configure default faceting columns
|
|
160
|
+
- `default_sequential_colorscale`: Default for heatmaps (now 'turbo')
|
|
161
|
+
- `default_qualitative_colorscale`: Default for categorical plots (now 'plotly')
|
|
109
162
|
|
|
110
163
|
**I/O improvements:**
|
|
111
164
|
- Centralized JSON/YAML I/O with auto-format detection
|
|
@@ -272,12 +325,12 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
|
|
|
272
325
|
**API and Behavior Changes:**
|
|
273
326
|
|
|
274
327
|
- **Effect system redesigned** (no deprecation):
|
|
275
|
-
|
|
276
|
-
|
|
328
|
+
- **Terminology changes**: Effect domains renamed for clarity: `operation` → `temporal`, `invest`/`investment` → `periodic`
|
|
329
|
+
- **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)
|
|
277
330
|
- **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
|
|
278
331
|
- **Bus and Effect object assignment**: Direct assignment of Bus/Effect objects is no longer supported. Use labels (strings) instead:
|
|
279
|
-
|
|
280
|
-
|
|
332
|
+
- `Flow.bus` must receive a string label, not a Bus object
|
|
333
|
+
- Effect shares must use effect labels (strings) in dictionaries, not Effect objects
|
|
281
334
|
- **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()`
|
|
282
335
|
|
|
283
336
|
**Class and Method Renaming:**
|
|
@@ -291,14 +344,14 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
|
|
|
291
344
|
|
|
292
345
|
- Investment binary variable: `is_invested` → `invested` in `InvestmentModel`
|
|
293
346
|
- Switch tracking variables in `OnOffModel`:
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
347
|
+
- `switch_on` → `switch|on`
|
|
348
|
+
- `switch_off` → `switch|off`
|
|
349
|
+
- `switch_on_nr` → `switch|count`
|
|
297
350
|
- Effect submodel variables (following terminology changes):
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
351
|
+
- `Effect(invest)|total` → `Effect(periodic)`
|
|
352
|
+
- `Effect(operation)|total` → `Effect(temporal)`
|
|
353
|
+
- `Effect(operation)|total_per_timestep` → `Effect(temporal)|per_timestep`
|
|
354
|
+
- `Effect|total` → `Effect`
|
|
302
355
|
|
|
303
356
|
**Data Structure Changes:**
|
|
304
357
|
|
|
@@ -519,7 +572,7 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
|
|
|
519
572
|
|
|
520
573
|
### ✨ Added
|
|
521
574
|
- **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
|
|
522
|
-
|
|
575
|
+
- *Note: This is still experimental and might change in the future*
|
|
523
576
|
|
|
524
577
|
### ♻️ Changed
|
|
525
578
|
- **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
|
|
@@ -561,8 +614,8 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
|
|
|
561
614
|
|
|
562
615
|
### 🐛 Fixed
|
|
563
616
|
- 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.
|
|
564
|
-
|
|
565
|
-
|
|
617
|
+
- Old implementation: $c(\text{t}_{i}) \cdot (1-\dot{\text{c}}_\text{rel,loss}(\text{t}_i)) \cdot \Delta \text{t}_{i}$
|
|
618
|
+
- Correct implementation: $c(\text{t}_{i}) \cdot (1-\dot{\text{c}}_\text{rel,loss}(\text{t}_i)) ^{\Delta \text{t}_{i}}$
|
|
566
619
|
|
|
567
620
|
### 🚧 Known Issues
|
|
568
621
|
- 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.
|
|
@@ -587,10 +640,10 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
|
|
|
587
640
|
|
|
588
641
|
### 💥 Breaking Changes
|
|
589
642
|
- Restructured the modeling of the On/Off state of Flows or Components
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
643
|
+
- Variable renaming: `...|consecutive_on_hours` → `...|ConsecutiveOn|hours`
|
|
644
|
+
- Variable renaming: `...|consecutive_off_hours` → `...|ConsecutiveOff|hours`
|
|
645
|
+
- Constraint renaming: `...|consecutive_on_hours_con1` → `...|ConsecutiveOn|con1`
|
|
646
|
+
- Similar pattern for all consecutive on/off constraints
|
|
594
647
|
|
|
595
648
|
### 🐛 Fixed
|
|
596
649
|
- Fixed the lower bound of `flow_rate` when using optional investments without OnOffParameters
|
|
@@ -636,10 +689,10 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
|
|
|
636
689
|
|
|
637
690
|
**Variable Structure:**
|
|
638
691
|
- Restructured the modeling of the On/Off state of Flows or Components
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
692
|
+
- Variable renaming: `...|consecutive_on_hours` → `...|ConsecutiveOn|hours`
|
|
693
|
+
- Variable renaming: `...|consecutive_off_hours` → `...|ConsecutiveOff|hours`
|
|
694
|
+
- Constraint renaming: `...|consecutive_on_hours_con1` → `...|ConsecutiveOn|con1`
|
|
695
|
+
- Similar pattern for all consecutive on/off constraints
|
|
643
696
|
|
|
644
697
|
### 🔥 Removed
|
|
645
698
|
- **Pyomo dependency** (replaced by linopy)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Contributing to FlixOpt
|
|
2
|
+
|
|
3
|
+
We warmly welcome contributions from the community! Whether you're fixing bugs, adding features, improving documentation, or sharing examples, your contributions are valuable.
|
|
4
|
+
|
|
5
|
+
## Ways to Contribute
|
|
6
|
+
|
|
7
|
+
### 🐛 Report Issues
|
|
8
|
+
Found a bug or have a feature request? Please [open an issue](https://github.com/flixOpt/flixopt/issues) on GitHub.
|
|
9
|
+
|
|
10
|
+
When reporting issues, please include:
|
|
11
|
+
- A clear description of the problem
|
|
12
|
+
- Steps to reproduce the issue
|
|
13
|
+
- Expected vs. actual behavior
|
|
14
|
+
- Your environment (OS, Python version, FlixOpt version)
|
|
15
|
+
- Minimal code example if applicable
|
|
16
|
+
|
|
17
|
+
### 💡 Share Examples
|
|
18
|
+
Help others learn FlixOpt by contributing examples:
|
|
19
|
+
- Real-world use cases
|
|
20
|
+
- Tutorial notebooks
|
|
21
|
+
- Integration examples with other tools
|
|
22
|
+
- Add them to the `examples/` directory
|
|
23
|
+
|
|
24
|
+
### 📖 Improve Documentation
|
|
25
|
+
Documentation improvements are always welcome:
|
|
26
|
+
- Fix typos or clarify existing docs
|
|
27
|
+
- Add missing documentation
|
|
28
|
+
- Translate documentation
|
|
29
|
+
- Improve code comments
|
|
30
|
+
|
|
31
|
+
### 🔧 Submit Code Contributions
|
|
32
|
+
Ready to contribute code? Great! See the sections below for setup and guidelines.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Development Setup
|
|
37
|
+
|
|
38
|
+
### Getting Started
|
|
39
|
+
1. Fork and clone the repository:
|
|
40
|
+
```bash
|
|
41
|
+
git clone https://github.com/flixOpt/flixopt.git
|
|
42
|
+
cd flixopt
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. Install development dependencies:
|
|
46
|
+
```bash
|
|
47
|
+
pip install -e ".[full, dev]"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
3. Set up pre-commit hooks (one-time setup):
|
|
51
|
+
```bash
|
|
52
|
+
pre-commit install
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
4. Verify your setup:
|
|
56
|
+
```bash
|
|
57
|
+
pytest
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Working with Documentation
|
|
61
|
+
FlixOpt uses [mkdocs](https://www.mkdocs.org/) to generate documentation.
|
|
62
|
+
|
|
63
|
+
To work on documentation:
|
|
64
|
+
```bash
|
|
65
|
+
pip install -e ".[docs]"
|
|
66
|
+
mkdocs serve
|
|
67
|
+
```
|
|
68
|
+
Then navigate to http://127.0.0.1:8000/
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Code Quality Standards
|
|
73
|
+
|
|
74
|
+
### Automated Checks
|
|
75
|
+
We use [Ruff](https://github.com/astral-sh/ruff) for linting and formatting. After the one-time setup above, **code quality checks run automatically on every commit**.
|
|
76
|
+
|
|
77
|
+
### Manual Checks
|
|
78
|
+
To run checks manually:
|
|
79
|
+
- `ruff check --fix .` - Check and fix linting issues
|
|
80
|
+
- `ruff format .` - Format code
|
|
81
|
+
- `pre-commit run --all-files` - Run all pre-commit checks
|
|
82
|
+
|
|
83
|
+
### Testing
|
|
84
|
+
All tests are located in the `tests/` directory with a flat structure:
|
|
85
|
+
- `test_component.py` - Component tests
|
|
86
|
+
- `test_flow.py` - Flow tests
|
|
87
|
+
- `test_storage.py` - Storage tests
|
|
88
|
+
- etc.
|
|
89
|
+
|
|
90
|
+
#### Running Tests
|
|
91
|
+
- `pytest` - Run the full test suite (excluding examples by default)
|
|
92
|
+
- `pytest tests/test_component.py` - Run a specific test file
|
|
93
|
+
- `pytest tests/test_component.py::TestClassName` - Run a specific test class
|
|
94
|
+
- `pytest tests/test_component.py::TestClassName::test_method` - Run a specific test
|
|
95
|
+
- `pytest -m slow` - Run only slow tests
|
|
96
|
+
- `pytest -m examples` - Run example tests (normally skipped)
|
|
97
|
+
- `pytest -k "keyword"` - Run tests matching a keyword
|
|
98
|
+
|
|
99
|
+
#### Common Test Patterns
|
|
100
|
+
The `tests/conftest.py` file provides shared fixtures:
|
|
101
|
+
- `solver_fixture` - Parameterized solver fixture (HiGHS, Gurobi)
|
|
102
|
+
- `highs_solver` - HiGHS solver instance
|
|
103
|
+
- Coordinate configuration fixtures for timesteps, periods, scenarios
|
|
104
|
+
|
|
105
|
+
Use these fixtures by adding them as function parameters:
|
|
106
|
+
```python
|
|
107
|
+
def test_my_feature(solver_fixture):
|
|
108
|
+
# solver_fixture is automatically provided by pytest
|
|
109
|
+
model = fx.FlowSystem(...)
|
|
110
|
+
model.solve(solver_fixture)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### Testing Guidelines
|
|
114
|
+
- Write tests for all new functionality
|
|
115
|
+
- Ensure all tests pass before submitting a PR
|
|
116
|
+
- Aim for 100% test coverage for new code
|
|
117
|
+
- Use descriptive test names that explain what's being tested
|
|
118
|
+
- Add the `@pytest.mark.slow` decorator for tests that take >5 seconds
|
|
119
|
+
|
|
120
|
+
### Coding Guidelines
|
|
121
|
+
- Follow [PEP 8](https://pep8.org/) style guidelines
|
|
122
|
+
- Write clear, self-documenting code with helpful comments
|
|
123
|
+
- Include type hints for function signatures
|
|
124
|
+
- Create or update tests for new functionality
|
|
125
|
+
- Aim for 100% test coverage for new code
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Workflow
|
|
130
|
+
|
|
131
|
+
### Branches & Pull Requests
|
|
132
|
+
1. Create a feature branch from `main`:
|
|
133
|
+
```bash
|
|
134
|
+
git checkout -b feature/your-feature-name
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
2. Make your changes and commit them with clear messages
|
|
138
|
+
|
|
139
|
+
3. Push your branch and open a Pull Request
|
|
140
|
+
|
|
141
|
+
4. Ensure all CI checks pass
|
|
142
|
+
|
|
143
|
+
### Branch Naming
|
|
144
|
+
- Features: `feature/feature-name`
|
|
145
|
+
- Bug fixes: `fix/bug-description`
|
|
146
|
+
- Documentation: `docs/what-changed`
|
|
147
|
+
|
|
148
|
+
### Commit Messages
|
|
149
|
+
- Use clear, descriptive commit messages
|
|
150
|
+
- Start with a verb (Add, Fix, Update, Remove, etc.)
|
|
151
|
+
- Keep the first line under 72 characters
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Releases
|
|
156
|
+
|
|
157
|
+
We follow **Semantic Versioning** (MAJOR.MINOR.PATCH). Releases are created manually from the `main` branch by maintainers.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Questions?
|
|
162
|
+
|
|
163
|
+
If you have questions or need help, feel free to:
|
|
164
|
+
- Open a discussion on GitHub
|
|
165
|
+
- Ask in an issue
|
|
166
|
+
- Reach out to the maintainers
|
|
167
|
+
|
|
168
|
+
Thank you for contributing to FlixOpt!
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flixopt
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.1
|
|
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>
|
|
@@ -67,7 +67,7 @@ Requires-Dist: networkx==3.0.0; extra == "dev"
|
|
|
67
67
|
Requires-Dist: werkzeug==3.0.0; extra == "dev"
|
|
68
68
|
Provides-Extra: docs
|
|
69
69
|
Requires-Dist: mkdocs==1.6.1; extra == "docs"
|
|
70
|
-
Requires-Dist: mkdocs-material==9.6.
|
|
70
|
+
Requires-Dist: mkdocs-material==9.6.22; extra == "docs"
|
|
71
71
|
Requires-Dist: mkdocstrings-python==1.18.2; extra == "docs"
|
|
72
72
|
Requires-Dist: mkdocs-table-reader-plugin==3.1.0; extra == "docs"
|
|
73
73
|
Requires-Dist: mkdocs-gen-files==0.5.0; extra == "docs"
|
|
@@ -112,7 +112,7 @@ class Calculation:
|
|
|
112
112
|
'periodic': effect.submodel.periodic.total.solution.values,
|
|
113
113
|
'total': effect.submodel.total.solution.values,
|
|
114
114
|
}
|
|
115
|
-
for effect in self.flow_system.effects
|
|
115
|
+
for effect in sorted(self.flow_system.effects.values(), key=lambda e: e.label_full.upper())
|
|
116
116
|
},
|
|
117
117
|
'Invest-Decisions': {
|
|
118
118
|
'Invested': {
|
|
@@ -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
|
|
@@ -528,6 +529,15 @@ class Storage(Component):
|
|
|
528
529
|
f'{self.discharging.size.minimum_size=}, {self.discharging.size.maximum_size=}.'
|
|
529
530
|
)
|
|
530
531
|
|
|
532
|
+
def __repr__(self) -> str:
|
|
533
|
+
"""Return string representation."""
|
|
534
|
+
# Use build_repr_from_init directly to exclude charging and discharging
|
|
535
|
+
return fx_io.build_repr_from_init(
|
|
536
|
+
self,
|
|
537
|
+
excluded_params={'self', 'label', 'charging', 'discharging', 'kwargs'},
|
|
538
|
+
skip_default_size=True,
|
|
539
|
+
) + fx_io.format_flow_details(self)
|
|
540
|
+
|
|
531
541
|
|
|
532
542
|
@register_class_for_io
|
|
533
543
|
class Transmission(Component):
|
|
@@ -16,9 +16,10 @@ import linopy
|
|
|
16
16
|
import numpy as np
|
|
17
17
|
import xarray as xr
|
|
18
18
|
|
|
19
|
+
from . import io as fx_io
|
|
19
20
|
from .core import PeriodicDataUser, Scalar, TemporalData, TemporalDataUser
|
|
20
21
|
from .features import ShareAllocationModel
|
|
21
|
-
from .structure import Element, ElementModel, FlowSystemModel, Submodel, register_class_for_io
|
|
22
|
+
from .structure import Element, ElementContainer, ElementModel, FlowSystemModel, Submodel, register_class_for_io
|
|
22
23
|
|
|
23
24
|
if TYPE_CHECKING:
|
|
24
25
|
from collections.abc import Iterator
|
|
@@ -448,13 +449,13 @@ PeriodicEffects = dict[str, Scalar]
|
|
|
448
449
|
EffectExpr = dict[str, linopy.LinearExpression] # Used to create Shares
|
|
449
450
|
|
|
450
451
|
|
|
451
|
-
class EffectCollection:
|
|
452
|
+
class EffectCollection(ElementContainer[Effect]):
|
|
452
453
|
"""
|
|
453
454
|
Handling all Effects
|
|
454
455
|
"""
|
|
455
456
|
|
|
456
457
|
def __init__(self, *effects: Effect):
|
|
457
|
-
|
|
458
|
+
super().__init__(element_type_name='effects')
|
|
458
459
|
self._standard_effect: Effect | None = None
|
|
459
460
|
self._objective_effect: Effect | None = None
|
|
460
461
|
|
|
@@ -474,7 +475,7 @@ class EffectCollection:
|
|
|
474
475
|
self.standard_effect = effect
|
|
475
476
|
if effect.is_objective:
|
|
476
477
|
self.objective_effect = effect
|
|
477
|
-
self.
|
|
478
|
+
self.add(effect) # Use the inherited add() method from ElementContainer
|
|
478
479
|
logger.info(f'Registered new Effect: {effect.label}')
|
|
479
480
|
|
|
480
481
|
def create_effect_values_dict(
|
|
@@ -520,10 +521,13 @@ class EffectCollection:
|
|
|
520
521
|
# Check circular loops in effects:
|
|
521
522
|
temporal, periodic = self.calculate_effect_share_factors()
|
|
522
523
|
|
|
523
|
-
# Validate all referenced sources exist
|
|
524
|
-
|
|
524
|
+
# Validate all referenced effects (both sources and targets) exist
|
|
525
|
+
edges = list(temporal.keys()) + list(periodic.keys())
|
|
526
|
+
unknown_sources = {src for src, _ in edges if src not in self}
|
|
527
|
+
unknown_targets = {tgt for _, tgt in edges if tgt not in self}
|
|
528
|
+
unknown = unknown_sources | unknown_targets
|
|
525
529
|
if unknown:
|
|
526
|
-
raise KeyError(f'Unknown effects used in
|
|
530
|
+
raise KeyError(f'Unknown effects used in effect share mappings: {sorted(unknown)}')
|
|
527
531
|
|
|
528
532
|
temporal_cycles = detect_cycles(tuples_to_adjacency_list([key for key in temporal]))
|
|
529
533
|
periodic_cycles = detect_cycles(tuples_to_adjacency_list([key for key in periodic]))
|
|
@@ -552,31 +556,23 @@ class EffectCollection:
|
|
|
552
556
|
else:
|
|
553
557
|
raise KeyError(f'Effect {effect} not found!')
|
|
554
558
|
try:
|
|
555
|
-
return
|
|
559
|
+
return super().__getitem__(effect) # Leverage ContainerMixin suggestions
|
|
556
560
|
except KeyError as e:
|
|
557
|
-
|
|
561
|
+
# Extract the original message and append context for cleaner output
|
|
562
|
+
original_msg = str(e).strip('\'"')
|
|
563
|
+
raise KeyError(f'{original_msg} Add the effect to the FlowSystem first.') from None
|
|
558
564
|
|
|
559
|
-
def __iter__(self) -> Iterator[
|
|
560
|
-
return iter(self.
|
|
561
|
-
|
|
562
|
-
def __len__(self) -> int:
|
|
563
|
-
return len(self._effects)
|
|
565
|
+
def __iter__(self) -> Iterator[str]:
|
|
566
|
+
return iter(self.keys()) # Iterate over keys like a normal dict
|
|
564
567
|
|
|
565
568
|
def __contains__(self, item: str | Effect) -> bool:
|
|
566
569
|
"""Check if the effect exists. Checks for label or object"""
|
|
567
570
|
if isinstance(item, str):
|
|
568
|
-
return item
|
|
571
|
+
return super().__contains__(item) # Check if the label exists
|
|
569
572
|
elif isinstance(item, Effect):
|
|
570
|
-
|
|
571
|
-
return True
|
|
572
|
-
if item in self.effects.values(): # Check if the object exists
|
|
573
|
-
return True
|
|
573
|
+
return item.label_full in self and self[item.label_full] is item
|
|
574
574
|
return False
|
|
575
575
|
|
|
576
|
-
@property
|
|
577
|
-
def effects(self) -> dict[str, Effect]:
|
|
578
|
-
return self._effects
|
|
579
|
-
|
|
580
576
|
@property
|
|
581
577
|
def standard_effect(self) -> Effect:
|
|
582
578
|
if self._standard_effect is None:
|
|
@@ -611,7 +607,7 @@ class EffectCollection:
|
|
|
611
607
|
dict[tuple[str, str], xr.DataArray],
|
|
612
608
|
]:
|
|
613
609
|
shares_periodic = {}
|
|
614
|
-
for name, effect in self.
|
|
610
|
+
for name, effect in self.items():
|
|
615
611
|
if effect.share_from_periodic:
|
|
616
612
|
for source, data in effect.share_from_periodic.items():
|
|
617
613
|
if source not in shares_periodic:
|
|
@@ -620,7 +616,7 @@ class EffectCollection:
|
|
|
620
616
|
shares_periodic = calculate_all_conversion_paths(shares_periodic)
|
|
621
617
|
|
|
622
618
|
shares_temporal = {}
|
|
623
|
-
for name, effect in self.
|
|
619
|
+
for name, effect in self.items():
|
|
624
620
|
if effect.share_from_temporal:
|
|
625
621
|
for source, data in effect.share_from_temporal.items():
|
|
626
622
|
if source not in shares_temporal:
|
|
@@ -670,7 +666,7 @@ class EffectCollectionModel(Submodel):
|
|
|
670
666
|
|
|
671
667
|
def _do_modeling(self):
|
|
672
668
|
super()._do_modeling()
|
|
673
|
-
for effect in self.effects:
|
|
669
|
+
for effect in self.effects.values():
|
|
674
670
|
effect.create_model(self._model)
|
|
675
671
|
self.penalty = self.add_submodels(
|
|
676
672
|
ShareAllocationModel(self._model, dims=(), label_of_element='Penalty'),
|
|
@@ -684,7 +680,7 @@ class EffectCollectionModel(Submodel):
|
|
|
684
680
|
)
|
|
685
681
|
|
|
686
682
|
def _add_share_between_effects(self):
|
|
687
|
-
for target_effect in self.effects:
|
|
683
|
+
for target_effect in self.effects.values():
|
|
688
684
|
# 1. temporal: <- receiving temporal shares from other effects
|
|
689
685
|
for source_effect, time_series in target_effect.share_from_temporal.items():
|
|
690
686
|
target_effect.submodel.temporal.add_share(
|
|
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING
|
|
|
11
11
|
import numpy as np
|
|
12
12
|
import xarray as xr
|
|
13
13
|
|
|
14
|
+
from . import io as fx_io
|
|
14
15
|
from .config import CONFIG
|
|
15
16
|
from .core import PlausibilityError, Scalar, TemporalData, TemporalDataUser
|
|
16
17
|
from .features import InvestmentModel, OnOffModel
|
|
@@ -86,10 +87,12 @@ class Component(Element):
|
|
|
86
87
|
super().__init__(label, meta_data=meta_data)
|
|
87
88
|
self.inputs: list[Flow] = inputs or []
|
|
88
89
|
self.outputs: list[Flow] = outputs or []
|
|
89
|
-
self._check_unique_flow_labels()
|
|
90
90
|
self.on_off_parameters = on_off_parameters
|
|
91
91
|
self.prevent_simultaneous_flows: list[Flow] = prevent_simultaneous_flows or []
|
|
92
92
|
|
|
93
|
+
self._check_unique_flow_labels()
|
|
94
|
+
self._connect_flows()
|
|
95
|
+
|
|
93
96
|
self.flows: dict[str, Flow] = {flow.label: flow for flow in self.inputs + self.outputs}
|
|
94
97
|
|
|
95
98
|
def create_model(self, model: FlowSystemModel) -> ComponentModel:
|
|
@@ -115,6 +118,48 @@ class Component(Element):
|
|
|
115
118
|
def _plausibility_checks(self) -> None:
|
|
116
119
|
self._check_unique_flow_labels()
|
|
117
120
|
|
|
121
|
+
def _connect_flows(self):
|
|
122
|
+
# Inputs
|
|
123
|
+
for flow in self.inputs:
|
|
124
|
+
if flow.component not in ('UnknownComponent', self.label_full):
|
|
125
|
+
raise ValueError(
|
|
126
|
+
f'Flow "{flow.label}" already assigned to component "{flow.component}". '
|
|
127
|
+
f'Cannot attach to "{self.label_full}".'
|
|
128
|
+
)
|
|
129
|
+
flow.component = self.label_full
|
|
130
|
+
flow.is_input_in_component = True
|
|
131
|
+
# Outputs
|
|
132
|
+
for flow in self.outputs:
|
|
133
|
+
if flow.component not in ('UnknownComponent', self.label_full):
|
|
134
|
+
raise ValueError(
|
|
135
|
+
f'Flow "{flow.label}" already assigned to component "{flow.component}". '
|
|
136
|
+
f'Cannot attach to "{self.label_full}".'
|
|
137
|
+
)
|
|
138
|
+
flow.component = self.label_full
|
|
139
|
+
flow.is_input_in_component = False
|
|
140
|
+
|
|
141
|
+
# Validate prevent_simultaneous_flows: only allow local flows
|
|
142
|
+
if self.prevent_simultaneous_flows:
|
|
143
|
+
# Deduplicate while preserving order
|
|
144
|
+
seen = set()
|
|
145
|
+
self.prevent_simultaneous_flows = [
|
|
146
|
+
f for f in self.prevent_simultaneous_flows if id(f) not in seen and not seen.add(id(f))
|
|
147
|
+
]
|
|
148
|
+
local = set(self.inputs + self.outputs)
|
|
149
|
+
foreign = [f for f in self.prevent_simultaneous_flows if f not in local]
|
|
150
|
+
if foreign:
|
|
151
|
+
names = ', '.join(f.label_full for f in foreign)
|
|
152
|
+
raise ValueError(
|
|
153
|
+
f'prevent_simultaneous_flows for "{self.label_full}" must reference its own flows. '
|
|
154
|
+
f'Foreign flows detected: {names}'
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def __repr__(self) -> str:
|
|
158
|
+
"""Return string representation with flow information."""
|
|
159
|
+
return fx_io.build_repr_from_init(
|
|
160
|
+
self, excluded_params={'self', 'label', 'inputs', 'outputs', 'kwargs'}, skip_default_size=True
|
|
161
|
+
) + fx_io.format_flow_details(self)
|
|
162
|
+
|
|
118
163
|
|
|
119
164
|
@register_class_for_io
|
|
120
165
|
class Bus(Element):
|
|
@@ -216,6 +261,10 @@ class Bus(Element):
|
|
|
216
261
|
def with_excess(self) -> bool:
|
|
217
262
|
return False if self.excess_penalty_per_flow_hour is None else True
|
|
218
263
|
|
|
264
|
+
def __repr__(self) -> str:
|
|
265
|
+
"""Return string representation."""
|
|
266
|
+
return super().__repr__() + fx_io.format_flow_details(self)
|
|
267
|
+
|
|
219
268
|
|
|
220
269
|
@register_class_for_io
|
|
221
270
|
class Connection:
|
|
@@ -493,6 +542,10 @@ class Flow(Element):
|
|
|
493
542
|
# Wenn kein InvestParameters existiert --> True; Wenn Investparameter, den Wert davon nehmen
|
|
494
543
|
return False if (isinstance(self.size, InvestParameters) and self.size.fixed_size is None) else True
|
|
495
544
|
|
|
545
|
+
def _format_invest_params(self, params: InvestParameters) -> str:
|
|
546
|
+
"""Format InvestParameters for display."""
|
|
547
|
+
return f'size: {params.format_for_repr()}'
|
|
548
|
+
|
|
496
549
|
|
|
497
550
|
class FlowModel(ElementModel):
|
|
498
551
|
element: Flow # Type hint
|