flixopt 2.1.7__tar.gz → 2.2.0rc2__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-2.1.7 → flixopt-2.2.0rc2}/.github/workflows/python-app.yaml +57 -7
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/CHANGELOG.md +20 -1
- {flixopt-2.1.7/flixopt.egg-info → flixopt-2.2.0rc2}/PKG-INFO +1 -1
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/__init__.py +1 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/commons.py +10 -1
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/components.py +112 -17
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/elements.py +22 -2
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/features.py +70 -7
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/interface.py +267 -8
- {flixopt-2.1.7 → flixopt-2.2.0rc2/flixopt.egg-info}/PKG-INFO +1 -1
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/conftest.py +13 -1
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_flow.py +161 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_linear_converter.py +1 -1
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/.github/CONTRIBUTING.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/.github/ISSUE_TEMPLATE/general_issue.yml +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/.github/pull_request_template.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/.gitignore +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/.pre-commit-config.yaml +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/LICENSE +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/README.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/SUMMARY.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/contribute.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/examples/00-Minimal Example.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/examples/01-Basic Example.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/examples/02-Complex Example.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/examples/03-Calculation Modes.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/examples/index.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/faq/contribute.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/faq/index.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/getting-started.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/images/architecture_flixOpt-pre2.0.0.png +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/images/architecture_flixOpt.png +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/images/flixopt-icon.svg +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/index.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/javascripts/mathjax.js +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/user-guide/Mathematical Notation/Bus.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/user-guide/Mathematical Notation/Flow.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/user-guide/Mathematical Notation/LinearConverter.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/user-guide/Mathematical Notation/Piecewise.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/user-guide/Mathematical Notation/Storage.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/user-guide/Mathematical Notation/index.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/user-guide/Mathematical Notation/others.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/docs/user-guide/index.md +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/examples/00_Minmal/minimal_example.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/examples/01_Simple/simple_example.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/examples/02_Complex/complex_example.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/examples/02_Complex/complex_example_results.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/examples/03_Calculation_types/Zeitreihen2020.csv +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/examples/03_Calculation_types/example_calculation_types.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/aggregation.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/calculation.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/config.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/config.yaml +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/core.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/effects.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/flow_system.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/io.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/linear_converters.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/network_app.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/plotting.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/results.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/solvers.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/structure.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt/utils.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt.egg-info/SOURCES.txt +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt.egg-info/dependency_links.txt +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt.egg-info/requires.txt +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/flixopt.egg-info/top_level.txt +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/mkdocs.yml +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/pics/architecture_flixOpt-pre2.0.0.png +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/pics/architecture_flixOpt.png +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/pics/flixOpt_plotting.jpg +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/pics/flixopt-icon.svg +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/pics/pics.pptx +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/pyproject.toml +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/renovate.json +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/scripts/extract_release_notes.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/scripts/gen_ref_pages.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/setup.cfg +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/__init__.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/ressources/Zeitreihen2020.csv +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/run_all_tests.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_bus.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_component.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_dataconverter.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_effect.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_examples.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_functional.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_integration.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_io.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_network_app.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_on_hours_computation.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_plots.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_results_plots.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_storage.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/test_timeseries.py +0 -0
- {flixopt-2.1.7 → flixopt-2.2.0rc2}/tests/todos.txt +0 -0
|
@@ -2,12 +2,15 @@ name: Python Package CI/CD
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
branches: [main
|
|
6
|
-
tags:
|
|
7
|
-
- 'v*.*.*' # Trigger on semantic version tags
|
|
5
|
+
branches: [main] # Only main branch
|
|
6
|
+
tags: ['v*.*.*']
|
|
8
7
|
pull_request:
|
|
9
|
-
branches: [main, dev
|
|
8
|
+
branches: [main, dev]
|
|
10
9
|
types: [opened, synchronize, reopened]
|
|
10
|
+
paths-ignore:
|
|
11
|
+
- 'docs/**'
|
|
12
|
+
- '*.md'
|
|
13
|
+
- 'README*'
|
|
11
14
|
workflow_dispatch: # Allow manual triggering
|
|
12
15
|
|
|
13
16
|
# Set permissions for security
|
|
@@ -139,11 +142,58 @@ jobs:
|
|
|
139
142
|
with:
|
|
140
143
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
141
144
|
|
|
142
|
-
- name:
|
|
145
|
+
- name: Check if pre-release
|
|
146
|
+
id: prerelease
|
|
147
|
+
run: |
|
|
148
|
+
if [[ "${{ github.ref }}" =~ (alpha|beta|rc) ]]; then
|
|
149
|
+
echo "is_prerelease=true" >> $GITHUB_OUTPUT
|
|
150
|
+
echo "✅ Detected pre-release"
|
|
151
|
+
else
|
|
152
|
+
echo "is_prerelease=false" >> $GITHUB_OUTPUT
|
|
153
|
+
echo "✅ Detected stable release"
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
- name: Create release notes
|
|
143
157
|
run: |
|
|
144
158
|
VERSION=${GITHUB_REF#refs/tags/v}
|
|
145
|
-
echo "
|
|
146
|
-
|
|
159
|
+
echo "Creating release notes for version: $VERSION"
|
|
160
|
+
|
|
161
|
+
if [[ "${{ steps.prerelease.outputs.is_prerelease }}" == "true" ]]; then
|
|
162
|
+
echo "📝 Generating pre-release notes"
|
|
163
|
+
cat > current_release_notes.md << EOF
|
|
164
|
+
# Pre-release $VERSION
|
|
165
|
+
|
|
166
|
+
This is a pre-release version for testing and feedback.
|
|
167
|
+
|
|
168
|
+
## Installation
|
|
169
|
+
\`\`\`bash
|
|
170
|
+
pip install flixopt==$VERSION --pre
|
|
171
|
+
\`\`\`
|
|
172
|
+
|
|
173
|
+
## What's Changed
|
|
174
|
+
See the [unreleased section](https://github.com/flixOpt/flixopt/blob/main/CHANGELOG.md#unreleased) in the changelog for upcoming features and changes.
|
|
175
|
+
|
|
176
|
+
## Feedback
|
|
177
|
+
Please report any issues or feedback on [GitHub Issues](https://github.com/flixOpt/flixopt/issues).
|
|
178
|
+
EOF
|
|
179
|
+
else
|
|
180
|
+
echo "📝 Extracting release notes from changelog"
|
|
181
|
+
if python scripts/extract_release_notes.py "$VERSION" > current_release_notes.md 2>/dev/null; then
|
|
182
|
+
echo "✅ Successfully extracted release notes"
|
|
183
|
+
else
|
|
184
|
+
echo "⚠️ No release notes found, using fallback"
|
|
185
|
+
cat > current_release_notes.md << EOF
|
|
186
|
+
# Release $VERSION
|
|
187
|
+
|
|
188
|
+
See the [full changelog](https://github.com/flixOpt/flixopt/blob/main/CHANGELOG.md) for details.
|
|
189
|
+
EOF
|
|
190
|
+
fi
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
echo "Generated release notes:"
|
|
194
|
+
echo "========================"
|
|
195
|
+
cat current_release_notes.md
|
|
196
|
+
echo "========================"
|
|
147
197
|
|
|
148
198
|
- name: Create GitHub Release
|
|
149
199
|
uses: softprops/action-gh-release@v2
|
|
@@ -6,8 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
8
|
<!-- This text won't be rendered
|
|
9
|
+
Take Care: The CI will automatically append a "Whats CHanged" section to the changelog.
|
|
10
|
+
This contains all COmmits, PR's and Contributers.
|
|
11
|
+
Therefore, the Changelog should focus on the user-facing changes.
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
Template:
|
|
14
|
+
----
|
|
15
|
+
## [Unreleased] - ????-??-??
|
|
11
16
|
|
|
12
17
|
### Changed
|
|
13
18
|
|
|
@@ -21,8 +26,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
21
26
|
|
|
22
27
|
### Development
|
|
23
28
|
|
|
29
|
+
----
|
|
30
|
+
Upcoming Release:
|
|
31
|
+
|
|
32
|
+
## [2.2.0] - 2025-09-13
|
|
33
|
+
THis release introduces a new interface `PiecewiseEffectsPerFlowHour` to model non-linear relations between flow rates and effects.
|
|
34
|
+
This greatly enhances Model flexibility.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- LinearConverter with `PiecewiseConversion` allowed flows to reach 0 values, even though they didnt have `OnOffParameters` nor `PiecewiseConversion` actually containing 0 in its `Piece`s.
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
- Added new Interface `PiecewiseEffectsPerFlowHour` to model non-linear relations between flow rates and effects.
|
|
41
|
+
|
|
24
42
|
Until here -->
|
|
25
43
|
|
|
44
|
+
|
|
26
45
|
## [2.1.7] - 2025-09-13
|
|
27
46
|
|
|
28
47
|
This update is a maintenance release to improve Code Quality, CI and update the dependencies.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flixopt
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0rc2
|
|
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>
|
|
@@ -18,7 +18,15 @@ from .core import TimeSeriesData
|
|
|
18
18
|
from .effects import Effect
|
|
19
19
|
from .elements import Bus, Flow
|
|
20
20
|
from .flow_system import FlowSystem
|
|
21
|
-
from .interface import
|
|
21
|
+
from .interface import (
|
|
22
|
+
InvestParameters,
|
|
23
|
+
OnOffParameters,
|
|
24
|
+
Piece,
|
|
25
|
+
Piecewise,
|
|
26
|
+
PiecewiseConversion,
|
|
27
|
+
PiecewiseEffects,
|
|
28
|
+
PiecewiseEffectsPerFlowHour,
|
|
29
|
+
)
|
|
22
30
|
|
|
23
31
|
__all__ = [
|
|
24
32
|
'TimeSeriesData',
|
|
@@ -48,4 +56,5 @@ __all__ = [
|
|
|
48
56
|
'results',
|
|
49
57
|
'linear_converters',
|
|
50
58
|
'solvers',
|
|
59
|
+
'PiecewiseEffectsPerFlowHour',
|
|
51
60
|
]
|
|
@@ -24,9 +24,119 @@ logger = logging.getLogger('flixopt')
|
|
|
24
24
|
|
|
25
25
|
@register_class_for_io
|
|
26
26
|
class LinearConverter(Component):
|
|
27
|
-
"""
|
|
28
|
-
|
|
27
|
+
"""Convert input flows into output flows using linear or piecewise linear conversion factors.
|
|
28
|
+
|
|
29
|
+
This component models conversion equipment where input flows are transformed
|
|
30
|
+
into output flows with fixed or variable conversion ratios, such as:
|
|
31
|
+
|
|
32
|
+
- Heat pumps and chillers with variable efficiency
|
|
33
|
+
- Power plants with fuel-to-electricity conversion
|
|
34
|
+
- Chemical processes with multiple inputs/outputs
|
|
35
|
+
- Pumps and compressors
|
|
36
|
+
- Combined heat and power (CHP) plants
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
label: Unique identifier for the component in the FlowSystem.
|
|
40
|
+
inputs: List of input Flow objects that feed into the converter.
|
|
41
|
+
outputs: List of output Flow objects produced by the converter.
|
|
42
|
+
on_off_parameters: Controls binary on/off behavior of the converter.
|
|
43
|
+
When specified, the component can be completely turned on or off, affecting
|
|
44
|
+
all connected flows. This creates binary variables in the optimization.
|
|
45
|
+
For better performance, consider using OnOffParameters on individual flows instead.
|
|
46
|
+
conversion_factors: Linear conversion ratios between flows as time series data.
|
|
47
|
+
List of dictionaries mapping flow labels to their conversion factors.
|
|
48
|
+
Mutually exclusive with piecewise_conversion.
|
|
49
|
+
piecewise_conversion: Piecewise linear conversion relationships between flows.
|
|
50
|
+
Enables modeling of variable efficiency or discrete operating modes.
|
|
51
|
+
Mutually exclusive with conversion_factors.
|
|
52
|
+
meta_data: Additional information stored with the component.
|
|
53
|
+
Saved in results but not used internally. Use only Python native types.
|
|
54
|
+
|
|
55
|
+
Warning:
|
|
56
|
+
When using `piecewise_conversion` without `on_off_parameters`, flow rates cannot
|
|
57
|
+
reach zero unless explicitly defined with zero-valued pieces (e.g., `fx.Piece(0, 0)`).
|
|
58
|
+
This prevents unintended zero flows and maintains mathematical consistency.
|
|
59
|
+
|
|
60
|
+
To allow zero flow rates:
|
|
61
|
+
|
|
62
|
+
- Add `on_off_parameters` to enable complete shutdown, or
|
|
63
|
+
- Include explicit zero pieces in your `piecewise_conversion` definition
|
|
64
|
+
|
|
65
|
+
This behavior was clarified in v2.1.7 to prevent optimization edge cases.
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
Simple heat pump with fixed COP:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
heat_pump = fx.LinearConverter(
|
|
72
|
+
label='heat_pump',
|
|
73
|
+
inputs=[electricity_flow],
|
|
74
|
+
outputs=[heat_flow],
|
|
75
|
+
conversion_factors=[
|
|
76
|
+
{
|
|
77
|
+
'electricity_flow': 1.0, # 1 kW electricity input
|
|
78
|
+
'heat_flow': 3.5, # 3.5 kW heat output (COP=3.5)
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Variable efficiency heat pump:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
heat_pump = fx.LinearConverter(
|
|
88
|
+
label='variable_heat_pump',
|
|
89
|
+
inputs=[electricity_flow],
|
|
90
|
+
outputs=[heat_flow],
|
|
91
|
+
piecewise_conversion=fx.PiecewiseConversion(
|
|
92
|
+
{
|
|
93
|
+
'electricity_flow': fx.Piecewise(
|
|
94
|
+
[
|
|
95
|
+
fx.Piece(0, 10), # Allow zero to 10 kW input
|
|
96
|
+
fx.Piece(10, 25), # Higher load operation
|
|
97
|
+
]
|
|
98
|
+
),
|
|
99
|
+
'heat_flow': fx.Piecewise(
|
|
100
|
+
[
|
|
101
|
+
fx.Piece(0, 35), # COP=3.5 at low loads
|
|
102
|
+
fx.Piece(35, 75), # COP=3.0 at high loads
|
|
103
|
+
]
|
|
104
|
+
),
|
|
105
|
+
}
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Combined heat and power plant:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
chp_plant = fx.LinearConverter(
|
|
114
|
+
label='chp_plant',
|
|
115
|
+
inputs=[natural_gas_flow],
|
|
116
|
+
outputs=[electricity_flow, heat_flow],
|
|
117
|
+
conversion_factors=[
|
|
118
|
+
{
|
|
119
|
+
'natural_gas_flow': 1.0, # 1 MW fuel input
|
|
120
|
+
'electricity_flow': 0.4, # 40% electrical efficiency
|
|
121
|
+
'heat_flow': 0.45, # 45% thermal efficiency
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
on_off_parameters=fx.OnOffParameters(
|
|
125
|
+
min_on_hours=4, # Minimum 4-hour operation
|
|
126
|
+
min_off_hours=2, # Minimum 2-hour downtime
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Note:
|
|
132
|
+
Either `conversion_factors` or `piecewise_conversion` must be specified, but not both.
|
|
133
|
+
The component automatically handles the mathematical relationships between all
|
|
134
|
+
connected flows according to the specified conversion ratios.
|
|
29
135
|
|
|
136
|
+
See Also:
|
|
137
|
+
PiecewiseConversion: For variable efficiency modeling
|
|
138
|
+
OnOffParameters: For binary on/off control
|
|
139
|
+
Flow: Input and output flow definitions
|
|
30
140
|
"""
|
|
31
141
|
|
|
32
142
|
def __init__(
|
|
@@ -39,21 +149,6 @@ class LinearConverter(Component):
|
|
|
39
149
|
piecewise_conversion: Optional[PiecewiseConversion] = None,
|
|
40
150
|
meta_data: Optional[Dict] = None,
|
|
41
151
|
):
|
|
42
|
-
"""
|
|
43
|
-
Args:
|
|
44
|
-
label: The label of the Element. Used to identify it in the FlowSystem
|
|
45
|
-
inputs: The input Flows
|
|
46
|
-
outputs: The output Flows
|
|
47
|
-
on_off_parameters: Information about on and off state of LinearConverter.
|
|
48
|
-
Component is On/Off, if all connected Flows are On/Off. This induces an On-Variable (binary) in all Flows!
|
|
49
|
-
If possible, use OnOffParameters in a single Flow instead to keep the number of binary variables low.
|
|
50
|
-
See class OnOffParameters.
|
|
51
|
-
conversion_factors: linear relation between flows.
|
|
52
|
-
Either 'conversion_factors' or 'piecewise_conversion' can be used!
|
|
53
|
-
piecewise_conversion: Define a piecewise linear relation between flow rates of different flows.
|
|
54
|
-
Either 'conversion_factors' or 'piecewise_conversion' can be used!
|
|
55
|
-
meta_data: used to store more information about the Element. Is not used internally, but saved in the results. Only use python native types.
|
|
56
|
-
"""
|
|
57
152
|
super().__init__(label, inputs, outputs, on_off_parameters, meta_data=meta_data)
|
|
58
153
|
self.conversion_factors = conversion_factors or []
|
|
59
154
|
self.piecewise_conversion = piecewise_conversion
|
|
@@ -12,8 +12,8 @@ import numpy as np
|
|
|
12
12
|
from .config import CONFIG
|
|
13
13
|
from .core import NumericData, NumericDataTS, PlausibilityError, Scalar, TimeSeriesCollection
|
|
14
14
|
from .effects import EffectValuesUser
|
|
15
|
-
from .features import InvestmentModel, OnOffModel, PreventSimultaneousUsageModel
|
|
16
|
-
from .interface import InvestParameters, OnOffParameters
|
|
15
|
+
from .features import InvestmentModel, OnOffModel, PiecewiseEffectsPerFlowHourModel, PreventSimultaneousUsageModel
|
|
16
|
+
from .interface import InvestParameters, OnOffParameters, PiecewiseEffectsPerFlowHour
|
|
17
17
|
from .structure import Element, ElementModel, SystemModel, register_class_for_io
|
|
18
18
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
@@ -159,6 +159,7 @@ class Flow(Element):
|
|
|
159
159
|
relative_minimum: NumericDataTS = 0,
|
|
160
160
|
relative_maximum: NumericDataTS = 1,
|
|
161
161
|
effects_per_flow_hour: Optional[EffectValuesUser] = None,
|
|
162
|
+
piecewise_effects_per_flow_hour: Optional[PiecewiseEffectsPerFlowHour] = None,
|
|
162
163
|
on_off_parameters: Optional[OnOffParameters] = None,
|
|
163
164
|
flow_hours_total_max: Optional[Scalar] = None,
|
|
164
165
|
flow_hours_total_min: Optional[Scalar] = None,
|
|
@@ -180,6 +181,7 @@ class Flow(Element):
|
|
|
180
181
|
def: :math:`load\_factor:= sumFlowHours/ (nominal\_val \cdot \Delta t_{tot})`
|
|
181
182
|
load_factor_max: maximal load factor (see minimal load factor)
|
|
182
183
|
effects_per_flow_hour: operational costs, costs per flow-"work"
|
|
184
|
+
piecewise_effects_per_flow_hour: piecewise relation between flow hours and effects
|
|
183
185
|
on_off_parameters: If present, flow can be "off", i.e. be zero (only relevant if relative_minimum > 0)
|
|
184
186
|
Therefore a binary var "on" is used. Further, several other restrictions and effects can be modeled
|
|
185
187
|
through this On/Off State (See OnOffParameters)
|
|
@@ -207,6 +209,7 @@ class Flow(Element):
|
|
|
207
209
|
self.load_factor_max = load_factor_max
|
|
208
210
|
# self.positive_gradient = TimeSeries('positive_gradient', positive_gradient, self)
|
|
209
211
|
self.effects_per_flow_hour = effects_per_flow_hour if effects_per_flow_hour is not None else {}
|
|
212
|
+
self.piecewise_effects_per_flow_hour = piecewise_effects_per_flow_hour
|
|
210
213
|
self.flow_hours_total_max = flow_hours_total_max
|
|
211
214
|
self.flow_hours_total_min = flow_hours_total_min
|
|
212
215
|
self.on_off_parameters = on_off_parameters
|
|
@@ -248,6 +251,8 @@ class Flow(Element):
|
|
|
248
251
|
self.effects_per_flow_hour = flow_system.create_effect_time_series(
|
|
249
252
|
self.label_full, self.effects_per_flow_hour, 'per_flow_hour'
|
|
250
253
|
)
|
|
254
|
+
if self.piecewise_effects_per_flow_hour is not None:
|
|
255
|
+
self.piecewise_effects_per_flow_hour.transform_data(flow_system, self.label_full)
|
|
251
256
|
if self.on_off_parameters is not None:
|
|
252
257
|
self.on_off_parameters.transform_data(flow_system, self.label_full)
|
|
253
258
|
if isinstance(self.size, InvestParameters):
|
|
@@ -398,6 +403,21 @@ class FlowModel(ElementModel):
|
|
|
398
403
|
target='operation',
|
|
399
404
|
)
|
|
400
405
|
|
|
406
|
+
if self.element.piecewise_effects_per_flow_hour is not None:
|
|
407
|
+
self.piecewise_effects = self.add(
|
|
408
|
+
PiecewiseEffectsPerFlowHourModel(
|
|
409
|
+
model=self._model,
|
|
410
|
+
label_of_element=self.label_of_element,
|
|
411
|
+
piecewise_origin=(
|
|
412
|
+
self.flow_rate.name,
|
|
413
|
+
self.element.piecewise_effects_per_flow_hour.piecewise_flow_rate,
|
|
414
|
+
),
|
|
415
|
+
piecewise_shares=self.element.piecewise_effects_per_flow_hour.piecewise_shares,
|
|
416
|
+
zero_point=self.on_off.on if self.on_off is not None else False,
|
|
417
|
+
),
|
|
418
|
+
)
|
|
419
|
+
self.piecewise_effects.do_modeling()
|
|
420
|
+
|
|
401
421
|
def _create_bounds_for_load_factor(self):
|
|
402
422
|
# TODO: Add Variable load_factor for better evaluation?
|
|
403
423
|
|
|
@@ -12,7 +12,7 @@ import numpy as np
|
|
|
12
12
|
from . import utils
|
|
13
13
|
from .config import CONFIG
|
|
14
14
|
from .core import NumericData, Scalar, TimeSeries
|
|
15
|
-
from .interface import InvestParameters, OnOffParameters, Piecewise
|
|
15
|
+
from .interface import InvestParameters, OnOffParameters, Piece, Piecewise
|
|
16
16
|
from .structure import Model, SystemModel
|
|
17
17
|
|
|
18
18
|
logger = logging.getLogger('flixopt')
|
|
@@ -841,7 +841,7 @@ class PiecewiseModel(Model):
|
|
|
841
841
|
label: str = '',
|
|
842
842
|
):
|
|
843
843
|
"""
|
|
844
|
-
Modeling a Piecewise relation between
|
|
844
|
+
Modeling a Piecewise relation between multiple variables.
|
|
845
845
|
The relation is defined by a list of Pieces, which are assigned to the variables.
|
|
846
846
|
Each Piece is a tuple of (start, end).
|
|
847
847
|
|
|
@@ -850,7 +850,9 @@ class PiecewiseModel(Model):
|
|
|
850
850
|
label_of_element: The label of the parent (Element). Used to construct the full label of the model.
|
|
851
851
|
label: The label of the model. Used to construct the full label of the model.
|
|
852
852
|
piecewise_variables: The variables to which the Pieces are assigned.
|
|
853
|
-
zero_point: A variable that can be used to define a zero point for the Piecewise relation.
|
|
853
|
+
zero_point: A variable that can be used to define a zero point for the Piecewise relation.
|
|
854
|
+
If None or False, no zero point is defined. THis leads to 0 not being possible,
|
|
855
|
+
unless its its contained in a Piece.
|
|
854
856
|
as_time_series: Whether the Piecewise relation is defined for a TimeSeries or a single variable.
|
|
855
857
|
"""
|
|
856
858
|
super().__init__(model, label_of_element, label)
|
|
@@ -896,7 +898,7 @@ class PiecewiseModel(Model):
|
|
|
896
898
|
# b) eq: -On(t) + Segment1.onSeg(t) + Segment2.onSeg(t) + ... = 0 zusätzlich kann alles auch Null sein
|
|
897
899
|
if isinstance(self._zero_point, linopy.Variable):
|
|
898
900
|
self.zero_point = self._zero_point
|
|
899
|
-
rhs = self.zero_point
|
|
901
|
+
sign, rhs = '<=', self.zero_point
|
|
900
902
|
elif self._zero_point is True:
|
|
901
903
|
self.zero_point = self.add(
|
|
902
904
|
self._model.add_variables(
|
|
@@ -904,13 +906,15 @@ class PiecewiseModel(Model):
|
|
|
904
906
|
),
|
|
905
907
|
'zero_point',
|
|
906
908
|
)
|
|
907
|
-
rhs = self.zero_point
|
|
909
|
+
sign, rhs = '<=', self.zero_point
|
|
908
910
|
else:
|
|
909
|
-
rhs = 1
|
|
911
|
+
sign, rhs = '=', 1
|
|
910
912
|
|
|
911
913
|
self.add(
|
|
912
914
|
self._model.add_constraints(
|
|
913
|
-
sum([piece.inside_piece for piece in self.pieces])
|
|
915
|
+
sum([piece.inside_piece for piece in self.pieces]),
|
|
916
|
+
sign,
|
|
917
|
+
rhs,
|
|
914
918
|
name=f'{self.label_full}|{variable.name}|single_segment',
|
|
915
919
|
),
|
|
916
920
|
f'{var_name}|single_segment',
|
|
@@ -1079,6 +1083,65 @@ class PiecewiseEffectsModel(Model):
|
|
|
1079
1083
|
)
|
|
1080
1084
|
|
|
1081
1085
|
|
|
1086
|
+
class PiecewiseEffectsPerFlowHourModel(Model):
|
|
1087
|
+
def __init__(
|
|
1088
|
+
self,
|
|
1089
|
+
model: SystemModel,
|
|
1090
|
+
label_of_element: str,
|
|
1091
|
+
piecewise_origin: Tuple[str, Piecewise],
|
|
1092
|
+
piecewise_shares: Dict[str, Piecewise],
|
|
1093
|
+
zero_point: Optional[Union[bool, linopy.Variable]],
|
|
1094
|
+
label: str = 'PiecewiseEffectsPerFlowHour',
|
|
1095
|
+
):
|
|
1096
|
+
super().__init__(model, label_of_element, label)
|
|
1097
|
+
assert len(piecewise_origin[1]) == len(list(piecewise_shares.values())[0]), (
|
|
1098
|
+
'Piece length of variable_segments and share_segments must be equal'
|
|
1099
|
+
)
|
|
1100
|
+
self._zero_point = zero_point
|
|
1101
|
+
self._piecewise_origin = piecewise_origin
|
|
1102
|
+
self._piecewise_shares = piecewise_shares
|
|
1103
|
+
|
|
1104
|
+
self.shares: Dict[str, linopy.Variable] = {}
|
|
1105
|
+
|
|
1106
|
+
self.piecewise_model: Optional[PiecewiseModel] = None
|
|
1107
|
+
|
|
1108
|
+
def do_modeling(self):
|
|
1109
|
+
self.shares = {
|
|
1110
|
+
effect: self.add(
|
|
1111
|
+
self._model.add_variables(coords=self._model.coords, name=f'{self.label_full}|{effect}'), f'{effect}'
|
|
1112
|
+
)
|
|
1113
|
+
for effect in self._piecewise_shares
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
piecewise_variables = {
|
|
1117
|
+
self._piecewise_origin[0]: self._piecewise_origin[1],
|
|
1118
|
+
**{
|
|
1119
|
+
self.shares[effect_label].name: self._piecewise_shares[effect_label]
|
|
1120
|
+
for effect_label in self._piecewise_shares
|
|
1121
|
+
},
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
self.piecewise_model = self.add(
|
|
1125
|
+
PiecewiseModel(
|
|
1126
|
+
model=self._model,
|
|
1127
|
+
label_of_element=self.label_of_element,
|
|
1128
|
+
piecewise_variables=piecewise_variables,
|
|
1129
|
+
zero_point=self._zero_point,
|
|
1130
|
+
as_time_series=True,
|
|
1131
|
+
label='PiecewiseEffectsPerFlowHour',
|
|
1132
|
+
)
|
|
1133
|
+
)
|
|
1134
|
+
|
|
1135
|
+
self.piecewise_model.do_modeling()
|
|
1136
|
+
|
|
1137
|
+
# Shares
|
|
1138
|
+
self._model.effects.add_share_to_effects(
|
|
1139
|
+
name=self.label_of_element,
|
|
1140
|
+
expressions={effect: variable * self._model.hours_per_step for effect, variable in self.shares.items()},
|
|
1141
|
+
target='operation',
|
|
1142
|
+
)
|
|
1143
|
+
|
|
1144
|
+
|
|
1082
1145
|
class PreventSimultaneousUsageModel(Model):
|
|
1083
1146
|
"""
|
|
1084
1147
|
Prevents multiple Multiple Binary variables from being 1 at the same time
|