From 52ff5270db1e6f6adbad66e5eb616dd41733d210 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 20 Nov 2025 10:24:45 +0100
Subject: [PATCH 23/79] FIx broken link
---
scripts/gen_ref_pages.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/gen_ref_pages.py b/scripts/gen_ref_pages.py
index 3c8eb600a..1eb65b800 100644
--- a/scripts/gen_ref_pages.py
+++ b/scripts/gen_ref_pages.py
@@ -46,7 +46,7 @@
index_file.write('# API Reference\n\n')
index_file.write(
'This section contains the documentation for all modules and classes in flixopt.\n'
- 'For more information on how to use the classes and functions, see the [User Guide](../user-guide/index.md) section.\n'
+ 'For more information on how to use the classes and functions, see the [User Guide](../user-guide/core-concepts/) section.\n'
)
# Print generated files for validation
From 3ad3d50996a9f849968c3c9b962482b11e9eebf8 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 20 Nov 2025 19:15:00 +0100
Subject: [PATCH 24/79] Add edit uri
---
mkdocs.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/mkdocs.yml b/mkdocs.yml
index bfc7c154a..7726fc7e5 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -7,6 +7,7 @@ site_description: Energy and Material Flow Optimization Framework
site_url: https://flixopt.github.io/flixopt/
repo_url: https://github.com/flixOpt/flixopt
repo_name: flixOpt/flixopt
+edit_uri: edit/main/docs/
nav:
- Home: index.md
From e30ef9cacde544f2d898153d32275163eb2463f7 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 20 Nov 2025 19:27:19 +0100
Subject: [PATCH 25/79] Hide bottom part
---
docs/stylesheets/extra.css | 35 +++++++++++++++++++++++------------
1 file changed, 23 insertions(+), 12 deletions(-)
diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css
index f73da80d0..15e1d5781 100644
--- a/docs/stylesheets/extra.css
+++ b/docs/stylesheets/extra.css
@@ -747,27 +747,38 @@ button:focus-visible {
Footer Alignment Fix
========================================================================= */
-/* Align footer with content width */
-.md-footer-meta__inner,
+/* Hide the social media footer section */
+.md-footer-meta {
+ display: none;
+}
+
+/* Ensure footer navigation spans properly */
+.md-footer {
+ width: 100%;
+}
+
+/* Footer navigation content matches page width */
+.md-footer .md-grid,
.md-footer__inner {
- max-width: 1300px;
- margin: 0 auto;
- padding-left: 1.2rem;
- padding-right: 1.2rem;
+ max-width: 1300px !important;
+ margin: 0 auto !important;
+ padding-left: 1.2rem !important;
+ padding-right: 1.2rem !important;
}
+/* Responsive padding to match content area */
@media screen and (min-width: 76.25em) {
- .md-footer-meta__inner,
+ .md-footer .md-grid,
.md-footer__inner {
- padding-left: 1rem;
- padding-right: 1rem;
+ padding-left: 1rem !important;
+ padding-right: 1rem !important;
}
}
@media screen and (min-width: 100em) {
- .md-footer-meta__inner,
+ .md-footer .md-grid,
.md-footer__inner {
- padding-left: 2rem;
- padding-right: 2rem;
+ padding-left: 2rem !important;
+ padding-right: 2rem !important;
}
}
From f3c09a751a27a38ddae41f1e0f81fa2ba58ac88a Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 20 Nov 2025 19:28:36 +0100
Subject: [PATCH 26/79] Hide bottom part
---
docs/stylesheets/extra.css | 18 +++---------------
1 file changed, 3 insertions(+), 15 deletions(-)
diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css
index 15e1d5781..df5a9c477 100644
--- a/docs/stylesheets/extra.css
+++ b/docs/stylesheets/extra.css
@@ -752,33 +752,21 @@ button:focus-visible {
display: none;
}
-/* Ensure footer navigation spans properly */
-.md-footer {
- width: 100%;
-}
-
/* Footer navigation content matches page width */
-.md-footer .md-grid,
-.md-footer__inner {
+.md-footer .md-grid {
max-width: 1300px !important;
- margin: 0 auto !important;
padding-left: 1.2rem !important;
padding-right: 1.2rem !important;
}
-/* Responsive padding to match content area */
@media screen and (min-width: 76.25em) {
- .md-footer .md-grid,
- .md-footer__inner {
+ .md-footer .md-grid {
padding-left: 1rem !important;
- padding-right: 1rem !important;
}
}
@media screen and (min-width: 100em) {
- .md-footer .md-grid,
- .md-footer__inner {
+ .md-footer .md-grid {
padding-left: 2rem !important;
- padding-right: 2rem !important;
}
}
From a4e22ec0a03de2b360560d78ac7249eefef0a3ff Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 20 Nov 2025 19:53:47 +0100
Subject: [PATCH 27/79] Restructure docs
---
.gitignore | 4 +
docs/changelog.md | 34 ++++
docs/home/citing.md | 46 +++++
docs/home/features.md | 75 +++++++
docs/home/installation.md | 102 ++++++++++
docs/home/license.md | 60 ++++++
docs/home/models.md | 139 +++++++++++++
docs/home/quick-start.md | 162 +++++++++++++++
docs/home/users.md | 73 +++++++
docs/user-guide/faq.md | 77 +++++++
docs/user-guide/index.md | 80 ++++++++
docs/user-guide/optimization/index.md | 229 +++++++++++++++++++++
docs/user-guide/support.md | 156 ++++++++++++++
docs/user-guide/troubleshooting.md | 282 ++++++++++++++++++++++++++
mkdocs.yml | 83 +++++---
15 files changed, 1577 insertions(+), 25 deletions(-)
create mode 100644 docs/changelog.md
create mode 100644 docs/home/citing.md
create mode 100644 docs/home/features.md
create mode 100644 docs/home/installation.md
create mode 100644 docs/home/license.md
create mode 100644 docs/home/models.md
create mode 100644 docs/home/quick-start.md
create mode 100644 docs/home/users.md
create mode 100644 docs/user-guide/faq.md
create mode 100644 docs/user-guide/index.md
create mode 100644 docs/user-guide/optimization/index.md
create mode 100644 docs/user-guide/support.md
create mode 100644 docs/user-guide/troubleshooting.md
diff --git a/.gitignore b/.gitignore
index cc2179b07..169c1a587 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,7 @@ venv/
.DS_Store
lib/
temp-plot.html
+.cache
+site/
+*.egg-info
+uv.lock
diff --git a/docs/changelog.md b/docs/changelog.md
new file mode 100644
index 000000000..c49cb5296
--- /dev/null
+++ b/docs/changelog.md
@@ -0,0 +1,34 @@
+# Release Notes
+
+## Version 3.0.0
+
+Major release with significant improvements and breaking changes.
+
+### Breaking Changes
+
+See the [Migration Guide](user-guide/migration-guide-v3.md) for detailed upgrade instructions.
+
+### New Features
+
+- Improved API design
+- Enhanced mathematical formulation
+- Better solver integration
+- Expanded documentation
+
+### Improvements
+
+- Performance optimizations
+- Better error messages
+- Enhanced logging capabilities
+
+### Bug Fixes
+
+- Various bug fixes and stability improvements
+
+## Previous Versions
+
+*Release history will be added here*
+
+---
+
+For the complete changelog, see the [GitHub Releases](https://github.com/flixOpt/flixopt/releases).
diff --git a/docs/home/citing.md b/docs/home/citing.md
new file mode 100644
index 000000000..fad415818
--- /dev/null
+++ b/docs/home/citing.md
@@ -0,0 +1,46 @@
+# Citing flixOpt
+
+If you use flixOpt in your research or project, please cite it appropriately.
+
+## Citation
+
+When referencing flixOpt in academic publications, please use:
+
+```bibtex
+@software{flixopt,
+ author = {flixOpt Contributors},
+ title = {flixOpt: Energy and Material Flow Optimization Framework},
+ url = {https://github.com/flixOpt/flixopt},
+ version = {3.0.0},
+ year = {2024}
+}
+```
+
+## Publications
+
+If you've published research using flixOpt, please let us know! We'd love to feature it here.
+
+### List of Publications
+
+*Coming soon: A list of academic publications that have used flixOpt*
+
+## Acknowledgment
+
+If you'd like to acknowledge flixOpt in your work without a formal citation, you can use:
+
+> This work uses flixOpt (https://github.com/flixOpt/flixopt), an open-source Python framework for energy and material flow optimization.
+
+## Contributing Back
+
+If flixOpt helped your research, consider contributing back:
+
+- **Share your model** - Submit it as an example
+- **Report issues** - Help improve the software
+- **Contribute code** - Add features or fix bugs
+- **Improve documentation** - Share your expertise
+
+See our [Contributing Guide](../contribute.md) for more information.
+
+## License
+
+flixOpt is released under the MIT License. See [License](license.md) for details.
diff --git a/docs/home/features.md b/docs/home/features.md
new file mode 100644
index 000000000..13a6f4e45
--- /dev/null
+++ b/docs/home/features.md
@@ -0,0 +1,75 @@
+# Features
+
+flixOpt is a comprehensive framework for modeling and optimizing energy and material flow systems. It provides a powerful set of features for both operational and investment optimization.
+
+## Core Capabilities
+
+### :material-cog: Optimization Types
+
+- **Operational Optimization** - Dispatch optimization with given capacities
+- **Investment Optimization** - Capacity expansion planning with binary or continuous sizing
+- **Multi-Period Planning** - Sequential investment decisions over multiple periods
+- **Two-Stage Optimization** - Separate investment and operational decisions
+
+### :material-chart-line: Modeling Features
+
+#### Components
+
+- **Flow** - Energy or material transfer with variable flow rates
+- **Bus** - Nodal balance point for connecting multiple flows
+- **Storage** - Energy storage with charge/discharge dynamics and efficiency losses
+- **LinearConverter** - Linear conversion relationships between flows
+
+#### Advanced Features
+
+- **Investment Parameters** - Binary or continuous capacity decisions with sizing constraints
+- **On/Off Parameters** - Discrete operational states with minimum run/idle times
+- **Piecewise Linearization** - Non-linear relationships approximated with piecewise segments
+- **Effects** - System-wide tracking of costs, emissions, and other impacts
+
+### :material-math-integral: Mathematical Formulation
+
+- **Mixed-Integer Linear Programming (MILP)** - Exact optimization with commercial and open-source solvers
+- **Time Series Support** - Native handling of time-indexed variables and parameters
+- **Flexible Constraints** - User-defined custom constraints and objectives
+- **Penalty Variables** - Soft constraint violations for infeasibility analysis
+
+### :material-puzzle: Solvers
+
+flixOpt supports multiple optimization solvers:
+
+- **HiGHS** - Open-source, high-performance LP/MIP solver (default)
+- **Gurobi** - Commercial solver with academic licenses available
+- **CPLEX** - IBM's commercial optimization solver
+- **GLPK** - GNU Linear Programming Kit
+
+### :material-file-export: Data Handling
+
+- **Flexible Input** - Python dictionaries, pandas DataFrames, or direct parameters
+- **Time Series** - Native support for time-indexed data with automatic alignment
+- **Results Export** - Comprehensive results in structured formats
+- **Visualization** - Built-in plotting capabilities for results analysis
+
+## Use Cases
+
+### Energy Systems
+
+- Power system dispatch and expansion planning
+- Combined heat and power (CHP) optimization
+- Renewable energy integration
+- Battery storage optimization
+- District heating networks
+
+### Industrial Applications
+
+- Process optimization with material flows
+- Multi-commodity networks
+- Supply chain optimization
+- Resource allocation
+
+## Performance
+
+- **Scalable** - Handles systems from simple examples to large-scale energy systems
+- **Efficient** - Optimized model building and constraint generation
+- **Fast** - Modern solvers with warm-start capabilities
+- **Flexible** - Modular design allows for easy extension and customization
diff --git a/docs/home/installation.md b/docs/home/installation.md
new file mode 100644
index 000000000..83d560443
--- /dev/null
+++ b/docs/home/installation.md
@@ -0,0 +1,102 @@
+# Installation
+
+This guide covers installing flixOpt and its dependencies.
+
+## Requirements
+
+- Python 3.9 or higher
+- pip package manager
+
+## Basic Installation
+
+Install flixOpt directly into your environment using pip:
+
+```bash
+pip install flixopt
+```
+
+This provides the core functionality with the HiGHS solver included.
+
+## Full Installation
+
+For all features including interactive network visualizations and time series aggregation:
+
+```bash
+pip install "flixopt[full]"
+```
+
+## Development Installation
+
+If you want to contribute to flixOpt or work with the latest development version:
+
+```bash
+git clone https://github.com/flixOpt/flixopt.git
+cd flixopt
+pip install -e ".[full,dev]"
+```
+
+## Solver Installation
+
+### HiGHS (Included)
+
+The HiGHS solver is included with flixOpt and works out of the box. No additional installation is required.
+
+### Gurobi (Optional)
+
+For academic use, Gurobi offers free licenses:
+
+1. Register for an academic license at [gurobi.com](https://www.gurobi.com/academia/)
+2. Install Gurobi:
+ ```bash
+ pip install gurobipy
+ ```
+3. Activate your license following Gurobi's instructions
+
+### CPLEX (Optional)
+
+IBM CPLEX is available with academic licenses:
+
+1. Download from [IBM Academic Initiative](https://www.ibm.com/academic/)
+2. Install following IBM's instructions
+3. Install the Python API
+
+### GLPK (Optional)
+
+The GNU Linear Programming Kit can be installed via:
+
+```bash
+pip install pyglpk
+```
+
+## Verification
+
+Verify your installation by running:
+
+```python
+import flixopt
+print(flixopt.__version__)
+```
+
+## Logging Configuration
+
+flixOpt uses [loguru](https://loguru.readthedocs.io/) for logging. Logging is silent by default but can be easily configured:
+
+```python
+from flixopt import CONFIG
+
+# Enable console logging
+CONFIG.Logging.console = True
+CONFIG.Logging.level = 'INFO'
+CONFIG.apply()
+
+# Or use a preset configuration for exploring
+CONFIG.exploring()
+```
+
+For more details on logging configuration, see the [`CONFIG.Logging`][flixopt.config.CONFIG.Logging] documentation.
+
+## Next Steps
+
+- Follow the [Quick Start](quick-start.md) guide
+- Explore the [Minimal Example](../examples/00-Minimal Example.md)
+- Read about [Core Concepts](../user-guide/core-concepts.md)
diff --git a/docs/home/license.md b/docs/home/license.md
new file mode 100644
index 000000000..107b382e5
--- /dev/null
+++ b/docs/home/license.md
@@ -0,0 +1,60 @@
+# License
+
+flixOpt is released under the MIT License.
+
+## MIT License
+
+```
+MIT License
+
+Copyright (c) 2022 Chair of Building Energy Systems and Heat Supply - TU Dresden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+```
+
+## What This Means
+
+The MIT License is a permissive open-source license that allows you to:
+
+✅ **Use** flixOpt for any purpose, including commercial applications
+✅ **Modify** the source code to fit your needs
+✅ **Distribute** copies of flixOpt
+✅ **Sublicense** under different terms
+✅ **Use privately** without making your modifications public
+
+**Requirements:**
+
+- Include the copyright notice and license text in copies of the software
+- The software is provided "as is" without warranty
+
+## Third-Party Licenses
+
+flixOpt depends on several open-source libraries, each with their own licenses:
+
+- **NumPy** - BSD License
+- **Pandas** - BSD License
+- **Matplotlib** - PSF License
+- **HiGHS** - MIT License
+- **NetworkX** - BSD License
+
+Please refer to the individual packages for their specific license terms.
+
+## Contributing
+
+By contributing to flixOpt, you agree that your contributions will be licensed under the MIT License. See our [Contributing Guide](../contribute.md) for more information.
diff --git a/docs/home/models.md b/docs/home/models.md
new file mode 100644
index 000000000..b24649124
--- /dev/null
+++ b/docs/home/models.md
@@ -0,0 +1,139 @@
+# Example Models & Use Cases
+
+flixOpt can model a wide variety of energy and material flow systems. This page provides an overview of common model types and applications.
+
+## Energy System Models
+
+### Simple Dispatch
+
+Optimize operation of existing capacity with given power plants, storage, and demand.
+
+**Typical components:**
+
+- Generator flows with capacity and cost parameters
+- Storage with charge/discharge dynamics
+- Fixed or time-varying demand
+- Grid connection with import/export limits
+
+**Applications:** Day-ahead dispatch, operational planning, market participation
+
+### Capacity Expansion
+
+Determine optimal investment in new capacity alongside operational decisions.
+
+**Typical components:**
+
+- Components with `InvestParameters` for sizing decisions
+- Multiple technology options (solar, wind, storage, etc.)
+- Long-term time series (full year or representative periods)
+- Investment cost parameters
+
+**Applications:** Generation expansion planning, storage sizing, grid reinforcement
+
+### Multi-Period Planning
+
+Sequential investment decisions across multiple time periods with changing conditions.
+
+**Typical components:**
+
+- Two-stage optimization with investment and operational models
+- Evolving demand and technology costs
+- Existing capacity degradation
+- Long-term scenarios
+
+**Applications:** Long-term energy system pathways, infrastructure planning
+
+## Sector Coupling Models
+
+### Power-to-Heat
+
+Integration of electric heat pumps, thermal storage, and power systems.
+
+**Typical components:**
+
+- Heat pumps as `LinearConverter` with COP
+- Thermal storage with temperature layers
+- Combined electricity and heat demand
+- District heating networks
+
+### Power-to-Gas
+
+Hydrogen production via electrolysis with storage and reconversion.
+
+**Typical components:**
+
+- Electrolyzer as `LinearConverter`
+- Hydrogen storage
+- Fuel cells or gas turbines for reconversion
+- Multiple energy carriers (electricity, hydrogen, heat)
+
+### Combined Heat and Power (CHP)
+
+Cogeneration systems with heat and power outputs.
+
+**Typical components:**
+
+- CHP unit with fixed heat/power ratio
+- Heat and electricity buses
+- Thermal and electrical storage
+- Multiple demand profiles
+
+## Industrial Applications
+
+### Process Optimization
+
+Material and energy flows in industrial processes.
+
+**Typical components:**
+
+- Multiple material flows with conversion
+- Energy inputs (electricity, gas, heat)
+- Process constraints and sequencing
+- Quality or composition requirements
+
+### Multi-Commodity Networks
+
+Systems with multiple interacting energy carriers.
+
+**Typical components:**
+
+- Multiple bus types (electricity, gas, heat, hydrogen)
+- Conversion technologies between carriers
+- Storage for different commodities
+- Network flow constraints
+
+## Modeling Patterns
+
+### Time Series Handling
+
+- **Full resolution** - Hourly data for entire year (8760 hours)
+- **Representative periods** - Typical days/weeks
+- **Aggregated periods** - Multi-hour timesteps
+- **Rolling horizon** - Sequential optimization windows
+
+### Uncertainty Modeling
+
+- **Scenario analysis** - Multiple demand/price scenarios
+- **Stochastic optimization** - Probabilistic parameters
+- **Robust optimization** - Worst-case scenarios
+- **Sensitivity analysis** - Parameter variation studies
+
+### Operational Constraints
+
+- **Ramping limits** - Maximum change between timesteps
+- **Minimum run time** - On/off parameters with duration tracking
+- **Load-following** - Constraints on partial load operation
+- **Reserve requirements** - Capacity held for contingencies
+
+## Getting Started
+
+To build these types of models:
+
+1. **Start with examples** - See [Examples](../examples/index.md) for working code
+2. **Learn core concepts** - Read [Core Concepts](../user-guide/core-concepts.md)
+3. **Use recipes** - Follow [Recipes](../user-guide/recipes/index.md) for common patterns
+4. **Study formulations** - Review [Mathematical Notation](../user-guide/mathematical-notation/index.md)
+
+## Model Library
+
+*Coming soon: A library of reusable model templates and building blocks*
diff --git a/docs/home/quick-start.md b/docs/home/quick-start.md
new file mode 100644
index 000000000..1837cee00
--- /dev/null
+++ b/docs/home/quick-start.md
@@ -0,0 +1,162 @@
+# Quick Start
+
+Get up and running with flixOpt in 5 minutes! This guide walks you through creating and solving your first energy system optimization.
+
+## Installation
+
+First, install flixOpt:
+
+```bash
+pip install flixopt
+```
+
+## Your First Model
+
+Let's create a simple energy system with a generator, demand, and battery storage.
+
+### 1. Import flixOpt
+
+```python
+import flixopt as fx
+import numpy as np
+```
+
+### 2. Create a Time Series
+
+```python
+# 24-hour period with hourly timesteps
+timesteps = 24
+time_series = fx.TimeSeriesData(
+ 'hours',
+ 0,
+ timesteps,
+ 1
+)
+```
+
+### 3. Set Up the Flow System
+
+```python
+# Create the flow system
+system = fx.FlowSystem(time_series)
+
+# Define an effect to minimize (costs)
+costs = fx.Effect('costs', 'EUR', 'Minimize total system costs')
+system.add_effects(costs)
+```
+
+### 4. Add Components
+
+```python
+# Electricity bus
+electricity_bus = fx.Bus('electricity', 'kW')
+
+# Solar generator with time-varying output
+solar_profile = np.array([0, 0, 0, 0, 0, 0, 0.2, 0.5, 0.8, 1.0,
+ 1.0, 0.9, 0.8, 0.7, 0.5, 0.3, 0.1, 0,
+ 0, 0, 0, 0, 0, 0])
+
+solar = fx.Component('solar', inputs=[], outputs=[
+ fx.Flow('power',
+ bus=electricity_bus,
+ size=100, # 100 kW capacity
+ relative_maximum=solar_profile,
+ effects_per_flow_hour={costs: 0}) # Free solar
+])
+
+# Demand
+demand_profile = np.array([30, 25, 20, 20, 25, 35, 50, 70, 80, 75,
+ 70, 65, 60, 65, 70, 80, 90, 95, 85, 70,
+ 60, 50, 40, 35])
+
+demand = fx.Component('demand', inputs=[
+ fx.Flow('consumption',
+ bus=electricity_bus,
+ size=1,
+ fixed_relative_value=demand_profile)
+], outputs=[])
+
+# Battery storage
+battery = fx.Storage(
+ 'battery',
+ charging=fx.Flow('charge', bus=electricity_bus, size=50),
+ discharging=fx.Flow('discharge', bus=electricity_bus, size=50),
+ capacity_in_flow_hours=100, # 100 kWh capacity
+ initial_charge_state=0.5, # Start at 50%
+ eta_charge=0.95,
+ eta_discharge=0.95,
+ effects_per_flow_hour={costs: 0.01} # Small battery wear cost
+)
+
+# Add all components to system
+system.add_components(solar, demand, battery)
+```
+
+### 5. Run Optimization
+
+```python
+# Create calculation
+calc = fx.FullCalculation('solar_battery_optimization', system)
+
+# Solve
+calc.solve()
+```
+
+### 6. View Results
+
+```python
+# Print some key results
+print(f"Total costs: {calc.results.objective_value:.2f} EUR")
+
+# Access time series results
+solar_output = calc.results.get_component('solar').get_flow('power').flow_rate
+battery_soc = calc.results.get_component('battery').charge_state
+
+# Plot results (requires matplotlib)
+import matplotlib.pyplot as plt
+
+fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))
+
+ax1.plot(solar_output, label='Solar Output')
+ax1.plot(demand_profile, label='Demand')
+ax1.set_ylabel('Power (kW)')
+ax1.legend()
+ax1.grid(True)
+
+ax2.plot(battery_soc)
+ax2.set_ylabel('Battery SOC (kWh)')
+ax2.set_xlabel('Hour')
+ax2.grid(True)
+
+plt.tight_layout()
+plt.show()
+```
+
+## What's Next?
+
+Now that you've created your first model, you can:
+
+- **Learn the concepts** - Read the [Core Concepts](../user-guide/core-concepts.md) guide
+- **Explore examples** - Check out more [Examples](../examples/index.md)
+- **Deep dive** - Study the [Mathematical Formulation](../user-guide/mathematical-notation/index.md)
+- **Build complex models** - Use [Recipes](../user-guide/recipes/index.md) for common patterns
+
+## Common Workflow
+
+Most flixOpt projects follow this pattern:
+
+1. **Define time series** - Set up the temporal resolution
+2. **Create flow system** - Initialize with time series and effects
+3. **Add buses** - Define connection points
+4. **Add components** - Create generators, storage, converters, loads
+5. **Configure flows** - Set capacity, bounds, and cost parameters
+6. **Run calculation** - Solve the optimization
+7. **Analyze results** - Extract and visualize outcomes
+
+## Tips
+
+- Start simple and add complexity incrementally
+- Use meaningful names for components and flows
+- Check solver status before analyzing results
+- Enable logging during development for debugging
+- Visualize results to verify model behavior
diff --git a/docs/home/users.md b/docs/home/users.md
new file mode 100644
index 000000000..9e27cda54
--- /dev/null
+++ b/docs/home/users.md
@@ -0,0 +1,73 @@
+# Who Uses flixOpt?
+
+flixOpt is used by researchers, engineers, and organizations working on energy system optimization and planning.
+
+## Academia & Research
+
+flixOpt is widely used in academic research for:
+
+- Energy system modeling and optimization studies
+- Renewable energy integration research
+- Storage and flexibility analysis
+- Teaching energy systems engineering
+- PhD and Master's thesis projects
+
+## Industry Applications
+
+Organizations use flixOpt for:
+
+- Energy system planning and operation
+- Investment decision support
+- Feasibility studies for renewable projects
+- District heating network optimization
+- Industrial process optimization
+
+## Application Domains
+
+### Power Systems
+
+- Dispatch optimization with renewable integration
+- Capacity expansion planning
+- Battery storage sizing and operation
+- Grid flexibility analysis
+
+### Heating & Cooling
+
+- District heating network optimization
+- Combined heat and power (CHP) systems
+- Heat pump integration
+- Thermal storage operation
+
+### Industrial Systems
+
+- Process heat optimization
+- Multi-energy systems
+- Supply chain and resource allocation
+- Cogeneration facilities
+
+### Research Topics
+
+- Sector coupling analysis
+- Hydrogen economy modeling
+- Carbon capture and storage integration
+- Energy community optimization
+- Microgrid design and operation
+
+## Example Projects
+
+If you're using flixOpt in your research or project, we'd love to hear about it! Consider:
+
+- Publishing your results and citing flixOpt (see [Citing](citing.md))
+- Sharing your model as an example
+- Contributing to the codebase
+- Joining our community discussions
+
+## Success Stories
+
+*Have a success story using flixOpt? Share it with the community!*
+
+## Get Involved
+
+- :material-github: [Contribute code or documentation](../contribute.md)
+- :material-forum: [Join discussions](https://github.com/flixOpt/flixopt/discussions)
+- :material-email: Share your research and applications
diff --git a/docs/user-guide/faq.md b/docs/user-guide/faq.md
new file mode 100644
index 000000000..a012a7ce7
--- /dev/null
+++ b/docs/user-guide/faq.md
@@ -0,0 +1,77 @@
+# Frequently Asked Questions
+
+Common questions about using flixOpt.
+
+## General
+
+### What is flixOpt?
+
+flixOpt is a Python framework for modeling and optimizing energy and material flow systems. It handles both operational optimization (dispatch) and investment optimization (capacity expansion planning).
+
+### What types of problems can flixOpt solve?
+
+flixOpt can solve:
+
+- Operational dispatch with fixed capacities
+- Capacity expansion planning with investment decisions
+- Multi-period planning with sequential investments
+- Combined operational and investment optimization
+- Multi-commodity systems with energy carriers
+- Stochastic optimization with scenarios
+
+### Which solvers does flixOpt support?
+
+flixOpt supports:
+
+- **HiGHS** (default, included)
+- **Gurobi** (commercial, academic licenses available)
+- we did support more, but saw no usage. Adding them back would be little effort
+
+### Is flixOpt free to use?
+
+Yes! flixOpt is released under the MIT License, which allows free use for commercial and academic purposes. However, some solvers like Gurobi and CPLEX require commercial licenses (though academic licenses are available).
+
+## Installation & Setup
+
+### How do I install flixOpt?
+
+```bash
+pip install flixopt
+```
+
+For full features:
+```bash
+pip install "flixopt[full]"
+```
+
+See [Installation](../home/installation.md) for details.
+
+### Do I need to install a solver separately?
+
+No! The HiGHS solver is included with flixOpt and works out of the box. Other solvers are optional.
+
+## Advanced Topics
+
+### Can I add custom constraints?
+
+Yes! You can add custom constraints directly to the optimization model using linopy. See the Advanced Usage section.
+
+### Does flixOpt support stochastic optimization?
+
+Currently, you can optimize the expected value across scenarios. Further stochastic optimization is on the roadmap.
+
+## Troubleshooting
+
+### Where can I get help?
+
+- Check [Troubleshooting](troubleshooting.md)
+- Search [GitHub Discussions](https://github.com/flixOpt/flixopt/discussions)
+- Open an [issue](https://github.com/flixOpt/flixopt/issues) if you found a bug
+- See [Support](support.md) for more resources
+
+## Not Finding Your Answer?
+
+- Browse the [User Guide](index.md)
+- Check the [Examples](../examples/index.md)
+- Review the [API Reference](../api-reference/)
+- Ask in [GitHub Discussions](https://github.com/flixOpt/flixopt/discussions)
diff --git a/docs/user-guide/index.md b/docs/user-guide/index.md
new file mode 100644
index 000000000..97b30cdbe
--- /dev/null
+++ b/docs/user-guide/index.md
@@ -0,0 +1,80 @@
+# User Guide
+
+Welcome to the flixOpt User Guide! This comprehensive guide will help you master energy and material flow optimization with flixOpt.
+
+## What You'll Learn
+
+This guide covers everything from basic concepts to advanced optimization techniques:
+
+### :material-school: Core Design
+
+Learn the fundamental concepts and component types that make up a flixOpt model:
+
+- **[Core Concepts](core-concepts.md)** - Understanding FlowSystems, Effects, Buses, and Components
+- **Component Types** - Detailed guides for Flow, Bus, Storage, and LinearConverter
+- **[What's New in v3.0](migration-guide-v3.md)** - Major improvements and breaking changes
+
+### :material-math-integral: Optimization
+
+Understand the mathematical formulation behind flixOpt:
+
+- **[Mathematical Notation](mathematical-notation/index.md)** - Complete mathematical documentation
+- **Elements** - Variables and constraints for Flow, Bus, Storage, LinearConverter
+- **Features** - Investment, On/Off operation, Piecewise linearization
+- **Effects & Objective** - System-wide cost and emission tracking
+- **Modeling Patterns** - Reusable constraint patterns
+
+### :material-hammer-wrench: Usage
+
+Practical guides for building and running models:
+
+- **Building Models** - Step-by-step model construction
+- **Running Optimization** - Solver configuration and execution
+- **Analyzing Results** - Extracting and interpreting outcomes
+- **Import & Export** - Data handling and result export
+- **Plotting** - Visualization techniques
+
+### :material-book-open-page-variant: Recipes
+
+Common patterns and best practices:
+
+- **[Recipes](recipes/index.md)** - Solutions to frequent modeling challenges
+- Design patterns for typical energy system problems
+- Tips for model performance and debugging
+
+## Learning Path
+
+### Beginners
+
+1. Read [Core Concepts](core-concepts.md)
+2. Follow the [Quick Start](../home/quick-start.md)
+3. Work through the [Minimal Example](../examples/00-Minimal Example.md)
+4. Explore [Basic Examples](../examples/01-Basic Example.md)
+
+### Intermediate Users
+
+1. Study [Mathematical Notation](mathematical-notation/index.md)
+2. Learn about [Investment Parameters](mathematical-notation/features/InvestParameters.md)
+3. Understand [On/Off Operation](mathematical-notation/features/OnOffParameters.md)
+4. Review [Recipes](recipes/index.md) for common patterns
+
+### Advanced Users
+
+1. Deep dive into [Modeling Patterns](mathematical-notation/modeling-patterns/index.md)
+2. Explore complex examples like [Two-Stage Optimization](../examples/05-Two-stage-optimization.md)
+3. Study [Piecewise Linearization](mathematical-notation/features/Piecewise.md)
+4. Contribute to the [Roadmap](../roadmap.md)
+
+## Getting Help
+
+- **[FAQ](#)** - Frequently asked questions
+- **[Troubleshooting](#)** - Common issues and solutions
+- **[Support](#)** - How to get help from the community
+
+## Contributing
+
+Found something unclear or incorrect? We welcome contributions!
+
+- **Report issues** on [GitHub](https://github.com/flixOpt/flixopt/issues)
+- **Suggest improvements** via [Discussions](https://github.com/flixOpt/flixopt/discussions)
+- **Contribute** following our [Contributing Guide](../contribute.md)
diff --git a/docs/user-guide/optimization/index.md b/docs/user-guide/optimization/index.md
new file mode 100644
index 000000000..59a447b41
--- /dev/null
+++ b/docs/user-guide/optimization/index.md
@@ -0,0 +1,229 @@
+# Optimization Overview
+
+flixOpt formulates energy and material flow problems as Mixed-Integer Linear Programming (MILP) models that can be solved with various optimization solvers.
+
+## What Gets Optimized?
+
+### Decision Variables
+
+flixOpt creates decision variables for:
+
+- **Flow rates** - Energy or material transfer at each timestep
+- **Storage states** - Charge levels over time
+- **Investment sizes** - Capacity decisions (when using `InvestParameters`)
+- **On/off states** - Binary operational decisions (when using `OnOffParameters`)
+- **Effect totals** - Aggregated costs, emissions, etc.
+
+### Objective Function
+
+The optimization minimizes or maximizes one `Effect`:
+
+```python
+costs = fx.Effect('costs', 'EUR', 'Minimize total system costs', is_objective=True)
+```
+
+Effects aggregate contributions from:
+
+- Flow-based costs (€/kWh)
+- Capacity-based costs (€/kW)
+- Investment costs (€)
+- Fixed costs (€)
+- Cross-effect relationships (e.g., carbon pricing)
+
+## Constraint Types
+
+### Balance Constraints
+
+**Nodal balance** at each bus ensures supply equals demand:
+
+$$\sum \text{inputs} = \sum \text{outputs}$$
+
+See [Bus](../mathematical-notation/elements/Bus.md) for details.
+
+### Capacity Constraints
+
+**Flow bounds** limit transfer rates:
+
+$$\text{flow}_\text{min} \leq \text{flow}(t) \leq \text{flow}_\text{max}$$
+
+See [Flow](../mathematical-notation/elements/Flow.md) for details.
+
+### Storage Dynamics
+
+**Charge state evolution** tracks energy levels:
+
+$$\text{charge}(t+1) = \text{charge}(t) + \eta_\text{charge} \cdot \text{charge\_flow}(t) - \frac{\text{discharge\_flow}(t)}{\eta_\text{discharge}}$$
+
+See [Storage](../mathematical-notation/elements/Storage.md) for details.
+
+### Conversion Relationships
+
+**Linear conversions** between flows:
+
+$$\text{output}(t) = \eta \cdot \text{input}(t)$$
+
+See [LinearConverter](../mathematical-notation/elements/LinearConverter.md) for details.
+
+## Optimization Types
+
+### Operational Optimization (Dispatch)
+
+Optimize operation with **fixed capacities**:
+
+- All component sizes are parameters
+- Only operational decisions (flow rates, storage states)
+- Typically shorter time horizons (days to weeks)
+- Fast solve times
+
+**Example:** Day-ahead power plant dispatch
+
+### Investment Optimization (Capacity Expansion)
+
+Optimize **capacity and operation together**:
+
+- Component sizes are decision variables
+- Uses `InvestParameters` for sizing
+- Longer time horizons (months to years)
+- Slower solve times due to binary/integer variables
+
+**Example:** Renewable energy system planning
+
+### Multi-Period Planning
+
+Sequential investment decisions across **multiple time periods**:
+
+- Two-stage optimization (investment + operation)
+- Evolving conditions and technology costs
+- Path-dependent decisions
+- Most complex formulation
+
+**Example:** Long-term energy transition pathways
+
+## Mathematical Formulation
+
+For complete mathematical details, see:
+
+- **[Mathematical Notation Overview](../mathematical-notation/index.md)**
+- **[Elements](../mathematical-notation/elements/Flow.md)** - Flow, Bus, Storage, LinearConverter
+- **[Features](../mathematical-notation/features/InvestParameters.md)** - Investment, On/Off, Piecewise
+- **[Effects & Objective](../mathematical-notation/effects-penalty-objective.md)**
+- **[Modeling Patterns](../mathematical-notation/modeling-patterns/index.md)**
+
+## Solver Options
+
+### Choosing a Solver
+
+flixOpt supports multiple solvers:
+
+| Solver | Type | Speed | License |
+|--------|------|-------|---------|
+| **HiGHS** | Open-source | Fast | Free |
+| **Gurobi** | Commercial | Fastest | Academic/Commercial |
+| **CPLEX** | Commercial | Fastest | Academic/Commercial |
+| **GLPK** | Open-source | Slower | Free |
+
+**Recommendation:** Start with HiGHS (default). Use Gurobi/CPLEX for large models or when speed matters.
+
+### Solver Configuration
+
+Specify solver when creating calculation:
+
+```python
+calc = fx.FullCalculation(
+ 'my_model',
+ system,
+ solver='gurobi', # Choose solver
+ solver_options={ # Solver-specific options
+ 'TimeLimit': 3600, # Maximum solve time (seconds)
+ 'MIPGap': 0.01, # 1% optimality gap acceptable
+ 'Threads': 4, # Parallel threads
+ 'Presolve': 2 # Aggressive presolve
+ }
+)
+```
+
+### Common Solver Options
+
+**Time limits:**
+```python
+solver_options={'TimeLimit': 3600} # Stop after 1 hour
+```
+
+**Optimality gap:**
+```python
+solver_options={'MIPGap': 0.05} # Stop at 5% gap
+```
+
+**Parallel processing:**
+```python
+solver_options={'Threads': 8} # Use 8 cores
+```
+
+**Solver output:**
+```python
+solver_options={'LogToConsole': 1} # Show solver progress
+```
+
+## Performance Optimization
+
+### Model Size Reduction
+
+- Use longer timesteps where acceptable
+- Aggregate time periods (representative days/weeks)
+- Remove unnecessary components
+- Simplify constraint formulations
+
+### Solver Tuning
+
+- Enable presolve and cuts
+- Adjust optimality tolerances
+- Use heuristics for quick feasible solutions
+- Enable warm starting from previous solutions
+
+### Problem Formulation
+
+- Avoid unnecessary binary variables
+- Use continuous relaxations where possible
+- Tighten variable bounds
+- Remove redundant constraints
+
+## Debugging Optimization
+
+### Infeasibility
+
+Model has no feasible solution:
+
+1. Enable penalty variables to identify conflicts
+2. Check balance constraints
+3. Verify capacity limits
+4. Review storage state requirements
+5. Simplify to isolate issue
+
+See [Troubleshooting](../troubleshooting.md) for details.
+
+### Poor Performance
+
+Optimization takes too long:
+
+1. Reduce problem size
+2. Try different solver
+3. Adjust solver options
+4. Simplify model formulation
+5. Use longer timesteps
+
+### Unexpected Results
+
+Solution doesn't match expectations:
+
+1. Verify input data
+2. Check units and scales
+3. Visualize intermediate results
+4. Start with simpler model
+5. Review constraint formulations
+
+## Next Steps
+
+- Study the [Mathematical Notation](../mathematical-notation/index.md)
+- Learn about [Investment Parameters](../mathematical-notation/features/InvestParameters.md)
+- Explore [Modeling Patterns](../mathematical-notation/modeling-patterns/index.md)
+- Review [Examples](../../examples/index.md)
diff --git a/docs/user-guide/support.md b/docs/user-guide/support.md
new file mode 100644
index 000000000..bfbba979e
--- /dev/null
+++ b/docs/user-guide/support.md
@@ -0,0 +1,156 @@
+# Support
+
+Get help with flixOpt through our community and resources.
+
+## Community Support
+
+### GitHub Discussions
+
+The best place to ask questions and share ideas:
+
+**[Visit GitHub Discussions →](https://github.com/flixOpt/flixopt/discussions)**
+
+Use discussions for:
+
+- General questions about using flixOpt
+- Modeling advice and best practices
+- Sharing your projects and models
+- Feature requests and ideas
+- Connecting with other users
+
+### GitHub Issues
+
+For bug reports and specific problems:
+
+**[Open an Issue →](https://github.com/flixOpt/flixopt/issues)**
+
+When reporting an issue, please include:
+
+- **Minimal reproducible example** - Smallest code that demonstrates the problem
+- **flixOpt version** - Run `python -c "import flixopt; print(flixopt.__version__)"`
+- **Python version** - Run `python --version`
+- **Operating system** - Windows, macOS, Linux
+- **Full error message** - Copy the complete traceback
+
+## Documentation
+
+### User Guide
+
+Comprehensive guides and tutorials:
+
+- **[User Guide](index.md)** - Complete documentation
+- **[FAQ](faq.md)** - Frequently asked questions
+- **[Troubleshooting](troubleshooting.md)** - Common issues and solutions
+
+### Examples
+
+Working code examples for common scenarios:
+
+- **[Examples Gallery](../examples/index.md)** - Browse all examples
+- **[Minimal Example](../examples/00-Minimal Example.md)** - Simple starting point
+- **[Basic Example](../examples/01-Basic Example.md)** - Fundamental concepts
+- **[Complex Example](../examples/02-Complex Example.md)** - Advanced modeling
+
+### API Reference
+
+Detailed technical documentation:
+
+- **[API Reference](../api-reference/)** - Complete class and method documentation
+- **[Mathematical Notation](mathematical-notation/index.md)** - Mathematical formulation
+
+## Learning Resources
+
+### Getting Started
+
+New to flixOpt? Start here:
+
+1. **[Quick Start](../home/quick-start.md)** - 5-minute introduction
+2. **[Installation](../home/installation.md)** - Setup guide
+3. **[Core Concepts](core-concepts.md)** - Fundamental concepts
+4. **[Minimal Example](../examples/00-Minimal Example.md)** - First model
+
+### Video Tutorials
+
+*Coming soon: Video tutorials and walkthroughs*
+
+### Publications & Papers
+
+Research using flixOpt:
+
+- **[Citing flixOpt](../home/citing.md)** - How to cite in your work
+- *Coming soon: List of publications*
+
+## Contributing
+
+Help improve flixOpt:
+
+### Ways to Contribute
+
+- **Report bugs** - Help us identify and fix issues
+- **Suggest features** - Share ideas for improvements
+- **Improve documentation** - Fix typos, clarify explanations
+- **Submit examples** - Share your models
+- **Write code** - Fix bugs or add features
+
+See our **[Contributing Guide](../contribute.md)** for details.
+
+### Development
+
+Want to contribute code?
+
+1. **Fork the repository** on GitHub
+2. **Clone your fork** locally
+3. **Install development dependencies:**
+ ```bash
+ pip install -e ".[full,dev]"
+ ```
+4. **Make your changes** on a feature branch
+5. **Run tests** to ensure nothing breaks
+6. **Submit a pull request**
+
+## Commercial Support
+
+### Consulting
+
+For commercial support, training, or custom development:
+
+*Contact information coming soon*
+
+### Academic Collaboration
+
+Interested in collaborating on research using flixOpt?
+
+- **Institution:** Chair of Building Energy Systems and Heat Supply, TU Dresden
+- *Contact information coming soon*
+
+## Stay Updated
+
+### Release Notes
+
+Follow development and new features:
+
+- **[Release Notes](../changelog.md)** - What's new in each version
+- **[Roadmap](../roadmap.md)** - Planned features and improvements
+
+### Social Media
+
+*Coming soon: Social media channels for updates and announcements*
+
+## Response Time
+
+Please note:
+
+- **Community support** is provided on a best-effort basis by volunteers
+- **Response time** varies; please be patient
+- **Well-documented questions** get faster responses
+- **Minimal examples** greatly help in troubleshooting
+
+## Code of Conduct
+
+All community interactions should be:
+
+- Respectful and inclusive
+- Constructive and helpful
+- Professional and courteous
+
+We're here to help each other succeed with flixOpt!
diff --git a/docs/user-guide/troubleshooting.md b/docs/user-guide/troubleshooting.md
new file mode 100644
index 000000000..ff9dfa8d4
--- /dev/null
+++ b/docs/user-guide/troubleshooting.md
@@ -0,0 +1,282 @@
+# Troubleshooting
+
+Common issues and their solutions when working with flixOpt.
+
+## Installation Issues
+
+### Import Error: No module named 'flixopt'
+
+**Problem:** Python can't find the flixOpt package.
+
+**Solutions:**
+
+1. Verify installation:
+ ```bash
+ pip list | grep flixopt
+ ```
+
+2. Install if missing:
+ ```bash
+ pip install flixopt
+ ```
+
+3. Check you're using the correct Python environment:
+ ```bash
+ which python
+ python --version
+ ```
+
+### Solver Not Found
+
+**Problem:** Error message about missing solver.
+
+**Solutions:**
+
+1. For HiGHS (default), reinstall flixOpt:
+ ```bash
+ pip install --upgrade --force-reinstall flixopt
+ ```
+
+2. For Gurobi/CPLEX, ensure the solver is installed and licensed
+3. Specify solver explicitly:
+ ```python
+ calc = fx.FullCalculation('model', system, solver='highs')
+ ```
+
+## Modeling Issues
+
+### Infeasible Model
+
+**Problem:** Solver reports the model is infeasible.
+
+**Diagnostic Steps:**
+
+1. **Enable penalty variables** to identify problematic constraints:
+ ```python
+ system.use_penalty_variables = True
+ ```
+
+2. **Check balance constraints:**
+ - Can supply meet demand at all timesteps?
+ - Are there isolated buses with no input or output?
+
+3. **Verify capacity limits:**
+ - Do components have sufficient size?
+ - Are upper/lower bounds consistent?
+
+4. **Review storage constraints:**
+ - Is initial charge state feasible?
+ - Can storage charge/discharge meet requirements?
+
+5. **Check temporal constraints:**
+ - Are minimum on/off times achievable?
+ - Do ramp rate limits allow necessary changes?
+
+**Common Causes:**
+
+- Demand exceeds total available capacity
+- Storage initial/final states incompatible
+- Over-constrained on/off requirements
+- Inconsistent flow bounds
+
+### Unbounded Model
+
+**Problem:** Solver reports the model is unbounded.
+
+**Solutions:**
+
+1. Add upper bounds to all decision variables
+2. Check that all flows have maximum limits
+3. Ensure investment parameters have maximum sizes
+4. Verify effect coefficients have correct signs
+
+### Unexpected Results
+
+**Problem:** Model solves but results don't make sense.
+
+**Debugging Steps:**
+
+1. **Enable logging:**
+ ```python
+ from flixopt import CONFIG
+ CONFIG.exploring()
+ ```
+
+2. **Start simple:**
+ - Build a minimal model first
+ - Add complexity incrementally
+ - Verify each addition
+
+3. **Check units:**
+ - Are all units consistent?
+ - Do time series align with timesteps?
+
+4. **Visualize results:**
+ - Plot flows over time
+ - Check energy balances
+ - Verify storage states
+
+5. **Validate input data:**
+ - Check for NaN or infinite values
+ - Ensure arrays have correct length
+ - Verify parameter signs (costs should be positive)
+
+## Performance Issues
+
+### Slow Solve Times
+
+**Problem:** Optimization takes too long.
+
+**Solutions:**
+
+1. **Reduce model size:**
+ - Use longer timesteps
+ - Aggregate time periods
+ - Remove unnecessary components
+
+2. **Simplify constraints:**
+ - Relax tight bounds where possible
+ - Remove redundant constraints
+ - Use continuous instead of binary variables when appropriate
+
+3. **Use a better solver:**
+ ```python
+ calc = fx.FullCalculation('model', system, solver='gurobi')
+ ```
+
+4. **Set solver options:**
+ ```python
+ calc = fx.FullCalculation(
+ 'model',
+ system,
+ solver='gurobi',
+ solver_options={
+ 'TimeLimit': 3600,
+ 'MIPGap': 0.01, # 1% optimality gap
+ 'Threads': 4
+ }
+ )
+ ```
+
+5. **Enable presolve and cuts:**
+ Most solvers have aggressive presolve options
+
+### Memory Issues
+
+**Problem:** Running out of memory.
+
+**Solutions:**
+
+1. Reduce time resolution
+2. Use sparse matrices (automatic in flixOpt)
+3. Process results in chunks
+4. Increase system RAM or use high-memory machine
+
+## Result Issues
+
+### Can't Access Results
+
+**Problem:** Error when accessing result attributes.
+
+**Solutions:**
+
+1. **Check solve status:**
+ ```python
+ print(calc.results.status)
+ ```
+
+2. **Verify optimization completed:**
+ ```python
+ if calc.results.status == 'optimal':
+ # Access results
+ else:
+ print(f"Solver status: {calc.results.status}")
+ ```
+
+3. **Check component/flow names:**
+ ```python
+ print(calc.results.component_names)
+ print(calc.results.get_component('name').flow_names)
+ ```
+
+### Missing Time Series Data
+
+**Problem:** Some time series results are None or empty.
+
+**Solutions:**
+
+1. Verify the variable was created (check conditions in documentation)
+2. Check if the component/feature is active
+3. Ensure results object is from a completed solve
+
+## Numerical Issues
+
+### Scaling Problems
+
+**Problem:** Warning about poor numerical conditioning.
+
+**Solutions:**
+
+1. **Normalize units:**
+ - Use MW instead of W
+ - Use MWh instead of Wh
+ - Keep coefficients between 1e-3 and 1e3
+
+2. **Check parameter magnitudes:**
+ - Avoid very large or very small numbers
+ - Scale costs and capacities appropriately
+
+3. **Set solver tolerances:**
+ ```python
+ solver_options={'FeasibilityTol': 1e-6, 'OptimalityTol': 1e-6}
+ ```
+
+### Numerical Precision
+
+**Problem:** Results have unexpected precision errors.
+
+**Solutions:**
+
+1. Use appropriate tolerance when comparing values
+2. Round results for display: `round(value, 2)`
+3. Check solver numerical tolerances
+
+## Common Error Messages
+
+### `KeyError: 'component_name'`
+
+**Cause:** Trying to access a component that doesn't exist.
+
+**Solution:** Check spelling and verify component was added to system.
+
+### `ValueError: Array length mismatch`
+
+**Cause:** Time series length doesn't match model timesteps.
+
+**Solution:** Ensure all time series have length equal to `time_series.number_of_time_steps`.
+
+### `AttributeError: 'NoneType' object has no attribute`
+
+**Cause:** Accessing results before solving or on failed solve.
+
+**Solution:** Check solve status before accessing results.
+
+## Getting Help
+
+If you've tried these solutions and still have issues:
+
+1. **Search existing issues:** [GitHub Issues](https://github.com/flixOpt/flixopt/issues)
+2. **Ask the community:** [GitHub Discussions](https://github.com/flixOpt/flixopt/discussions)
+3. **Report a bug:** Open a new [issue](https://github.com/flixOpt/flixopt/issues/new) with:
+ - Minimal reproducible example
+ - flixOpt version
+ - Python version
+ - Operating system
+ - Full error message
+
+## Additional Resources
+
+- [FAQ](faq.md) - Frequently asked questions
+- [Support](support.md) - How to get help
+- [Examples](../examples/index.md) - Working code examples
+- [API Reference](../api-reference/) - Detailed documentation
diff --git a/mkdocs.yml b/mkdocs.yml
index 7726fc7e5..44a41f141 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -10,35 +10,68 @@ repo_name: flixOpt/flixopt
edit_uri: edit/main/docs/
nav:
- - Home: index.md
+ - Home:
+ - Home: index.md
+ - Overview:
+ - Features: home/features.md
+ - Release Notes: changelog.md
+ - Getting Started:
+ - Installation: home/installation.md
+ - Quick Start: home/quick-start.md
+ - First Model: examples/00-Minimal Example.md
+ - Reference:
+ - Users: home/users.md
+ - Models: home/models.md
+ - Citing: home/citing.md
+ - License: home/license.md
+ - Support:
+ - FAQ: user-guide/faq.md
+ - Troubleshooting: user-guide/troubleshooting.md
+ - Community: user-guide/support.md
+
- User Guide:
- - Getting Started: getting-started.md
- - Core Concepts: user-guide/core-concepts.md
- - Migration to v3.0.0: user-guide/migration-guide-v3.md
- - Mathematical Notation:
- - Overview: user-guide/mathematical-notation/index.md
- - Dimensions: user-guide/mathematical-notation/dimensions.md
- - Elements:
- - Flow: user-guide/mathematical-notation/elements/Flow.md
- - Bus: user-guide/mathematical-notation/elements/Bus.md
- - Storage: user-guide/mathematical-notation/elements/Storage.md
- - LinearConverter: user-guide/mathematical-notation/elements/LinearConverter.md
- - Features:
- - InvestParameters: user-guide/mathematical-notation/features/InvestParameters.md
- - OnOffParameters: user-guide/mathematical-notation/features/OnOffParameters.md
- - Piecewise: user-guide/mathematical-notation/features/Piecewise.md
- - Effects, Penalty & Objective: user-guide/mathematical-notation/effects-penalty-objective.md
- - Modeling Patterns:
- - Overview: user-guide/mathematical-notation/modeling-patterns/index.md
- - Bounds and States: user-guide/mathematical-notation/modeling-patterns/bounds-and-states.md
- - Duration Tracking: user-guide/mathematical-notation/modeling-patterns/duration-tracking.md
- - State Transitions: user-guide/mathematical-notation/modeling-patterns/state-transitions.md
+ - Overview: user-guide/index.md
+ - What's New in v3.0: user-guide/migration-guide-v3.md
+ - Core Design:
+ - Core Concepts: user-guide/core-concepts.md
+ - Optimization:
+ - Overview: user-guide/optimization/index.md
+ - Mathematical Formulation:
+ - Overview: user-guide/mathematical-notation/index.md
+ - Dimensions: user-guide/mathematical-notation/dimensions.md
+ - Elements:
+ - Flow: user-guide/mathematical-notation/elements/Flow.md
+ - Bus: user-guide/mathematical-notation/elements/Bus.md
+ - Storage: user-guide/mathematical-notation/elements/Storage.md
+ - LinearConverter: user-guide/mathematical-notation/elements/LinearConverter.md
+ - Features:
+ - Investment: user-guide/mathematical-notation/features/InvestParameters.md
+ - On/Off Operation: user-guide/mathematical-notation/features/OnOffParameters.md
+ - Piecewise Linearization: user-guide/mathematical-notation/features/Piecewise.md
+ - Effects & Objective: user-guide/mathematical-notation/effects-penalty-objective.md
+ - Modeling Patterns:
+ - Overview: user-guide/mathematical-notation/modeling-patterns/index.md
+ - Bounds and States: user-guide/mathematical-notation/modeling-patterns/bounds-and-states.md
+ - Duration Tracking: user-guide/mathematical-notation/modeling-patterns/duration-tracking.md
+ - State Transitions: user-guide/mathematical-notation/modeling-patterns/state-transitions.md
- Recipes: user-guide/recipes/index.md
- Roadmap: roadmap.md
- - Examples: examples/
- - Contribute: contribute.md
+
+ - Examples:
+ - Overview: examples/index.md
+ - Basic Examples:
+ - examples/00-Minimal Example.md
+ - examples/01-Basic Example.md
+ - Operational Optimization:
+ - examples/02-Complex Example.md
+ - examples/03-Calculation Modes.md
+ - Planning & Investment:
+ - examples/04-Scenarios.md
+ - examples/05-Two-stage-optimization.md
+
- API Reference: api-reference/
- - Release Notes: changelog/
+
+ - Contributing: contribute.md
theme:
name: material
From c09721cfd91bbb539a036a8aa3fe5afba6ec516a Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 20 Nov 2025 20:02:51 +0100
Subject: [PATCH 28/79] Show navigation in home
---
docs/index.md | 2 --
1 file changed, 2 deletions(-)
diff --git a/docs/index.md b/docs/index.md
index f196e1940..b611f02a2 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,7 +1,5 @@
---
title: Home
-hide:
- - navigation
---
From 28fa4cbb229fbc19d7d0de7bc8aba8a085388252 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 20 Nov 2025 20:08:19 +0100
Subject: [PATCH 29/79] Add Changelog fromating
---
.github/workflows/python-app.yaml | 12 +--
CHANGELOG.md | 2 +-
docs/changelog.md | 34 -------
scripts/extract_changelog.py | 152 ------------------------------
scripts/format_changelog.py | 82 ++++++++++++++++
5 files changed, 89 insertions(+), 193 deletions(-)
delete mode 100644 docs/changelog.md
delete mode 100644 scripts/extract_changelog.py
create mode 100644 scripts/format_changelog.py
diff --git a/.github/workflows/python-app.yaml b/.github/workflows/python-app.yaml
index 009c85efa..9b2e10023 100644
--- a/.github/workflows/python-app.yaml
+++ b/.github/workflows/python-app.yaml
@@ -390,15 +390,15 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
- - name: Extract changelog to docs
+ - name: Copy and format changelog
run: |
- # Install packaging dependency for changelog extraction
- uv pip install --system packaging
+ # Copy CHANGELOG.md to docs
+ cp CHANGELOG.md docs/changelog.md
- # Extract individual release files
- python scripts/extract_changelog.py
+ # Format the changelog headers for docs
+ python scripts/format_changelog.py
- echo "✅ Extracted changelog to docs/changelog/"
+ echo "✅ Copied and formatted changelog to docs/changelog.md"
- name: Install documentation dependencies
run: |
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b5e54c233..4f4a54691 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,7 +16,7 @@ This contains all commits, PRs, and contributors.
Therefore, the Changelog should focus on the user-facing changes.
Please remove all irrelevant sections before releasing.
-Please keep the format of the changelog consistent with the other releases, so the extraction for mkdocs works.
+Please keep the format of the changelog consistent: ## [VERSION] - YYYY-MM-DD
---
## [Template] - ????-??-??
diff --git a/docs/changelog.md b/docs/changelog.md
deleted file mode 100644
index c49cb5296..000000000
--- a/docs/changelog.md
+++ /dev/null
@@ -1,34 +0,0 @@
-# Release Notes
-
-## Version 3.0.0
-
-Major release with significant improvements and breaking changes.
-
-### Breaking Changes
-
-See the [Migration Guide](user-guide/migration-guide-v3.md) for detailed upgrade instructions.
-
-### New Features
-
-- Improved API design
-- Enhanced mathematical formulation
-- Better solver integration
-- Expanded documentation
-
-### Improvements
-
-- Performance optimizations
-- Better error messages
-- Enhanced logging capabilities
-
-### Bug Fixes
-
-- Various bug fixes and stability improvements
-
-## Previous Versions
-
-*Release history will be added here*
-
----
-
-For the complete changelog, see the [GitHub Releases](https://github.com/flixOpt/flixopt/releases).
diff --git a/scripts/extract_changelog.py b/scripts/extract_changelog.py
deleted file mode 100644
index d05229896..000000000
--- a/scripts/extract_changelog.py
+++ /dev/null
@@ -1,152 +0,0 @@
-#!/usr/bin/env python3
-"""
-Extract individual releases from CHANGELOG.md to docs/changelog/
-Simple script to create one file per release.
-"""
-
-import os
-import re
-from pathlib import Path
-
-from packaging.version import InvalidVersion, Version
-from packaging.version import parse as parse_version
-
-
-def extract_releases():
- """Extract releases from CHANGELOG.md and save to individual files."""
-
- changelog_path = Path('CHANGELOG.md')
- output_dir = Path('docs/changelog')
-
- if not changelog_path.exists():
- print('❌ CHANGELOG.md not found')
- return
-
- # Create output directory
- output_dir.mkdir(parents=True, exist_ok=True)
-
- # Read changelog
- with open(changelog_path, encoding='utf-8') as f:
- content = f.read()
-
- # Remove template section (HTML comments)
- content = re.sub(r'', '', content, flags=re.DOTALL)
-
- # Split by release headers
- sections = re.split(r'^## \[', content, flags=re.MULTILINE)
-
- releases = []
- for section in sections[1:]: # Skip first empty section
- # Extract version and date from start of section
- match = re.match(r'([^\]]+)\] - ([^\n]+)\n(.*)', section, re.DOTALL)
- if match:
- version, date, release_content = match.groups()
- releases.append((version, date.strip(), release_content.strip()))
-
- print(f'🔍 Found {len(releases)} releases')
-
- # Sort releases by version (oldest first) to keep existing file prefixes stable.
- def version_key(release):
- try:
- return parse_version(release[0])
- except InvalidVersion:
- return parse_version('0.0.0') # fallback for invalid versions
-
- releases.sort(key=version_key, reverse=False)
-
- # Show what we captured for debugging
- if releases:
- print(f'🔧 First release content length: {len(releases[0][2])}')
-
- for i, (version_str, date, release_content) in enumerate(releases):
- # Clean up version for filename with numeric prefix (newest first)
- index = 99999 - i # Newest first, while keeping the same file names for old releases
- prefix = f'{index:05d}' # Zero-padded 5-digit number
- filename = f'{prefix}-v{version_str.replace(" ", "-")}.md'
- filepath = output_dir / filename
-
- # Clean up content - remove trailing --- separators and emojis from headers
- cleaned_content = re.sub(r'\s*---\s*$', '', release_content.strip())
-
- # Generate navigation links
- nav_links = []
-
- # Previous version (older release)
- if i > 0:
- prev_index = 99999 - (i - 1)
- prev_version = releases[i - 1][0]
- prev_filename = f'{prev_index:05d}-v{prev_version.replace(" ", "-")}.md'
- nav_links.append(f'← [Previous: {prev_version}]({prev_filename})')
-
- # Next version (newer release)
- if i < len(releases) - 1:
- next_index = 99999 - (i + 1)
- next_version = releases[i + 1][0]
- next_filename = f'{next_index:05d}-v{next_version.replace(" ", "-")}.md'
- nav_links.append(f'[Next: {next_version}]({next_filename}) →')
-
- # Always add link back to index
- nav_links.append('[📋 All Releases](index.md)')
- # Add GitHub tag link only for valid PEP 440 versions (skip e.g. "Unreleased")
- ver_obj = parse_version(version_str)
- if isinstance(ver_obj, Version):
- nav_links.append(f'[🏷️ GitHub Release](https://github.com/flixOpt/flixopt/releases/tag/v{version_str})')
- # Create content with navigation
- content_lines = [
- f'# {version_str} - {date.strip()}',
- '',
- ' | '.join(nav_links),
- '',
- '---',
- '',
- cleaned_content,
- '',
- '---',
- '',
- ' | '.join(nav_links),
- ]
-
- # Write file
- with open(filepath, 'w', encoding='utf-8') as f:
- f.write('\n'.join(content_lines))
-
- print(f'✅ Created {filename}')
-
- print(f'🎉 Extracted {len(releases)} releases to docs/changelog/')
-
-
-def extract_index():
- changelog_path = Path('CHANGELOG.md')
- output_dir = Path('docs/changelog')
- index_path = output_dir / 'index.md'
-
- if not changelog_path.exists():
- print('❌ CHANGELOG.md not found')
- return
-
- # Create output directory
- output_dir.mkdir(parents=True, exist_ok=True)
-
- # Read changelog
- with open(changelog_path, encoding='utf-8') as f:
- content = f.read()
-
- intro_match = re.search(r'# Changelog\s+([\s\S]*?)(?=
-### 📦 Dependencies
+## [Upcoming] - ????-??-??
### 📝 Docs
- Added missing examples to docs.
-### 👷 Development
-
-### 🚧 Known Issues
-
---
-Until here -->
-
## [4.0.0] - 2025-11-19
**Summary**: This release introduces clearer parameter naming for linear converters and constraints, enhanced period handling with automatic weight computation, and new sum-over-all-periods constraints for multi-period optimization. All deprecated parameter names continue to work with warnings.
From ab3f68fea336192d97d91ba2fb444b80292ef810 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 20 Nov 2025 20:16:58 +0100
Subject: [PATCH 31/79] Simplify users.md
---
docs/home/users.md | 70 ++++++++--------------------------------------
1 file changed, 12 insertions(+), 58 deletions(-)
diff --git a/docs/home/users.md b/docs/home/users.md
index 9e27cda54..d27f99576 100644
--- a/docs/home/users.md
+++ b/docs/home/users.md
@@ -1,73 +1,27 @@
# Who Uses flixOpt?
-flixOpt is used by researchers, engineers, and organizations working on energy system optimization and planning.
+flixOpt is developed and used primarily in academic research for energy system optimization.
-## Academia & Research
+## Primary Users
-flixOpt is widely used in academic research for:
+- **Researchers** - Energy system modeling and optimization studies
+- **Students** - Master's and PhD thesis projects
+- **Engineers** - Feasibility studies and system planning
-- Energy system modeling and optimization studies
-- Renewable energy integration research
-- Storage and flexibility analysis
-- Teaching energy systems engineering
-- PhD and Master's thesis projects
-
-## Industry Applications
-
-Organizations use flixOpt for:
-
-- Energy system planning and operation
-- Investment decision support
-- Feasibility studies for renewable projects
-- District heating network optimization
-- Industrial process optimization
-
-## Application Domains
-
-### Power Systems
+## Typical Applications
- Dispatch optimization with renewable integration
- Capacity expansion planning
-- Battery storage sizing and operation
-- Grid flexibility analysis
-
-### Heating & Cooling
-
+- Battery and thermal storage sizing
- District heating network optimization
- Combined heat and power (CHP) systems
-- Heat pump integration
-- Thermal storage operation
-
-### Industrial Systems
+- Multi-energy systems and sector coupling
-- Process heat optimization
-- Multi-energy systems
-- Supply chain and resource allocation
-- Cogeneration facilities
-
-### Research Topics
-
-- Sector coupling analysis
-- Hydrogen economy modeling
-- Carbon capture and storage integration
-- Energy community optimization
-- Microgrid design and operation
-
-## Example Projects
+## Get Involved
-If you're using flixOpt in your research or project, we'd love to hear about it! Consider:
+Using flixOpt in your research? Consider:
-- Publishing your results and citing flixOpt (see [Citing](citing.md))
+- [Citing flixOpt](citing.md) in your publications
- Sharing your model as an example
- Contributing to the codebase
-- Joining our community discussions
-
-## Success Stories
-
-*Have a success story using flixOpt? Share it with the community!*
-
-## Get Involved
-
-- :material-github: [Contribute code or documentation](../contribute.md)
-- :material-forum: [Join discussions](https://github.com/flixOpt/flixopt/discussions)
-- :material-email: Share your research and applications
+- Joining [discussions](https://github.com/flixOpt/flixopt/discussions)
From 1e3ae2d62b94cec749663eadaef03b567840d58a Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 20 Nov 2025 20:18:30 +0100
Subject: [PATCH 32/79] Simplify models.md
---
docs/home/models.md | 140 ++++++++++----------------------------------
1 file changed, 30 insertions(+), 110 deletions(-)
diff --git a/docs/home/models.md b/docs/home/models.md
index b24649124..8365c4023 100644
--- a/docs/home/models.md
+++ b/docs/home/models.md
@@ -1,139 +1,59 @@
-# Example Models & Use Cases
+# Model Types
-flixOpt can model a wide variety of energy and material flow systems. This page provides an overview of common model types and applications.
+flixOpt can model various energy and material flow systems.
-## Energy System Models
+## Common Model Types
-### Simple Dispatch
+### Dispatch Optimization
-Optimize operation of existing capacity with given power plants, storage, and demand.
+Optimize operation with fixed capacities:
-**Typical components:**
-
-- Generator flows with capacity and cost parameters
-- Storage with charge/discharge dynamics
+- Generator and storage operation
- Fixed or time-varying demand
-- Grid connection with import/export limits
-
-**Applications:** Day-ahead dispatch, operational planning, market participation
+- Grid connection constraints
+- Typically short term time horizons
### Capacity Expansion
-Determine optimal investment in new capacity alongside operational decisions.
-
-**Typical components:**
-
-- Components with `InvestParameters` for sizing decisions
-- Multiple technology options (solar, wind, storage, etc.)
-- Long-term time series (full year or representative periods)
-- Investment cost parameters
+Investment and operational decisions combined:
-**Applications:** Generation expansion planning, storage sizing, grid reinforcement
+- Sizing with `InvestParameters`
+- Technology comparison (solar, wind, storage)
+- Annual time series or representative periods
+- Investment costs and constraints
### Multi-Period Planning
-Sequential investment decisions across multiple time periods with changing conditions.
+Sequential decisions across multiple periods:
-**Typical components:**
+- Two-stage optimization
+- Evolving costs and demand
+- Long-term transformation pathways
-- Two-stage optimization with investment and operational models
-- Evolving demand and technology costs
-- Existing capacity degradation
-- Long-term scenarios
-
-**Applications:** Long-term energy system pathways, infrastructure planning
-
-## Sector Coupling Models
+## Sector Coupling
### Power-to-Heat
-
-Integration of electric heat pumps, thermal storage, and power systems.
-
-**Typical components:**
-
-- Heat pumps as `LinearConverter` with COP
-- Thermal storage with temperature layers
-- Combined electricity and heat demand
+- Heat pumps (`LinearConverter` with COP)
+- Thermal storage
- District heating networks
### Power-to-Gas
-
-Hydrogen production via electrolysis with storage and reconversion.
-
-**Typical components:**
-
-- Electrolyzer as `LinearConverter`
+- Electrolyzers
- Hydrogen storage
-- Fuel cells or gas turbines for reconversion
-- Multiple energy carriers (electricity, hydrogen, heat)
+- Fuel cells
-### Combined Heat and Power (CHP)
-
-Cogeneration systems with heat and power outputs.
-
-**Typical components:**
-
-- CHP unit with fixed heat/power ratio
-- Heat and electricity buses
-- Thermal and electrical storage
+### Combined Heat and Power
+- CHP units with heat/power ratios
- Multiple demand profiles
-## Industrial Applications
-
-### Process Optimization
+## Time Resolution
-Material and energy flows in industrial processes.
-
-**Typical components:**
-
-- Multiple material flows with conversion
-- Energy inputs (electricity, gas, heat)
-- Process constraints and sequencing
-- Quality or composition requirements
-
-### Multi-Commodity Networks
-
-Systems with multiple interacting energy carriers.
-
-**Typical components:**
-
-- Multiple bus types (electricity, gas, heat, hydrogen)
-- Conversion technologies between carriers
-- Storage for different commodities
-- Network flow constraints
-
-## Modeling Patterns
-
-### Time Series Handling
-
-- **Full resolution** - Hourly data for entire year (8760 hours)
+- **Full resolution** - 8760 hours/year
- **Representative periods** - Typical days/weeks
-- **Aggregated periods** - Multi-hour timesteps
-- **Rolling horizon** - Sequential optimization windows
-
-### Uncertainty Modeling
-
-- **Scenario analysis** - Multiple demand/price scenarios
-- **Stochastic optimization** - Probabilistic parameters
-- **Robust optimization** - Worst-case scenarios
-- **Sensitivity analysis** - Parameter variation studies
-
-### Operational Constraints
-
-- **Ramping limits** - Maximum change between timesteps
-- **Minimum run time** - On/off parameters with duration tracking
-- **Load-following** - Constraints on partial load operation
-- **Reserve requirements** - Capacity held for contingencies
+- **Multi-hour timesteps** - Aggregated resolution
## Getting Started
-To build these types of models:
-
-1. **Start with examples** - See [Examples](../examples/index.md) for working code
-2. **Learn core concepts** - Read [Core Concepts](../user-guide/core-concepts.md)
-3. **Use recipes** - Follow [Recipes](../user-guide/recipes/index.md) for common patterns
-4. **Study formulations** - Review [Mathematical Notation](../user-guide/mathematical-notation/index.md)
-
-## Model Library
-
-*Coming soon: A library of reusable model templates and building blocks*
+1. Explore [Examples](../examples/index.md)
+2. Read [Core Concepts](../user-guide/core-concepts.md)
+3. Review [Mathematical Notation](../user-guide/mathematical-notation/index.md)
From 47895f1ece0284e213f1a742b3aa8b89f6c4bcc1 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 20 Nov 2025 20:19:11 +0100
Subject: [PATCH 33/79] Shorten citing.md
---
docs/home/citing.md | 21 +++++++--------------
1 file changed, 7 insertions(+), 14 deletions(-)
diff --git a/docs/home/citing.md b/docs/home/citing.md
index fad415818..85026c2ba 100644
--- a/docs/home/citing.md
+++ b/docs/home/citing.md
@@ -1,6 +1,6 @@
# Citing flixOpt
-If you use flixOpt in your research or project, please cite it appropriately.
+If you use flixOpt in your research, please cite it.
## Citation
@@ -11,7 +11,7 @@ When referencing flixOpt in academic publications, please use:
author = {flixOpt Contributors},
title = {flixOpt: Energy and Material Flow Optimization Framework},
url = {https://github.com/flixOpt/flixopt},
- version = {3.0.0},
+ version = {4.0.0},
year = {2024}
}
```
@@ -24,22 +24,15 @@ If you've published research using flixOpt, please let us know! We'd love to fea
*Coming soon: A list of academic publications that have used flixOpt*
-## Acknowledgment
-
-If you'd like to acknowledge flixOpt in your work without a formal citation, you can use:
-
-> This work uses flixOpt (https://github.com/flixOpt/flixopt), an open-source Python framework for energy and material flow optimization.
-
## Contributing Back
-If flixOpt helped your research, consider contributing back:
+If flixOpt helped your research:
-- **Share your model** - Submit it as an example
-- **Report issues** - Help improve the software
-- **Contribute code** - Add features or fix bugs
-- **Improve documentation** - Share your expertise
+- Share your model as an example
+- Report issues or contribute code
+- Improve documentation
-See our [Contributing Guide](../contribute.md) for more information.
+See the [Contributing Guide](../contribute.md).
## License
From 753a0df85ae8bdb5d0e7c888a7b1ca31330d5581 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 20 Nov 2025 20:19:44 +0100
Subject: [PATCH 34/79] Shorten support.md
---
docs/user-guide/support.md | 25 +------------------------
1 file changed, 1 insertion(+), 24 deletions(-)
diff --git a/docs/user-guide/support.md b/docs/user-guide/support.md
index bfbba979e..7f8288601 100644
--- a/docs/user-guide/support.md
+++ b/docs/user-guide/support.md
@@ -108,33 +108,10 @@ Want to contribute code?
5. **Run tests** to ensure nothing breaks
6. **Submit a pull request**
-## Commercial Support
-
-### Consulting
-
-For commercial support, training, or custom development:
-
-*Contact information coming soon*
-
-### Academic Collaboration
-
-Interested in collaborating on research using flixOpt?
-
-- **Institution:** Chair of Building Energy Systems and Heat Supply, TU Dresden
-- *Contact information coming soon*
-
## Stay Updated
-### Release Notes
-
-Follow development and new features:
-
- **[Release Notes](../changelog.md)** - What's new in each version
-- **[Roadmap](../roadmap.md)** - Planned features and improvements
-
-### Social Media
-
-*Coming soon: Social media channels for updates and announcements*
+- **[Roadmap](../roadmap.md)** - Planned features
## Response Time
From 3c7250b1230b471a925244e8666d6483d0f16597 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 20 Nov 2025 20:21:42 +0100
Subject: [PATCH 35/79] Update CHANGELOG.md
---
CHANGELOG.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e3b0d4882..a5fb7b9c2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -51,11 +51,13 @@ If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOp
Until here -->
-## [Upcoming] - ????-??-??
+## [Upcoming]
### 📝 Docs
- Added missing examples to docs.
+This is not yet publicly released!
+
---
## [4.0.0] - 2025-11-19
From 560a4eeadb9377a6238b48c9933f1a4784db2136 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 20 Nov 2025 20:22:56 +0100
Subject: [PATCH 36/79] Simplify installation.md
---
docs/home/installation.md | 22 +---------------------
1 file changed, 1 insertion(+), 21 deletions(-)
diff --git a/docs/home/installation.md b/docs/home/installation.md
index 83d560443..c7f2b9a34 100644
--- a/docs/home/installation.md
+++ b/docs/home/installation.md
@@ -2,10 +2,6 @@
This guide covers installing flixOpt and its dependencies.
-## Requirements
-
-- Python 3.9 or higher
-- pip package manager
## Basic Installation
@@ -32,7 +28,7 @@ If you want to contribute to flixOpt or work with the latest development version
```bash
git clone https://github.com/flixOpt/flixopt.git
cd flixopt
-pip install -e ".[full,dev]"
+pip install -e ".[full,dev,docs]"
```
## Solver Installation
@@ -52,22 +48,6 @@ For academic use, Gurobi offers free licenses:
```
3. Activate your license following Gurobi's instructions
-### CPLEX (Optional)
-
-IBM CPLEX is available with academic licenses:
-
-1. Download from [IBM Academic Initiative](https://www.ibm.com/academic/)
-2. Install following IBM's instructions
-3. Install the Python API
-
-### GLPK (Optional)
-
-The GNU Linear Programming Kit can be installed via:
-
-```bash
-pip install pyglpk
-```
-
## Verification
Verify your installation by running:
From 4b7d611aed55efc818a5b7a9517c34cab34efc52 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 20 Nov 2025 20:32:21 +0100
Subject: [PATCH 37/79] Simplify quick-start.md
---
docs/home/quick-start.md | 78 +++++++++++++---------------------------
1 file changed, 24 insertions(+), 54 deletions(-)
diff --git a/docs/home/quick-start.md b/docs/home/quick-start.md
index 1837cee00..d51ed02dc 100644
--- a/docs/home/quick-start.md
+++ b/docs/home/quick-start.md
@@ -7,7 +7,7 @@ Get up and running with flixOpt in 5 minutes! This guide walks you through creat
First, install flixOpt:
```bash
-pip install flixopt
+pip install "flixopt[full]"
```
## Your First Model
@@ -21,27 +21,21 @@ import flixopt as fx
import numpy as np
```
-### 2. Create a Time Series
+### 2. Define your time horizon
```python
-# 24-hour period with hourly timesteps
-timesteps = 24
-time_series = fx.TimeSeriesData(
- 'hours',
- 0,
- timesteps,
- 1
-)
+# 24h period with hourly timesteps
+timesteps = pd.date_range('2024-01-01', periods=24, freq='h')
```
-### 3. Set Up the Flow System
+### 2. Set Up the Flow System
```python
# Create the flow system
-system = fx.FlowSystem(time_series)
+flow_system = fx.FlowSystem(timesteps)
# Define an effect to minimize (costs)
-costs = fx.Effect('costs', 'EUR', 'Minimize total system costs')
+costs = fx.Effect('costs', 'EUR', 'Minimize total system costs', is_objective=True)
system.add_effects(costs)
```
@@ -56,12 +50,14 @@ solar_profile = np.array([0, 0, 0, 0, 0, 0, 0.2, 0.5, 0.8, 1.0,
1.0, 0.9, 0.8, 0.7, 0.5, 0.3, 0.1, 0,
0, 0, 0, 0, 0, 0])
-solar = fx.Component('solar', inputs=[], outputs=[
- fx.Flow('power',
- bus=electricity_bus,
- size=100, # 100 kW capacity
- relative_maximum=solar_profile,
- effects_per_flow_hour={costs: 0}) # Free solar
+solar = fx.Source(
+ 'solar',
+ outputs=[fx.Flow(
+ 'power',
+ bus=electricity_bus,
+ size=100, # 100 kW capacity
+ relative_maximum=solar_profile
+ )
])
# Demand
@@ -69,12 +65,12 @@ demand_profile = np.array([30, 25, 20, 20, 25, 35, 50, 70, 80, 75,
70, 65, 60, 65, 70, 80, 90, 95, 85, 70,
60, 50, 40, 35])
-demand = fx.Component('demand', inputs=[
+demand = fx.Sink('demand', inputs=[
fx.Flow('consumption',
bus=electricity_bus,
size=1,
fixed_relative_value=demand_profile)
-], outputs=[])
+])
# Battery storage
battery = fx.Storage(
@@ -82,54 +78,28 @@ battery = fx.Storage(
charging=fx.Flow('charge', bus=electricity_bus, size=50),
discharging=fx.Flow('discharge', bus=electricity_bus, size=50),
capacity_in_flow_hours=100, # 100 kWh capacity
- initial_charge_state=0.5, # Start at 50%
+ initial_charge_state=50, # Start at 50%
eta_charge=0.95,
eta_discharge=0.95,
effects_per_flow_hour={costs: 0.01} # Small battery wear cost
)
# Add all components to system
-system.add_components(solar, demand, battery)
+flow_system.add_components(solar, demand, battery)
```
### 5. Run Optimization
```python
# Create calculation
-calc = fx.FullCalculation('solar_battery_optimization', system)
-
-# Solve
-calc.solve()
+calc = fx.FullCalculation('solar_battery_optimization', system).solve()
```
-### 6. View Results
+### 6. Save Results
```python
-# Print some key results
-print(f"Total costs: {calc.results.objective_value:.2f} EUR")
-
-# Access time series results
-solar_output = calc.results.get_component('solar').get_flow('power').flow_rate
-battery_soc = calc.results.get_component('battery').charge_state
-
-# Plot results (requires matplotlib)
-import matplotlib.pyplot as plt
-
-fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))
-
-ax1.plot(solar_output, label='Solar Output')
-ax1.plot(demand_profile, label='Demand')
-ax1.set_ylabel('Power (kW)')
-ax1.legend()
-ax1.grid(True)
-
-ax2.plot(battery_soc)
-ax2.set_ylabel('Battery SOC (kWh)')
-ax2.set_xlabel('Hour')
-ax2.grid(True)
-
-plt.tight_layout()
-plt.show()
+# This includes the modeled FlowSystem. SO you can restore both results and inputs
+calc.results.to_file()
```
## What's Next?
@@ -151,7 +121,7 @@ Most flixOpt projects follow this pattern:
4. **Add components** - Create generators, storage, converters, loads
5. **Configure flows** - Set capacity, bounds, and cost parameters
6. **Run calculation** - Solve the optimization
-7. **Analyze results** - Extract and visualize outcomes
+7. **Save Results** - For later analysis. Or only extract needed data
## Tips
From 0c18ff2dfe3c4fc2df2aa4a9865dd4fc6fc1bc0c Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 19:30:09 +0100
Subject: [PATCH 38/79] =?UTF-8?q?Updated=20FullCalculation=20=E2=86=92=20O?=
=?UTF-8?q?ptimization=20in=20documentation=20=20Fixed=20mkdocs.yml=20navi?=
=?UTF-8?q?gation=20Fixed=20broken=20link=20in=20support.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/home/quick-start.md | 4 ++--
docs/roadmap.md | 2 +-
docs/user-guide/optimization/index.md | 32 ++++-----------------------
docs/user-guide/troubleshooting.md | 6 ++---
mkdocs.yml | 2 +-
5 files changed, 11 insertions(+), 35 deletions(-)
diff --git a/docs/home/quick-start.md b/docs/home/quick-start.md
index d51ed02dc..121749f09 100644
--- a/docs/home/quick-start.md
+++ b/docs/home/quick-start.md
@@ -91,8 +91,8 @@ flow_system.add_components(solar, demand, battery)
### 5. Run Optimization
```python
-# Create calculation
-calc = fx.FullCalculation('solar_battery_optimization', system).solve()
+# Create and run optimization
+calc = fx.Optimization('solar_battery_optimization', system).solve()
```
### 6. Save Results
diff --git a/docs/roadmap.md b/docs/roadmap.md
index fbad1043c..13233f014 100644
--- a/docs/roadmap.md
+++ b/docs/roadmap.md
@@ -18,7 +18,7 @@ We believe optimization modeling should be **approachable for beginners** yet **
## 🔮 Medium-term (6-12 months)
- **Modeling to Generate Alternatives (MGA)** - Built-in support for exploring near-optimal solution spaces to produce more robust, diverse solutions under uncertainty. See [PyPSA](https://docs.pypsa.org/latest/user-guide/optimization/modelling-to-generate-alternatives/) and [Calliope](https://calliope.readthedocs.io/en/latest/examples/modes/) for reference implementations
-- **Advanced stochastic optimization** - Build sophisticated new `Calculation` classes to perform different stochastic optimization approaches, like PyPSA's [two-stage stochastic programming and risk preferences with Conditional Value-at-Risk (CVaR)](https://docs.pypsa.org/latest/user-guide/optimization/stochastic/)
+- **Advanced stochastic optimization** - Build sophisticated new `Optimization` classes to perform different stochastic optimization approaches, like PyPSA's [two-stage stochastic programming and risk preferences with Conditional Value-at-Risk (CVaR)](https://docs.pypsa.org/latest/user-guide/optimization/stochastic/)
- **Enhanced component library** - More pre-built, domain-specific components (sector coupling, hydrogen systems, thermal networks, demand-side management)
## 🌟 Long-term (12+ months)
diff --git a/docs/user-guide/optimization/index.md b/docs/user-guide/optimization/index.md
index 59a447b41..d4da2244d 100644
--- a/docs/user-guide/optimization/index.md
+++ b/docs/user-guide/optimization/index.md
@@ -129,41 +129,17 @@ flixOpt supports multiple solvers:
Specify solver when creating calculation:
```python
-calc = fx.FullCalculation(
+calc = fx.Optimization(
'my_model',
- system,
- solver='gurobi', # Choose solver
- solver_options={ # Solver-specific options
- 'TimeLimit': 3600, # Maximum solve time (seconds)
- 'MIPGap': 0.01, # 1% optimality gap acceptable
+ flow_system,
+ solver=fx.solvers.Gurobi(time_limit_seconds=3600, mip_gap=0.01), # Choose solver with our simple, unified interface
+ solver_options={ # Add solver-specific options we didnt map
'Threads': 4, # Parallel threads
'Presolve': 2 # Aggressive presolve
}
)
```
-### Common Solver Options
-
-**Time limits:**
-```python
-solver_options={'TimeLimit': 3600} # Stop after 1 hour
-```
-
-**Optimality gap:**
-```python
-solver_options={'MIPGap': 0.05} # Stop at 5% gap
-```
-
-**Parallel processing:**
-```python
-solver_options={'Threads': 8} # Use 8 cores
-```
-
-**Solver output:**
-```python
-solver_options={'LogToConsole': 1} # Show solver progress
-```
-
## Performance Optimization
### Model Size Reduction
diff --git a/docs/user-guide/troubleshooting.md b/docs/user-guide/troubleshooting.md
index ff9dfa8d4..027c14654 100644
--- a/docs/user-guide/troubleshooting.md
+++ b/docs/user-guide/troubleshooting.md
@@ -40,7 +40,7 @@ Common issues and their solutions when working with flixOpt.
2. For Gurobi/CPLEX, ensure the solver is installed and licensed
3. Specify solver explicitly:
```python
- calc = fx.FullCalculation('model', system, solver='highs')
+ calc = fx.Optimization('model', system, solver='highs')
```
## Modeling Issues
@@ -141,12 +141,12 @@ Common issues and their solutions when working with flixOpt.
3. **Use a better solver:**
```python
- calc = fx.FullCalculation('model', system, solver='gurobi')
+ calc = fx.Optimization('model', system, solver='gurobi')
```
4. **Set solver options:**
```python
- calc = fx.FullCalculation(
+ calc = fx.Optimization(
'model',
system,
solver='gurobi',
diff --git a/mkdocs.yml b/mkdocs.yml
index 44a41f141..873d9355d 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -64,7 +64,7 @@ nav:
- examples/01-Basic Example.md
- Operational Optimization:
- examples/02-Complex Example.md
- - examples/03-Calculation Modes.md
+ - examples/03-Optimization Modes.md
- Planning & Investment:
- examples/04-Scenarios.md
- examples/05-Two-stage-optimization.md
From 25a2a686838662ec8d405d78f9951fc7bdb4393b Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 19:39:47 +0100
Subject: [PATCH 39/79] Fixed solver calls in docs
---
docs/home/quick-start.md | 3 ++-
docs/user-guide/optimization/index.md | 21 ++++++++++++---------
docs/user-guide/troubleshooting.md | 17 ++++++++---------
3 files changed, 22 insertions(+), 19 deletions(-)
diff --git a/docs/home/quick-start.md b/docs/home/quick-start.md
index 121749f09..701895d72 100644
--- a/docs/home/quick-start.md
+++ b/docs/home/quick-start.md
@@ -92,7 +92,8 @@ flow_system.add_components(solar, demand, battery)
```python
# Create and run optimization
-calc = fx.Optimization('solar_battery_optimization', system).solve()
+calc = fx.Optimization('solar_battery_optimization', flow_system)
+calc.solve(fx.solvers.HighsSolver())
```
### 6. Save Results
diff --git a/docs/user-guide/optimization/index.md b/docs/user-guide/optimization/index.md
index d4da2244d..a5dd500c5 100644
--- a/docs/user-guide/optimization/index.md
+++ b/docs/user-guide/optimization/index.md
@@ -126,17 +126,20 @@ flixOpt supports multiple solvers:
### Solver Configuration
-Specify solver when creating calculation:
+Specify solver when solving:
```python
-calc = fx.Optimization(
- 'my_model',
- flow_system,
- solver=fx.solvers.Gurobi(time_limit_seconds=3600, mip_gap=0.01), # Choose solver with our simple, unified interface
- solver_options={ # Add solver-specific options we didnt map
- 'Threads': 4, # Parallel threads
- 'Presolve': 2 # Aggressive presolve
- }
+calc = fx.Optimization('my_model', flow_system)
+
+calc.solve(
+ solver=fx.solvers.GurobiSolver(
+ time_limit_seconds=3600,
+ mip_gap=0.01,
+ extra_options={ # Add solver-specific options we didn't map
+ 'Threads': 4, # Parallel threads
+ 'Presolve': 2 # Aggressive presolve
+ }
+ )
)
```
diff --git a/docs/user-guide/troubleshooting.md b/docs/user-guide/troubleshooting.md
index 027c14654..be18223ad 100644
--- a/docs/user-guide/troubleshooting.md
+++ b/docs/user-guide/troubleshooting.md
@@ -40,7 +40,7 @@ Common issues and their solutions when working with flixOpt.
2. For Gurobi/CPLEX, ensure the solver is installed and licensed
3. Specify solver explicitly:
```python
- calc = fx.Optimization('model', system, solver='highs')
+ calc = fx.Optimization('model', flow_system, solver=fx.solvers.HighsSolver())
```
## Modeling Issues
@@ -141,20 +141,19 @@ Common issues and their solutions when working with flixOpt.
3. **Use a better solver:**
```python
- calc = fx.Optimization('model', system, solver='gurobi')
+ calc = fx.Optimization('model', flow_system, solver=fx.solvers.GurobiSolver())
```
4. **Set solver options:**
```python
calc = fx.Optimization(
'model',
- system,
- solver='gurobi',
- solver_options={
- 'TimeLimit': 3600,
- 'MIPGap': 0.01, # 1% optimality gap
- 'Threads': 4
- }
+ flow_system,
+ solver=fx.solvers.GurobiSolver(
+ time_limit_seconds=3600,
+ mip_gap=0.01, # 1% optimality gap
+ extra_options={'Threads': 4}
+ )
)
```
From b0e00a5de9ba26905cd9455c33c3eff5200e7985 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 20:02:23 +0100
Subject: [PATCH 40/79] Move files and restructure
---
docs/getting-started.md | 65 -------------------
docs/home/installation.md | 19 ++++--
docs/index.md | 6 +-
docs/{home => user-guide}/features.md | 0
.../mathematical-notation/others.md | 3 -
mkdocs.yml | 60 ++++++++---------
6 files changed, 45 insertions(+), 108 deletions(-)
delete mode 100644 docs/getting-started.md
rename docs/{home => user-guide}/features.md (100%)
delete mode 100644 docs/user-guide/mathematical-notation/others.md
diff --git a/docs/getting-started.md b/docs/getting-started.md
deleted file mode 100644
index 0cdd2a5a7..000000000
--- a/docs/getting-started.md
+++ /dev/null
@@ -1,65 +0,0 @@
-# Getting Started with FlixOpt
-
-This guide will help you install FlixOpt, understand its basic concepts, and run your first optimization model.
-
-## Installation
-
-### Basic Installation
-
-Install FlixOpt directly into your environment using pip:
-
-```bash
-pip install flixopt
-```
-
-This provides the core functionality with the HiGHS solver included.
-
-### Full Installation
-
-For all features including interactive network visualizations and time series aggregation:
-
-```bash
-pip install "flixopt[full]"
-```
-
-## Logging
-
-FlixOpt uses Python's standard logging module with optional colored output via [colorlog](https://github.com/borntyping/python-colorlog). Logging is silent by default but can be easily configured.
-
-```python
-from flixopt import CONFIG
-
-# Enable colored console logging
-CONFIG.Logging.enable_console('INFO')
-
-# Or use a preset configuration for exploring
-CONFIG.exploring()
-```
-
-For advanced logging configuration, you can use Python's standard logging module directly:
-
-```python
-import logging
-logging.basicConfig(level=logging.DEBUG)
-```
-
-For more details on logging configuration, see the [`CONFIG.Logging`][flixopt.config.CONFIG.Logging] documentation.
-
-## Basic Workflow
-
-Working with FlixOpt follows a general pattern:
-
-1. **Create a [`FlowSystem`][flixopt.flow_system.FlowSystem]** with a time series
-2. **Define [`Effects`][flixopt.effects.Effect]** (costs, emissions, etc.)
-3. **Define [`Buses`][flixopt.elements.Bus]** as connection points in your system
-4. **Add [`Components`][flixopt.components]** like converters, storage, sources/sinks with their Flows
-5. **Run [`Optimizations`][flixopt.optimization]** to optimize your system
-6. **Analyze [`Results`][flixopt.results]** using built-in or external visualization tools
-
-## Next Steps
-
-Now that you've installed FlixOpt and understand the basic workflow, you can:
-
-- Learn about the [core concepts of flixopt](user-guide/core-concepts.md)
-- Explore some [examples](examples/index.md)
-- Check the [API reference](api-reference/index.md) for detailed documentation
diff --git a/docs/home/installation.md b/docs/home/installation.md
index c7f2b9a34..afb24172b 100644
--- a/docs/home/installation.md
+++ b/docs/home/installation.md
@@ -59,20 +59,29 @@ print(flixopt.__version__)
## Logging Configuration
-flixOpt uses [loguru](https://loguru.readthedocs.io/) for logging. Logging is silent by default but can be easily configured:
+flixOpt uses Python's standard logging module with optional colored output via [colorlog](https://github.com/borntyping/python-colorlog). Logging is silent by default but can be easily configured:
```python
from flixopt import CONFIG
-# Enable console logging
-CONFIG.Logging.console = True
-CONFIG.Logging.level = 'INFO'
-CONFIG.apply()
+# Enable colored console logging
+CONFIG.Logging.enable_console('INFO')
# Or use a preset configuration for exploring
CONFIG.exploring()
```
+Since flixOpt uses Python's standard logging, you can also configure it directly:
+
+```python
+import logging
+
+# Get the flixopt logger and configure it
+logger = logging.getLogger('flixopt')
+logger.setLevel(logging.DEBUG)
+logger.addHandler(logging.StreamHandler())
+```
+
For more details on logging configuration, see the [`CONFIG.Logging`][flixopt.config.CONFIG.Logging] documentation.
## Next Steps
diff --git a/docs/index.md b/docs/index.md
index b611f02a2..faf9339ec 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -11,7 +11,7 @@ title: Home
Model, optimize, and analyze complex energy systems with a powerful Python framework designed for flexibility and performance.
- 🚀 Get Started
+ 🚀 Get Started
💡 View Examples
⭐ GitHub
@@ -22,7 +22,7 @@ title: Home
-- :rocket: **[Getting Started](getting-started/)**
+- :rocket: **[Getting Started](home/installation/)**
---
@@ -123,7 +123,7 @@ title: Home
Ready to optimize your energy system?
- ▶️ Start Building
+ ▶️ Start Building
diff --git a/docs/home/features.md b/docs/user-guide/features.md
similarity index 100%
rename from docs/home/features.md
rename to docs/user-guide/features.md
diff --git a/docs/user-guide/mathematical-notation/others.md b/docs/user-guide/mathematical-notation/others.md
deleted file mode 100644
index bdc602308..000000000
--- a/docs/user-guide/mathematical-notation/others.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Work in Progress
-
-This is a work in progress.
diff --git a/mkdocs.yml b/mkdocs.yml
index 873d9355d..3a7aa8c76 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -12,49 +12,45 @@ edit_uri: edit/main/docs/
nav:
- Home:
- Home: index.md
- - Overview:
- - Features: home/features.md
- - Release Notes: changelog.md
- Getting Started:
- Installation: home/installation.md
- Quick Start: home/quick-start.md
- - First Model: examples/00-Minimal Example.md
- - Reference:
+ - About:
- Users: home/users.md
- Models: home/models.md
- Citing: home/citing.md
- License: home/license.md
- - Support:
- - FAQ: user-guide/faq.md
- - Troubleshooting: user-guide/troubleshooting.md
- - Community: user-guide/support.md
- User Guide:
- Overview: user-guide/index.md
- - What's New in v3.0: user-guide/migration-guide-v3.md
- - Core Design:
- - Core Concepts: user-guide/core-concepts.md
- - Optimization:
- - Overview: user-guide/optimization/index.md
- - Mathematical Formulation:
- - Overview: user-guide/mathematical-notation/index.md
- - Dimensions: user-guide/mathematical-notation/dimensions.md
- - Elements:
- - Flow: user-guide/mathematical-notation/elements/Flow.md
- - Bus: user-guide/mathematical-notation/elements/Bus.md
- - Storage: user-guide/mathematical-notation/elements/Storage.md
- - LinearConverter: user-guide/mathematical-notation/elements/LinearConverter.md
- - Features:
- - Investment: user-guide/mathematical-notation/features/InvestParameters.md
- - On/Off Operation: user-guide/mathematical-notation/features/OnOffParameters.md
- - Piecewise Linearization: user-guide/mathematical-notation/features/Piecewise.md
- - Effects & Objective: user-guide/mathematical-notation/effects-penalty-objective.md
- - Modeling Patterns:
- - Overview: user-guide/mathematical-notation/modeling-patterns/index.md
- - Bounds and States: user-guide/mathematical-notation/modeling-patterns/bounds-and-states.md
- - Duration Tracking: user-guide/mathematical-notation/modeling-patterns/duration-tracking.md
- - State Transitions: user-guide/mathematical-notation/modeling-patterns/state-transitions.md
+ - Features: user-guide/features.md
+ - Release Notes: changelog.md
+ - Core Concepts: user-guide/core-concepts.md
+ - Mathematical Notation:
+ - Overview: user-guide/mathematical-notation/index.md
+ - Dimensions: user-guide/mathematical-notation/dimensions.md
+ - Elements:
+ - Flow: user-guide/mathematical-notation/elements/Flow.md
+ - Bus: user-guide/mathematical-notation/elements/Bus.md
+ - Storage: user-guide/mathematical-notation/elements/Storage.md
+ - LinearConverter: user-guide/mathematical-notation/elements/LinearConverter.md
+ - Features:
+ - Investment: user-guide/mathematical-notation/features/InvestParameters.md
+ - On/Off Operation: user-guide/mathematical-notation/features/OnOffParameters.md
+ - Piecewise Linearization: user-guide/mathematical-notation/features/Piecewise.md
+ - Effects & Objective: user-guide/mathematical-notation/effects-penalty-objective.md
+ - Modeling Patterns:
+ - Overview: user-guide/mathematical-notation/modeling-patterns/index.md
+ - Bounds and States: user-guide/mathematical-notation/modeling-patterns/bounds-and-states.md
+ - Duration Tracking: user-guide/mathematical-notation/modeling-patterns/duration-tracking.md
+ - State Transitions: user-guide/mathematical-notation/modeling-patterns/state-transitions.md
+ - Optimization: user-guide/optimization/index.md
- Recipes: user-guide/recipes/index.md
+ - Support:
+ - FAQ: user-guide/faq.md
+ - Troubleshooting: user-guide/troubleshooting.md
+ - Community: user-guide/support.md
+ - Migration Guide v3: user-guide/migration-guide-v3.md
- Roadmap: roadmap.md
- Examples:
From bad8f052164cff585a6ea02deab50c2073d4a327 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 20:08:50 +0100
Subject: [PATCH 41/79] Delete old docs script
---
scripts/extract_changelog.py | 0
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 scripts/extract_changelog.py
diff --git a/scripts/extract_changelog.py b/scripts/extract_changelog.py
deleted file mode 100644
index e69de29bb..000000000
From 988b6cee141d2e8d3b94357dcd7bf9fb2704757a Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 20:20:59 +0100
Subject: [PATCH 42/79] Improve docs structure
---
docs/user-guide/building-models/index.md | 20 +++++
docs/user-guide/features.md | 75 ----------------
docs/user-guide/index.md | 105 ++++++++++++-----------
mkdocs.yml | 12 +--
4 files changed, 81 insertions(+), 131 deletions(-)
create mode 100644 docs/user-guide/building-models/index.md
delete mode 100644 docs/user-guide/features.md
diff --git a/docs/user-guide/building-models/index.md b/docs/user-guide/building-models/index.md
new file mode 100644
index 000000000..27808ea56
--- /dev/null
+++ b/docs/user-guide/building-models/index.md
@@ -0,0 +1,20 @@
+# Building Models
+
+!!! note "Under Development"
+ This section is being expanded with detailed tutorials.
+
+Learn how to construct FlowSystem models step by step:
+
+- Defining time horizons and dimensions
+- Creating buses and flows
+- Adding components (Sources, Sinks, Converters, Storage)
+- Configuring effects and objectives
+- Using advanced features (Investment, On/Off, Piecewise)
+
+## Getting Started
+
+For now, see:
+
+- **[Core Concepts](../core-concepts.md)** - Understand the fundamental building blocks
+- **[Examples](../../examples/index.md)** - Working code you can learn from
+- **[Mathematical Notation](../mathematical-notation/index.md)** - Detailed specifications of each element
diff --git a/docs/user-guide/features.md b/docs/user-guide/features.md
deleted file mode 100644
index 13a6f4e45..000000000
--- a/docs/user-guide/features.md
+++ /dev/null
@@ -1,75 +0,0 @@
-# Features
-
-flixOpt is a comprehensive framework for modeling and optimizing energy and material flow systems. It provides a powerful set of features for both operational and investment optimization.
-
-## Core Capabilities
-
-### :material-cog: Optimization Types
-
-- **Operational Optimization** - Dispatch optimization with given capacities
-- **Investment Optimization** - Capacity expansion planning with binary or continuous sizing
-- **Multi-Period Planning** - Sequential investment decisions over multiple periods
-- **Two-Stage Optimization** - Separate investment and operational decisions
-
-### :material-chart-line: Modeling Features
-
-#### Components
-
-- **Flow** - Energy or material transfer with variable flow rates
-- **Bus** - Nodal balance point for connecting multiple flows
-- **Storage** - Energy storage with charge/discharge dynamics and efficiency losses
-- **LinearConverter** - Linear conversion relationships between flows
-
-#### Advanced Features
-
-- **Investment Parameters** - Binary or continuous capacity decisions with sizing constraints
-- **On/Off Parameters** - Discrete operational states with minimum run/idle times
-- **Piecewise Linearization** - Non-linear relationships approximated with piecewise segments
-- **Effects** - System-wide tracking of costs, emissions, and other impacts
-
-### :material-math-integral: Mathematical Formulation
-
-- **Mixed-Integer Linear Programming (MILP)** - Exact optimization with commercial and open-source solvers
-- **Time Series Support** - Native handling of time-indexed variables and parameters
-- **Flexible Constraints** - User-defined custom constraints and objectives
-- **Penalty Variables** - Soft constraint violations for infeasibility analysis
-
-### :material-puzzle: Solvers
-
-flixOpt supports multiple optimization solvers:
-
-- **HiGHS** - Open-source, high-performance LP/MIP solver (default)
-- **Gurobi** - Commercial solver with academic licenses available
-- **CPLEX** - IBM's commercial optimization solver
-- **GLPK** - GNU Linear Programming Kit
-
-### :material-file-export: Data Handling
-
-- **Flexible Input** - Python dictionaries, pandas DataFrames, or direct parameters
-- **Time Series** - Native support for time-indexed data with automatic alignment
-- **Results Export** - Comprehensive results in structured formats
-- **Visualization** - Built-in plotting capabilities for results analysis
-
-## Use Cases
-
-### Energy Systems
-
-- Power system dispatch and expansion planning
-- Combined heat and power (CHP) optimization
-- Renewable energy integration
-- Battery storage optimization
-- District heating networks
-
-### Industrial Applications
-
-- Process optimization with material flows
-- Multi-commodity networks
-- Supply chain optimization
-- Resource allocation
-
-## Performance
-
-- **Scalable** - Handles systems from simple examples to large-scale energy systems
-- **Efficient** - Optimized model building and constraint generation
-- **Fast** - Modern solvers with warm-start capabilities
-- **Flexible** - Modular design allows for easy extension and customization
diff --git a/docs/user-guide/index.md b/docs/user-guide/index.md
index 97b30cdbe..bfb288ea4 100644
--- a/docs/user-guide/index.md
+++ b/docs/user-guide/index.md
@@ -1,80 +1,83 @@
# User Guide
-Welcome to the flixOpt User Guide! This comprehensive guide will help you master energy and material flow optimization with flixOpt.
+Welcome to the flixOpt User Guide! This guide will help you master energy and material flow optimization with flixOpt.
-## What You'll Learn
+## What is flixOpt?
-This guide covers everything from basic concepts to advanced optimization techniques:
+flixOpt is a comprehensive framework for modeling and optimizing energy and material flow systems. It supports:
-### :material-school: Core Design
+- **Operational Optimization** - Dispatch optimization with fixed capacities
+- **Investment Optimization** - Capacity expansion planning with binary or continuous sizing
+- **Multi-Period Planning** - Sequential investment decisions across multiple periods
+- **Scenario Analysis** - Stochastic modeling with weighted scenarios
-Learn the fundamental concepts and component types that make up a flixOpt model:
+## Key Features
-- **[Core Concepts](core-concepts.md)** - Understanding FlowSystems, Effects, Buses, and Components
-- **Component Types** - Detailed guides for Flow, Bus, Storage, and LinearConverter
-- **[What's New in v3.0](migration-guide-v3.md)** - Major improvements and breaking changes
+
-### :material-math-integral: Optimization
+- :material-puzzle: **Flexible Components**
-Understand the mathematical formulation behind flixOpt:
+ ---
-- **[Mathematical Notation](mathematical-notation/index.md)** - Complete mathematical documentation
-- **Elements** - Variables and constraints for Flow, Bus, Storage, LinearConverter
-- **Features** - Investment, On/Off operation, Piecewise linearization
-- **Effects & Objective** - System-wide cost and emission tracking
-- **Modeling Patterns** - Reusable constraint patterns
+ Flow, Bus, Storage, LinearConverter - build any system topology
-### :material-hammer-wrench: Usage
+- :material-cog: **Advanced Modeling**
-Practical guides for building and running models:
+ ---
-- **Building Models** - Step-by-step model construction
-- **Running Optimization** - Solver configuration and execution
-- **Analyzing Results** - Extracting and interpreting outcomes
-- **Import & Export** - Data handling and result export
-- **Plotting** - Visualization techniques
+ Investment decisions, On/Off states, Piecewise linearization
-### :material-book-open-page-variant: Recipes
+- :material-calculator: **Multiple Solvers**
-Common patterns and best practices:
+ ---
-- **[Recipes](recipes/index.md)** - Solutions to frequent modeling challenges
-- Design patterns for typical energy system problems
-- Tips for model performance and debugging
+ HiGHS (default), Gurobi, CPLEX - choose what fits your needs
+
+- :material-chart-line: **Built-in Analysis**
+
+ ---
+
+ Plotting, export, and result exploration tools
+
+
## Learning Path
-### Beginners
+This guide follows a sequential learning path:
+
+| Step | Section | What You'll Learn |
+|------|---------|-------------------|
+| 1 | [Core Concepts](core-concepts.md) | Fundamental building blocks: FlowSystem, Bus, Flow, Components, Effects |
+| 2 | [Building Models](building-models/index.md) | How to construct models step by step |
+| 3 | [Running Optimizations](optimization/index.md) | Solver configuration and execution |
+| 4 | [Analyzing Results](results/index.md) | Extracting and visualizing outcomes |
+| 5 | [Mathematical Notation](mathematical-notation/index.md) | Deep dive into the math behind each element |
+| 6 | [Recipes](recipes/index.md) | Common patterns and solutions |
+
+## Quick Links
-1. Read [Core Concepts](core-concepts.md)
-2. Follow the [Quick Start](../home/quick-start.md)
-3. Work through the [Minimal Example](../examples/00-Minimal Example.md)
-4. Explore [Basic Examples](../examples/01-Basic Example.md)
+### Getting Started
-### Intermediate Users
+- [Quick Start](../home/quick-start.md) - Build your first model in 5 minutes
+- [Minimal Example](../examples/00-Minimal Example.md) - Simplest possible model
+- [Core Concepts](core-concepts.md) - Understand the fundamentals
-1. Study [Mathematical Notation](mathematical-notation/index.md)
-2. Learn about [Investment Parameters](mathematical-notation/features/InvestParameters.md)
-3. Understand [On/Off Operation](mathematical-notation/features/OnOffParameters.md)
-4. Review [Recipes](recipes/index.md) for common patterns
+### Reference
-### Advanced Users
+- [Mathematical Notation](mathematical-notation/index.md) - Detailed specifications
+- [API Reference](../api-reference/index.md) - Complete class documentation
+- [Examples](../examples/index.md) - Working code to learn from
-1. Deep dive into [Modeling Patterns](mathematical-notation/modeling-patterns/index.md)
-2. Explore complex examples like [Two-Stage Optimization](../examples/05-Two-stage-optimization.md)
-3. Study [Piecewise Linearization](mathematical-notation/features/Piecewise.md)
-4. Contribute to the [Roadmap](../roadmap.md)
+### Help
-## Getting Help
+- [FAQ](faq.md) - Frequently asked questions
+- [Troubleshooting](troubleshooting.md) - Common issues and solutions
+- [Community](support.md) - Get help from the community
-- **[FAQ](#)** - Frequently asked questions
-- **[Troubleshooting](#)** - Common issues and solutions
-- **[Support](#)** - How to get help from the community
+## Use Cases
-## Contributing
+flixOpt handles any flow-based optimization problem:
-Found something unclear or incorrect? We welcome contributions!
+**Energy Systems**: Power dispatch, CHP optimization, renewable integration, battery storage, district heating
-- **Report issues** on [GitHub](https://github.com/flixOpt/flixopt/issues)
-- **Suggest improvements** via [Discussions](https://github.com/flixOpt/flixopt/discussions)
-- **Contribute** following our [Contributing Guide](../contribute.md)
+**Industrial Applications**: Process optimization, multi-commodity networks, supply chains, resource allocation
diff --git a/mkdocs.yml b/mkdocs.yml
index 3a7aa8c76..d4d21d5df 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -23,9 +23,10 @@ nav:
- User Guide:
- Overview: user-guide/index.md
- - Features: user-guide/features.md
- - Release Notes: changelog.md
- Core Concepts: user-guide/core-concepts.md
+ - Building Models: user-guide/building-models/index.md
+ - Running Optimizations: user-guide/optimization/index.md
+ - Analyzing Results: user-guide/results/index.md
- Mathematical Notation:
- Overview: user-guide/mathematical-notation/index.md
- Dimensions: user-guide/mathematical-notation/dimensions.md
@@ -44,14 +45,15 @@ nav:
- Bounds and States: user-guide/mathematical-notation/modeling-patterns/bounds-and-states.md
- Duration Tracking: user-guide/mathematical-notation/modeling-patterns/duration-tracking.md
- State Transitions: user-guide/mathematical-notation/modeling-patterns/state-transitions.md
- - Optimization: user-guide/optimization/index.md
- Recipes: user-guide/recipes/index.md
- Support:
- FAQ: user-guide/faq.md
- Troubleshooting: user-guide/troubleshooting.md
- Community: user-guide/support.md
- - Migration Guide v3: user-guide/migration-guide-v3.md
- - Roadmap: roadmap.md
+ - Migration & Updates:
+ - Migration Guide v3: user-guide/migration-guide-v3.md
+ - Release Notes: changelog.md
+ - Roadmap: roadmap.md
- Examples:
- Overview: examples/index.md
From 8f7ccb24d168593d2653466b69ee7a581953a160 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 20:39:45 +0100
Subject: [PATCH 43/79] Imrpove Optimization Modes
---
docs/user-guide/optimization/index.md | 228 +++++++++++---------------
1 file changed, 99 insertions(+), 129 deletions(-)
diff --git a/docs/user-guide/optimization/index.md b/docs/user-guide/optimization/index.md
index a5dd500c5..97dbad2ff 100644
--- a/docs/user-guide/optimization/index.md
+++ b/docs/user-guide/optimization/index.md
@@ -1,119 +1,95 @@
-# Optimization Overview
+# Running Optimizations
-flixOpt formulates energy and material flow problems as Mixed-Integer Linear Programming (MILP) models that can be solved with various optimization solvers.
+This section covers how to run optimizations in flixOpt, including different optimization modes and solver configuration.
-## What Gets Optimized?
+## Optimization Modes
-### Decision Variables
+flixOpt provides three optimization modes to handle different problem sizes and requirements:
-flixOpt creates decision variables for:
+### Optimization (Full)
-- **Flow rates** - Energy or material transfer at each timestep
-- **Storage states** - Charge levels over time
-- **Investment sizes** - Capacity decisions (when using `InvestParameters`)
-- **On/off states** - Binary operational decisions (when using `OnOffParameters`)
-- **Effect totals** - Aggregated costs, emissions, etc.
-
-### Objective Function
-
-The optimization minimizes or maximizes one `Effect`:
+[`Optimization`][flixopt.optimization.Optimization] solves the entire problem at once.
```python
-costs = fx.Effect('costs', 'EUR', 'Minimize total system costs', is_objective=True)
-```
-
-Effects aggregate contributions from:
-
-- Flow-based costs (€/kWh)
-- Capacity-based costs (€/kW)
-- Investment costs (€)
-- Fixed costs (€)
-- Cross-effect relationships (e.g., carbon pricing)
-
-## Constraint Types
-
-### Balance Constraints
-
-**Nodal balance** at each bus ensures supply equals demand:
-
-$$\sum \text{inputs} = \sum \text{outputs}$$
-
-See [Bus](../mathematical-notation/elements/Bus.md) for details.
-
-### Capacity Constraints
-
-**Flow bounds** limit transfer rates:
-
-$$\text{flow}_\text{min} \leq \text{flow}(t) \leq \text{flow}_\text{max}$$
-
-See [Flow](../mathematical-notation/elements/Flow.md) for details.
-
-### Storage Dynamics
-
-**Charge state evolution** tracks energy levels:
-
-$$\text{charge}(t+1) = \text{charge}(t) + \eta_\text{charge} \cdot \text{charge\_flow}(t) - \frac{\text{discharge\_flow}(t)}{\eta_\text{discharge}}$$
+import flixopt as fx
-See [Storage](../mathematical-notation/elements/Storage.md) for details.
-
-### Conversion Relationships
-
-**Linear conversions** between flows:
+optimization = fx.Optimization('my_model', flow_system)
+optimization.solve(fx.solvers.HighsSolver())
+```
-$$\text{output}(t) = \eta \cdot \text{input}(t)$$
+**Best for:**
-See [LinearConverter](../mathematical-notation/elements/LinearConverter.md) for details.
+- Small to medium problems
+- When you need the globally optimal solution
+- Problems without time-coupling simplifications
-## Optimization Types
+### SegmentedOptimization
-### Operational Optimization (Dispatch)
+[`SegmentedOptimization`][flixopt.optimization.SegmentedOptimization] splits the time horizon into segments and solves them sequentially.
-Optimize operation with **fixed capacities**:
+```python
+optimization = fx.SegmentedOptimization(
+ 'segmented_model',
+ flow_system,
+ segment_length=24, # Hours per segment
+ overlap_length=4 # Hours of overlap between segments
+)
+optimization.solve(fx.solvers.HighsSolver())
+```
-- All component sizes are parameters
-- Only operational decisions (flow rates, storage states)
-- Typically shorter time horizons (days to weeks)
-- Fast solve times
+**Best for:**
-**Example:** Day-ahead power plant dispatch
+- Large problems that don't fit in memory
+- Long time horizons (weeks, months)
+- Problems where decisions are mostly local in time
-### Investment Optimization (Capacity Expansion)
+**Trade-offs:**
-Optimize **capacity and operation together**:
+- Faster solve times
+- May miss globally optimal solutions
+- Overlap helps maintain solution quality at segment boundaries
-- Component sizes are decision variables
-- Uses `InvestParameters` for sizing
-- Longer time horizons (months to years)
-- Slower solve times due to binary/integer variables
+### ClusteredOptimization
-**Example:** Renewable energy system planning
+[`ClusteredOptimization`][flixopt.optimization.ClusteredOptimization] uses time series aggregation to reduce problem size by identifying representative periods.
-### Multi-Period Planning
+```python
+clustering_params = fx.ClusteringParameters(
+ n_periods=8, # Number of typical periods
+ hours_per_period=24 # Hours per typical period
+)
-Sequential investment decisions across **multiple time periods**:
+optimization = fx.ClusteredOptimization(
+ 'clustered_model',
+ flow_system,
+ clustering_params
+)
+optimization.solve(fx.solvers.HighsSolver())
+```
-- Two-stage optimization (investment + operation)
-- Evolving conditions and technology costs
-- Path-dependent decisions
-- Most complex formulation
+**Best for:**
-**Example:** Long-term energy transition pathways
+- Investment planning problems
+- Year-long optimizations
+- When computational speed is critical
-## Mathematical Formulation
+**Trade-offs:**
-For complete mathematical details, see:
+- Much faster solve times
+- Approximates the full problem
+- Best when patterns repeat (e.g., typical days)
-- **[Mathematical Notation Overview](../mathematical-notation/index.md)**
-- **[Elements](../mathematical-notation/elements/Flow.md)** - Flow, Bus, Storage, LinearConverter
-- **[Features](../mathematical-notation/features/InvestParameters.md)** - Investment, On/Off, Piecewise
-- **[Effects & Objective](../mathematical-notation/effects-penalty-objective.md)**
-- **[Modeling Patterns](../mathematical-notation/modeling-patterns/index.md)**
+## Choosing an Optimization Mode
-## Solver Options
+| Mode | Problem Size | Solve Time | Solution Quality |
+|------|-------------|------------|------------------|
+| `Optimization` | Small-Medium | Slow | Optimal |
+| `SegmentedOptimization` | Large | Medium | Near-optimal |
+| `ClusteredOptimization` | Very Large | Fast | Approximate |
-### Choosing a Solver
+## Solver Configuration
-flixOpt supports multiple solvers:
+### Available Solvers
| Solver | Type | Speed | License |
|--------|------|-------|---------|
@@ -122,87 +98,81 @@ flixOpt supports multiple solvers:
| **CPLEX** | Commercial | Fastest | Academic/Commercial |
| **GLPK** | Open-source | Slower | Free |
-**Recommendation:** Start with HiGHS (default). Use Gurobi/CPLEX for large models or when speed matters.
+**Recommendation:** Start with HiGHS (included by default). Use Gurobi/CPLEX for large models or when speed matters.
-### Solver Configuration
-
-Specify solver when solving:
+### Solver Options
```python
-calc = fx.Optimization('my_model', flow_system)
+# Basic usage with defaults
+optimization.solve(fx.solvers.HighsSolver())
-calc.solve(
- solver=fx.solvers.GurobiSolver(
+# With custom options
+optimization.solve(
+ fx.solvers.GurobiSolver(
time_limit_seconds=3600,
mip_gap=0.01,
- extra_options={ # Add solver-specific options we didn't map
- 'Threads': 4, # Parallel threads
- 'Presolve': 2 # Aggressive presolve
+ extra_options={
+ 'Threads': 4,
+ 'Presolve': 2
}
)
)
```
-## Performance Optimization
+Common solver parameters:
+
+- `time_limit_seconds` - Maximum solve time
+- `mip_gap` - Acceptable optimality gap (0.01 = 1%)
+- `log_to_console` - Show solver output
+
+## Performance Tips
### Model Size Reduction
- Use longer timesteps where acceptable
-- Aggregate time periods (representative days/weeks)
+- Use `ClusteredOptimization` for long horizons
- Remove unnecessary components
- Simplify constraint formulations
### Solver Tuning
- Enable presolve and cuts
-- Adjust optimality tolerances
-- Use heuristics for quick feasible solutions
-- Enable warm starting from previous solutions
+- Adjust optimality tolerances for faster (approximate) solutions
+- Use parallel threads when available
### Problem Formulation
- Avoid unnecessary binary variables
-- Use continuous relaxations where possible
+- Use continuous investment sizes when possible
- Tighten variable bounds
- Remove redundant constraints
-## Debugging Optimization
+## Debugging
### Infeasibility
-Model has no feasible solution:
+If your model has no feasible solution:
-1. Enable penalty variables to identify conflicts
-2. Check balance constraints
-3. Verify capacity limits
+1. Enable penalty variables: `flow_system.use_penalty_variables = True`
+2. Check balance constraints - can supply meet demand?
+3. Verify capacity limits are consistent
4. Review storage state requirements
-5. Simplify to isolate issue
-
-See [Troubleshooting](../troubleshooting.md) for details.
-
-### Poor Performance
-
-Optimization takes too long:
+5. Simplify model to isolate the issue
-1. Reduce problem size
-2. Try different solver
-3. Adjust solver options
-4. Simplify model formulation
-5. Use longer timesteps
+See [Troubleshooting](../troubleshooting.md) for more details.
### Unexpected Results
-Solution doesn't match expectations:
+If solutions don't match expectations:
-1. Verify input data
-2. Check units and scales
+1. Verify input data (units, scales)
+2. Enable logging: `fx.CONFIG.exploring()`
3. Visualize intermediate results
-4. Start with simpler model
-5. Review constraint formulations
+4. Start with a simpler model
+5. Check constraint formulations
## Next Steps
-- Study the [Mathematical Notation](../mathematical-notation/index.md)
-- Learn about [Investment Parameters](../mathematical-notation/features/InvestParameters.md)
-- Explore [Modeling Patterns](../mathematical-notation/modeling-patterns/index.md)
-- Review [Examples](../../examples/index.md)
+- See [Examples](../../examples/03-Optimization Modes.md) for working code
+- Learn about [Mathematical Notation](../mathematical-notation/index.md)
+- Explore [Recipes](../recipes/index.md) for common patterns
From 0abec83f5a550e7ac36d1f3ee8de1d62fa26aa88 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 20:42:34 +0100
Subject: [PATCH 44/79] Imrpove Optimization Modes
---
docs/user-guide/optimization/index.md | 27 ++++++++++++++++++++++-----
1 file changed, 22 insertions(+), 5 deletions(-)
diff --git a/docs/user-guide/optimization/index.md b/docs/user-guide/optimization/index.md
index 97dbad2ff..7010acfc5 100644
--- a/docs/user-guide/optimization/index.md
+++ b/docs/user-guide/optimization/index.md
@@ -153,11 +153,28 @@ Common solver parameters:
If your model has no feasible solution:
-1. Enable penalty variables: `flow_system.use_penalty_variables = True`
-2. Check balance constraints - can supply meet demand?
-3. Verify capacity limits are consistent
-4. Review storage state requirements
-5. Simplify model to isolate the issue
+1. **Enable excess penalties on buses** to allow balance violations:
+ ```python
+ # Allow imbalance with high penalty cost (default is 1e5)
+ heat_bus = fx.Bus('Heat', excess_penalty_per_flow_hour=1e5)
+
+ # Or disable penalty to enforce strict balance
+ electricity_bus = fx.Bus('Electricity', excess_penalty_per_flow_hour=None)
+ ```
+ When `excess_penalty_per_flow_hour` is set, the optimization can violate bus balance constraints by paying a penalty, helping identify which constraints cause infeasibility.
+
+2. **Use Gurobi for infeasibility analysis** - When using GurobiSolver and the model is infeasible, flixOpt automatically extracts and logs the Irreducible Inconsistent Subsystem (IIS):
+ ```python
+ # Gurobi provides detailed infeasibility analysis
+ optimization.solve(fx.solvers.GurobiSolver())
+ # If infeasible, check the model documentation file for IIS details
+ ```
+ The infeasible constraints are saved to the model documentation file in the results folder.
+
+3. Check balance constraints - can supply meet demand?
+4. Verify capacity limits are consistent
+5. Review storage state requirements
+6. Simplify model to isolate the issue
See [Troubleshooting](../troubleshooting.md) for more details.
From 4f105c9518a9c4b3e6198af6480ca057fbecc1fd Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 20:50:43 +0100
Subject: [PATCH 45/79] Rewrite the core concepts to be user facing
---
docs/user-guide/core-concepts.md | 263 ++++++++++++++++++-------------
1 file changed, 152 insertions(+), 111 deletions(-)
diff --git a/docs/user-guide/core-concepts.md b/docs/user-guide/core-concepts.md
index f165f1e4e..3bccb554c 100644
--- a/docs/user-guide/core-concepts.md
+++ b/docs/user-guide/core-concepts.md
@@ -1,155 +1,196 @@
-# Core concepts of flixopt
+# Core Concepts
-FlixOpt is built around a set of core concepts that work together to represent and optimize **any system involving flows and conversions** - whether that's energy systems, material flows, supply chains, water networks, or production processes.
+This page introduces the fundamental concepts of flixOpt through practical scenarios. Understanding these concepts will help you model any system involving flows and conversions.
-This page provides a high-level overview of these concepts and how they interact.
+## The Big Picture
-## Main building blocks
+Imagine you're managing a district heating system. You have:
-### FlowSystem
+- A **gas boiler** that burns natural gas to produce heat
+- A **heat pump** that uses electricity to extract heat from the environment
+- A **thermal storage tank** to buffer heat production and demand
+- **Buildings** that need heat throughout the day
+- Access to the **gas grid** and **electricity grid**
-The [`FlowSystem`][flixopt.flow_system.FlowSystem] is the central organizing unit in FlixOpt.
-Every FlixOpt model starts with creating a FlowSystem. It:
+Your goal: **minimize total operating costs** while meeting all heat demands.
-- Defines the timesteps for the optimization
-- Contains and connects [components](#components), [buses](#buses), and [flows](#flows)
-- Manages the [effects](#effects) (objectives and constraints)
+This is exactly the kind of problem flixOpt solves. Let's see how each concept maps to this scenario.
-FlowSystem provides two ways to access elements:
+## Buses: Where Things Connect
-- **Dict-like interface**: Access any element by label: `flow_system['Boiler']`, `'Boiler' in flow_system`, `flow_system.keys()`
-- **Direct containers**: Access type-specific containers: `flow_system.components`, `flow_system.buses`, `flow_system.effects`, `flow_system.flows`
+A [`Bus`][flixopt.elements.Bus] is a connection point where energy or material flows meet. Think of it as a junction or hub.
-Element labels must be unique across all types. See the [`FlowSystem` API reference][flixopt.flow_system.FlowSystem] for detailed examples and usage patterns.
+!!! example "In our heating system"
+ - **Heat Bus** — where heat from the boiler, heat pump, and storage meets the building demand
+ - **Gas Bus** — connection to the gas grid
+ - **Electricity Bus** — connection to the power grid
-### Flows
+**The key rule:** At every bus, **inputs must equal outputs** at each timestep.
-[`Flow`][flixopt.elements.Flow] objects represent the movement of energy or material between a [Bus](#buses) and a [Component](#components) in a predefined direction.
+$$\sum inputs = \sum outputs$$
-- Have a `size` which, generally speaking, defines how much energy or material can be moved. Usually measured in MW, kW, m³/h, etc.
-- Have a `flow_rate`, which defines how fast energy or material is transported. Usually measured in MW, kW, m³/h, etc.
-- Have constraints to limit the flow-rate (min/max, total flow hours, on/off etc.)
-- Can have fixed profiles (for demands or renewable generation)
-- Can have [Effects](#effects) associated by their use (costs, emissions, labour, ...)
+This balance constraint is what makes your model physically meaningful — energy can't appear or disappear.
-#### Flow Hours
-While the **Flow Rate** defines the rate in which energy or material is transported, the **Flow Hours** define the amount of energy or material that is transported.
-Its defined by the flow_rate times the duration of the timestep in hours.
+## Flows: What Moves Between Elements
-Examples:
+A [`Flow`][flixopt.elements.Flow] represents the movement of energy or material. Every flow connects a component to a bus, with a defined direction.
-| Flow Rate | Timestep | Flow Hours |
-|-----------|----------|------------|
-| 10 (MW) | 1 hour | 10 (MWh) |
-| 10 (MW) | 6 minutes | 0.1 (MWh) |
-| 10 (kg/h) | 1 hour | 10 (kg) |
+!!! example "In our heating system"
+ - Heat flowing **from** the boiler **to** the Heat Bus
+ - Gas flowing **from** the Gas Bus **to** the boiler
+ - Heat flowing **from** the Heat Bus **to** the buildings
-### Buses
+Flows have:
-[`Bus`][flixopt.elements.Bus] objects represent nodes or connection points in a FlowSystem. They:
+- A **size** (capacity) — *"This boiler can deliver up to 500 kW"*
+- A **flow rate** — *"Right now it's running at 300 kW"*
-- Balance incoming and outgoing flows
-- Can represent physical networks like heat, electricity, or gas
-- Handle infeasible balances gently by allowing the balance to be closed in return for a big Penalty (optional)
+## Components: The Equipment
-### Components
+[`Components`][flixopt.elements.Component] are the physical (or logical) elements that transform, store, or transfer flows.
-[`Component`][flixopt.elements.Component] objects usually represent physical entities in your system that interact with [`Flows`][flixopt.elements.Flow]. The generic component types work across all domains:
+### Converters — Transform One Thing Into Another
-- [`LinearConverters`][flixopt.components.LinearConverter] - Converts input flows to output flows with (piecewise) linear relationships
- - *Energy: boilers, heat pumps, turbines*
- - *Manufacturing: assembly lines, processing equipment*
- - *Chemistry: reactors, separators*
-- [`Storages`][flixopt.components.Storage] - Stores energy or material over time
- - *Energy: batteries, thermal storage, gas storage*
- - *Logistics: warehouses, buffer inventory*
- - *Water: reservoirs, tanks*
-- [`Sources`][flixopt.components.Source] / [`Sinks`][flixopt.components.Sink] / [`SourceAndSinks`][flixopt.components.SourceAndSink] - Produce or consume flows
- - *Energy: demands, renewable generation*
- - *Manufacturing: raw material supply, product demand*
- - *Supply chain: suppliers, customers*
-- [`Transmissions`][flixopt.components.Transmission] - Moves flows between locations with possible losses
- - *Energy: pipelines, power lines*
- - *Logistics: transport routes*
- - *Water: distribution networks*
+A [`LinearConverter`][flixopt.components.LinearConverter] takes inputs and produces outputs with a defined efficiency.
-**Pre-built specialized components** for energy systems include [`Boilers`][flixopt.linear_converters.Boiler], [`HeatPumps`][flixopt.linear_converters.HeatPump], [`CHPs`][flixopt.linear_converters.CHP], etc. These can serve as blueprints for custom domain-specific components.
+!!! example "In our heating system"
+ - **Gas Boiler**: Gas → Heat (η = 90%)
+ - **Heat Pump**: Electricity → Heat (COP = 3.5)
-### Effects
+The conversion relationship:
-[`Effect`][flixopt.effects.Effect] objects represent impacts or metrics related to your system. While commonly used to allocate costs, they're completely flexible:
+$$output = \eta \cdot input$$
-**Energy systems:**
-- Costs (investment, operation)
-- Emissions (CO₂, NOx, etc.)
-- Primary energy consumption
+### Storages — Save for Later
-**Other domains:**
-- Production time, labor hours (manufacturing)
-- Water consumption, wastewater (process industries)
-- Transport distance, vehicle utilization (logistics)
-- Space consumption
-- Any custom metric relevant to your domain
+A [`Storage`][flixopt.components.Storage] accumulates and releases energy or material over time.
-These can be freely defined and crosslink to each other (`CO₂` ──[specific CO₂-costs]─→ `Costs`).
-One effect is designated as the **optimization objective** (typically Costs), while others can be constrained.
-This approach allows for multi-criteria optimization using both:
+!!! example "In our heating system"
+ - **Thermal Tank**: Store excess heat during cheap hours, use it during expensive hours
- - **Weighted Sum Method**: Optimize a theoretical Effect which other Effects crosslink to
- - **ε-constraint method**: Constrain effects to specific limits
+The storage tracks its state over time:
-### Optimization
+$$charge(t+1) = charge(t) + charging - discharging$$
-A [`FlowSystem`][flixopt.flow_system.FlowSystem] can be converted to a Model and optimized by creating an [`Optimization`][flixopt.optimization.Optimization] from it.
+### Sources & Sinks — System Boundaries
-FlixOpt offers different optimization modes:
+[`Sources`][flixopt.components.Source] and [`Sinks`][flixopt.components.Sink] connect your system to the outside world.
-- [`Optimization`][flixopt.optimization.Optimization] - Solves the entire problem at once
-- [`SegmentedOptimization`][flixopt.optimization.SegmentedOptimization] - Solves the problem in segments (with optional overlap), improving performance for large problems
-- [`ClusteredOptimization`][flixopt.optimization.ClusteredOptimization] - Uses typical periods to reduce computational requirements
+!!! example "In our heating system"
+ - **Gas Source**: Buy gas from the grid at market prices
+ - **Electricity Source**: Buy power at time-varying prices
+ - **Heat Sink**: The building demand that must be met
-### Results
+## Effects: What You're Tracking
-The results of an optimization are stored in a [`Results`][flixopt.results.Results] object.
-This object contains the solutions of the optimization as well as all information about the [`Optimization`][flixopt.optimization.Optimization] and the [`FlowSystem`][flixopt.flow_system.FlowSystem] it was created from.
-The solution is stored as an `xarray.Dataset`, but can be accessed through their associated Component, Bus or Effect.
+An [`Effect`][flixopt.effects.Effect] represents any metric you want to track or optimize. One effect is your **objective** (what you minimize or maximize), others can be **constraints**.
-This [`Results`][flixopt.results.Results] object can be saved to file and reloaded from file, allowing you to analyze the results anytime after the solve.
+!!! example "In our heating system"
+ - **Costs** (objective) — minimize total operating costs
+ - **CO₂ Emissions** (constraint) — stay below 1000 tonnes/year
+ - **Gas Consumption** (tracking) — report total gas used
-## How These Concepts Work Together
+Effects can be linked: *"Each kg of CO₂ costs €80 in emissions trading"* — this creates a connection from the CO₂ effect to the Costs effect.
-The process of working with FlixOpt can be divided into 3 steps:
+## FlowSystem: Putting It All Together
-1. Create a [`FlowSystem`][flixopt.flow_system.FlowSystem], containing all the elements and data of your system
- - Define the time horizon of your system (and optionally your periods and scenarios, see [Dimensions](mathematical-notation/dimensions.md)))
- - Add [`Effects`][flixopt.effects.Effect] to represent costs, emissions, etc.
- - Add [`Buses`][flixopt.elements.Bus] as connection points in your system and [`Sinks`][flixopt.components.Sink] & [`Sources`][flixopt.components.Source] as connections to the outer world (markets, power grid, ...)
- - Add [`Components`][flixopt.components] like [`Boilers`][flixopt.linear_converters.Boiler], [`HeatPumps`][flixopt.linear_converters.HeatPump], [`CHPs`][flixopt.linear_converters.CHP], etc.
- - Add
- - [`FlowSystems`][flixopt.flow_system.FlowSystem] can also be loaded from a netCDF file*
-2. Translate the model to a mathematical optimization problem
- - Create an [`Optimization`][flixopt.optimization.Optimization] from your FlowSystem and choose a Solver
- - ...The Optimization is translated internally to a mathematical optimization problem...
- - ...and solved by the chosen solver.
-3. Analyze the results
- - The results are stored in a [`Results`][flixopt.results.Results] object
- - This object can be saved to file and reloaded from file, retaining all information about the optimization
- - As it contains the used [`FlowSystem`][flixopt.flow_system.FlowSystem], it fully documents all assumptions taken to create the results.
+The [`FlowSystem`][flixopt.flow_system.FlowSystem] is your complete model. It contains all buses, components, flows, and effects, plus the **time definition** for your optimization.
+
+```python
+import flixopt as fx
+
+# Define timesteps (e.g., hourly for one week)
+timesteps = pd.date_range('2024-01-01', periods=168, freq='h')
+
+# Create the system
+flow_system = fx.FlowSystem(timesteps)
+
+# Add elements
+flow_system.add_elements(heat_bus, gas_bus, electricity_bus)
+flow_system.add_elements(boiler, heat_pump, storage)
+flow_system.add_elements(costs_effect, co2_effect)
+```
+
+## The Workflow: Model → Optimize → Analyze
+
+Working with flixOpt follows three steps:
+
+```mermaid
+graph LR
+ A[1. Build FlowSystem] --> B[2. Run Optimization]
+ B --> C[3. Analyze Results]
+```
+
+### 1. Build Your Model
+
+Define your system structure, parameters, and time series data.
+
+### 2. Run the Optimization
+
+Create an [`Optimization`][flixopt.optimization.Optimization] and solve it:
+
+```python
+optimization = fx.Optimization('my_model', flow_system)
+results = optimization.solve(fx.solvers.HighsSolver())
+```
+
+### 3. Analyze Results
+
+The [`Results`][flixopt.results.Results] object contains all solution data:
+
+```python
+# Access component results
+boiler_output = results['Boiler'].node_balance()
+
+# Get total costs
+total_costs = results.solution['Costs']
+```

Conceptual Usage and IO operations of FlixOpt
-## Advanced Usage
-As flixopt is build on [linopy](https://github.com/PyPSA/linopy), any model created with FlixOpt can be extended or modified using the great [linopy API](https://linopy.readthedocs.io/en/latest/api.html).
-This allows to adjust your model to very specific requirements without loosing the convenience of FlixOpt.
-
-
-
-
-
-
-
-
-
+## Quick Reference
+
+| Concept | What It Represents | Real-World Example |
+|---------|-------------------|-------------------|
+| **Bus** | Connection point | Heat network, electrical grid |
+| **Flow** | Energy/material movement | Heat delivery, gas consumption |
+| **LinearConverter** | Transformation equipment | Boiler, heat pump, turbine |
+| **Storage** | Time-shifting capability | Battery, thermal tank, warehouse |
+| **Source/Sink** | System boundary | Grid connection, demand |
+| **Effect** | Metric to track/optimize | Costs, emissions, energy use |
+| **FlowSystem** | Complete model | Your entire system |
+
+## Beyond Energy Systems
+
+While our example used a heating system, flixOpt works for any flow-based optimization:
+
+| Domain | Buses | Components | Effects |
+|--------|-------|------------|---------|
+| **District Heating** | Heat, Gas, Electricity | Boilers, CHPs, Heat Pumps | Costs, CO₂ |
+| **Manufacturing** | Raw Materials, Products | Machines, Assembly Lines | Costs, Time, Labor |
+| **Supply Chain** | Warehouses, Locations | Transport, Storage | Costs, Distance |
+| **Water Networks** | Reservoirs, Treatment | Pumps, Pipes | Costs, Energy |
+
+## Next Steps
+
+- **[Building Models](building-models/index.md)** — Step-by-step guide to constructing models
+- **[Examples](../examples/index.md)** — Working code for common scenarios
+- **[Mathematical Notation](mathematical-notation/index.md)** — Detailed constraint formulations
+
+## Advanced: Extending with linopy
+
+flixOpt is built on [linopy](https://github.com/PyPSA/linopy). You can access and extend the underlying optimization model for custom constraints:
+
+```python
+# Access the linopy model after building
+optimization.do_modeling()
+model = optimization.model
+
+# Add custom constraints using linopy API
+model.add_constraints(...)
+```
+
+This allows advanced users to add domain-specific constraints while keeping flixOpt's convenience for standard modeling.
From d20c53b345f6754a3b5ab75db4fa264aad782927 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 21:08:54 +0100
Subject: [PATCH 46/79] Reorganize Mathematical Notation
---
.../effects-penalty-objective.md | 552 ++++++++----------
.../mathematical-notation/elements/Bus.md | 177 +++---
.../mathematical-notation/elements/Flow.md | 307 +++++-----
.../elements/LinearConverter.md | 341 ++++++-----
.../mathematical-notation/elements/Storage.md | 322 +++++-----
.../user-guide/mathematical-notation/index.md | 162 +++--
mkdocs.yml | 13 +-
7 files changed, 962 insertions(+), 912 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/effects-penalty-objective.md b/docs/user-guide/mathematical-notation/effects-penalty-objective.md
index 12c240d04..8216a2595 100644
--- a/docs/user-guide/mathematical-notation/effects-penalty-objective.md
+++ b/docs/user-guide/mathematical-notation/effects-penalty-objective.md
@@ -1,384 +1,289 @@
-# Effects, Penalty & Objective
+# Effects & Objective
-Effects quantify system-wide impacts like costs, emissions, or resource consumption, aggregating contributions from Elements across the FlowSystem. One Effect serves as the optimization objective, while others can be tracked and constrained.
+Effects are how you track and optimize metrics in your system. One effect is your **objective** (what you minimize), while others can be **constraints** or just tracked for reporting.
-=== "Variables"
+!!! example "Common effects"
+ - **Costs** — minimize total costs (objective)
+ - **CO₂ emissions** — track or constrain to meet targets
+ - **Primary energy** — report for efficiency analysis
+ - **Peak power** — constrain maximum grid import
- | Symbol | Python Name | Description | Domain | Created When |
- |--------|-------------|-------------|--------|--------------|
- | $E_{e,\text{per}}$ | `(periodic)\|total` | Total periodic effect (time-independent) | $\mathbb{R}$ | Always (per Effect) |
- | $E_{e,\text{temp}}(\text{t}_i)$ | `(temporal)\|total` | Temporal effect at time $\text{t}_i$ | $\mathbb{R}$ | Always (per Effect) |
- | $E_e$ | `total` | Total effect (periodic + temporal combined) | $\mathbb{R}$ | Always (per Effect) |
- | $\Phi$ | `penalty` | Penalty term for constraint violations | $\mathbb{R}_+$ | Always (system-wide) |
- | - | `total_over_periods` | Weighted sum of total effect across all periods | $\mathbb{R}$ | `minimum_over_periods` or `maximum_over_periods` specified |
+## Core Concept: Aggregating Contributions
-=== "Constraints"
+Every element in your model can contribute to effects. These contributions are aggregated into totals:
- **Element shares to periodic effects**:
-
- $$\label{eq:Share_periodic}
- s_{l \rightarrow e, \text{per}} = \sum_{v \in \mathcal{V}_{l, \text{per}}} v \cdot \text{a}_{v \rightarrow e}
- $$
-
- Where $\mathcal{V}_{l, \text{per}}$ are periodic (investment-related) variables of element $l$, and $\text{a}_{v \rightarrow e}$ is the effect factor (e.g., €/kW).
-
- ---
-
- **Element shares to temporal effects**:
-
- $$\label{eq:Share_temporal}
- s_{l \rightarrow e, \text{temp}}(\text{t}_i) = \sum_{v \in \mathcal{V}_{l,\text{temp}}} v(\text{t}_i) \cdot \text{a}_{v \rightarrow e}(\text{t}_i)
- $$
-
- Where $\mathcal{V}_{l, \text{temp}}$ are temporal (operational) variables, and $\text{a}_{v \rightarrow e}(\text{t}_i)$ is the time-varying effect factor (e.g., €/kWh).
-
- ---
-
- **Total periodic effect**:
-
- $$\label{eq:Effect_periodic}
- E_{e, \text{per}} =
- \sum_{l \in \mathcal{L}} s_{l \rightarrow e,\text{per}} +
- \sum_{x \in \mathcal{E}\backslash e} E_{x, \text{per}} \cdot \text{r}_{x \rightarrow e,\text{per}}
- $$
-
- Aggregates element periodic shares plus cross-effect contributions (e.g., CO₂ → costs via carbon pricing).
-
- ---
-
- **Total temporal effect**:
-
- $$\label{eq:Effect_temporal}
- E_{e, \text{temp}}(\text{t}_{i}) =
- \sum_{l \in \mathcal{L}} s_{l \rightarrow e, \text{temp}}(\text{t}_i) +
- \sum_{x \in \mathcal{E}\backslash e} E_{x, \text{temp}}(\text{t}_i) \cdot \text{r}_{x \rightarrow {e},\text{temp}}(\text{t}_i)
- $$
-
- Aggregates element temporal shares plus cross-effect contributions at each timestep.
-
- ---
-
- **Total temporal effect (sum over time)**:
-
- $$\label{eq:Effect_temporal_total}
- E_{e,\text{temp},\text{tot}} = \sum_{i=1}^n E_{e,\text{temp}}(\text{t}_{i})
- $$
-
- ---
-
- **Total combined effect**:
-
- $$\label{eq:Effect_Total}
- E_{e} = E_{e,\text{per}} + E_{e,\text{temp},\text{tot}}
- $$
-
- ---
-
- **Penalty term**:
-
- $$\label{eq:Penalty}
- \Phi = \sum_{l \in \mathcal{L}} \left( s_{l \rightarrow \Phi} +\sum_{\text{t}_i \in \mathcal{T}} s_{l \rightarrow \Phi}(\text{t}_{i}) \right)
- $$
-
- Accumulates penalty shares from elements (primarily from [Bus](elements/Bus.md) via `excess_penalty_per_flow_hour`).
-
- ---
-
- **Objective function**:
-
- $$\label{eq:Objective}
- \min \left( E_{\Omega} + \Phi \right)
- $$
-
- Where $E_{\Omega}$ is the chosen objective effect (designated with `is_objective=True`).
-
- ---
-
- **Effect bounds - Temporal total** (when `minimum_temporal` or `maximum_temporal` specified):
-
- $$\label{eq:Bounds_Temporal_Total}
- E_{e,\text{temp}}^\text{L} \leq E_{e,\text{temp},\text{tot}} \leq E_{e,\text{temp}}^\text{U}
- $$
-
- ---
-
- **Effect bounds - Temporal per timestep** (when `minimum_per_hour` or `maximum_per_hour` specified):
-
- $$\label{eq:Bounds_Timestep}
- E_{e,\text{temp}}^\text{L}(\text{t}_i) \leq E_{e,\text{temp}}(\text{t}_i) \leq E_{e,\text{temp}}^\text{U}(\text{t}_i)
- $$
-
- ---
-
- **Effect bounds - Periodic** (when `minimum_periodic` or `maximum_periodic` specified):
-
- $$\label{eq:Bounds_Periodic}
- E_{e,\text{per}}^\text{L} \leq E_{e,\text{per}} \leq E_{e,\text{per}}^\text{U}
- $$
-
- ---
+$$
+E_{total} = \sum_{elements} contributions
+$$
- **Effect bounds - Total combined** (when `minimum_total` or `maximum_total` specified):
+For example, total costs might come from:
- $$\label{eq:Bounds_Total}
- E_e^\text{L} \leq E_e \leq E_e^\text{U}
- $$
+- Gas consumption × gas price
+- Electricity import × electricity price
+- Equipment startup costs
+- Investment costs (annualized)
- ---
+## The Objective Function
- **Effect bounds - Across all periods** (when `minimum_over_periods` or `maximum_over_periods` specified):
+flixOpt minimizes one effect (plus any penalties):
- $$\label{eq:Bounds_Over_Periods}
- E_e^\text{L,periods} \leq \sum_{y \in \mathcal{Y}} w_y \cdot E_e(y) \leq E_e^\text{U,periods}
- $$
+$$
+\min \quad E_{objective} + penalty
+$$
- **Mathematical Patterns:** Effect Aggregation, Weighted Objectives
+The penalty term comes from bus balance violations (see [Bus](elements/Bus.md)).
-=== "Parameters"
+## Two Types of Effects
- | Symbol | Python Parameter | Description | Default |
- |--------|------------------|-------------|---------|
- | $\mathcal{E}$ | - | Set of all effects in the system | From registered Effects |
- | $\mathcal{L}$ | - | Set of all elements in the FlowSystem | From all Elements |
- | $\mathcal{T}$ | - | Set of all timesteps | From system time index |
- | $\mathcal{V}_{l, \text{per}}$ | - | Subset of periodic (investment) variables for element $l$ | Element-dependent |
- | $\mathcal{V}_{l, \text{temp}}$ | - | Subset of temporal (operational) variables for element $l$ | Element-dependent |
- | $\mathcal{Y}$ | - | Set of periods | From system dimensions |
- | $\text{a}_{v \rightarrow e}$ | - | Effect factor (e.g., €/kW for investment) | Element-specific |
- | $\text{a}_{v \rightarrow e}(\text{t}_i)$ | - | Time-varying effect factor (e.g., €/kWh) | Element-specific |
- | $\text{r}_{x \rightarrow e, \text{per}}$ | `share_from_periodic` | Periodic conversion factor from effect $x$ to $e$ | None |
- | $\text{r}_{x \rightarrow e, \text{temp}}(\text{t}_i)$ | `share_from_temporal` | Temporal conversion factor from effect $x$ to $e$ | None |
- | $w_y$ | `weights` | Period weights (e.g., discount factors) | From FlowSystem or Effect-specific |
- | - | `description` | Descriptive name for the effect | None |
- | - | `is_objective` | If True, this effect is the optimization objective | False |
- | - | `is_standard` | If True, allows direct value input without effect dictionaries | False |
- | - | `maximum_over_periods` | Maximum weighted sum across all periods | None |
- | - | `maximum_per_hour` | Maximum contribution per timestep | None |
- | - | `maximum_periodic` | Maximum periodic contribution | None |
- | - | `maximum_temporal` | Maximum total temporal contribution | None |
- | - | `maximum_total` | Maximum total effect (periodic + temporal) | None |
- | - | `minimum_over_periods` | Minimum weighted sum across all periods | None |
- | - | `minimum_per_hour` | Minimum contribution per timestep | None |
- | - | `minimum_periodic` | Minimum periodic contribution | None |
- | - | `minimum_temporal` | Minimum total temporal contribution | None |
- | - | `minimum_total` | Minimum total effect (periodic + temporal) | None |
- | - | `unit` | Unit of the effect (informative only) | None |
+### Temporal Effects (Operational)
-=== "Use Cases"
+Vary with time — accumulated over all timesteps:
- ## Basic Cost Objective
+$$
+E_{temporal} = \sum_t contribution(t) \cdot \Delta t
+$$
+!!! example "Fuel costs"
```python
- from flixopt import Effect, Flow
-
- # Define cost effect
- cost = Effect(
- label='system_costs',
- unit='€',
- is_objective=True, # This is what we minimize
- )
-
- # Elements contribute via effects parameters
- generator = Flow(
- label='electricity_out',
- bus='grid',
+ gas_flow = fx.Flow(
+ label='gas',
+ bus=gas_bus,
size=100,
- effects_per_flow_hour={'system_costs': 45}, # €45/MWh operational cost
+ effects_per_flow_hour={'costs': 50}, # €50/MWh
)
```
+ Contribution: $50 \cdot p_{gas}(t) \cdot \Delta t$ at each timestep
- **Variables:** $E_{\text{costs},\text{per}}$, $E_{\text{costs},\text{temp}}(\text{t}_i)$, $E_{\text{costs}}$
-
- **Constraints:** $\eqref{eq:Share_temporal}$ accumulating operational costs, $\eqref{eq:Effect_temporal}$ aggregating across elements, $\eqref{eq:Effect_Total}$ combining domains, $\eqref{eq:Objective}$ minimizing total cost
+### Periodic Effects (Investment)
- ---
+Time-independent — incurred once per period:
- ## Multi-Criteria: Cost with Emission Constraint
+$$
+E_{periodic} = \sum_{investments} size \cdot specific\_cost
+$$
+!!! example "Battery investment"
```python
- from flixopt import Effect, Flow
-
- # Cost objective
- cost = Effect(
- label='costs',
- unit='€',
- is_objective=True,
- )
-
- # CO₂ emissions as constraint
- co2 = Effect(
- label='co2_emissions',
- unit='kg',
- maximum_temporal=100000, # Max 100 tons CO₂ per period
- )
-
- # Generator with both effects
- generator = Flow(
- label='electricity_out',
- bus='grid',
- size=100,
- effects_per_flow_hour={
- 'costs': 45, # €45/MWh
- 'co2_emissions': 0.8, # 0.8 kg CO₂/kWh
- },
+ battery = fx.Storage(
+ ...,
+ capacity_in_flow_hours=InvestParameters(
+ maximum_size=1000,
+ specific_effects={'costs': 200}, # €200/kWh annualized
+ ),
)
```
+ Contribution: $200 \cdot capacity$ (once, not per timestep)
- **Variables:** $E_{\text{costs}}$, $E_{\text{CO}_2}$, $\Phi$
+### Total Effect
- **Constraints:** $\eqref{eq:Objective}$ minimizing costs, $\eqref{eq:Bounds_Temporal_Total}$ with $E_{\text{CO}_2,\text{temp},\text{tot}} \leq 100{,}000$ kg
-
- **Behavior:** ε-constraint method - optimize one objective while constraining another
+$$
+E_{total} = E_{periodic} + E_{temporal}
+$$
- ---
+## Cross-Effects: Linking Metrics
- ## Cross-Effect: Carbon Pricing
+Effects can contribute to each other. This enables carbon pricing, multi-criteria optimization, and complex cost structures.
+!!! example "Carbon pricing"
+ CO₂ emissions cost €80/tonne:
```python
- from flixopt import Effect
+ co2 = fx.Effect(label='co2', unit='kg')
- # CO₂ emissions
- co2 = Effect(
- label='co2_emissions',
- unit='kg',
- )
-
- # Cost with carbon pricing
- cost = Effect(
+ costs = fx.Effect(
label='costs',
unit='€',
is_objective=True,
- share_from_temporal={'co2_emissions': 0.05}, # €50/ton CO₂
- )
- ```
-
- **Variables:** $E_{\text{CO}_2,\text{temp}}(\text{t}_i)$, $E_{\text{costs},\text{temp}}(\text{t}_i)$
-
- **Constraints:** $\eqref{eq:Effect_temporal}$ with cross-effect term: $E_{\text{costs},\text{temp}}(\text{t}_i) = s_{\text{costs}}(\text{t}_i) + E_{\text{CO}_2,\text{temp}}(\text{t}_i) \cdot 0.05$
-
- **Behavior:** Weighted sum method - CO₂ emissions automatically converted to costs
-
- ---
-
- ## Investment Constraints
-
- ```python
- from flixopt import Effect, Flow, InvestParameters
-
- # Investment budget constraint
- capex = Effect(
- label='capital_costs',
- unit='€',
- maximum_periodic=5_000_000, # €5M investment budget
- )
-
- cost = Effect(
- label='total_costs',
- unit='€',
- is_objective=True,
- )
-
- # Battery with investment
- battery_flow = Flow(
- label='storage_capacity',
- bus='electricity',
- size=InvestParameters(
- minimum_size=0,
- maximum_size=1000,
- effects_of_investment_per_size={'capital_costs': 600}, # €600/kWh
- effects_of_investment_per_size={'total_costs': 80}, # Annualized: €80/kWh/year
- ),
- )
- ```
-
- **Variables:** $E_{\text{capex},\text{per}}$, $E_{\text{costs},\text{per}}$, $E_{\text{costs},\text{temp}}(\text{t}_i)$
-
- **Constraints:** $\eqref{eq:Share_periodic}$ from investment, $\eqref{eq:Bounds_Periodic}$ with $E_{\text{capex},\text{per}} \leq 5{,}000{,}000$ €, $\eqref{eq:Objective}$ minimizing total costs
-
- ---
-
- ## Hourly Peak Constraint
-
- ```python
- from flixopt import Effect, Flow
-
- # Peak power limit
- peak_power = Effect(
- label='grid_power',
- unit='kW',
- maximum_per_hour=500, # Max 500 kW at any timestep
- )
-
- grid_import = Flow(
- label='grid_import',
- bus='electricity',
- size=1000,
- effects_per_flow_hour={'grid_power': 1}, # 1 kW per kW flow
+ share_from_temporal={'co2': 0.08}, # €0.08/kg = €80/tonne
)
```
- **Variables:** $E_{\text{peak},\text{temp}}(\text{t}_i)$
-
- **Constraints:** $\eqref{eq:Bounds_Timestep}$ with $E_{\text{peak},\text{temp}}(\text{t}_i) \leq 500$ kW for all $\text{t}_i$
+ Now CO₂ emissions automatically contribute to costs.
----
+## Effect Constraints
-## Multi-Dimensional Objective
+Besides optimizing one effect, you can constrain others:
-When the FlowSystem includes **periods** and/or **scenarios** (see [Dimensions](dimensions.md)), the objective aggregates effects using weights.
+### Total Limit
-### Time Only (Base Case)
+```python
+co2 = fx.Effect(
+ label='co2',
+ unit='kg',
+ maximum_total=100_000, # Max 100 tonnes total
+)
+```
$$
-\min \quad E_{\Omega} + \Phi = \sum_{\text{t}_i \in \mathcal{T}} E_{\Omega,\text{temp}}(\text{t}_i) + E_{\Omega,\text{per}} + \Phi
+E_{co2,total} \leq 100{,}000
$$
-### Time + Scenario
+### Per-Timestep Limit
+
+```python
+peak_power = fx.Effect(
+ label='peak_power',
+ unit='kW',
+ maximum_per_hour=500, # Max 500 kW at any timestep
+)
+```
$$
-\min \quad \sum_{s \in \mathcal{S}} w_s \cdot \left( E_{\Omega}(s) + \Phi(s) \right)
+E_{peak}(t) \leq 500 \quad \forall t
$$
-Where:
-- Periodic effects are **shared across scenarios**: $E_{\Omega,\text{per}}$
-- Temporal effects are **scenario-specific**: $E_{\Omega,\text{temp}}(s) = \sum_{\text{t}_i} E_{\Omega,\text{temp}}(\text{t}_i, s)$
-- $w_s$ is the scenario weight (typically probability)
+### Investment Budget
-### Time + Period
+```python
+capex = fx.Effect(
+ label='capex',
+ unit='€',
+ maximum_periodic=5_000_000, # €5M investment budget
+)
+```
$$
-\min \quad \sum_{y \in \mathcal{Y}} w_y \cdot \left( E_{\Omega}(y) + \Phi(y) \right)
+E_{capex,periodic} \leq 5{,}000{,}000
$$
-Where $w_y$ is the period weight (typically annual discount factor).
-
-### Time + Period + Scenario (Full)
+## Variables
+
+| Variable | Description | When Created |
+|----------|-------------|--------------|
+| $E_{e,temporal}(t)$ | Temporal effect at timestep $t$ | Always |
+| $E_{e,periodic}$ | Periodic (investment) effect | Always |
+| $E_{e,total}$ | Total effect | Always |
+| $penalty$ | Sum of all penalty contributions | Always |
+
+## Parameters
+
+| Parameter | Python Name | Description |
+|-----------|-------------|-------------|
+| - | `is_objective` | If True, this effect is minimized |
+| - | `is_standard` | If True, allows shorthand effect syntax |
+| - | `maximum_total` | Upper bound on total effect |
+| - | `minimum_total` | Lower bound on total effect |
+| - | `maximum_per_hour` | Upper bound per timestep |
+| - | `maximum_periodic` | Upper bound on periodic (investment) part |
+| - | `share_from_temporal` | Cross-effect contributions (temporal) |
+| - | `share_from_periodic` | Cross-effect contributions (periodic) |
+
+## Usage Examples
+
+### Basic Cost Minimization
+
+```python
+costs = fx.Effect(
+ label='costs',
+ unit='€',
+ is_objective=True,
+ is_standard=True, # Allows shorthand: effects_per_flow_hour={'costs': 50}
+)
+
+# Elements contribute via effects_per_flow_hour
+gas_flow = fx.Flow(
+ label='gas',
+ bus=gas_bus,
+ size=100,
+ effects_per_flow_hour={'costs': 50},
+)
+```
+
+### Cost + CO₂ Constraint (ε-Constraint Method)
+
+```python
+costs = fx.Effect(label='costs', unit='€', is_objective=True)
+co2 = fx.Effect(label='co2', unit='kg', maximum_total=50_000)
+
+# Generator contributes to both
+generator_flow = fx.Flow(
+ label='power',
+ bus=electricity_bus,
+ size=100,
+ effects_per_flow_hour={
+ 'costs': 45, # €45/MWh
+ 'co2': 0.4, # 0.4 kg/kWh = 400 kg/MWh
+ },
+)
+```
+
+Minimize costs while staying under 50 tonnes CO₂.
+
+### Carbon Pricing (Weighted Sum Method)
+
+```python
+co2 = fx.Effect(label='co2', unit='kg')
+
+costs = fx.Effect(
+ label='costs',
+ unit='€',
+ is_objective=True,
+ share_from_temporal={'co2': 0.08}, # €80/tonne
+)
+
+# Generator only specifies CO₂
+generator_flow = fx.Flow(
+ label='power',
+ bus=electricity_bus,
+ size=100,
+ effects_per_flow_hour={'co2': 0.4},
+)
+```
+
+CO₂ automatically converted to costs. No need to specify cost contribution separately.
+
+### Peak Power Constraint
+
+```python
+peak = fx.Effect(
+ label='grid_peak',
+ unit='kW',
+ maximum_per_hour=500,
+)
+
+grid_import = fx.Flow(
+ label='grid',
+ bus=electricity_bus,
+ size=1000,
+ effects_per_flow_hour={'grid_peak': 1}, # 1 kW per kW flow
+)
+```
+
+Grid import limited to 500 kW at any timestep.
+
+### Investment Budget
+
+```python
+capex = fx.Effect(label='capex', unit='€', maximum_periodic=1_000_000)
+opex = fx.Effect(label='opex', unit='€')
+total_costs = fx.Effect(label='total', unit='€', is_objective=True)
+
+# Link them
+total_costs.share_from_periodic = {'capex': 1}
+total_costs.share_from_temporal = {'opex': 1}
+
+# Battery investment
+battery = fx.Storage(
+ ...,
+ capacity_in_flow_hours=InvestParameters(
+ maximum_size=1000,
+ specific_effects={'capex': 500}, # €500/kWh
+ ),
+)
+```
+
+Optimize total costs while respecting €1M investment budget.
+
+## Multi-Dimensional Effects
+
+When using periods and scenarios (see [Dimensions](dimensions.md)), effects aggregate with weights:
$$
-\min \quad \sum_{y \in \mathcal{Y}} \left[ w_y \cdot E_{\Omega,\text{per}}(y) + \sum_{s \in \mathcal{S}} w_{y,s} \cdot \left( E_{\Omega,\text{temp}}(y,s) + \Phi(y,s) \right) \right]
+\min \sum_{periods} w_{period} \cdot \sum_{scenarios} w_{scenario} \cdot E(period, scenario)
$$
-**Key Principle:**
-- **Periodic effects within a period are shared across all scenarios** (investment made once)
-- **Temporal effects are independent per scenario** (different operations)
-- Scenarios and periods are **operationally independent**, coupled only through the weighted objective
-
----
-
-## Summary Table
-
-| Concept | Formulation | Time Dependency | Dimension Indexing |
-|---------|-------------|-----------------|-------------------|
-| **Temporal share** | $s_{l \rightarrow e, \text{temp}}(\text{t}_i)$ | Time-dependent | $(t, y, s)$ when present |
-| **Periodic share** | $s_{l \rightarrow e, \text{per}}$ | Time-independent | $(y)$ when periods present |
-| **Total temporal effect** | $E_{e,\text{temp},\text{tot}} = \sum_{\text{t}_i} E_{e,\text{temp}}(\text{t}_i)$ | Sum over time | Depends on dimensions |
-| **Total periodic effect** | $E_{e,\text{per}}$ | Constant | $(y)$ when periods present |
-| **Total effect** | $E_e = E_{e,\text{per}} + E_{e,\text{temp},\text{tot}}$ | Combined | Depends on dimensions |
-| **Objective** | $\min(E_{\Omega} + \Phi)$ | With weights when multi-dimensional | See formulations above |
-
----
+**Key principle:** Investment decisions (periodic effects) are shared across scenarios within a period, while operational effects are scenario-specific.
-## Implementation
+## Implementation Details
- **Element Class:** [`Effect`][flixopt.effects.Effect]
- **Model Class:** [`EffectModel`][flixopt.effects.EffectModel]
@@ -386,7 +291,8 @@ $$
## See Also
-- **System-Level:** [Dimensions](dimensions.md) - Multi-dimensional modeling with periods and scenarios
-- **Elements:** [Flow](elements/Flow.md) - Temporal contributions via `effects_per_flow_hour`
-- **Features:** [InvestParameters](features/InvestParameters.md) - Periodic contributions via investment effects
-- **Components:** [Bus](elements/Bus.md) - Penalty contributions via `excess_penalty_per_flow_hour`
+- [Bus](elements/Bus.md) — Penalty contributions from balance violations
+- [Flow](elements/Flow.md) — Temporal contributions via `effects_per_flow_hour`
+- [InvestParameters](features/InvestParameters.md) — Periodic contributions from investments
+- [Dimensions](dimensions.md) — Multi-period and scenario handling
+- [Core Concepts: Effects](../core-concepts.md#effects-what-youre-tracking) — High-level overview
diff --git a/docs/user-guide/mathematical-notation/elements/Bus.md b/docs/user-guide/mathematical-notation/elements/Bus.md
index 6f529d1e9..53653bf37 100644
--- a/docs/user-guide/mathematical-notation/elements/Bus.md
+++ b/docs/user-guide/mathematical-notation/elements/Bus.md
@@ -1,116 +1,147 @@
# Bus
-A Bus represents a node in the energy/material flow network where flow balance constraints ensure conservation (inflows equal outflows).
+A Bus is a connection point where flows meet and must balance. Think of it as a junction in your system where energy or material from multiple sources combines and is distributed to multiple consumers.
-=== "Variables"
+!!! example "Real-world examples"
+ - **Heat Bus** — where boiler output, heat pump output, and storage discharge meet building demand
+ - **Electricity Bus** — where generators, grid imports, and battery discharge meet electrical loads
+ - **Gas Bus** — connection point to the gas grid
- | Symbol | Python Name | Description | Domain | Created When |
- |--------|-------------|-------------|--------|--------------|
- | $p_{f_\text{in}}(\text{t}_i)$ | - | Flow rate of incoming flow $f_\text{in}$ at time $\text{t}_i$ | $\mathbb{R}$ | Always (from connected Flows) |
- | $p_{f_\text{out}}(\text{t}_i)$ | - | Flow rate of outgoing flow $f_\text{out}$ at time $\text{t}_i$ | $\mathbb{R}$ | Always (from connected Flows) |
- | $\phi_\text{in}(\text{t}_i)$ | `excess_input` | Missing inflow (shortage) at time $\text{t}_i$ | $\mathbb{R}_+$ | `excess_penalty_per_flow_hour` is specified |
- | $\phi_\text{out}(\text{t}_i)$ | `excess_output` | Excess outflow (surplus) at time $\text{t}_i$ | $\mathbb{R}_+$ | `excess_penalty_per_flow_hour` is specified |
+## The Balance Equation
-=== "Constraints"
+The fundamental rule: **what goes in must equal what goes out**.
- **Nodal balance equation** (always active):
+$$
+\sum_{f \in inputs} p_f(t) = \sum_{f \in outputs} p_f(t)
+$$
- $$\label{eq:bus_balance}
- \sum_{f_\text{in} \in \mathcal{F}_\text{in}} p_{f_\text{in}}(\text{t}_i) =
- \sum_{f_\text{out} \in \mathcal{F}_\text{out}} p_{f_\text{out}}(\text{t}_i)
- $$
+At every timestep $t$, the sum of all incoming flow rates must equal the sum of all outgoing flow rates.
- ---
+!!! note "Direction matters"
+ Flows have a defined direction. An *input* to the bus means energy/material flowing **into** the bus. An *output* means flowing **out of** the bus.
- **Modified balance with excess** (when `excess_penalty_per_flow_hour` specified):
+## When Balance Can't Be Met: Excess Variables
- $$\label{eq:bus_balance_excess}
- \sum_{f_\text{in} \in \mathcal{F}_\text{in}} p_{f_\text{in}}(\text{t}_i) + \phi_\text{in}(\text{t}_i) =
- \sum_{f_\text{out} \in \mathcal{F}_\text{out}} p_{f_\text{out}}(\text{t}_i) + \phi_\text{out}(\text{t}_i)
- $$
+Sometimes your model might be infeasible — the available supply simply can't meet demand. Rather than the solver failing with an unhelpful error, flixOpt can introduce **excess variables** that allow imbalance at a penalty cost.
- ---
+With excess enabled, the balance becomes:
- **Penalty cost** (when `excess_penalty_per_flow_hour` specified):
+$$
+\sum_{f \in inputs} p_f(t) + \phi_{in}(t) = \sum_{f \in outputs} p_f(t) + \phi_{out}(t)
+$$
- $$\label{eq:bus_penalty}
- \Phi(\text{t}_i) = \text a_{b \rightarrow \Phi}(\text{t}_i) \cdot \Delta \text{t}_i \cdot [ \phi_\text{in}(\text{t}_i) + \phi_\text{out}(\text{t}_i) ]
- $$
+Where:
- **Mathematical Patterns:** [Basic Equality](../modeling-patterns/bounds-and-states.md)
+- $\phi_{in}(t)$ — "virtual supply" to cover shortages (excess input)
+- $\phi_{out}(t)$ — "virtual demand" to absorb surplus (excess output)
-=== "Parameters"
+Both variables are penalized in the objective:
- | Symbol | Python Parameter | Description | Default |
- |--------|------------------|-------------|---------|
- | $\text a_{b \rightarrow \Phi}(\text{t}_i)$ | `excess_penalty_per_flow_hour` | Penalty coefficient for balance violations (cost per unit flow-hour) | 1e5 |
- | $\Delta \text{t}_i$ | - | Timestep duration (hours) | From system time index |
- | $\mathcal{F}_\text{in}$ | - | Set of all incoming flows to the bus | From connected Flows |
- | $\mathcal{F}_\text{out}$ | - | Set of all outgoing flows from the bus | From connected Flows |
+$$
+penalty(t) = (\phi_{in}(t) + \phi_{out}(t)) \cdot \Delta t \cdot penalty\_rate
+$$
-=== "Use Cases"
+!!! tip "Debugging with excess"
+ If excess variables are non-zero in your solution, it means your system couldn't meet all constraints. Check:
- ## Basic Bus with Strict Balance
+ - Is demand too high for available capacity?
+ - Are there timesteps where no supply is available?
+ - Did you forget to connect a component?
- ```python
- from flixopt import Bus
+## Variables
- electricity_grid = Bus(
- label='electricity_grid',
- excess_penalty_per_flow_hour=None, # No imbalance allowed
- )
- ```
+| Variable | Python Name | Description | When Created |
+|----------|-------------|-------------|--------------|
+| $\phi_{in}(t)$ | `excess_input` | Virtual supply to cover shortages | `excess_penalty_per_flow_hour` is set |
+| $\phi_{out}(t)$ | `excess_output` | Virtual demand to absorb surplus | `excess_penalty_per_flow_hour` is set |
- **Variables:** Flow rates from connected flows (no excess variables)
+## Parameters
- **Constraints:** $\eqref{eq:bus_balance}$ enforces strict equality: all electricity inflows must exactly equal all outflows at every timestep.
+| Parameter | Python Name | Description | Default |
+|-----------|-------------|-------------|---------|
+| $penalty\_rate$ | `excess_penalty_per_flow_hour` | Cost per unit of imbalance | `1e5` (high) |
- ---
+## Usage Examples
- ## Bus with Excess Penalty
+### Strict Balance (No Imbalance Allowed)
- ```python
- from flixopt import Bus
+```python
+electricity_bus = fx.Bus(
+ label='electricity',
+ excess_penalty_per_flow_hour=None # No slack allowed
+)
+```
- heat_network = Bus(
- label='heating_network',
- excess_penalty_per_flow_hour=1000, # High penalty for unmet demand
- )
- ```
+If balance can't be achieved, the solver returns infeasible.
- **Variables:** Flow rates + `excess_input` + `excess_output`
+### Balance with Penalty (Debugging/Soft Constraints)
- **Constraints:** $\eqref{eq:bus_balance_excess}$ allows violations with penalty $\eqref{eq:bus_penalty}$
+```python
+heat_bus = fx.Bus(
+ label='heat',
+ excess_penalty_per_flow_hour=1e5 # High penalty for imbalance
+)
+```
- This allows the model to violate the heat balance if necessary, but applies a penalty of 1000 cost units per kWh of unbalanced flow. Useful for debugging infeasible models or modeling emergency scenarios.
+Imbalance is allowed but heavily penalized. Use this to:
- ---
+- Debug infeasible models
+- Model emergency scenarios
+- Allow small numerical tolerances
- ## Time-Varying Penalty
+### Time-Varying Penalty
- ```python
- from flixopt import Bus
- import numpy as np
+```python
+# Higher penalty during peak hours
+penalty_profile = [100, 100, 500, 500, 500, 100, 100, ...]
- material_hub = Bus(
- label='material_processing_hub',
- excess_penalty_per_flow_hour=np.array([100, 200, 300, 500]), # Higher penalty during peak hours
- )
- ```
+material_bus = fx.Bus(
+ label='material',
+ excess_penalty_per_flow_hour=penalty_profile
+)
+```
- **Variables:** Flow rates + `excess_input` + `excess_output`
+## How Buses Connect Components
- **Constraints:** $\eqref{eq:bus_balance_excess}$ with time-varying penalty $\eqref{eq:bus_penalty}$ where $\text a_{b \rightarrow \Phi}(\text{t}_i)$ varies by timestep.
+Buses don't exist in isolation — they connect components through flows:
----
+```
+ ┌─────────┐
+ gas_in ───────►│ Gas │
+ │ Bus │
+ └────┬────┘
+ │ gas_out
+ ▼
+ ┌─────────┐
+ │ Boiler │
+ └────┬────┘
+ │ heat_out
+ ▼
+ ┌─────────┐
+ storage_out ──►│ Heat │◄─── heat_pump_out
+ │ Bus │
+ └────┬────┘
+ │ demand_in
+ ▼
+ ┌─────────┐
+ │ Demand │
+ │ (Sink) │
+ └─────────┘
+```
-## Implementation
+Each arrow is a Flow. The Bus ensures that at every timestep:
+
+$$
+heat\_pump\_out + boiler\_out + storage\_out = demand\_in
+$$
+
+## Implementation Details
- **Element Class:** [`Bus`][flixopt.elements.Bus]
- **Model Class:** [`BusModel`][flixopt.elements.BusModel]
## See Also
-- **Elements:** [Flow](Flow.md) · [Storage](Storage.md) · [LinearConverter](LinearConverter.md)
-- **System-Level:** [Effects, Penalty & Objective](../effects-penalty-objective.md)
-- **Patterns:** [Modeling Patterns](../modeling-patterns/index.md)
+- [Flow](Flow.md) — The flows that connect to buses
+- [Effects & Objective](../effects-penalty-objective.md) — How penalties affect the objective
+- [Core Concepts: Buses](../../core-concepts.md#buses-where-things-connect) — High-level overview
diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md
index 76d4a62ee..9492d2fba 100644
--- a/docs/user-guide/mathematical-notation/elements/Flow.md
+++ b/docs/user-guide/mathematical-notation/elements/Flow.md
@@ -1,195 +1,220 @@
# Flow
-A Flow represents the transfer of energy or material between a Bus and a Component, with the flow rate as the primary optimization variable.
+A Flow represents the movement of energy or material between a component and a bus. It's the primary optimization variable — the solver decides how much flows at each timestep.
-=== "Variables"
+!!! example "Real-world examples"
+ - Heat output from a boiler to the heat bus
+ - Electricity import from the grid
+ - Gas consumption by a CHP unit
+ - Charging power into a battery
- | Symbol | Python Name | Description | Domain | Created When |
- |--------|-------------|-------------|--------|--------------|
- | $p(\text{t}_{i})$ | `flow_rate` | Flow rate at each timestep | $\mathbb{R}$ | Always |
- | $\text P$ | `size` | Flow capacity (decision variable) | $\mathbb{R}_+$ | `size` is `InvestParameters` |
- | $s_\text{invest}$ | `invest_binary` | Binary investment decision | $\{0,1\}$ | `size` is `InvestParameters` |
- | - | `total_flow_hours` | Cumulative flow-hours per period | $\mathbb{R}_+$ | `flow_hours_min/max` or `load_factor_min/max` specified |
- | $s(\text{t}_i)$ | `on_off_state` | Binary on/off state | $\{0,1\}$ | `on_off_parameters` specified |
- | - | `switch_on` | Startup indicator | $\{0,1\}$ | `on_off_parameters` specified |
- | - | `switch_off` | Shutdown indicator | $\{0,1\}$ | `on_off_parameters` specified |
+## Core Concept: Size and Flow Rate
-=== "Constraints"
+Every flow has two key quantities:
- **Flow rate bounds** (always active):
+- **Size** ($P$) — The capacity or maximum possible flow rate. Think of it as "how big is the pipe?"
+- **Flow Rate** ($p(t)$) — The actual flow at each timestep. This is what the optimizer decides.
- $$\label{eq:flow_bounds}
- \text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) \leq p(\text{t}_{i}) \leq \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i})
- $$
+The fundamental constraint:
- ---
+$$
+p_{min}(t) \leq p(t) \leq p_{max}(t)
+$$
- **Load factor** (when `load_factor_min` or `load_factor_max` specified):
+Usually, bounds are defined *relative* to the size:
- $$\label{eq:flow_load_factor}
- \text{LF}_\text{min} \cdot \text P \cdot N_t \leq \sum_{i} p(\text{t}_{i}) \leq \text{LF}_\text{max} \cdot \text P \cdot N_t
- $$
+$$
+P \cdot relative\_min(t) \leq p(t) \leq P \cdot relative\_max(t)
+$$
- ---
+!!! example "A 100 kW boiler that can modulate down to 30%"
+ - Size: $P = 100$ kW
+ - Relative minimum: 0.3
+ - Relative maximum: 1.0
+ - Constraint: $30 \leq p(t) \leq 100$ kW
- **Flow hours limits** (when any flow hours parameter specified):
+## Flow Hours: Energy vs Power
- $$\label{eq:flow_hours}
- \text{FH}_\text{min} \leq \sum_{i} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{FH}_\text{max}
- $$
+**Flow rate** is power (kW, MW). **Flow hours** is energy (kWh, MWh) — flow rate times time:
- ---
+$$
+flow\_hours(t) = p(t) \cdot \Delta t
+$$
- **Fixed profile** (when `fixed_relative_profile` specified):
+| Flow Rate | Timestep | Flow Hours |
+|-----------|----------|------------|
+| 100 kW | 1 hour | 100 kWh |
+| 100 kW | 15 min | 25 kWh |
+| 50 MW | 1 hour | 50 MWh |
- $$\label{eq:flow_profile}
- p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i})
- $$
+This matters for costs: `effects_per_flow_hour` is cost per energy (€/MWh), not per power.
- ---
+## Constraints
- **On/off operation** (when `on_off_parameters` specified):
+### Capacity Bounds (Always Active)
- See [OnOffParameters](../features/OnOffParameters.md)
+$$
+P \cdot relative\_min(t) \leq p(t) \leq P \cdot relative\_max(t)
+$$
- ---
+### Fixed Profile (Renewable Generation, Demands)
- **Mathematical Patterns:** [Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds), [Scaled Bounds with State](../modeling-patterns/bounds-and-states.md#scaled-bounds-with-state)
+When you have a known profile (solar irradiance, demand curve):
-=== "Parameters"
+$$
+p(t) = P \cdot profile(t)
+$$
- | Symbol | Python Parameter | Description | Default |
- |--------|------------------|-------------|---------|
- | $\text{FH}_\text{max}$ | `flow_hours_max` | Maximum cumulative flow-hours | None |
- | $\text{FH}_\text{min}$ | `flow_hours_min` | Minimum cumulative flow-hours | None |
- | $\text{LF}_\text{max}$ | `load_factor_max` | Maximum average utilization (0-1) | 1 |
- | $\text{LF}_\text{min}$ | `load_factor_min` | Minimum average utilization (0-1) | 0 |
- | $\text P$ | `size` | Flow capacity | Required |
- | $\text p^{\text{L}}_{\text{rel}}(\text{t}_{i})$ | `relative_minimum` | Relative lower bound (fraction of size) | 0 |
- | $\text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$ | `relative_maximum` | Relative upper bound (fraction of size) | 1 |
- | $\text{profile}(\text{t}_{i})$ | `fixed_relative_profile` | Array of relative flow rates | None |
- | - | `fixed_relative_profile` | Array of relative flow rates | None |
- | - | `flow_hours_max_over_periods` | Maximum flow-hours across all periods | None |
- | - | `flow_hours_min_over_periods` | Minimum flow-hours across all periods | None |
- | - | `on_off_parameters` | OnOffParameters instance | None |
- | - | `previous_flow_rate` | Flow rate before optimization horizon | None |
+The flow rate is fixed to the profile — no optimization freedom.
-=== "Use Cases"
+### Load Factor Limits
- ## Basic Fixed Capacity Flow
+Constrain average utilization over the period:
- ```python
- from flixopt import Flow
+$$
+LF_{min} \cdot P \cdot N_t \leq \sum_t p(t) \leq LF_{max} \cdot P \cdot N_t
+$$
- generator_output = Flow(
- label='electricity_out',
- bus='electricity_grid',
- size=100, # 100 MW capacity
- relative_minimum=0.4, # Cannot operate below 40 MW
- effects_per_flow_hour={'fuel_cost': 45, 'co2_emissions': 0.8},
- )
- ```
+Where $N_t$ is the number of timesteps.
- **Variables:** `flow_rate[t]`
+!!! example "Baseload plant must run at least 70% average"
+ - Size: 200 MW
+ - Load factor minimum: 0.7
+ - Over 8760 hours: must produce at least $200 \times 0.7 \times 8760 = 1{,}226{,}400$ MWh
- **Constraints:** $\eqref{eq:flow_bounds}$ with $40 \leq p(t) \leq 100$ MW
+### Flow Hours Limits
- ---
+Constrain total energy over the period:
- ## Investment Decision
+$$
+FH_{min} \leq \sum_t p(t) \cdot \Delta t \leq FH_{max}
+$$
- ```python
- from flixopt import Flow, InvestParameters
+!!! example "Annual gas limit of 10,000 MWh"
+ Sets $FH_{max} = 10{,}000$ MWh.
- battery_flow = Flow(
- label='electricity_storage',
- bus='electricity_grid',
- size=InvestParameters(
- minimum_size=10, # Minimum 10 MWh
- maximum_size=100, # Maximum 100 MWh
- specific_effects={'cost': 150_000}, # €150k/MWh annualized
- ),
- )
- ```
+## Variables
- **Variables:** `flow_rate[t]`, `size`, `invest_binary`
+| Variable | Python Name | Description | When Created |
+|----------|-------------|-------------|--------------|
+| $p(t)$ | `flow_rate` | Flow rate at each timestep | Always |
+| $P$ | `size` | Capacity (decision variable) | When `size` is `InvestParameters` |
+| $s(t)$ | `on_off_state` | Binary on/off state | When `on_off_parameters` set |
- **Constraints:** $\eqref{eq:flow_bounds}$ with $0 \leq p(t) \leq \text{size}$, plus investment constraints
+## Parameters
- ---
+| Parameter | Python Name | Description | Default |
+|-----------|-------------|-------------|---------|
+| $P$ | `size` | Flow capacity | Required |
+| $relative\_min$ | `relative_minimum` | Min as fraction of size | 0 |
+| $relative\_max$ | `relative_maximum` | Max as fraction of size | 1 |
+| $profile$ | `fixed_relative_profile` | Fixed relative profile | None |
+| $LF_{min}$ | `load_factor_min` | Minimum average utilization | None |
+| $LF_{max}$ | `load_factor_max` | Maximum average utilization | None |
+| $FH_{min}$ | `flow_hours_min` | Minimum total energy | None |
+| $FH_{max}$ | `flow_hours_max` | Maximum total energy | None |
- ## On/Off Operation
+## Usage Examples
- ```python
- from flixopt import Flow, OnOffParameters
+### Basic Flow with Fixed Capacity
- heat_pump_flow = Flow(
- label='heat_output',
- bus='heating_network',
- size=50, # 50 kW thermal
- relative_minimum=0.3, # Minimum 15 kW when on
- on_off_parameters=OnOffParameters(
- effects_per_switch_on={'startup_cost': 100},
- consecutive_on_hours_min=2, # Min run time
- consecutive_off_hours_min=1, # Min off time
- ),
- )
- ```
+```python
+heat_output = fx.Flow(
+ label='heat_out',
+ bus=heat_bus,
+ size=100, # 100 kW capacity
+ relative_minimum=0.3, # Can't go below 30 kW
+)
+```
- **Variables:** `flow_rate[t]`, `on_off_state[t]`, `switch_on[t]`, `switch_off[t]`
+### Flow with Costs
- **Constraints:** $\eqref{eq:flow_bounds}$ plus on/off constraints from [OnOffParameters](../features/OnOffParameters.md)
+```python
+gas_input = fx.Flow(
+ label='gas_in',
+ bus=gas_bus,
+ size=150,
+ effects_per_flow_hour={'costs': 50}, # €50/MWh gas price
+)
+```
- ---
+### Fixed Profile (Solar PV)
- ## Fixed Profile (Renewable)
+```python
+solar_profile = [0, 0, 0.1, 0.4, 0.8, 1.0, 0.9, 0.6, 0.2, 0, 0, 0] # Relative to peak
- ```python
- import numpy as np
- from flixopt import Flow
+solar_output = fx.Flow(
+ label='solar_out',
+ bus=electricity_bus,
+ size=500, # 500 kW peak
+ fixed_relative_profile=solar_profile,
+)
+```
- solar_generation = Flow(
- label='solar_power',
- bus='electricity_grid',
- size=25, # 25 MW installed
- fixed_relative_profile=np.array([0, 0.1, 0.4, 0.8, 0.9, 0.7, 0.3, 0.1, 0]),
- )
- ```
+### Investment Decision (Optimized Size)
- **Variables:** `flow_rate[t]` (fixed by profile)
+```python
+from flixopt import InvestParameters
- **Constraints:** $\eqref{eq:flow_profile}$
+battery_flow = fx.Flow(
+ label='battery_power',
+ bus=electricity_bus,
+ size=InvestParameters(
+ minimum_size=0,
+ maximum_size=1000, # Up to 1 MW
+ specific_effects={'costs': 100_000}, # €100k/MW/year
+ ),
+)
+```
- ---
-
- ## Load Factor Constraint
-
- ```python
- from flixopt import Flow
-
- baseload_plant = Flow(
- label='baseload_output',
- bus='electricity',
- size=200, # 200 MW
- load_factor_min=0.7, # Must run at least 70% average
- effects_per_flow_hour={'cost': 30},
- )
- ```
-
- **Variables:** `flow_rate[t]`, `total_flow_hours`
-
- **Constraints:** $\eqref{eq:flow_bounds}$, $\eqref{eq:flow_load_factor}$
-
----
-
-## Implementation
+See [InvestParameters](../features/InvestParameters.md) for details.
+
+### On/Off Operation
+
+```python
+from flixopt import OnOffParameters
+
+generator_output = fx.Flow(
+ label='power_out',
+ bus=electricity_bus,
+ size=50,
+ relative_minimum=0.4, # 40% minimum when ON
+ on_off_parameters=OnOffParameters(
+ effects_per_switch_on={'costs': 500}, # €500 startup cost
+ consecutive_on_hours_min=2, # Must run at least 2 hours
+ ),
+)
+```
+
+See [OnOffParameters](../features/OnOffParameters.md) for details.
+
+## How Flows Connect to Components
+
+Flows are always part of a component:
+
+```python
+boiler = fx.linear_converters.Boiler(
+ label='boiler',
+ eta=0.9,
+ # These flows are created automatically:
+ # - inputs: gas flow from gas_bus
+ # - outputs: heat flow to heat_bus
+ Q_th=fx.Flow(label='heat', bus=heat_bus, size=100),
+ Q_fu=fx.Flow(label='fuel', bus=gas_bus, size=111), # 100/0.9
+)
+```
+
+The component defines how input and output flows relate (conversion equations).
+
+## Implementation Details
- **Element Class:** [`Flow`][flixopt.elements.Flow]
- **Model Class:** [`FlowModel`][flixopt.elements.FlowModel]
## See Also
-- **Features:** [OnOffParameters](../features/OnOffParameters.md) · [InvestParameters](../features/InvestParameters.md)
-- **Elements:** [Bus](Bus.md) · [Storage](Storage.md) · [LinearConverter](LinearConverter.md)
-- **Patterns:** [Modeling Patterns](../modeling-patterns/index.md) · [Bounds and States](../modeling-patterns/bounds-and-states.md)
-- **Effects:** [Effects, Penalty & Objective](../effects-penalty-objective.md)
+- [Bus](Bus.md) — Where flows connect
+- [LinearConverter](LinearConverter.md) — Components that use flows
+- [InvestParameters](../features/InvestParameters.md) — Optimizing flow capacity
+- [OnOffParameters](../features/OnOffParameters.md) — Binary on/off operation
+- [Core Concepts: Flows](../../core-concepts.md#flows-what-moves-between-elements) — High-level overview
diff --git a/docs/user-guide/mathematical-notation/elements/LinearConverter.md b/docs/user-guide/mathematical-notation/elements/LinearConverter.md
index 5cd4a57f8..8100d9315 100644
--- a/docs/user-guide/mathematical-notation/elements/LinearConverter.md
+++ b/docs/user-guide/mathematical-notation/elements/LinearConverter.md
@@ -1,178 +1,249 @@
# LinearConverter
-A LinearConverter defines linear relationships (ratios) between incoming and outgoing flows, representing energy/material conversion processes with fixed or variable efficiencies.
+A LinearConverter transforms inputs into outputs with defined conversion ratios. It's the workhorse for modeling any equipment that converts one form of energy or material into another.
-=== "Variables"
+!!! example "Real-world examples"
+ - **Gas boiler** — gas → heat (90% efficiency)
+ - **Heat pump** — electricity → heat (COP 3.5 = 350% "efficiency")
+ - **CHP** — gas → electricity + heat (35% electrical, 50% thermal)
+ - **Electrolyzer** — electricity → hydrogen (65% efficiency)
- | Symbol | Python Name | Description | Domain | Created When |
- |--------|-------------|-------------|--------|--------------|
- | $p_{f_\text{in}}(\text{t}_i)$ | - | Flow rate of incoming flow $f_\text{in}$ at time $\text{t}_i$ | $\mathbb{R}$ | Always (from input Flows) |
- | $p_{f_\text{out}}(\text{t}_i)$ | - | Flow rate of outgoing flow $f_\text{out}$ at time $\text{t}_i$ | $\mathbb{R}$ | Always (from output Flows) |
- | $\lambda_k$ | - | Piecewise lambda variables | $\mathbb{R}_+$ | `piecewise_conversion` is specified |
+## Core Concept: Conversion Factors
-=== "Constraints"
+The fundamental equation links inputs and outputs:
- **General linear ratio constraint** (when `conversion_factors` specified):
+$$
+\sum_{f \in inputs} a_f \cdot p_f(t) = \sum_{f \in outputs} b_f \cdot p_f(t)
+$$
- $$\label{eq:Linear-Transformer-Ratio}
- \sum_{f_{\text{in}} \in \mathcal F_{in}} \text a_{f_{\text{in}}}(\text{t}_i) \cdot p_{f_\text{in}}(\text{t}_i) = \sum_{f_{\text{out}} \in \mathcal F_{out}} \text b_{f_\text{out}}(\text{t}_i) \cdot p_{f_\text{out}}(\text{t}_i)
- $$
+The conversion factors $a$ and $b$ define the relationship between flows.
- ---
+### Simple Case: One Input, One Output
- **Simplified single-input single-output** (special case of above):
+For a gas boiler with 90% efficiency:
- $$\label{eq:Linear-Transformer-Ratio-simple}
- \text a(\text{t}_i) \cdot p_{f_\text{in}}(\text{t}_i) = p_{f_\text{out}}(\text{t}_i)
- $$
+$$
+0.9 \cdot p_{gas}(t) = 1 \cdot p_{heat}(t)
+$$
- Where $\text a$ represents the conversion efficiency or conversion ratio (COP for heat pumps, thermal efficiency for boilers, electrical efficiency for generators).
+Or equivalently: $p_{heat}(t) = 0.9 \cdot p_{gas}(t)$
- ---
+!!! note "Direction of efficiency"
+ The factor is on the **input** side. If you put in 100 kW of gas, you get out 90 kW of heat.
- **Piecewise linear conversion** (when `piecewise_conversion` specified):
+### Multiple Outputs: CHP
- See [Piecewise](../features/Piecewise.md) for the detailed mathematical formulation of piecewise linear relationships between flows.
+A CHP unit produces both electricity and heat from fuel:
- **Mathematical Patterns:** [Linear Equality Constraints](../modeling-patterns/bounds-and-states.md), [Piecewise Linear Approximations](../features/Piecewise.md)
+$$
+0.35 \cdot p_{fuel}(t) = p_{electricity}(t)
+$$
-=== "Parameters"
+$$
+0.50 \cdot p_{fuel}(t) = p_{heat}(t)
+$$
- | Symbol | Python Parameter | Description | Default |
- |--------|------------------|-------------|---------|
- | $\mathcal F_{in}$ | `inputs` | Set of all incoming flows | Required |
- | $\mathcal F_{out}$ | `outputs` | Set of all outgoing flows | Required |
- | $\text a_{f_\text{in}}(\text{t}_i)$ | `conversion_factors` | Conversion factor for incoming flow (weight/efficiency coefficient) | None |
- | $\text b_{f_\text{out}}(\text{t}_i)$ | `conversion_factors` | Conversion factor for outgoing flow (weight/efficiency coefficient) | None |
- | - | `on_off_parameters` | OnOffParameters for on/off operation | None |
- | - | `piecewise_conversion` | PiecewiseConversion for non-linear behavior | None |
+These are two separate conversion equations from the same input.
-=== "Use Cases"
+### Heat Pump: COP > 1
- ## Simple Boiler (Single Input/Output)
+A heat pump has COP (Coefficient of Performance) of 3.5:
- ```python
- from flixopt import LinearConverter, Flow
+$$
+3.5 \cdot p_{electricity}(t) = p_{heat}(t)
+$$
- boiler = LinearConverter(
- label='gas_boiler',
- inputs=[Flow(label='gas_in', bus='natural_gas', size=100)],
- outputs=[Flow(label='heat_out', bus='heating', size=90)],
- conversion_factors=[{'gas_in': 0.9, 'heat_out': 1}], # 90% efficiency
- )
- ```
+The factor is greater than 1 because the heat pump also extracts energy from the environment.
- **Variables:** Flow rates from input and output flows
+## Variables
- **Constraints:** $\eqref{eq:Linear-Transformer-Ratio-simple}$ with $0.9 \cdot p_\text{gas}(t) = p_\text{heat}(t)$, representing 90% thermal efficiency
+| Variable | Python Name | Description | When Created |
+|----------|-------------|-------------|--------------|
+| $p_{in}(t)$ | (from input Flows) | Input flow rates | Always |
+| $p_{out}(t)$ | (from output Flows) | Output flow rates | Always |
- ---
+The converter itself doesn't create new variables — it creates constraints linking the flow variables.
- ## CHP Plant (One Input, Two Outputs)
+## Parameters
- ```python
- from flixopt import LinearConverter, Flow
+| Parameter | Python Name | Description |
+|-----------|-------------|-------------|
+| Input flows | `inputs` | List of input Flows |
+| Output flows | `outputs` | List of output Flows |
+| Conversion factors | `conversion_factors` | List of factor dictionaries |
- chp = LinearConverter(
- label='chp_unit',
- inputs=[Flow(label='fuel_in', bus='natural_gas', size=100)],
- outputs=[
- Flow(label='electricity_out', bus='electricity', size=35),
- Flow(label='heat_out', bus='heating', size=55),
- ],
- conversion_factors=[
- {'fuel_in': 0.35, 'electricity_out': 1}, # 35% electrical efficiency
- {'fuel_in': 0.55, 'heat_out': 1}, # 55% thermal efficiency
- ],
- )
- ```
+## Usage Examples
- **Variables:** Flow rates from one input and two output flows
+### Gas Boiler (Simple)
- **Constraints:** Two instances of $\eqref{eq:Linear-Transformer-Ratio}$:
- - $0.35 \cdot p_\text{fuel}(t) = p_\text{elec}(t)$
- - $0.55 \cdot p_\text{fuel}(t) = p_\text{heat}(t)$
+```python
+boiler = fx.LinearConverter(
+ label='gas_boiler',
+ inputs=[fx.Flow(label='gas', bus=gas_bus, size=111)],
+ outputs=[fx.Flow(label='heat', bus=heat_bus, size=100)],
+ conversion_factors=[{
+ 'gas': 0.9, # 90% of gas input...
+ 'heat': 1, # ...becomes heat output
+ }],
+)
+```
- ---
+**Constraint:** $0.9 \cdot p_{gas}(t) = p_{heat}(t)$
- ## Heat Pump with Temperature-Dependent COP
+### CHP Unit (One Input, Two Outputs)
- ```python
- from flixopt import LinearConverter, Flow, Piecewise, Piece, PiecewiseConversion
+```python
+chp = fx.LinearConverter(
+ label='chp',
+ inputs=[fx.Flow(label='fuel', bus=gas_bus, size=100)],
+ outputs=[
+ fx.Flow(label='electricity', bus=electricity_bus, size=35),
+ fx.Flow(label='heat', bus=heat_bus, size=50),
+ ],
+ conversion_factors=[
+ {'fuel': 0.35, 'electricity': 1}, # 35% electrical efficiency
+ {'fuel': 0.50, 'heat': 1}, # 50% thermal efficiency
+ ],
+)
+```
- # COP varies from 2.5 (cold) to 4.0 (warm)
- cop_curve = Piecewise(
- [
- Piece((0, 0), (100, 250)), # 0-100 kW elec → 0-250 kW heat (COP 2.5)
- Piece((100, 250), (200, 600)), # 100-200 kW elec → 250-600 kW heat (COP 3.5)
- ]
- )
+**Constraints:**
- heat_pump = LinearConverter(
- label='heat_pump',
- inputs=[Flow(label='electricity_in', bus='electricity', size=200)],
- outputs=[Flow(label='heat_out', bus='heating', size=600)],
- piecewise_conversion=PiecewiseConversion(
- origin_flow='electricity_in',
- piecewise_shares={'heat_out': cop_curve},
- ),
- )
- ```
+- $0.35 \cdot p_{fuel}(t) = p_{electricity}(t)$
+- $0.50 \cdot p_{fuel}(t) = p_{heat}(t)$
- **Variables:** Flow rates + piecewise lambda variables $\lambda_k$
+Total efficiency: 85%
- **Constraints:** Piecewise constraints linking electricity input to heat output with variable COP (see [Piecewise](../features/Piecewise.md))
+### Heat Pump
- ---
-
- ## Converter with Investment Decision
-
- ```python
- from flixopt import LinearConverter, Flow, InvestParameters
-
- electrolyzer = LinearConverter(
- label='hydrogen_electrolyzer',
- inputs=[Flow(
- label='electricity_in',
- bus='electricity',
- size=InvestParameters(
- minimum_size=0,
- maximum_size=1000, # Up to 1 MW
- specific_effects={'cost': 800}, # €800/kW annualized
- ),
- )],
- outputs=[Flow(label='h2_out', bus='hydrogen', size=1)], # Sized by ratio
- conversion_factors=[{'electricity_in': 0.65, 'h2_out': 1}], # 65% efficiency
- )
- ```
-
- **Variables:** Flow rates + `size` variable for investment
-
- **Constraints:** $\eqref{eq:Linear-Transformer-Ratio-simple}$ plus investment constraints from [InvestParameters](../features/InvestParameters.md)
-
----
-
-## Specialized LinearConverter Classes
-
-FlixOpt provides specialized classes that automatically configure the conversion factors based on physical relationships:
-
-- **[`HeatPump`][flixopt.linear_converters.HeatPump]** - COP-based electric heating
-- **[`Power2Heat`][flixopt.linear_converters.Power2Heat]** - Direct electric heating (efficiency ≤ 1)
-- **[`CHP`][flixopt.linear_converters.CHP]** - Combined heat and power generation
-- **[`Boiler`][flixopt.linear_converters.Boiler]** - Fuel to heat conversion
-- **[`HeatPumpWithSource`][flixopt.linear_converters.HeatPumpWithSource]** - Heat pump with explicit ambient source
-
-These provide more intuitive interfaces for common applications.
-
----
-
-## Implementation
+```python
+heat_pump = fx.LinearConverter(
+ label='heat_pump',
+ inputs=[fx.Flow(label='electricity', bus=electricity_bus, size=100)],
+ outputs=[fx.Flow(label='heat', bus=heat_bus, size=350)],
+ conversion_factors=[{
+ 'electricity': 3.5, # COP of 3.5
+ 'heat': 1,
+ }],
+)
+```
+
+**Constraint:** $3.5 \cdot p_{electricity}(t) = p_{heat}(t)$
+
+### Time-Varying Efficiency
+
+Efficiency can vary with time (e.g., heat pump COP depends on outside temperature):
+
+```python
+cop_profile = [3.0, 3.2, 3.5, 4.0, 3.8, 3.5, ...] # COP varies by timestep
+
+heat_pump = fx.LinearConverter(
+ label='heat_pump',
+ inputs=[fx.Flow(label='electricity', bus=electricity_bus, size=100)],
+ outputs=[fx.Flow(label='heat', bus=heat_bus, size=400)],
+ conversion_factors=[{
+ 'electricity': cop_profile,
+ 'heat': 1,
+ }],
+)
+```
+
+## Pre-Built Specialized Components
+
+flixOpt provides convenience classes that set up conversion factors automatically:
+
+### Boiler
+
+```python
+boiler = fx.linear_converters.Boiler(
+ label='boiler',
+ eta=0.9, # Thermal efficiency
+ Q_th=fx.Flow(label='heat', bus=heat_bus, size=100),
+ Q_fu=fx.Flow(label='fuel', bus=gas_bus), # Size calculated automatically
+)
+```
+
+### HeatPump
+
+```python
+heat_pump = fx.linear_converters.HeatPump(
+ label='heat_pump',
+ COP=3.5,
+ P_el=fx.Flow(label='electricity', bus=electricity_bus, size=100),
+ Q_th=fx.Flow(label='heat', bus=heat_bus), # Size = 350 kW
+)
+```
+
+### CHP
+
+```python
+chp = fx.linear_converters.CHP(
+ label='chp',
+ eta_el=0.35,
+ eta_th=0.50,
+ P_el=fx.Flow(label='electricity', bus=electricity_bus, size=35),
+ Q_th=fx.Flow(label='heat', bus=heat_bus, size=50),
+ Q_fu=fx.Flow(label='fuel', bus=gas_bus, size=100),
+)
+```
+
+## Advanced: Piecewise Linear Conversion
+
+For non-linear relationships (e.g., part-load efficiency curves), use piecewise linearization:
+
+```python
+from flixopt import Piecewise, Piece, PiecewiseConversion
+
+# Efficiency varies with load
+efficiency_curve = Piecewise([
+ Piece(start=(0, 0), end=(50, 40)), # 80% efficiency at low load
+ Piece(start=(50, 40), end=(100, 90)), # 90% efficiency at high load
+])
+
+boiler = fx.LinearConverter(
+ label='boiler',
+ inputs=[fx.Flow(label='gas', bus=gas_bus, size=100)],
+ outputs=[fx.Flow(label='heat', bus=heat_bus, size=90)],
+ piecewise_conversion=PiecewiseConversion(
+ origin_flow='gas',
+ piecewise_shares={'heat': efficiency_curve},
+ ),
+)
+```
+
+See [Piecewise Linearization](../features/Piecewise.md) for details.
+
+## On/Off Operation
+
+Add startup costs and minimum run times:
+
+```python
+from flixopt import OnOffParameters
+
+generator = fx.LinearConverter(
+ label='generator',
+ inputs=[fx.Flow(label='fuel', bus=fuel_bus, size=100)],
+ outputs=[fx.Flow(label='power', bus=electricity_bus, size=40, relative_minimum=0.4)],
+ conversion_factors=[{'fuel': 0.4, 'power': 1}],
+ on_off_parameters=OnOffParameters(
+ effects_per_switch_on={'costs': 1000}, # €1000 startup cost
+ consecutive_on_hours_min=4, # Must run at least 4 hours
+ ),
+)
+```
+
+See [On/Off Operation](../features/OnOffParameters.md) for details.
+
+## Implementation Details
- **Component Class:** [`LinearConverter`][flixopt.components.LinearConverter]
- **Model Class:** [`LinearConverterModel`][flixopt.components.LinearConverterModel]
+- **Specialized Classes:** [`Boiler`][flixopt.linear_converters.Boiler], [`HeatPump`][flixopt.linear_converters.HeatPump], [`CHP`][flixopt.linear_converters.CHP]
## See Also
-- **Elements:** [Flow](Flow.md) · [Bus](Bus.md) · [Storage](Storage.md)
-- **Features:** [Piecewise](../features/Piecewise.md) · [InvestParameters](../features/InvestParameters.md) · [OnOffParameters](../features/OnOffParameters.md)
-- **Patterns:** [Modeling Patterns](../modeling-patterns/index.md)
+- [Flow](Flow.md) — Input and output flows
+- [Bus](Bus.md) — Where converters connect
+- [Piecewise Linearization](../features/Piecewise.md) — Non-linear efficiency curves
+- [On/Off Operation](../features/OnOffParameters.md) — Binary operation
+- [Core Concepts: Converters](../../core-concepts.md#converters-transform-one-thing-into-another) — High-level overview
diff --git a/docs/user-guide/mathematical-notation/elements/Storage.md b/docs/user-guide/mathematical-notation/elements/Storage.md
index 27e19d1e9..70300b0ba 100644
--- a/docs/user-guide/mathematical-notation/elements/Storage.md
+++ b/docs/user-guide/mathematical-notation/elements/Storage.md
@@ -1,187 +1,215 @@
# Storage
-A Storage component represents energy or material accumulation with charging/discharging flows, state of charge tracking, and efficiency losses.
+A Storage component accumulates energy or material over time, allowing you to decouple when you produce from when you consume. This is essential for integrating renewables, managing peak demand, and arbitraging price differences.
-=== "Variables"
+!!! example "Real-world examples"
+ - **Battery** — store electricity when cheap, discharge when expensive
+ - **Thermal tank** — buffer heat production from demand
+ - **Warehouse** — inventory buffer in supply chains
+ - **Pumped hydro** — large-scale electricity storage
- | Symbol | Python Name | Description | Domain | Created When |
- |--------|-------------|-------------|--------|--------------|
- | $c(\text{t}_i)$ | `charge_state` | State of charge at time $\text{t}_i$ | $\mathbb{R}_+$ | Always |
- | $p_{f_\text{in}}(\text{t}_i)$ | - | Input flow rate (charging power) at time $\text{t}_i$ | $\mathbb{R}_+$ | Always (from `charging` Flow) |
- | $p_{f_\text{out}}(\text{t}_i)$ | - | Output flow rate (discharging power) at time $\text{t}_i$ | $\mathbb{R}_+$ | Always (from `discharging` Flow) |
- | - | `netto_discharge` | Net discharge rate (discharge - charge) | $\mathbb{R}$ | Always |
- | $\text C$ | `size` | Storage capacity (decision variable) | $\mathbb{R}_+$ | `capacity_in_flow_hours` is `InvestParameters` |
+## Core Concept: Charge State Over Time
-=== "Constraints"
+A storage has a **charge state** $c(t)$ that evolves over time based on charging and discharging:
- **State of charge bounds** (always active):
+$$
+c(t+1) = c(t) \cdot (1 - loss)^{\Delta t} + charge(t) \cdot \eta_{in} - \frac{discharge(t)}{\eta_{out}}
+$$
- $$\label{eq:Storage_Bounds}
- \text C \cdot \text c^{\text{L}}_{\text{rel}}(\text t_{i})
- \leq c(\text{t}_i) \leq
- \text C \cdot \text c^{\text{U}}_{\text{rel}}(\text t_{i})
- $$
+Where:
- ---
+- $c(t)$ — Energy/material stored at time $t$
+- $charge(t)$ — Charging flow rate (power in)
+- $discharge(t)$ — Discharging flow rate (power out)
+- $\eta_{in}$ — Charging efficiency (losses when storing)
+- $\eta_{out}$ — Discharging efficiency (losses when retrieving)
+- $loss$ — Self-discharge rate per hour
- **Storage balance equation** (always active):
+!!! note "Units"
+ - **Capacity** is in energy units (kWh, MWh)
+ - **Charging/discharging flows** are in power units (kW, MW)
+ - The timestep duration $\Delta t$ converts between them
- $$\label{eq:storage_balance}
- \begin{align}
- c(\text{t}_{i+1}) &= c(\text{t}_{i}) \cdot (1-\dot{\text{c}}_\text{rel,loss}(\text{t}_i))^{\Delta \text{t}_{i}} \nonumber \\
- &\quad + p_{f_\text{in}}(\text{t}_i) \cdot \Delta \text{t}_i \cdot \eta_\text{in}(\text{t}_i) \nonumber \\
- &\quad - p_{f_\text{out}}(\text{t}_i) \cdot \Delta \text{t}_i / \eta_\text{out}(\text{t}_i)
- \end{align}
- $$
+## The Storage Balance Equation
- ---
+At each timestep, the charge state updates:
- **Initial charge state** (when `initial_charge_state` specified):
+$$
+c(t_{i+1}) = c(t_i) \cdot (1 - loss)^{\Delta t_i} + p_{in}(t_i) \cdot \Delta t_i \cdot \eta_{in} - p_{out}(t_i) \cdot \Delta t_i / \eta_{out}
+$$
- $$\label{eq:storage_initial}
- c(\text{t}_0) = \text{c}_\text{initial}
- $$
+This equation captures:
- Or for cyclic condition (`initial_charge_state='equals_final'`):
-
- $$\label{eq:storage_cyclic}
- c(\text{t}_0) = c(\text{t}_\text{end})
- $$
+1. **Starting state** — what was stored at the beginning
+2. **Self-discharge** — losses over time (exponential decay)
+3. **Charging** — energy added (with efficiency loss)
+4. **Discharging** — energy removed (with efficiency loss)
- ---
+## Charge State Bounds
- **Final charge state bounds** (when `minimal_final_charge_state` or `maximal_final_charge_state` specified):
+The charge state is bounded by the storage capacity:
- $$\label{eq:storage_final}
- \text c^{\text{L}}_{\text{final}} \leq c(\text{t}_\text{end}) \leq \text c^{\text{U}}_{\text{final}}
- $$
+$$
+C \cdot relative\_min(t) \leq c(t) \leq C \cdot relative\_max(t)
+$$
- **Mathematical Patterns:** [Basic Bounds](../modeling-patterns/bounds-and-states.md#basic-bounds), [Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)
+!!! example "Battery with 20-80% operating range"
+ - Capacity: 1000 kWh
+ - `relative_minimum_charge_state`: 0.2
+ - `relative_maximum_charge_state`: 0.8
+ - Effective range: 200-800 kWh (protects battery life)
-=== "Parameters"
+## Initial and Final Conditions
- | Symbol | Python Parameter | Description | Default |
- |--------|------------------|-------------|---------|
- | $\text C$ | `capacity_in_flow_hours` | Storage capacity | Required |
- | $\text c^{\text{L}}_{\text{rel}}(\text t_{i})$ | `relative_minimum_charge_state` | Relative lower bound (fraction of capacity) | 0 |
- | $\text c^{\text{U}}_{\text{rel}}(\text t_{i})$ | `relative_maximum_charge_state` | Relative upper bound (fraction of capacity) | 1 |
- | $\text{c}_\text{initial}$ | `initial_charge_state` | Charge at start | 0 |
- | $\text c^{\text{L}}_{\text{final}}$ | `minimal_final_charge_state` | Minimum absolute charge required at end | None |
- | $\text c^{\text{U}}_{\text{final}}$ | `maximal_final_charge_state` | Maximum absolute charge allowed at end | None |
- | $\Delta \text{t}_{i}$ | - | Timestep duration | hours |
- | $\dot{\text{c}}_\text{rel,loss}(\text{t}_i)$ | `relative_loss_per_hour` | Relative self-discharge rate per hour | 0 |
- | $\eta_\text{in}(\text{t}_i)$ | `eta_charge` | Charging efficiency (0-1) | 1 |
- | $\eta_\text{out}(\text{t}_i)$ | `eta_discharge` | Discharging efficiency (0-1) | 1 |
- | - | `balanced` | If True, forces charging and discharging flows to have equal sizes | False |
- | - | `prevent_simultaneous_charge_and_discharge` | Prevents charging and discharging simultaneously | True |
- | - | `relative_minimum_final_charge_state` | Minimum relative charge at end | None |
- | - | `relative_maximum_final_charge_state` | Maximum relative charge at end | None |
+### Initial State
-=== "Use Cases"
+What's in the storage at the start?
- ## Basic Battery Storage
+$$
+c(t_0) = initial\_charge\_state
+$$
- ```python
- from flixopt import Storage, Flow
+### Final State
- battery = Storage(
- label='battery',
- charging=Flow(label='charge', bus='electricity', size=50), # 50 kW charging
- discharging=Flow(label='discharge', bus='electricity', size=50), # 50 kW discharging
- capacity_in_flow_hours=200, # 200 kWh capacity
- initial_charge_state=100, # Start at 100 kWh (50% SOC)
- eta_charge=0.95, # 95% charging efficiency
- eta_discharge=0.95, # 95% discharging efficiency
- relative_loss_per_hour=0.001, # 0.1% self-discharge per hour
- )
- ```
+Optionally constrain where the storage ends up:
- **Variables:** `charge_state[t]`, flow rates, `netto_discharge[t]`
+$$
+c_{final,min} \leq c(t_{end}) \leq c_{final,max}
+$$
- **Constraints:** $\eqref{eq:Storage_Bounds}$ with $0 \leq c(t) \leq 200$ kWh, $\eqref{eq:storage_balance}$, $\eqref{eq:storage_initial}$ with $c(t_0) = 100$ kWh
+### Cyclic Condition
- ---
+For periodic optimization (e.g., typical day), ensure the storage ends where it started:
- ## Thermal Storage with Final State Constraint
+$$
+c(t_0) = c(t_{end})
+$$
+
+This prevents "gaming" where the optimizer drains storage without replenishing.
+
+## Variables
+
+| Variable | Python Name | Description | When Created |
+|----------|-------------|-------------|--------------|
+| $c(t)$ | `charge_state` | Energy stored at each timestep | Always |
+| $p_{in}(t)$ | (from charging Flow) | Charging power | Always |
+| $p_{out}(t)$ | (from discharging Flow) | Discharging power | Always |
+| $C$ | `size` | Storage capacity (decision variable) | When `capacity_in_flow_hours` is `InvestParameters` |
+
+## Parameters
+
+| Parameter | Python Name | Description | Default |
+|-----------|-------------|-------------|---------|
+| $C$ | `capacity_in_flow_hours` | Storage capacity (kWh, MWh) | Required |
+| $\eta_{in}$ | `eta_charge` | Charging efficiency (0-1) | 1.0 |
+| $\eta_{out}$ | `eta_discharge` | Discharging efficiency (0-1) | 1.0 |
+| $loss$ | `relative_loss_per_hour` | Self-discharge rate per hour | 0 |
+| $c_0$ | `initial_charge_state` | Starting charge (absolute or `'equals_final'`) | 0 |
+| $c_{min}(t)$ | `relative_minimum_charge_state` | Min charge as fraction of capacity | 0 |
+| $c_{max}(t)$ | `relative_maximum_charge_state` | Max charge as fraction of capacity | 1 |
+| $c_{final,min}$ | `minimal_final_charge_state` | Minimum charge at end | None |
+| $c_{final,max}$ | `maximal_final_charge_state` | Maximum charge at end | None |
- ```python
- from flixopt import Storage, Flow
+## Usage Examples
- thermal_storage = Storage(
- label='heat_tank',
- charging=Flow(label='heat_in', bus='heating', size=100),
- discharging=Flow(label='heat_out', bus='heating', size=100),
- capacity_in_flow_hours=500, # 500 kWh thermal capacity
- initial_charge_state=250, # Start half-full
- minimal_final_charge_state=250, # End at least half-full
- eta_charge=0.98, # Minimal losses charging
- eta_discharge=0.98,
- relative_loss_per_hour=0.02, # 2% heat loss per hour
- )
- ```
+### Basic Battery
- **Variables:** `charge_state[t]`, flow rates, `netto_discharge[t]`
+```python
+battery = fx.Storage(
+ label='battery',
+ charging=fx.Flow(label='charge', bus=electricity_bus, size=50), # 50 kW charging
+ discharging=fx.Flow(label='discharge', bus=electricity_bus, size=50), # 50 kW discharging
+ capacity_in_flow_hours=200, # 200 kWh capacity
+ initial_charge_state=100, # Start at 100 kWh (50% SOC)
+ eta_charge=0.95,
+ eta_discharge=0.95,
+)
+```
- **Constraints:** $\eqref{eq:Storage_Bounds}$, $\eqref{eq:storage_balance}$, $\eqref{eq:storage_initial}$, $\eqref{eq:storage_final}$ with $c(t_\text{end}) \geq 250$ kWh
+Round-trip efficiency: $0.95 \times 0.95 = 90.25\%$
- ---
+### Thermal Storage with Losses
- ## Storage with Investment Decision
-
- ```python
- from flixopt import Storage, Flow, InvestParameters
-
- optimized_battery = Storage(
- label='battery_investment',
- charging=Flow(label='charge', bus='electricity', size=100),
- discharging=Flow(label='discharge', bus='electricity', size=100),
- capacity_in_flow_hours=InvestParameters(
- minimum_size=0, # Can choose not to build
- maximum_size=1000, # Up to 1 MWh
- specific_effects={'cost': 200}, # €200 per kWh annualized
- ),
- initial_charge_state=0,
- eta_charge=0.92,
- eta_discharge=0.92,
- )
- ```
-
- **Variables:** `charge_state[t]`, flow rates, `netto_discharge[t]`, `size` (decision variable)
-
- **Constraints:** $\eqref{eq:Storage_Bounds}$ with variable $\text C$, $\eqref{eq:storage_balance}$, plus investment constraints from [InvestParameters](../features/InvestParameters.md)
-
- ---
-
- ## Cyclic Storage Condition
-
- ```python
- from flixopt import Storage, Flow
-
- pumped_hydro = Storage(
- label='pumped_hydro',
- charging=Flow(label='pump', bus='electricity', size=100),
- discharging=Flow(label='turbine', bus='electricity', size=120),
- capacity_in_flow_hours=10000, # 10 MWh
- initial_charge_state='equals_final', # End with same charge as start
- eta_charge=0.85, # Pumping efficiency
- eta_discharge=0.90, # Turbine efficiency
- relative_loss_per_hour=0.0001, # Minimal evaporation
- )
- ```
-
- **Variables:** `charge_state[t]`, flow rates, `netto_discharge[t]`
-
- **Constraints:** $\eqref{eq:Storage_Bounds}$, $\eqref{eq:storage_balance}$, $\eqref{eq:storage_cyclic}$ enforcing $c(t_0) = c(t_\text{end})$
-
----
-
-## Implementation
+```python
+thermal_tank = fx.Storage(
+ label='heat_tank',
+ charging=fx.Flow(label='heat_in', bus=heat_bus, size=100),
+ discharging=fx.Flow(label='heat_out', bus=heat_bus, size=100),
+ capacity_in_flow_hours=500, # 500 kWh thermal
+ initial_charge_state=250,
+ eta_charge=0.98,
+ eta_discharge=0.98,
+ relative_loss_per_hour=0.02, # 2% heat loss per hour
+)
+```
+
+### Storage with Cyclic Condition
+
+For typical-day optimization, ensure the storage doesn't cheat:
+
+```python
+daily_storage = fx.Storage(
+ label='daily_buffer',
+ charging=fx.Flow(label='in', bus=some_bus, size=100),
+ discharging=fx.Flow(label='out', bus=some_bus, size=100),
+ capacity_in_flow_hours=400,
+ initial_charge_state='equals_final', # Must end where it started
+)
+```
+
+### Storage with Investment Decision
+
+```python
+from flixopt import InvestParameters
+
+battery_investment = fx.Storage(
+ label='battery',
+ charging=fx.Flow(label='charge', bus=electricity_bus, size=100),
+ discharging=fx.Flow(label='discharge', bus=electricity_bus, size=100),
+ capacity_in_flow_hours=InvestParameters(
+ minimum_size=0,
+ maximum_size=1000, # Up to 1 MWh
+ specific_effects={'costs': 200}, # €200/kWh annualized
+ ),
+ eta_charge=0.92,
+ eta_discharge=0.92,
+)
+```
+
+### Different Charge/Discharge Power
+
+```python
+pumped_hydro = fx.Storage(
+ label='pumped_hydro',
+ charging=fx.Flow(label='pump', bus=electricity_bus, size=100), # 100 MW pumping
+ discharging=fx.Flow(label='turbine', bus=electricity_bus, size=120), # 120 MW generating
+ capacity_in_flow_hours=10000, # 10 GWh reservoir
+ eta_charge=0.85, # Pumping losses
+ eta_discharge=0.90, # Turbine losses
+)
+```
+
+## Preventing Simultaneous Charging and Discharging
+
+By default, storage can't charge and discharge simultaneously (which would waste energy through round-trip losses). This is controlled by:
+
+```python
+storage = fx.Storage(
+ ...,
+ prevent_simultaneous_charge_and_discharge=True, # Default
+)
+```
+
+This adds a constraint linking the charging and discharging flows.
+
+## Implementation Details
- **Component Class:** [`Storage`][flixopt.components.Storage]
- **Model Class:** [`StorageModel`][flixopt.components.StorageModel]
## See Also
-- **Elements:** [Flow](Flow.md) · [Bus](Bus.md) · [LinearConverter](LinearConverter.md)
-- **Features:** [InvestParameters](../features/InvestParameters.md) · [OnOffParameters](../features/OnOffParameters.md)
-- **Patterns:** [Modeling Patterns](../modeling-patterns/index.md) · [Bounds and States](../modeling-patterns/bounds-and-states.md)
+- [Flow](Flow.md) — Charging and discharging flows
+- [Bus](Bus.md) — Where storage connects
+- [InvestParameters](../features/InvestParameters.md) — Optimizing storage capacity
+- [Core Concepts: Storages](../../core-concepts.md#storages-save-for-later) — High-level overview
diff --git a/docs/user-guide/mathematical-notation/index.md b/docs/user-guide/mathematical-notation/index.md
index 27e7b7e9a..ed2f2e7ba 100644
--- a/docs/user-guide/mathematical-notation/index.md
+++ b/docs/user-guide/mathematical-notation/index.md
@@ -1,123 +1,113 @@
-
# Mathematical Notation
-This section provides the **mathematical formulations** underlying FlixOpt's optimization models. It is intended as **reference documentation** for users who want to understand the mathematical details behind the high-level FlixOpt API described in the [FlixOpt Concepts](../core-concepts.md) guide.
-
-**For typical usage**, refer to the [FlixOpt Concepts](../core-concepts.md) guide, [Examples](../../examples/index.md), and [API Reference](../../api-reference/index.md) - you don't need to understand these mathematical formulations to use FlixOpt effectively.
-
----
-
-## Naming Conventions
+This section provides the detailed mathematical formulations behind flixOpt. It expands on the concepts introduced in [Core Concepts](../core-concepts.md) with precise equations, variables, and constraints.
-FlixOpt uses the following naming conventions:
+!!! tip "When to read this"
+ You don't need this section to use flixOpt effectively. It's here for:
-- All optimization variables are denoted by italic letters (e.g., $x$, $y$, $z$)
-- All parameters and constants are denoted by non italic small letters (e.g., $\text{a}$, $\text{b}$, $\text{c}$)
-- All Sets are denoted by greek capital letters (e.g., $\mathcal{F}$, $\mathcal{E}$)
-- All units of a set are denoted by greek small letters (e.g., $\mathcal{f}$, $\mathcal{e}$)
-- The letter $i$ is used to denote an index (e.g., $i=1,\dots,\text n$)
-- All time steps are denoted by the letter $\text{t}$ (e.g., $\text{t}_0$, $\text{t}_1$, $\text{t}_i$)
+ - Understanding exactly what the solver is optimizing
+ - Debugging unexpected model behavior
+ - Extending flixOpt with custom constraints
+ - Academic work requiring formal notation
-## Dimensions and Time Steps
+## Structure
-FlixOpt supports multi-dimensional optimization with up to three dimensions: **time** (mandatory), **period** (optional), and **scenario** (optional).
+The documentation follows the same structure as Core Concepts:
-**All mathematical formulations in this documentation are independent of whether periods or scenarios are present.** The equations shown are written with time index $\text{t}_i$ only, but automatically expand to additional dimensions when periods/scenarios are added.
+| Core Concept | Mathematical Details |
+|--------------|---------------------|
+| **Buses** — where things connect | [Bus](elements/Bus.md) — balance equations, penalty terms |
+| **Flows** — what moves | [Flow](elements/Flow.md) — capacity bounds, load factors, profiles |
+| **Converters** — transform things | [LinearConverter](elements/LinearConverter.md) — conversion ratios |
+| **Storages** — save for later | [Storage](elements/Storage.md) — charge dynamics, efficiency losses |
+| **Effects** — what you track | [Effects & Objective](effects-penalty-objective.md) — cost aggregation, constraints |
-For complete details on dimensions, their relationships, and influence on formulations, see **[Dimensions](dimensions.md)**.
+Additional sections cover:
-### Time Steps
+- **[Features](features/InvestParameters.md)** — Investment decisions, on/off operation, piecewise linearization
+- **[Dimensions](dimensions.md)** — Time, periods, and scenarios
+- **[Modeling Patterns](modeling-patterns/index.md)** — Internal implementation details (advanced)
-Time steps are defined as a sequence of discrete time steps $\text{t}_i \in \mathcal{T} \quad \text{for} \quad i \in \{1, 2, \dots, \text{n}\}$ (left-aligned in its timespan).
-From this sequence, the corresponding time intervals $\Delta \text{t}_i \in \Delta \mathcal{T}$ are derived as
+## Notation Conventions
-$$\Delta \text{t}_i = \text{t}_{i+1} - \text{t}_i \quad \text{for} \quad i \in \{1, 2, \dots, \text{n}-1\}$$
+### Variables (What the optimizer decides)
-The final time interval $\Delta \text{t}_\text n$ defaults to $\Delta \text{t}_\text n = \Delta \text{t}_{\text n-1}$, but is of course customizable.
-Non-equidistant time steps are also supported.
+Optimization variables are shown in *italic*:
----
+| Symbol | Meaning | Example |
+|--------|---------|---------|
+| $p(t)$ | Flow rate at time $t$ | Heat output of a boiler |
+| $c(t)$ | Charge state at time $t$ | Energy stored in a battery |
+| $P$ | Size/capacity (when optimized) | Installed capacity of a heat pump |
+| $s(t)$ | Binary on/off state | Whether a generator is running |
-## Documentation Structure
+### Parameters (What you provide)
-This reference is organized to match the FlixOpt API structure:
+Parameters and constants are shown in upright text:
-### Elements
-Mathematical formulations for core FlixOpt elements (corresponding to [`flixopt.elements`][flixopt.elements]):
+| Symbol | Meaning | Example |
+|--------|---------|---------|
+| $\eta$ | Efficiency | Boiler thermal efficiency (0.9) |
+| $\Delta t$ | Timestep duration | 1 hour |
+| $p_{min}$, $p_{max}$ | Flow bounds | Min/max operating power |
-- [Flow](elements/Flow.md) - Flow rate constraints and bounds
-- [Bus](elements/Bus.md) - Nodal balance equations
-- [Storage](elements/Storage.md) - Storage balance and charge state evolution
-- [LinearConverter](elements/LinearConverter.md) - Linear conversion relationships
+### Sets and Indices
-**User API:** When you create a `Flow`, `Bus`, `Storage`, or `LinearConverter` in your FlixOpt model, these mathematical formulations are automatically applied.
+| Symbol | Meaning |
+|--------|---------|
+| $t \in \mathcal{T}$ | Time steps |
+| $f \in \mathcal{F}$ | Flows |
+| $e \in \mathcal{E}$ | Effects |
-### Features
-Mathematical formulations for optional features (corresponding to parameters in FlixOpt classes):
+## The Optimization Problem
-- [InvestParameters](features/InvestParameters.md) - Investment decision modeling
-- [OnOffParameters](features/OnOffParameters.md) - Binary on/off operation
-- [Piecewise](features/Piecewise.md) - Piecewise linear approximations
+At its core, flixOpt solves:
-**User API:** When you pass `invest_parameters` or `on_off_parameters` to a `Flow` or component, these formulations are applied.
+$$
+\min \quad objective + penalty
+$$
-### System-Level
-- [Effects, Penalty & Objective](effects-penalty-objective.md) - Cost allocation and objective function
+**Subject to:**
-**User API:** When you create [`Effect`][flixopt.effects.Effect] objects and set `effects_per_flow_hour`, these formulations govern how costs are calculated.
+- Balance constraints at each bus
+- Capacity bounds on each flow
+- Storage dynamics over time
+- Conversion relationships in converters
+- Any additional effect constraints
-### Modeling Patterns (Advanced)
-**Internal implementation details** - These low-level patterns are used internally by Elements and Features. They are documented here for:
+The following pages detail each of these components.
-- Developers extending FlixOpt
-- Advanced users debugging models or understanding solver behavior
-- Researchers comparing mathematical formulations
+## Quick Example
-**Normal users do not need to read this section** - the patterns are automatically applied when you use Elements and Features:
+Consider a simple system: a gas boiler connected to a heat bus serving a demand.
-- [Bounds and States](modeling-patterns/bounds-and-states.md) - Variable bounding patterns
-- [Duration Tracking](modeling-patterns/duration-tracking.md) - Consecutive time period tracking
-- [State Transitions](modeling-patterns/state-transitions.md) - State change modeling
+**Variables:**
----
+- $p_{gas}(t)$ — gas consumption at each timestep
+- $p_{heat}(t)$ — heat production at each timestep
-## Quick Reference
+**Constraints:**
-### Components Cross-Reference
+1. **Conversion** (boiler efficiency 90%):
+ $$p_{heat}(t) = 0.9 \cdot p_{gas}(t)$$
-| Concept | Documentation | Python Class |
-|---------|---------------|--------------|
-| **Flow rate bounds** | [Flow](elements/Flow.md) | [`Flow`][flixopt.elements.Flow] |
-| **Bus balance** | [Bus](elements/Bus.md) | [`Bus`][flixopt.elements.Bus] |
-| **Storage balance** | [Storage](elements/Storage.md) | [`Storage`][flixopt.components.Storage] |
-| **Linear conversion** | [LinearConverter](elements/LinearConverter.md) | [`LinearConverter`][flixopt.components.LinearConverter] |
+2. **Capacity bounds** (boiler max 100 kW):
+ $$0 \leq p_{heat}(t) \leq 100$$
-### Features Cross-Reference
+3. **Balance** (meet demand):
+ $$p_{heat}(t) = demand(t)$$
-| Concept | Documentation | Python Class |
-|---------|---------------|--------------|
-| **Binary investment** | [InvestParameters](features/InvestParameters.md) | [`InvestParameters`][flixopt.interface.InvestParameters] |
-| **On/off operation** | [OnOffParameters](features/OnOffParameters.md) | [`OnOffParameters`][flixopt.interface.OnOffParameters] |
-| **Piecewise segments** | [Piecewise](features/Piecewise.md) | [`Piecewise`][flixopt.interface.Piecewise] |
+**Objective** (minimize gas cost at €50/MWh):
+$$\min \sum_t p_{gas}(t) \cdot \Delta t \cdot 50$$
-### Modeling Patterns Cross-Reference
+This simple example shows how the concepts combine. Real models have many more components, but the principles remain the same.
-| Pattern | Documentation | Implementation |
-|---------|---------------|----------------|
-| **Basic bounds** | [bounds-and-states](modeling-patterns/bounds-and-states.md#basic-bounds) | [`BoundingPatterns.basic_bounds()`][flixopt.modeling.BoundingPatterns.basic_bounds] |
-| **Bounds with state** | [bounds-and-states](modeling-patterns/bounds-and-states.md#bounds-with-state) | [`BoundingPatterns.bounds_with_state()`][flixopt.modeling.BoundingPatterns.bounds_with_state] |
-| **Scaled bounds** | [bounds-and-states](modeling-patterns/bounds-and-states.md#scaled-bounds) | [`BoundingPatterns.scaled_bounds()`][flixopt.modeling.BoundingPatterns.scaled_bounds] |
-| **Duration tracking** | [duration-tracking](modeling-patterns/duration-tracking.md) | [`ModelingPrimitives.consecutive_duration_tracking()`][flixopt.modeling.ModelingPrimitives.consecutive_duration_tracking] |
-| **State transitions** | [state-transitions](modeling-patterns/state-transitions.md) | [`BoundingPatterns.state_transition_bounds()`][flixopt.modeling.BoundingPatterns.state_transition_bounds] |
+## Next Steps
-### Python Class Lookup
+Start with the element that's most relevant to your question:
-| Class | Documentation | API Reference |
-|-------|---------------|---------------|
-| `Flow` | [Flow](elements/Flow.md) | [`Flow`][flixopt.elements.Flow] |
-| `Bus` | [Bus](elements/Bus.md) | [`Bus`][flixopt.elements.Bus] |
-| `Storage` | [Storage](elements/Storage.md) | [`Storage`][flixopt.components.Storage] |
-| `LinearConverter` | [LinearConverter](elements/LinearConverter.md) | [`LinearConverter`][flixopt.components.LinearConverter] |
-| `InvestParameters` | [InvestParameters](features/InvestParameters.md) | [`InvestParameters`][flixopt.interface.InvestParameters] |
-| `OnOffParameters` | [OnOffParameters](features/OnOffParameters.md) | [`OnOffParameters`][flixopt.interface.OnOffParameters] |
-| `Piecewise` | [Piecewise](features/Piecewise.md) | [`Piecewise`][flixopt.interface.Piecewise] |
+- **Why isn't my demand being met?** → [Bus](elements/Bus.md) (balance constraints)
+- **Why is my component not running?** → [Flow](elements/Flow.md) (capacity bounds)
+- **How does storage charge/discharge?** → [Storage](elements/Storage.md) (charge dynamics)
+- **How are efficiencies handled?** → [LinearConverter](elements/LinearConverter.md) (conversion)
+- **How are costs calculated?** → [Effects & Objective](effects-penalty-objective.md)
diff --git a/mkdocs.yml b/mkdocs.yml
index d4d21d5df..34c2a8b5e 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -29,17 +29,16 @@ nav:
- Analyzing Results: user-guide/results/index.md
- Mathematical Notation:
- Overview: user-guide/mathematical-notation/index.md
- - Dimensions: user-guide/mathematical-notation/dimensions.md
- - Elements:
- - Flow: user-guide/mathematical-notation/elements/Flow.md
- - Bus: user-guide/mathematical-notation/elements/Bus.md
- - Storage: user-guide/mathematical-notation/elements/Storage.md
- - LinearConverter: user-guide/mathematical-notation/elements/LinearConverter.md
+ - Bus: user-guide/mathematical-notation/elements/Bus.md
+ - Flow: user-guide/mathematical-notation/elements/Flow.md
+ - LinearConverter: user-guide/mathematical-notation/elements/LinearConverter.md
+ - Storage: user-guide/mathematical-notation/elements/Storage.md
+ - Effects & Objective: user-guide/mathematical-notation/effects-penalty-objective.md
- Features:
- Investment: user-guide/mathematical-notation/features/InvestParameters.md
- On/Off Operation: user-guide/mathematical-notation/features/OnOffParameters.md
- Piecewise Linearization: user-guide/mathematical-notation/features/Piecewise.md
- - Effects & Objective: user-guide/mathematical-notation/effects-penalty-objective.md
+ - Dimensions: user-guide/mathematical-notation/dimensions.md
- Modeling Patterns:
- Overview: user-guide/mathematical-notation/modeling-patterns/index.md
- Bounds and States: user-guide/mathematical-notation/modeling-patterns/bounds-and-states.md
From 4641f373890f0f17c3c99c8de8ffade561c6d9fa Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 21:24:36 +0100
Subject: [PATCH 47/79] =?UTF-8?q?=20=201.=20Minimal=20variable=20names=20?=
=?UTF-8?q?=E2=80=94=20Changed=20from=20words=20to=20symbols:=20=20=20=20?=
=?UTF-8?q?=20-=20penalty=5Frate=20=E2=86=92=20$c=5F\phi$=20=20=20=20=20-?=
=?UTF-8?q?=20relative=5Fmin=20=E2=86=92=20$p=5F{rel}^{min}$=20=20=20=20?=
=?UTF-8?q?=20-=20flow=5Fhours=20=E2=86=92=20$h=5Ff$=20=20=20=20=20-=20los?=
=?UTF-8?q?s=20=E2=86=92=20$\dot{c}=5F{loss}$=20=20=20=20=20-=20etc.=20=20?=
=?UTF-8?q?=202.=20Tabs=20for=20conditional=20constraints=20=E2=80=94=20Us?=
=?UTF-8?q?ed=20=3D=3D=3D=20"Tab=20Name"=20syntax=20for:=20=20=20=20=20-?=
=?UTF-8?q?=20Bus.md:=20"Without=20Excess=20(Strict)"=20vs=20"With=20Exces?=
=?UTF-8?q?s=20(Soft)"=20=20=20=20=20-=20Flow.md:=20"Standard=20(No=20On/O?=
=?UTF-8?q?ff)"=20vs=20"With=20On/Off"=20vs=20"Fixed=20Profile"=20=20=20?=
=?UTF-8?q?=20=20-=20Storage.md:=20"Fixed=20Initial"=20vs=20"Cyclic"=20vs?=
=?UTF-8?q?=20"Final=20Bounds"=20=20=20=20=20-=20LinearConverter.md:=20"Si?=
=?UTF-8?q?ngle=20Input/Output"=20vs=20"Multiple=20Outputs"=20vs=20"COP=20?=
=?UTF-8?q?>=201"=20vs=20"Time-Varying"=20=20=20=20=20-=20Effects.md:=20"T?=
=?UTF-8?q?emporal=20(Operational)"=20vs=20"Periodic=20(Investment)"=20vs?=
=?UTF-8?q?=20"Total"=20=20=203.=20Corrected=20Flow=20constraints=20?=
=?UTF-8?q?=E2=80=94=20Clarified=20that:=20=20=20=20=20-=20Without=20on/of?=
=?UTF-8?q?f=20parameters:=20flow=20cannot=20be=20zero=20if=20relative=5Fm?=
=?UTF-8?q?inimum=20>=200=20=20=20=20=20-=20With=20on/off=20parameters:=20?=
=?UTF-8?q?flow=20can=20be=20zero=20(when=20off)=20OR=20within=20bounds=20?=
=?UTF-8?q?(when=20on)=20=20=204.=20Cleaner=20structure=20=E2=80=94=20Remo?=
=?UTF-8?q?ved=20redundant=20content,=20focused=20on=20essential=20formula?=
=?UTF-8?q?s=20and=20examples?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../effects-penalty-objective.md | 108 ++++----
.../mathematical-notation/elements/Bus.md | 50 ++--
.../mathematical-notation/elements/Flow.md | 211 +++++++--------
.../elements/LinearConverter.md | 251 ++++++++----------
.../mathematical-notation/elements/Storage.md | 192 ++++++--------
5 files changed, 356 insertions(+), 456 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/effects-penalty-objective.md b/docs/user-guide/mathematical-notation/effects-penalty-objective.md
index 8216a2595..ca159ab4b 100644
--- a/docs/user-guide/mathematical-notation/effects-penalty-objective.md
+++ b/docs/user-guide/mathematical-notation/effects-penalty-objective.md
@@ -13,72 +13,62 @@ Effects are how you track and optimize metrics in your system. One effect is you
Every element in your model can contribute to effects. These contributions are aggregated into totals:
$$
-E_{total} = \sum_{elements} contributions
+E_e = \sum_{l \in \mathcal{L}} s_{l \rightarrow e}
$$
-For example, total costs might come from:
-
-- Gas consumption × gas price
-- Electricity import × electricity price
-- Equipment startup costs
-- Investment costs (annualized)
+Where $s_{l \rightarrow e}$ is the share from element $l$ to effect $e$.
## The Objective Function
flixOpt minimizes one effect (plus any penalties):
$$
-\min \quad E_{objective} + penalty
+\min \quad E_\Omega + \Phi
$$
-The penalty term comes from bus balance violations (see [Bus](elements/Bus.md)).
+Where $E_\Omega$ is the objective effect and $\Phi$ is the penalty from bus violations.
## Two Types of Effects
-### Temporal Effects (Operational)
+=== "Temporal (Operational)"
-Vary with time — accumulated over all timesteps:
+ Accumulated over timesteps:
-$$
-E_{temporal} = \sum_t contribution(t) \cdot \Delta t
-$$
+ $$
+ E_{e,temp} = \sum_t s_{l \rightarrow e}(t) \cdot \Delta t
+ $$
-!!! example "Fuel costs"
- ```python
- gas_flow = fx.Flow(
- label='gas',
- bus=gas_bus,
- size=100,
- effects_per_flow_hour={'costs': 50}, # €50/MWh
- )
- ```
- Contribution: $50 \cdot p_{gas}(t) \cdot \Delta t$ at each timestep
+ !!! example "Fuel costs"
+ ```python
+ gas_flow = fx.Flow(
+ label='gas', bus=gas_bus, size=100,
+ effects_per_flow_hour={'costs': 50}, # €50/MWh
+ )
+ ```
+ Contribution: $50 \cdot p(t) \cdot \Delta t$
-### Periodic Effects (Investment)
+=== "Periodic (Investment)"
-Time-independent — incurred once per period:
+ Time-independent — incurred once:
-$$
-E_{periodic} = \sum_{investments} size \cdot specific\_cost
-$$
+ $$
+ E_{e,per} = \sum_{inv} P \cdot c_{inv}
+ $$
-!!! example "Battery investment"
- ```python
- battery = fx.Storage(
- ...,
- capacity_in_flow_hours=InvestParameters(
+ !!! example "Battery investment"
+ ```python
+ capacity=fx.InvestParameters(
maximum_size=1000,
- specific_effects={'costs': 200}, # €200/kWh annualized
- ),
- )
- ```
- Contribution: $200 \cdot capacity$ (once, not per timestep)
+ specific_effects={'costs': 200}, # €200/kWh
+ )
+ ```
+ Contribution: $200 \cdot C$
-### Total Effect
+=== "Total"
-$$
-E_{total} = E_{periodic} + E_{temporal}
-$$
+ $$
+ E_e = E_{e,per} + E_{e,temp}
+ $$
## Cross-Effects: Linking Metrics
@@ -147,25 +137,25 @@ $$
## Variables
-| Variable | Description | When Created |
-|----------|-------------|--------------|
-| $E_{e,temporal}(t)$ | Temporal effect at timestep $t$ | Always |
-| $E_{e,periodic}$ | Periodic (investment) effect | Always |
-| $E_{e,total}$ | Total effect | Always |
-| $penalty$ | Sum of all penalty contributions | Always |
+| Symbol | Python Name | Description | When Created |
+|--------|-------------|-------------|--------------|
+| $E_{e,temp}(t)$ | `(temporal)\|total` | Temporal effect at $t$ | Always |
+| $E_{e,per}$ | `(periodic)\|total` | Periodic effect | Always |
+| $E_e$ | `total` | Total effect | Always |
+| $\Phi$ | `penalty` | Sum of penalties | Always |
## Parameters
-| Parameter | Python Name | Description |
-|-----------|-------------|-------------|
-| - | `is_objective` | If True, this effect is minimized |
-| - | `is_standard` | If True, allows shorthand effect syntax |
-| - | `maximum_total` | Upper bound on total effect |
-| - | `minimum_total` | Lower bound on total effect |
-| - | `maximum_per_hour` | Upper bound per timestep |
-| - | `maximum_periodic` | Upper bound on periodic (investment) part |
-| - | `share_from_temporal` | Cross-effect contributions (temporal) |
-| - | `share_from_periodic` | Cross-effect contributions (periodic) |
+| Symbol | Python Name | Description |
+|--------|-------------|-------------|
+| - | `is_objective` | Minimize this effect |
+| - | `is_standard` | Allow shorthand syntax |
+| $E_e^{max}$ | `maximum_total` | Upper bound on total |
+| $E_e^{min}$ | `minimum_total` | Lower bound on total |
+| $E_{e,temp}^{max}(t)$ | `maximum_per_hour` | Upper bound per timestep |
+| $E_{e,per}^{max}$ | `maximum_periodic` | Upper bound on periodic |
+| $r_{x \rightarrow e}$ | `share_from_temporal` | Cross-effect factor (temporal) |
+| $r_{x \rightarrow e}$ | `share_from_periodic` | Cross-effect factor (periodic) |
## Usage Examples
diff --git a/docs/user-guide/mathematical-notation/elements/Bus.md b/docs/user-guide/mathematical-notation/elements/Bus.md
index 53653bf37..097aa1830 100644
--- a/docs/user-guide/mathematical-notation/elements/Bus.md
+++ b/docs/user-guide/mathematical-notation/elements/Bus.md
@@ -11,35 +11,35 @@ A Bus is a connection point where flows meet and must balance. Think of it as a
The fundamental rule: **what goes in must equal what goes out**.
-$$
-\sum_{f \in inputs} p_f(t) = \sum_{f \in outputs} p_f(t)
-$$
+=== "Without Excess (Strict)"
-At every timestep $t$, the sum of all incoming flow rates must equal the sum of all outgoing flow rates.
+ $$
+ \sum_{f \in \mathcal{F}_{in}} p_f(t) = \sum_{f \in \mathcal{F}_{out}} p_f(t)
+ $$
-!!! note "Direction matters"
- Flows have a defined direction. An *input* to the bus means energy/material flowing **into** the bus. An *output* means flowing **out of** the bus.
+ At every timestep $t$, the sum of all incoming flow rates must equal the sum of all outgoing flow rates. If this can't be satisfied, the model is infeasible.
-## When Balance Can't Be Met: Excess Variables
+=== "With Excess (Soft)"
-Sometimes your model might be infeasible — the available supply simply can't meet demand. Rather than the solver failing with an unhelpful error, flixOpt can introduce **excess variables** that allow imbalance at a penalty cost.
+ When `excess_penalty_per_flow_hour` is set, slack variables allow imbalance:
-With excess enabled, the balance becomes:
+ $$
+ \sum_{f \in \mathcal{F}_{in}} p_f(t) + \phi_{in}(t) = \sum_{f \in \mathcal{F}_{out}} p_f(t) + \phi_{out}(t)
+ $$
-$$
-\sum_{f \in inputs} p_f(t) + \phi_{in}(t) = \sum_{f \in outputs} p_f(t) + \phi_{out}(t)
-$$
+ Where:
-Where:
+ - $\phi_{in}(t)$ — "virtual supply" to cover shortages
+ - $\phi_{out}(t)$ — "virtual demand" to absorb surplus
-- $\phi_{in}(t)$ — "virtual supply" to cover shortages (excess input)
-- $\phi_{out}(t)$ — "virtual demand" to absorb surplus (excess output)
+ Both are penalized in the objective:
-Both variables are penalized in the objective:
+ $$
+ \Phi_b(t) = (\phi_{in}(t) + \phi_{out}(t)) \cdot \Delta t \cdot c_\phi
+ $$
-$$
-penalty(t) = (\phi_{in}(t) + \phi_{out}(t)) \cdot \Delta t \cdot penalty\_rate
-$$
+!!! note "Direction matters"
+ Flows have a defined direction. An *input* to the bus means energy/material flowing **into** the bus. An *output* means flowing **out of** the bus.
!!! tip "Debugging with excess"
If excess variables are non-zero in your solution, it means your system couldn't meet all constraints. Check:
@@ -50,16 +50,18 @@ $$
## Variables
-| Variable | Python Name | Description | When Created |
-|----------|-------------|-------------|--------------|
+| Symbol | Python Name | Description | When Created |
+|--------|-------------|-------------|--------------|
| $\phi_{in}(t)$ | `excess_input` | Virtual supply to cover shortages | `excess_penalty_per_flow_hour` is set |
| $\phi_{out}(t)$ | `excess_output` | Virtual demand to absorb surplus | `excess_penalty_per_flow_hour` is set |
## Parameters
-| Parameter | Python Name | Description | Default |
-|-----------|-------------|-------------|---------|
-| $penalty\_rate$ | `excess_penalty_per_flow_hour` | Cost per unit of imbalance | `1e5` (high) |
+| Symbol | Python Name | Description | Default |
+|--------|-------------|-------------|---------|
+| $c_\phi$ | `excess_penalty_per_flow_hour` | Penalty cost per unit imbalance | `1e5` |
+| $\mathcal{F}_{in}$ | - | Set of input flows | From connected flows |
+| $\mathcal{F}_{out}$ | - | Set of output flows | From connected flows |
## Usage Examples
diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md
index 9492d2fba..f092c2184 100644
--- a/docs/user-guide/mathematical-notation/elements/Flow.md
+++ b/docs/user-guide/mathematical-notation/elements/Flow.md
@@ -12,118 +12,118 @@ A Flow represents the movement of energy or material between a component and a b
Every flow has two key quantities:
-- **Size** ($P$) — The capacity or maximum possible flow rate. Think of it as "how big is the pipe?"
-- **Flow Rate** ($p(t)$) — The actual flow at each timestep. This is what the optimizer decides.
+- **Size** ($P$) — The capacity or maximum possible flow rate
+- **Flow Rate** ($p(t)$) — The actual flow at each timestep (optimization variable)
-The fundamental constraint:
+## Capacity Bounds
-$$
-p_{min}(t) \leq p(t) \leq p_{max}(t)
-$$
+=== "Standard (No On/Off)"
-Usually, bounds are defined *relative* to the size:
+ The flow rate is bounded by the size:
-$$
-P \cdot relative\_min(t) \leq p(t) \leq P \cdot relative\_max(t)
-$$
+ $$
+ P \cdot p_{rel}^{min}(t) \leq p(t) \leq P \cdot p_{rel}^{max}(t)
+ $$
-!!! example "A 100 kW boiler that can modulate down to 30%"
- - Size: $P = 100$ kW
- - Relative minimum: 0.3
- - Relative maximum: 1.0
- - Constraint: $30 \leq p(t) \leq 100$ kW
+ The flow **cannot be zero** if $p_{rel}^{min} > 0$.
-## Flow Hours: Energy vs Power
+ !!! example "100 kW boiler, min 30%"
+ - $P = 100$, $p_{rel}^{min} = 0.3$, $p_{rel}^{max} = 1$
+ - Constraint: $30 \leq p(t) \leq 100$
-**Flow rate** is power (kW, MW). **Flow hours** is energy (kWh, MWh) — flow rate times time:
+=== "With On/Off"
-$$
-flow\_hours(t) = p(t) \cdot \Delta t
-$$
+ When `on_off_parameters` is specified, the flow can also be zero:
-| Flow Rate | Timestep | Flow Hours |
-|-----------|----------|------------|
-| 100 kW | 1 hour | 100 kWh |
-| 100 kW | 15 min | 25 kWh |
-| 50 MW | 1 hour | 50 MWh |
+ $$
+ s(t) \cdot P \cdot p_{rel}^{min}(t) \leq p(t) \leq s(t) \cdot P \cdot p_{rel}^{max}(t)
+ $$
-This matters for costs: `effects_per_flow_hour` is cost per energy (€/MWh), not per power.
+ Where $s(t) \in \{0, 1\}$ is the binary on/off state.
-## Constraints
+ - When $s(t) = 0$: $p(t) = 0$ (off)
+ - When $s(t) = 1$: $P \cdot p_{rel}^{min} \leq p(t) \leq P \cdot p_{rel}^{max}$ (on)
-### Capacity Bounds (Always Active)
+ See [OnOffParameters](../features/OnOffParameters.md) for details.
-$$
-P \cdot relative\_min(t) \leq p(t) \leq P \cdot relative\_max(t)
-$$
+=== "Fixed Profile"
+
+ When `fixed_relative_profile` is set, the flow rate is fixed:
-### Fixed Profile (Renewable Generation, Demands)
+ $$
+ p(t) = P \cdot \pi(t)
+ $$
+
+ No optimization freedom — used for demands and renewable generation.
+
+## Flow Hours: Energy vs Power
-When you have a known profile (solar irradiance, demand curve):
+**Flow rate** is power (kW, MW). **Flow hours** is energy (kWh, MWh):
$$
-p(t) = P \cdot profile(t)
+h_f(t) = p(t) \cdot \Delta t
$$
-The flow rate is fixed to the profile — no optimization freedom.
+| Flow Rate | Timestep | Flow Hours |
+|-----------|----------|------------|
+| 100 kW | 1 hour | 100 kWh |
+| 100 kW | 15 min | 25 kWh |
-### Load Factor Limits
+This matters for costs: `effects_per_flow_hour` is cost per energy (€/MWh).
-Constrain average utilization over the period:
+## Additional Constraints
-$$
-LF_{min} \cdot P \cdot N_t \leq \sum_t p(t) \leq LF_{max} \cdot P \cdot N_t
-$$
+=== "Load Factor"
-Where $N_t$ is the number of timesteps.
+ Constrain average utilization (`load_factor_min/max`):
-!!! example "Baseload plant must run at least 70% average"
- - Size: 200 MW
- - Load factor minimum: 0.7
- - Over 8760 hours: must produce at least $200 \times 0.7 \times 8760 = 1{,}226{,}400$ MWh
+ $$
+ \lambda_{min} \cdot P \cdot n_t \leq \sum_t p(t) \leq \lambda_{max} \cdot P \cdot n_t
+ $$
-### Flow Hours Limits
+ Where $n_t$ is the number of timesteps.
-Constrain total energy over the period:
+=== "Flow Hours Limits"
-$$
-FH_{min} \leq \sum_t p(t) \cdot \Delta t \leq FH_{max}
-$$
+ Constrain total energy (`flow_hours_min/max`):
-!!! example "Annual gas limit of 10,000 MWh"
- Sets $FH_{max} = 10{,}000$ MWh.
+ $$
+ h_{min} \leq \sum_t p(t) \cdot \Delta t \leq h_{max}
+ $$
## Variables
-| Variable | Python Name | Description | When Created |
-|----------|-------------|-------------|--------------|
-| $p(t)$ | `flow_rate` | Flow rate at each timestep | Always |
-| $P$ | `size` | Capacity (decision variable) | When `size` is `InvestParameters` |
-| $s(t)$ | `on_off_state` | Binary on/off state | When `on_off_parameters` set |
+| Symbol | Python Name | Description | When Created |
+|--------|-------------|-------------|--------------|
+| $p(t)$ | `flow_rate` | Flow rate at timestep $t$ | Always |
+| $P$ | `size` | Capacity (variable) | `size` is `InvestParameters` |
+| $s(t)$ | `on_off_state` | Binary state | `on_off_parameters` set |
+| $s^{on}(t)$ | `switch_on` | Switch-on indicator | `on_off_parameters` set |
+| $s^{off}(t)$ | `switch_off` | Switch-off indicator | `on_off_parameters` set |
## Parameters
-| Parameter | Python Name | Description | Default |
-|-----------|-------------|-------------|---------|
+| Symbol | Python Name | Description | Default |
+|--------|-------------|-------------|---------|
| $P$ | `size` | Flow capacity | Required |
-| $relative\_min$ | `relative_minimum` | Min as fraction of size | 0 |
-| $relative\_max$ | `relative_maximum` | Max as fraction of size | 1 |
-| $profile$ | `fixed_relative_profile` | Fixed relative profile | None |
-| $LF_{min}$ | `load_factor_min` | Minimum average utilization | None |
-| $LF_{max}$ | `load_factor_max` | Maximum average utilization | None |
-| $FH_{min}$ | `flow_hours_min` | Minimum total energy | None |
-| $FH_{max}$ | `flow_hours_max` | Maximum total energy | None |
+| $p_{rel}^{min}(t)$ | `relative_minimum` | Min as fraction of size | 0 |
+| $p_{rel}^{max}(t)$ | `relative_maximum` | Max as fraction of size | 1 |
+| $\pi(t)$ | `fixed_relative_profile` | Fixed profile | None |
+| $\lambda_{min}$ | `load_factor_min` | Min average utilization | None |
+| $\lambda_{max}$ | `load_factor_max` | Max average utilization | None |
+| $h_{min}$ | `flow_hours_min` | Min total energy | None |
+| $h_{max}$ | `flow_hours_max` | Max total energy | None |
## Usage Examples
-### Basic Flow with Fixed Capacity
+### Basic Flow
```python
heat_output = fx.Flow(
label='heat_out',
bus=heat_bus,
size=100, # 100 kW capacity
- relative_minimum=0.3, # Can't go below 30 kW
+ relative_minimum=0.3, # Min 30 kW when operating
)
```
@@ -134,78 +134,50 @@ gas_input = fx.Flow(
label='gas_in',
bus=gas_bus,
size=150,
- effects_per_flow_hour={'costs': 50}, # €50/MWh gas price
+ effects_per_flow_hour={'costs': 50}, # €50/MWh
)
```
-### Fixed Profile (Solar PV)
+### Fixed Profile (Solar)
```python
-solar_profile = [0, 0, 0.1, 0.4, 0.8, 1.0, 0.9, 0.6, 0.2, 0, 0, 0] # Relative to peak
-
-solar_output = fx.Flow(
- label='solar_out',
+solar = fx.Flow(
+ label='solar',
bus=electricity_bus,
size=500, # 500 kW peak
- fixed_relative_profile=solar_profile,
+ fixed_relative_profile=[0, 0.1, 0.4, 0.8, 0.9, 0.6, 0.2, 0],
)
```
-### Investment Decision (Optimized Size)
+### With On/Off Operation
```python
-from flixopt import InvestParameters
-
-battery_flow = fx.Flow(
- label='battery_power',
+generator = fx.Flow(
+ label='power',
bus=electricity_bus,
- size=InvestParameters(
- minimum_size=0,
- maximum_size=1000, # Up to 1 MW
- specific_effects={'costs': 100_000}, # €100k/MW/year
+ size=50,
+ relative_minimum=0.4, # 40% min when ON, but can be OFF
+ on_off_parameters=fx.OnOffParameters(
+ effects_per_switch_on={'costs': 500},
+ consecutive_on_hours_min=2,
),
)
```
-See [InvestParameters](../features/InvestParameters.md) for details.
-
-### On/Off Operation
+### Investment Decision
```python
-from flixopt import OnOffParameters
-
-generator_output = fx.Flow(
- label='power_out',
+battery_flow = fx.Flow(
+ label='power',
bus=electricity_bus,
- size=50,
- relative_minimum=0.4, # 40% minimum when ON
- on_off_parameters=OnOffParameters(
- effects_per_switch_on={'costs': 500}, # €500 startup cost
- consecutive_on_hours_min=2, # Must run at least 2 hours
+ size=fx.InvestParameters(
+ minimum_size=0,
+ maximum_size=1000,
+ specific_effects={'costs': 100_000},
),
)
```
-See [OnOffParameters](../features/OnOffParameters.md) for details.
-
-## How Flows Connect to Components
-
-Flows are always part of a component:
-
-```python
-boiler = fx.linear_converters.Boiler(
- label='boiler',
- eta=0.9,
- # These flows are created automatically:
- # - inputs: gas flow from gas_bus
- # - outputs: heat flow to heat_bus
- Q_th=fx.Flow(label='heat', bus=heat_bus, size=100),
- Q_fu=fx.Flow(label='fuel', bus=gas_bus, size=111), # 100/0.9
-)
-```
-
-The component defines how input and output flows relate (conversion equations).
-
## Implementation Details
- **Element Class:** [`Flow`][flixopt.elements.Flow]
@@ -214,7 +186,6 @@ The component defines how input and output flows relate (conversion equations).
## See Also
- [Bus](Bus.md) — Where flows connect
-- [LinearConverter](LinearConverter.md) — Components that use flows
-- [InvestParameters](../features/InvestParameters.md) — Optimizing flow capacity
-- [OnOffParameters](../features/OnOffParameters.md) — Binary on/off operation
-- [Core Concepts: Flows](../../core-concepts.md#flows-what-moves-between-elements) — High-level overview
+- [LinearConverter](LinearConverter.md) — Components using flows
+- [OnOffParameters](../features/OnOffParameters.md) — Binary operation
+- [InvestParameters](../features/InvestParameters.md) — Capacity optimization
diff --git a/docs/user-guide/mathematical-notation/elements/LinearConverter.md b/docs/user-guide/mathematical-notation/elements/LinearConverter.md
index 8100d9315..09d7ee67d 100644
--- a/docs/user-guide/mathematical-notation/elements/LinearConverter.md
+++ b/docs/user-guide/mathematical-notation/elements/LinearConverter.md
@@ -1,249 +1,216 @@
# LinearConverter
-A LinearConverter transforms inputs into outputs with defined conversion ratios. It's the workhorse for modeling any equipment that converts one form of energy or material into another.
+A LinearConverter transforms inputs into outputs with defined conversion ratios. It's the workhorse for modeling equipment that converts energy or material.
!!! example "Real-world examples"
- - **Gas boiler** — gas → heat (90% efficiency)
- - **Heat pump** — electricity → heat (COP 3.5 = 350% "efficiency")
- - **CHP** — gas → electricity + heat (35% electrical, 50% thermal)
- - **Electrolyzer** — electricity → hydrogen (65% efficiency)
+ - **Gas boiler** — gas → heat (η = 90%)
+ - **Heat pump** — electricity → heat (COP = 3.5)
+ - **CHP** — gas → electricity + heat
+ - **Electrolyzer** — electricity → hydrogen
## Core Concept: Conversion Factors
The fundamental equation links inputs and outputs:
$$
-\sum_{f \in inputs} a_f \cdot p_f(t) = \sum_{f \in outputs} b_f \cdot p_f(t)
+\sum_{f \in \mathcal{F}_{in}} a_f \cdot p_f(t) = \sum_{f \in \mathcal{F}_{out}} b_f \cdot p_f(t)
$$
-The conversion factors $a$ and $b$ define the relationship between flows.
+The conversion factors $a_f$ and $b_f$ define the relationship.
-### Simple Case: One Input, One Output
+=== "Single Input/Output"
-For a gas boiler with 90% efficiency:
+ For a boiler with 90% efficiency:
-$$
-0.9 \cdot p_{gas}(t) = 1 \cdot p_{heat}(t)
-$$
+ $$
+ 0.9 \cdot p_{gas}(t) = 1 \cdot p_{heat}(t)
+ $$
-Or equivalently: $p_{heat}(t) = 0.9 \cdot p_{gas}(t)$
+ Or: $p_{heat}(t) = 0.9 \cdot p_{gas}(t)$
-!!! note "Direction of efficiency"
- The factor is on the **input** side. If you put in 100 kW of gas, you get out 90 kW of heat.
+=== "Multiple Outputs (CHP)"
-### Multiple Outputs: CHP
+ CHP produces electricity and heat from fuel:
-A CHP unit produces both electricity and heat from fuel:
+ $$
+ 0.35 \cdot p_{fuel}(t) = p_{el}(t)
+ $$
-$$
-0.35 \cdot p_{fuel}(t) = p_{electricity}(t)
-$$
+ $$
+ 0.50 \cdot p_{fuel}(t) = p_{th}(t)
+ $$
-$$
-0.50 \cdot p_{fuel}(t) = p_{heat}(t)
-$$
+ Total efficiency: 85%
-These are two separate conversion equations from the same input.
+=== "COP > 1 (Heat Pump)"
-### Heat Pump: COP > 1
+ Heat pump with COP = 3.5:
-A heat pump has COP (Coefficient of Performance) of 3.5:
+ $$
+ 3.5 \cdot p_{el}(t) = p_{th}(t)
+ $$
-$$
-3.5 \cdot p_{electricity}(t) = p_{heat}(t)
-$$
+ Factor > 1 because it extracts heat from environment.
+
+=== "Time-Varying"
-The factor is greater than 1 because the heat pump also extracts energy from the environment.
+ Efficiency can vary with time (e.g., COP depends on temperature):
+
+ $$
+ \eta(t) \cdot p_{in}(t) = p_{out}(t)
+ $$
## Variables
-| Variable | Python Name | Description | When Created |
-|----------|-------------|-------------|--------------|
+| Symbol | Python Name | Description | When Created |
+|--------|-------------|-------------|--------------|
| $p_{in}(t)$ | (from input Flows) | Input flow rates | Always |
| $p_{out}(t)$ | (from output Flows) | Output flow rates | Always |
-The converter itself doesn't create new variables — it creates constraints linking the flow variables.
+The converter creates **constraints** linking flow variables, not new variables.
## Parameters
-| Parameter | Python Name | Description |
-|-----------|-------------|-------------|
-| Input flows | `inputs` | List of input Flows |
-| Output flows | `outputs` | List of output Flows |
-| Conversion factors | `conversion_factors` | List of factor dictionaries |
+| Symbol | Python Name | Description |
+|--------|-------------|-------------|
+| $\mathcal{F}_{in}$ | `inputs` | List of input Flows |
+| $\mathcal{F}_{out}$ | `outputs` | List of output Flows |
+| $a_f$, $b_f$ | `conversion_factors` | Factor dictionaries |
## Usage Examples
-### Gas Boiler (Simple)
+### Gas Boiler
```python
boiler = fx.LinearConverter(
- label='gas_boiler',
+ label='boiler',
inputs=[fx.Flow(label='gas', bus=gas_bus, size=111)],
outputs=[fx.Flow(label='heat', bus=heat_bus, size=100)],
- conversion_factors=[{
- 'gas': 0.9, # 90% of gas input...
- 'heat': 1, # ...becomes heat output
- }],
+ conversion_factors=[{'gas': 0.9, 'heat': 1}],
)
```
-**Constraint:** $0.9 \cdot p_{gas}(t) = p_{heat}(t)$
-
-### CHP Unit (One Input, Two Outputs)
+### CHP Unit
```python
chp = fx.LinearConverter(
label='chp',
inputs=[fx.Flow(label='fuel', bus=gas_bus, size=100)],
outputs=[
- fx.Flow(label='electricity', bus=electricity_bus, size=35),
+ fx.Flow(label='el', bus=elec_bus, size=35),
fx.Flow(label='heat', bus=heat_bus, size=50),
],
conversion_factors=[
- {'fuel': 0.35, 'electricity': 1}, # 35% electrical efficiency
- {'fuel': 0.50, 'heat': 1}, # 50% thermal efficiency
+ {'fuel': 0.35, 'el': 1},
+ {'fuel': 0.50, 'heat': 1},
],
)
```
-**Constraints:**
-
-- $0.35 \cdot p_{fuel}(t) = p_{electricity}(t)$
-- $0.50 \cdot p_{fuel}(t) = p_{heat}(t)$
-
-Total efficiency: 85%
-
### Heat Pump
```python
-heat_pump = fx.LinearConverter(
- label='heat_pump',
- inputs=[fx.Flow(label='electricity', bus=electricity_bus, size=100)],
+hp = fx.LinearConverter(
+ label='hp',
+ inputs=[fx.Flow(label='el', bus=elec_bus, size=100)],
outputs=[fx.Flow(label='heat', bus=heat_bus, size=350)],
- conversion_factors=[{
- 'electricity': 3.5, # COP of 3.5
- 'heat': 1,
- }],
+ conversion_factors=[{'el': 3.5, 'heat': 1}],
)
```
-**Constraint:** $3.5 \cdot p_{electricity}(t) = p_{heat}(t)$
-
-### Time-Varying Efficiency
-
-Efficiency can vary with time (e.g., heat pump COP depends on outside temperature):
+### Time-Varying COP
```python
-cop_profile = [3.0, 3.2, 3.5, 4.0, 3.8, 3.5, ...] # COP varies by timestep
+cop = [3.0, 3.2, 3.5, 4.0, 3.8, ...] # Varies by timestep
-heat_pump = fx.LinearConverter(
- label='heat_pump',
- inputs=[fx.Flow(label='electricity', bus=electricity_bus, size=100)],
+hp = fx.LinearConverter(
+ label='hp',
+ inputs=[fx.Flow(label='el', bus=elec_bus, size=100)],
outputs=[fx.Flow(label='heat', bus=heat_bus, size=400)],
- conversion_factors=[{
- 'electricity': cop_profile,
- 'heat': 1,
- }],
+ conversion_factors=[{'el': cop, 'heat': 1}],
)
```
-## Pre-Built Specialized Components
-
-flixOpt provides convenience classes that set up conversion factors automatically:
+## Specialized Components
-### Boiler
+flixOpt provides convenience classes:
```python
+# Boiler
boiler = fx.linear_converters.Boiler(
- label='boiler',
- eta=0.9, # Thermal efficiency
+ label='boiler', eta=0.9,
Q_th=fx.Flow(label='heat', bus=heat_bus, size=100),
- Q_fu=fx.Flow(label='fuel', bus=gas_bus), # Size calculated automatically
+ Q_fu=fx.Flow(label='fuel', bus=gas_bus),
)
-```
-
-### HeatPump
-```python
-heat_pump = fx.linear_converters.HeatPump(
- label='heat_pump',
- COP=3.5,
- P_el=fx.Flow(label='electricity', bus=electricity_bus, size=100),
- Q_th=fx.Flow(label='heat', bus=heat_bus), # Size = 350 kW
+# Heat Pump
+hp = fx.linear_converters.HeatPump(
+ label='hp', COP=3.5,
+ P_el=fx.Flow(label='el', bus=elec_bus, size=100),
+ Q_th=fx.Flow(label='heat', bus=heat_bus),
)
-```
-### CHP
-
-```python
+# CHP
chp = fx.linear_converters.CHP(
- label='chp',
- eta_el=0.35,
- eta_th=0.50,
- P_el=fx.Flow(label='electricity', bus=electricity_bus, size=35),
+ label='chp', eta_el=0.35, eta_th=0.50,
+ P_el=fx.Flow(label='el', bus=elec_bus, size=35),
Q_th=fx.Flow(label='heat', bus=heat_bus, size=50),
Q_fu=fx.Flow(label='fuel', bus=gas_bus, size=100),
)
```
-## Advanced: Piecewise Linear Conversion
+## Advanced Features
-For non-linear relationships (e.g., part-load efficiency curves), use piecewise linearization:
+=== "Piecewise Linear"
-```python
-from flixopt import Piecewise, Piece, PiecewiseConversion
+ For non-linear efficiency curves:
-# Efficiency varies with load
-efficiency_curve = Piecewise([
- Piece(start=(0, 0), end=(50, 40)), # 80% efficiency at low load
- Piece(start=(50, 40), end=(100, 90)), # 90% efficiency at high load
-])
+ ```python
+ from flixopt import Piecewise, Piece, PiecewiseConversion
-boiler = fx.LinearConverter(
- label='boiler',
- inputs=[fx.Flow(label='gas', bus=gas_bus, size=100)],
- outputs=[fx.Flow(label='heat', bus=heat_bus, size=90)],
- piecewise_conversion=PiecewiseConversion(
- origin_flow='gas',
- piecewise_shares={'heat': efficiency_curve},
- ),
-)
-```
+ curve = Piecewise([
+ Piece(start=(0, 0), end=(50, 40)), # 80% at low load
+ Piece(start=(50, 40), end=(100, 90)), # 90% at high load
+ ])
-See [Piecewise Linearization](../features/Piecewise.md) for details.
+ boiler = fx.LinearConverter(
+ ...,
+ piecewise_conversion=PiecewiseConversion(
+ origin_flow='gas',
+ piecewise_shares={'heat': curve},
+ ),
+ )
+ ```
-## On/Off Operation
+ See [Piecewise](../features/Piecewise.md).
-Add startup costs and minimum run times:
+=== "On/Off Operation"
-```python
-from flixopt import OnOffParameters
-
-generator = fx.LinearConverter(
- label='generator',
- inputs=[fx.Flow(label='fuel', bus=fuel_bus, size=100)],
- outputs=[fx.Flow(label='power', bus=electricity_bus, size=40, relative_minimum=0.4)],
- conversion_factors=[{'fuel': 0.4, 'power': 1}],
- on_off_parameters=OnOffParameters(
- effects_per_switch_on={'costs': 1000}, # €1000 startup cost
- consecutive_on_hours_min=4, # Must run at least 4 hours
- ),
-)
-```
+ Add startup costs and minimum run times:
+
+ ```python
+ gen = fx.LinearConverter(
+ label='gen',
+ inputs=[fx.Flow(label='fuel', bus=fuel_bus, size=100)],
+ outputs=[fx.Flow(label='el', bus=elec_bus, size=40, relative_minimum=0.4)],
+ conversion_factors=[{'fuel': 0.4, 'el': 1}],
+ on_off_parameters=fx.OnOffParameters(
+ effects_per_switch_on={'costs': 1000},
+ consecutive_on_hours_min=4,
+ ),
+ )
+ ```
-See [On/Off Operation](../features/OnOffParameters.md) for details.
+ See [OnOffParameters](../features/OnOffParameters.md).
## Implementation Details
- **Component Class:** [`LinearConverter`][flixopt.components.LinearConverter]
- **Model Class:** [`LinearConverterModel`][flixopt.components.LinearConverterModel]
-- **Specialized Classes:** [`Boiler`][flixopt.linear_converters.Boiler], [`HeatPump`][flixopt.linear_converters.HeatPump], [`CHP`][flixopt.linear_converters.CHP]
## See Also
- [Flow](Flow.md) — Input and output flows
- [Bus](Bus.md) — Where converters connect
-- [Piecewise Linearization](../features/Piecewise.md) — Non-linear efficiency curves
-- [On/Off Operation](../features/OnOffParameters.md) — Binary operation
-- [Core Concepts: Converters](../../core-concepts.md#converters-transform-one-thing-into-another) — High-level overview
+- [Piecewise](../features/Piecewise.md) — Non-linear efficiency
+- [OnOffParameters](../features/OnOffParameters.md) — Binary operation
diff --git a/docs/user-guide/mathematical-notation/elements/Storage.md b/docs/user-guide/mathematical-notation/elements/Storage.md
index 70300b0ba..841dcebed 100644
--- a/docs/user-guide/mathematical-notation/elements/Storage.md
+++ b/docs/user-guide/mathematical-notation/elements/Storage.md
@@ -1,114 +1,92 @@
# Storage
-A Storage component accumulates energy or material over time, allowing you to decouple when you produce from when you consume. This is essential for integrating renewables, managing peak demand, and arbitraging price differences.
+A Storage component accumulates energy or material over time, allowing you to decouple when you produce from when you consume.
!!! example "Real-world examples"
- **Battery** — store electricity when cheap, discharge when expensive
- **Thermal tank** — buffer heat production from demand
- - **Warehouse** — inventory buffer in supply chains
- **Pumped hydro** — large-scale electricity storage
## Core Concept: Charge State Over Time
-A storage has a **charge state** $c(t)$ that evolves over time based on charging and discharging:
+A storage has a **charge state** $c(t)$ that evolves based on charging and discharging:
$$
-c(t+1) = c(t) \cdot (1 - loss)^{\Delta t} + charge(t) \cdot \eta_{in} - \frac{discharge(t)}{\eta_{out}}
+c(t_{i+1}) = c(t_i) \cdot (1 - \dot{c}_{loss})^{\Delta t} + p_{in}(t_i) \cdot \Delta t \cdot \eta_{in} - p_{out}(t_i) \cdot \Delta t / \eta_{out}
$$
Where:
-- $c(t)$ — Energy/material stored at time $t$
-- $charge(t)$ — Charging flow rate (power in)
-- $discharge(t)$ — Discharging flow rate (power out)
-- $\eta_{in}$ — Charging efficiency (losses when storing)
-- $\eta_{out}$ — Discharging efficiency (losses when retrieving)
-- $loss$ — Self-discharge rate per hour
-
-!!! note "Units"
- - **Capacity** is in energy units (kWh, MWh)
- - **Charging/discharging flows** are in power units (kW, MW)
- - The timestep duration $\Delta t$ converts between them
-
-## The Storage Balance Equation
-
-At each timestep, the charge state updates:
-
-$$
-c(t_{i+1}) = c(t_i) \cdot (1 - loss)^{\Delta t_i} + p_{in}(t_i) \cdot \Delta t_i \cdot \eta_{in} - p_{out}(t_i) \cdot \Delta t_i / \eta_{out}
-$$
-
-This equation captures:
-
-1. **Starting state** — what was stored at the beginning
-2. **Self-discharge** — losses over time (exponential decay)
-3. **Charging** — energy added (with efficiency loss)
-4. **Discharging** — energy removed (with efficiency loss)
+| Symbol | Meaning |
+|--------|---------|
+| $c(t)$ | Charge state (energy stored) |
+| $p_{in}(t)$ | Charging power |
+| $p_{out}(t)$ | Discharging power |
+| $\eta_{in}$ | Charging efficiency |
+| $\eta_{out}$ | Discharging efficiency |
+| $\dot{c}_{loss}$ | Self-discharge rate per hour |
## Charge State Bounds
-The charge state is bounded by the storage capacity:
-
$$
-C \cdot relative\_min(t) \leq c(t) \leq C \cdot relative\_max(t)
+C \cdot c_{rel}^{min}(t) \leq c(t) \leq C \cdot c_{rel}^{max}(t)
$$
-!!! example "Battery with 20-80% operating range"
- - Capacity: 1000 kWh
- - `relative_minimum_charge_state`: 0.2
- - `relative_maximum_charge_state`: 0.8
- - Effective range: 200-800 kWh (protects battery life)
+Where $C$ is the storage capacity.
+
+!!! example "Battery with 20-80% SOC range"
+ $c_{rel}^{min} = 0.2$, $c_{rel}^{max} = 0.8$ → Effective range: 20-80% of capacity
## Initial and Final Conditions
-### Initial State
+=== "Fixed Initial"
-What's in the storage at the start?
+ $$
+ c(t_0) = c_0
+ $$
-$$
-c(t_0) = initial\_charge\_state
-$$
+ Storage starts with a specified charge.
-### Final State
+=== "Cyclic"
-Optionally constrain where the storage ends up:
+ $$
+ c(t_0) = c(t_{end})
+ $$
-$$
-c_{final,min} \leq c(t_{end}) \leq c_{final,max}
-$$
+ Storage must end where it started. Use `initial_charge_state='equals_final'`.
-### Cyclic Condition
+ Prevents the optimizer from "cheating" by draining storage without replenishing.
-For periodic optimization (e.g., typical day), ensure the storage ends where it started:
+=== "Final Bounds"
-$$
-c(t_0) = c(t_{end})
-$$
+ $$
+ c_{final}^{min} \leq c(t_{end}) \leq c_{final}^{max}
+ $$
-This prevents "gaming" where the optimizer drains storage without replenishing.
+ Constrain the final charge state.
## Variables
-| Variable | Python Name | Description | When Created |
-|----------|-------------|-------------|--------------|
-| $c(t)$ | `charge_state` | Energy stored at each timestep | Always |
-| $p_{in}(t)$ | (from charging Flow) | Charging power | Always |
-| $p_{out}(t)$ | (from discharging Flow) | Discharging power | Always |
-| $C$ | `size` | Storage capacity (decision variable) | When `capacity_in_flow_hours` is `InvestParameters` |
+| Symbol | Python Name | Description | When Created |
+|--------|-------------|-------------|--------------|
+| $c(t)$ | `charge_state` | Charge at timestep $t$ | Always |
+| $p_{in}(t)$ | (from `charging` Flow) | Charging power | Always |
+| $p_{out}(t)$ | (from `discharging` Flow) | Discharging power | Always |
+| $C$ | `size` | Capacity (variable) | `capacity_in_flow_hours` is `InvestParameters` |
## Parameters
-| Parameter | Python Name | Description | Default |
-|-----------|-------------|-------------|---------|
-| $C$ | `capacity_in_flow_hours` | Storage capacity (kWh, MWh) | Required |
-| $\eta_{in}$ | `eta_charge` | Charging efficiency (0-1) | 1.0 |
-| $\eta_{out}$ | `eta_discharge` | Discharging efficiency (0-1) | 1.0 |
-| $loss$ | `relative_loss_per_hour` | Self-discharge rate per hour | 0 |
-| $c_0$ | `initial_charge_state` | Starting charge (absolute or `'equals_final'`) | 0 |
-| $c_{min}(t)$ | `relative_minimum_charge_state` | Min charge as fraction of capacity | 0 |
-| $c_{max}(t)$ | `relative_maximum_charge_state` | Max charge as fraction of capacity | 1 |
-| $c_{final,min}$ | `minimal_final_charge_state` | Minimum charge at end | None |
-| $c_{final,max}$ | `maximal_final_charge_state` | Maximum charge at end | None |
+| Symbol | Python Name | Description | Default |
+|--------|-------------|-------------|---------|
+| $C$ | `capacity_in_flow_hours` | Storage capacity | Required |
+| $\eta_{in}$ | `eta_charge` | Charging efficiency | 1.0 |
+| $\eta_{out}$ | `eta_discharge` | Discharging efficiency | 1.0 |
+| $\dot{c}_{loss}$ | `relative_loss_per_hour` | Self-discharge rate | 0 |
+| $c_0$ | `initial_charge_state` | Starting charge | 0 |
+| $c_{rel}^{min}(t)$ | `relative_minimum_charge_state` | Min SOC fraction | 0 |
+| $c_{rel}^{max}(t)$ | `relative_maximum_charge_state` | Max SOC fraction | 1 |
+| $c_{final}^{min}$ | `minimal_final_charge_state` | Min final charge | None |
+| $c_{final}^{max}$ | `maximal_final_charge_state` | Max final charge | None |
## Usage Examples
@@ -117,10 +95,10 @@ This prevents "gaming" where the optimizer drains storage without replenishing.
```python
battery = fx.Storage(
label='battery',
- charging=fx.Flow(label='charge', bus=electricity_bus, size=50), # 50 kW charging
- discharging=fx.Flow(label='discharge', bus=electricity_bus, size=50), # 50 kW discharging
- capacity_in_flow_hours=200, # 200 kWh capacity
- initial_charge_state=100, # Start at 100 kWh (50% SOC)
+ charging=fx.Flow(label='charge', bus=elec_bus, size=50),
+ discharging=fx.Flow(label='discharge', bus=elec_bus, size=50),
+ capacity_in_flow_hours=200, # 200 kWh
+ initial_charge_state=100, # Start at 50% SOC
eta_charge=0.95,
eta_discharge=0.95,
)
@@ -131,45 +109,40 @@ Round-trip efficiency: $0.95 \times 0.95 = 90.25\%$
### Thermal Storage with Losses
```python
-thermal_tank = fx.Storage(
- label='heat_tank',
- charging=fx.Flow(label='heat_in', bus=heat_bus, size=100),
- discharging=fx.Flow(label='heat_out', bus=heat_bus, size=100),
- capacity_in_flow_hours=500, # 500 kWh thermal
- initial_charge_state=250,
+tank = fx.Storage(
+ label='tank',
+ charging=fx.Flow(label='in', bus=heat_bus, size=100),
+ discharging=fx.Flow(label='out', bus=heat_bus, size=100),
+ capacity_in_flow_hours=500,
+ relative_loss_per_hour=0.02, # 2%/hour heat loss
eta_charge=0.98,
eta_discharge=0.98,
- relative_loss_per_hour=0.02, # 2% heat loss per hour
)
```
-### Storage with Cyclic Condition
-
-For typical-day optimization, ensure the storage doesn't cheat:
+### Cyclic (Typical Day)
```python
-daily_storage = fx.Storage(
- label='daily_buffer',
- charging=fx.Flow(label='in', bus=some_bus, size=100),
- discharging=fx.Flow(label='out', bus=some_bus, size=100),
+buffer = fx.Storage(
+ label='buffer',
+ charging=fx.Flow(label='in', bus=bus, size=100),
+ discharging=fx.Flow(label='out', bus=bus, size=100),
capacity_in_flow_hours=400,
- initial_charge_state='equals_final', # Must end where it started
+ initial_charge_state='equals_final', # Must end where started
)
```
-### Storage with Investment Decision
+### Investment Decision
```python
-from flixopt import InvestParameters
-
-battery_investment = fx.Storage(
+battery = fx.Storage(
label='battery',
- charging=fx.Flow(label='charge', bus=electricity_bus, size=100),
- discharging=fx.Flow(label='discharge', bus=electricity_bus, size=100),
- capacity_in_flow_hours=InvestParameters(
+ charging=fx.Flow(label='charge', bus=elec_bus, size=100),
+ discharging=fx.Flow(label='discharge', bus=elec_bus, size=100),
+ capacity_in_flow_hours=fx.InvestParameters(
minimum_size=0,
- maximum_size=1000, # Up to 1 MWh
- specific_effects={'costs': 200}, # €200/kWh annualized
+ maximum_size=1000,
+ specific_effects={'costs': 200}, # €200/kWh
),
eta_charge=0.92,
eta_discharge=0.92,
@@ -180,18 +153,18 @@ battery_investment = fx.Storage(
```python
pumped_hydro = fx.Storage(
- label='pumped_hydro',
- charging=fx.Flow(label='pump', bus=electricity_bus, size=100), # 100 MW pumping
- discharging=fx.Flow(label='turbine', bus=electricity_bus, size=120), # 120 MW generating
- capacity_in_flow_hours=10000, # 10 GWh reservoir
- eta_charge=0.85, # Pumping losses
- eta_discharge=0.90, # Turbine losses
+ label='hydro',
+ charging=fx.Flow(label='pump', bus=elec_bus, size=100), # 100 MW
+ discharging=fx.Flow(label='turbine', bus=elec_bus, size=120), # 120 MW
+ capacity_in_flow_hours=10000,
+ eta_charge=0.85,
+ eta_discharge=0.90,
)
```
-## Preventing Simultaneous Charging and Discharging
+## Simultaneous Charge/Discharge
-By default, storage can't charge and discharge simultaneously (which would waste energy through round-trip losses). This is controlled by:
+By default, storage cannot charge and discharge simultaneously:
```python
storage = fx.Storage(
@@ -200,8 +173,6 @@ storage = fx.Storage(
)
```
-This adds a constraint linking the charging and discharging flows.
-
## Implementation Details
- **Component Class:** [`Storage`][flixopt.components.Storage]
@@ -211,5 +182,4 @@ This adds a constraint linking the charging and discharging flows.
- [Flow](Flow.md) — Charging and discharging flows
- [Bus](Bus.md) — Where storage connects
-- [InvestParameters](../features/InvestParameters.md) — Optimizing storage capacity
-- [Core Concepts: Storages](../../core-concepts.md#storages-save-for-later) — High-level overview
+- [InvestParameters](../features/InvestParameters.md) — Capacity optimization
From 780c544981e0c6ab12fea51cdc7c899004a78080 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 21:39:21 +0100
Subject: [PATCH 48/79] The Flow.md now has four tabs for capacity bounds:
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. Fixed Size — Standard bounds without on/off
2. Fixed Size + On/Off — Can be zero when off
3. Variable Size — Investment decision on capacity
4. Variable Size + On/Off — Both investment and on/off, with big-M linearization for the bilinear term $s(t) \cdot P$
---
.../mathematical-notation/elements/Flow.md | 49 +++++++++++++++++--
1 file changed, 46 insertions(+), 3 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md
index f092c2184..36bfe70c3 100644
--- a/docs/user-guide/mathematical-notation/elements/Flow.md
+++ b/docs/user-guide/mathematical-notation/elements/Flow.md
@@ -15,9 +15,9 @@ Every flow has two key quantities:
- **Size** ($P$) — The capacity or maximum possible flow rate
- **Flow Rate** ($p(t)$) — The actual flow at each timestep (optimization variable)
-## Capacity Bounds
+## Flow Rate Bounds
-=== "Standard (No On/Off)"
+=== "Fixed Size"
The flow rate is bounded by the size:
@@ -31,7 +31,7 @@ Every flow has two key quantities:
- $P = 100$, $p_{rel}^{min} = 0.3$, $p_{rel}^{max} = 1$
- Constraint: $30 \leq p(t) \leq 100$
-=== "With On/Off"
+=== "Fixed Size + On/Off"
When `on_off_parameters` is specified, the flow can also be zero:
@@ -46,6 +46,49 @@ Every flow has two key quantities:
See [OnOffParameters](../features/OnOffParameters.md) for details.
+=== "Variable Size"
+
+ When `size` is `InvestParameters`, the capacity $P$ becomes a variable:
+
+ $$
+ P^{min} \leq P \leq P^{max}
+ $$
+
+ $$
+ P \cdot p_{rel}^{min}(t) \leq p(t) \leq P \cdot p_{rel}^{max}(t)
+ $$
+
+ See [InvestParameters](../features/InvestParameters.md) for details.
+
+=== "Variable Size + On/Off"
+
+ !!! warning "Work in Progress"
+ This section needs review. The linearization constraints below may not be accurate.
+
+ When both `size` is `InvestParameters` and `on_off_parameters` is set:
+
+ $$
+ P^{min} \leq P \leq P^{max}
+ $$
+
+ $$
+ s(t) \cdot P \cdot p_{rel}^{min}(t) \leq p(t) \leq s(t) \cdot P \cdot p_{rel}^{max}(t)
+ $$
+
+ This creates a bilinear term $s(t) \cdot P$. flixOpt linearizes this using big-M constraints:
+
+ $$
+ p(t) \leq P^{max} \cdot s(t)
+ $$
+
+ $$
+ p(t) \leq P - P^{min} \cdot (1 - s(t))
+ $$
+
+ $$
+ p(t) \geq P - P^{max} \cdot (1 - s(t))
+ $$
+
=== "Fixed Profile"
When `fixed_relative_profile` is set, the flow rate is fixed:
From 1a9e2c752f5191db7283aa534c9022c894d200ff Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 21:55:14 +0100
Subject: [PATCH 49/79] InvestParameters.md: - Story-driven intro with
real-world examples - Core concept: "Size as a Variable" - Tabs for:
Binary (Fixed Size) | Continuous (Size Range) | Mandatory - Tabs for
effects: Fixed | Specific | Retirement | Piecewise - Minimal variable
names: $P$, $s_{inv}$, $c_{fix}$, $c_{spec}$, etc. - Cost annualization
formula
OnOffParameters.md:
- Story-driven intro with real-world examples
- Core concept: "Binary State" with flow bound modification
- Tabs for state transitions: Switch Detection | Startup Costs | Running Costs
- Tabs for duration constraints: Min Run Time | Min Off Time | Max Run Time | Total Hours | Max Startups
- Minimal variable names: $s(t)$, $s^{on}(t)$, $s^{off}(t)$, $T_{on}^{min}$, etc.
Piecewise.md:
- Story-driven intro with ASCII diagram
- Core concept: Linear segments with weighted combinations
- Tabs for constraints: Single Piece Active | With Zero Point
- Tabs for piece patterns: Continuous (Touching) | Gap (Forbidden Region) | Zero Point
- Minimal variable names: $\beta_k$, $\lambda_0$, $\lambda_1$, etc.
- Practical examples for heat pumps, boilers, and investment
---
.../features/InvestParameters.md | 354 +++++++-----------
.../features/OnOffParameters.md | 269 +++++++------
.../features/Piecewise.md | 338 ++++++++---------
3 files changed, 424 insertions(+), 537 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/features/InvestParameters.md b/docs/user-guide/mathematical-notation/features/InvestParameters.md
index 3271a4009..d3fd81a21 100644
--- a/docs/user-guide/mathematical-notation/features/InvestParameters.md
+++ b/docs/user-guide/mathematical-notation/features/InvestParameters.md
@@ -1,271 +1,197 @@
# InvestParameters
-InvestParameters enable investment decision modeling in optimization, supporting both binary (invest/don't invest) and continuous sizing choices with comprehensive cost modeling.
+InvestParameters enable investment decisions — should we build this? If so, how big?
-=== "Variables"
+!!! example "Real-world examples"
+ - **Solar PV** — Install 100 kW system or not (binary)
+ - **Battery** — Choose capacity between 10-1000 kWh (continuous)
+ - **Boiler replacement** — Upgrade or pay demolition costs
- | Symbol | Python Name | Description | Domain | Created When |
- |--------|-------------|-------------|--------|--------------|
- | $v_\text{invest}$ | `size` | Investment size (continuous or fixed) | $\mathbb{R}_+$ | Always |
- | $s_\text{invest}$ | `invested` | Binary investment decision | $\{0,1\}$ | `mandatory=False` (optional investment) |
+## Core Concept: Size as a Variable
-=== "Constraints"
+When `size` is an `InvestParameters`, the capacity $P$ becomes an optimization variable instead of a fixed parameter.
- **Binary investment decision** (when `fixed_size` specified):
+=== "Binary (Fixed Size)"
- $$\label{eq:invest_binary}
- v_\text{invest} = s_\text{invest} \cdot \text{size}_\text{fixed}
- $$
-
- ---
-
- **Continuous sizing decision** (when `minimum_size` and `maximum_size` specified):
+ Build a predefined size or nothing:
- $$\label{eq:invest_continuous}
- s_\text{invest} \cdot \text{size}_\text{min} \leq v_\text{invest} \leq s_\text{invest} \cdot \text{size}_\text{max}
$$
-
- When `mandatory=False`: $s_\text{invest} = 0$ means no investment ($v_\text{invest} = 0$), $s_\text{invest} = 1$ means invest with size in $[\text{size}_\text{min}, \text{size}_\text{max}]$
-
- ---
-
- **Mandatory investment** (when `mandatory=True`):
-
- $$\label{eq:invest_mandatory}
- s_\text{invest} = 1
+ P = s_{inv} \cdot P_{fixed}
$$
- ---
-
- **Investment effects - Fixed** (when `effects_of_investment` specified):
-
- $$\label{eq:invest_fixed_effects}
- E_{e,\text{fix}} = s_\text{invest} \cdot \text{fix}_e
- $$
+ Where $s_{inv} \in \{0, 1\}$ is the binary investment decision.
- One-time effects incurred if investment is made, independent of size (permits, grid connection, one-time environmental impacts).
+ - $s_{inv} = 0$: Don't invest, $P = 0$
+ - $s_{inv} = 1$: Invest, $P = P_{fixed}$
- ---
+=== "Continuous (Size Range)"
- **Investment effects - Specific** (when `effects_of_investment_per_size` specified):
+ Choose size within bounds:
- $$\label{eq:invest_specific_effects}
- E_{e,\text{spec}} = v_\text{invest} \cdot \text{spec}_e
+ $$
+ s_{inv} \cdot P^{min} \leq P \leq s_{inv} \cdot P^{max}
$$
- Effects proportional to investment size (equipment costs €/kW, material requirements kg/kW, recurring maintenance €/kW/year).
+ - $s_{inv} = 0$: Don't invest, $P = 0$
+ - $s_{inv} = 1$: Invest, $P \in [P^{min}, P^{max}]$
- ---
+=== "Mandatory"
- **Retirement effects** (when `effects_of_retirement` specified and `mandatory=False`):
+ Must invest, only choose size:
- $$\label{eq:invest_retirement_effects}
- E_{e,\text{retirement}} = (1 - s_\text{invest}) \cdot \text{retirement}_e
+ $$
+ P^{min} \leq P \leq P^{max}
$$
- Effects incurred if investment is NOT made (demolition/disposal costs, decommissioning, penalties, opportunity costs).
+ No binary variable — investment is required.
- ---
+## Investment Effects (Costs)
- **Piecewise effects** (when `piecewise_effects_of_investment` specified):
+=== "Fixed Effects"
- $$\label{eq:invest_piecewise_effects}
- E_{e,\text{pw}} = \sum_{k=1}^{K} \lambda_k \cdot r_{e,k}
- $$
-
- Subject to:
+ One-time costs if investing (permits, grid connection):
$$
- v_\text{invest} = \sum_{k=1}^{K} \lambda_k \cdot v_k
+ E_{e,fix} = s_{inv} \cdot c_{fix}
$$
- Non-linear effect relationships using piecewise linear approximations for economies of scale, technology learning curves, or threshold effects. See [Piecewise](Piecewise.md) for detailed formulation.
-
- ---
+=== "Specific Effects"
- **Total investment effects**:
+ Costs proportional to size (€/kW):
- $$\label{eq:invest_total_effects}
- E_{e,\text{invest}} = E_{e,\text{fix}} + E_{e,\text{spec}} + E_{e,\text{pw}} + E_{e,\text{retirement}}
+ $$
+ E_{e,spec} = P \cdot c_{spec}
$$
- **Mathematical Patterns:** [Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state), [Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds), [Piecewise Linear Approximations](Piecewise.md)
-
-=== "Parameters"
-
- | Symbol | Python Parameter | Description | Default |
- |--------|------------------|-------------|---------|
- | $\text{fix}_e$ | `effects_of_investment` | Fixed effects if investment is made | None |
- | $\text{retirement}_e$ | `effects_of_retirement` | Effects if NOT investing | None |
- | $\text{size}_\text{fixed}$ | `fixed_size` | Predefined component size (binary decision) | None |
- | $\text{size}_\text{max}$ | `maximum_size` | Maximum investment size | CONFIG.Modeling.big |
- | $\text{size}_\text{min}$ | `minimum_size` | Minimum investment size (if investing) | CONFIG.Modeling.epsilon |
- | $\text{spec}_e$ | `effects_of_investment_per_size` | Per-unit effects proportional to size | None |
- | - | `linked_periods` | Describes which periods are linked for multi-period optimization | None |
- | - | `mandatory` | If True, investment must occur | False |
- | - | `piecewise_effects_of_investment` | PiecewiseEffects for non-linear cost structures | None |
-
-=== "Use Cases"
-
- ## Binary Investment (Solar Panels)
-
- ```python
- from flixopt import Flow, InvestParameters
-
- solar_flow = Flow(
- label='solar_generation',
- bus='electricity',
- size=InvestParameters(
- fixed_size=100, # 100 kW system (all or nothing)
- mandatory=False, # Optional investment
- effects_of_investment={'cost': 25000}, # Fixed installation
- effects_of_investment_per_size={'cost': 1200}, # €1200/kW
- ),
- )
- ```
-
- **Variables:** `size`, `invested` (binary)
-
- **Constraints:** $\eqref{eq:invest_binary}$ with $v_\text{invest} = s_\text{invest} \cdot 100$ kW, $\eqref{eq:invest_fixed_effects}$ with fixed cost of €25,000, $\eqref{eq:invest_specific_effects}$ with €1,200/kW
-
- ---
+=== "Retirement Effects"
- ## Continuous Sizing (Battery)
+ Costs if NOT investing (demolition):
- ```python
- from flixopt import Storage, Flow, InvestParameters
+ $$
+ E_{e,ret} = (1 - s_{inv}) \cdot c_{ret}
+ $$
- battery = Storage(
- label='battery_storage',
- charging=Flow(label='charge', bus='electricity', size=100),
- discharging=Flow(label='discharge', bus='electricity', size=100),
- capacity_in_flow_hours=InvestParameters(
- minimum_size=10, # Minimum 10 kWh if investing
- maximum_size=1000, # Maximum 1 MWh
- effects_of_investment={'cost': 5000}, # Grid connection
- effects_of_investment_per_size={'cost': 600}, # €600/kWh
- ),
- )
- ```
-
- **Variables:** `size` (continuous), `invested` (binary)
-
- **Constraints:** $\eqref{eq:invest_continuous}$ with $s_\text{invest} \cdot 10 \leq v_\text{invest} \leq s_\text{invest} \cdot 1000$ kWh, $\eqref{eq:invest_fixed_effects}$, $\eqref{eq:invest_specific_effects}$
-
- ---
-
- ## With Retirement Costs (Replacement Decision)
-
- ```python
- from flixopt import LinearConverter, Flow, InvestParameters
-
- boiler = LinearConverter(
- label='boiler_replacement',
- inputs=[Flow(
- label='gas_in',
- bus='natural_gas',
- size=InvestParameters(
- minimum_size=50, # 50 kW minimum
- maximum_size=200, # 200 kW maximum
- effects_of_investment={'cost': 15000}, # New installation
- effects_of_investment_per_size={'cost': 400}, # €400/kW
- effects_of_retirement={'cost': 8000}, # Demolition if not replaced
- ),
- )],
- outputs=[Flow(label='heat_out', bus='heating', size=1)],
- conversion_factors=[{'gas_in': 0.9, 'heat_out': 1}],
- )
- ```
-
- **Variables:** `size`, `invested`
-
- **Constraints:** $\eqref{eq:invest_continuous}$, $\eqref{eq:invest_fixed_effects}$, $\eqref{eq:invest_specific_effects}$, $\eqref{eq:invest_retirement_effects}$ with €8,000 cost if $s_\text{invest} = 0$
-
- ---
-
- ## Economies of Scale (Piecewise Costs)
-
- ```python
- from flixopt import Storage, Flow, InvestParameters, Piecewise, Piece, PiecewiseEffects
-
- battery_investment = InvestParameters(
- minimum_size=10,
- maximum_size=1000,
- piecewise_effects_of_investment=PiecewiseEffects(
- piecewise_origin=Piecewise([
- Piece((0, 0), (100, 100)), # Small (0-100 kWh)
- Piece((100, 100), (500, 500)), # Medium (100-500 kWh)
- Piece((500, 500), (1000, 1000)), # Large (500-1000 kWh)
- ]),
- piecewise_shares={
- 'cost': Piecewise([
- Piece((0, 0), (100, 80000)), # €800/kWh (small)
- Piece((100, 80000), (500, 350000)), # €750-€600/kWh (medium)
- Piece((500, 350000), (1000, 850000)), # €600-€500/kWh (large, bulk discount)
- ])
- },
- ),
- )
- ```
+=== "Piecewise Effects"
- **Variables:** `size`, `invested`, piecewise lambda variables $\lambda_k$
+ Non-linear costs (economies of scale):
- **Constraints:** $\eqref{eq:invest_continuous}$, $\eqref{eq:invest_piecewise_effects}$ representing decreasing cost per kWh with scale (bulk discount from €800/kWh to €500/kWh)
+ $$
+ E_{e,pw} = f_{pw}(P)
+ $$
- ---
+ See [Piecewise](Piecewise.md) for details.
- ## Mandatory Investment (Upgrade Required)
+**Total investment effects:**
- ```python
- from flixopt import Flow, InvestParameters
+$$
+E_{e,inv} = E_{e,fix} + E_{e,spec} + E_{e,ret} + E_{e,pw}
+$$
- grid_upgrade = Flow(
- label='grid_connection',
- bus='electricity',
- size=InvestParameters(
- minimum_size=100,
- maximum_size=500,
- mandatory=True, # Must upgrade
- effects_of_investment_per_size={'cost': 1000}, # €1000/kW
+## Variables
+
+| Symbol | Python Name | Description | When Created |
+|--------|-------------|-------------|--------------|
+| $P$ | `size` | Investment size | Always |
+| $s_{inv}$ | `invested` | Binary decision | `mandatory=False` |
+
+## Parameters
+
+| Symbol | Python Name | Description | Default |
+|--------|-------------|-------------|---------|
+| $P_{fixed}$ | `fixed_size` | Fixed size (binary decision) | None |
+| $P^{min}$ | `minimum_size` | Minimum size if investing | ε |
+| $P^{max}$ | `maximum_size` | Maximum size | Big M |
+| $c_{fix}$ | `effects_of_investment` | Fixed effects | None |
+| $c_{spec}$ | `effects_of_investment_per_size` | Per-unit effects | None |
+| $c_{ret}$ | `effects_of_retirement` | Effects if not investing | None |
+| - | `mandatory` | Force investment | False |
+| - | `piecewise_effects_of_investment` | Non-linear effects | None |
+
+## Usage Examples
+
+### Binary Investment (Solar)
+
+```python
+solar = fx.Flow(
+ label='solar',
+ bus=elec_bus,
+ size=fx.InvestParameters(
+ fixed_size=100, # 100 kW or nothing
+ effects_of_investment={'costs': 25000}, # Fixed: €25k
+ effects_of_investment_per_size={'costs': 1200}, # €1200/kW
+ ),
+)
+```
+
+### Continuous Sizing (Battery)
+
+```python
+battery = fx.Storage(
+ ...,
+ capacity_in_flow_hours=fx.InvestParameters(
+ minimum_size=10, # At least 10 kWh
+ maximum_size=1000, # At most 1 MWh
+ effects_of_investment={'costs': 5000}, # Grid connection
+ effects_of_investment_per_size={'costs': 600}, # €600/kWh
+ ),
+)
+```
+
+### With Retirement Costs
+
+```python
+boiler = fx.LinearConverter(
+ ...,
+ inputs=[fx.Flow(
+ label='gas',
+ bus=gas_bus,
+ size=fx.InvestParameters(
+ minimum_size=50,
+ maximum_size=200,
+ effects_of_investment_per_size={'costs': 400},
+ effects_of_retirement={'costs': 8000}, # Demolition if not replaced
),
- )
- ```
-
- **Variables:** `size` (no binary variable since mandatory)
-
- **Constraints:** $\eqref{eq:invest_mandatory}$ forcing $s_\text{invest} = 1$, $\eqref{eq:invest_continuous}$ simplified to $100 \leq v_\text{invest} \leq 500$ kW
-
----
+ )],
+ ...
+)
+```
+
+### Mandatory Investment
+
+```python
+upgrade = fx.Flow(
+ label='grid',
+ bus=elec_bus,
+ size=fx.InvestParameters(
+ minimum_size=100,
+ maximum_size=500,
+ mandatory=True, # Must invest
+ effects_of_investment_per_size={'costs': 1000},
+ ),
+)
+```
## Cost Annualization
-**Important:** All investment cost values must be properly weighted to match the optimization model's time horizon.
+Investment costs must be annualized to match the optimization time horizon:
-For long-term investments, costs should be annualized:
-
-$$\label{eq:annualization}
-\text{cost}_\text{annual} = \frac{\text{cost}_\text{capital} \cdot r}{1 - (1 + r)^{-n}}
$$
-
-Where $r$ is the discount rate and $n$ is the equipment lifetime (years).
-
-**Example:** €1,000,000 equipment with 20-year life and 5% discount rate:
-
-$$
-\text{cost}_\text{annual} = \frac{1{,}000{,}000 \cdot 0.05}{1 - (1.05)^{-20}} \approx €80{,}243/\text{year}
+c_{annual} = \frac{c_{capital} \cdot r}{1 - (1 + r)^{-n}}
$$
----
+Where $r$ is the discount rate and $n$ is the lifetime (years).
+
+!!! example "€1M equipment, 20 years, 5% discount"
+ $c_{annual} = \frac{1{,}000{,}000 \cdot 0.05}{1 - 1.05^{-20}} \approx €80{,}243/year$
-## Implementation
+## Implementation Details
- **Feature Class:** [`InvestParameters`][flixopt.interface.InvestParameters]
- **Model Class:** [`InvestmentModel`][flixopt.features.InvestmentModel]
-- **Used by:** [`Flow`](../elements/Flow.md) · [`Storage`](../elements/Storage.md) · [`LinearConverter`](../elements/LinearConverter.md)
## See Also
-- **Elements:** [Flow](../elements/Flow.md) · [Storage](../elements/Storage.md) · [LinearConverter](../elements/LinearConverter.md)
-- **Features:** [OnOffParameters](OnOffParameters.md) · [Piecewise](Piecewise.md)
-- **Patterns:** [Bounds and States](../modeling-patterns/bounds-and-states.md)
-- **System-Level:** [Effects, Penalty & Objective](../effects-penalty-objective.md)
+- [Flow](../elements/Flow.md) — Using investment in flows
+- [Storage](../elements/Storage.md) — Storage capacity investment
+- [Piecewise](Piecewise.md) — Non-linear cost structures
+- [Effects & Objective](../effects-penalty-objective.md) — How costs are tracked
diff --git a/docs/user-guide/mathematical-notation/features/OnOffParameters.md b/docs/user-guide/mathematical-notation/features/OnOffParameters.md
index cf07279d8..d9b61b18e 100644
--- a/docs/user-guide/mathematical-notation/features/OnOffParameters.md
+++ b/docs/user-guide/mathematical-notation/features/OnOffParameters.md
@@ -1,191 +1,176 @@
# OnOffParameters
-OnOffParameters define operational constraints and effects for binary on/off equipment behavior, capturing realistic operational limits and associated costs.
+OnOffParameters add binary on/off behavior — equipment that can be completely off or operating within its capacity range.
-=== "Variables"
+!!! example "Real-world examples"
+ - **Power plant** — Startup costs, minimum run time
+ - **Batch reactor** — Must run complete cycles
+ - **Chiller** — Maximum operating hours per year
- | Symbol | Python Name | Description | Domain | Created When |
- |--------|-------------|-------------|--------|--------------|
- | $s(\text{t}_i)$ | `on` | Binary on/off state at time $\text{t}_i$ | $\{0,1\}$ | Always |
- | - | `off` | Binary off state (complement of `on`) | $\{0,1\}$ | Any consecutive off constraints specified |
- | - | `switch_on` | Startup indicator at time $\text{t}_i$ | $\{0,1\}$ | `switch_on_max` or `effects_per_switch_on` or consecutive constraints specified |
- | - | `switch_off` | Shutdown indicator at time $\text{t}_i$ | $\{0,1\}$ | `switch_on_max` or `effects_per_switch_on` or consecutive constraints specified |
- | - | `on_hours_total` | Total operating hours in period | $\mathbb{R}_+$ | `on_hours_min` or `on_hours_max` specified |
- | - | `switch_count` | Count of startups in period | $\mathbb{Z}_+$ | `switch_on_max` specified |
- | - | `consecutive_on_hours` | Duration tracking for minimum/maximum run times | $\mathbb{R}_+$ | `consecutive_on_hours_min` or `consecutive_on_hours_max` specified |
- | - | `consecutive_off_hours` | Duration tracking for minimum/maximum off times | $\mathbb{R}_+$ | `consecutive_off_hours_min` or `consecutive_off_hours_max` specified |
+## Core Concept: Binary State
-=== "Constraints"
+The on/off state $s(t) \in \{0, 1\}$ determines whether equipment operates:
- **Complementary on/off states** (when off state needed):
+- $s(t) = 0$: Off — flow rate must be zero
+- $s(t) = 1$: On — flow rate within capacity bounds
- $$\label{eq:onoff_complementary}
- s(\text{t}_i) + s_\text{off}(\text{t}_i) = 1
- $$
-
- ---
+This modifies the flow bounds (see [Flow](../elements/Flow.md)):
- **Total operating hours** (when `on_hours_min` or `on_hours_max` specified):
+$$
+s(t) \cdot P \cdot p_{rel}^{min} \leq p(t) \leq s(t) \cdot P \cdot p_{rel}^{max}
+$$
- $$\label{eq:onoff_total_hours}
- H_\text{min} \leq \sum_i s(\text{t}_i) \cdot \Delta \text{t}_i \leq H_\text{max}
- $$
+## State Transitions
- ---
+=== "Switch Detection"
- **State transitions** (when switch tracking enabled):
+ Track when equipment turns on/off:
- $$\label{eq:onoff_transitions}
- \text{switch\_on}(\text{t}_i) - \text{switch\_off}(\text{t}_i) = s(\text{t}_i) - s(\text{t}_{i-1})
$$
-
- ---
-
- **Maximum startup count** (when `switch_on_max` specified):
-
- $$\label{eq:onoff_switch_count}
- \sum_i \text{switch\_on}(\text{t}_i) \leq N_\text{switch,max}
+ s^{on}(t) - s^{off}(t) = s(t) - s(t-1)
$$
- ---
+ Where:
- **Minimum consecutive on time** (when `consecutive_on_hours_min` specified):
-
- $$\label{eq:onoff_min_on}
- \text{If } s(\text{t}_{i-1}) = 0 \text{ and } s(\text{t}_i) = 1, \text{ then } \sum_{j=i}^{i+k} s(\text{t}_j) \cdot \Delta \text{t}_j \geq T_\text{on,min}
- $$
+ - $s^{on}(t) = 1$ when switching on (was off, now on)
+ - $s^{off}(t) = 1$ when switching off (was on, now off)
- ---
+=== "Startup Costs"
- **Maximum consecutive on time** (when `consecutive_on_hours_max` specified):
+ Effects incurred each time equipment starts:
- $$\label{eq:onoff_max_on}
- \sum_{j=i}^{i+k} s(\text{t}_j) \cdot \Delta \text{t}_j \leq T_\text{on,max}
$$
-
- ---
-
- **Minimum consecutive off time** (when `consecutive_off_hours_min` specified):
-
- $$\label{eq:onoff_min_off}
- \text{If } s(\text{t}_{i-1}) = 1 \text{ and } s(\text{t}_i) = 0, \text{ then } \sum_{j=i}^{i+k} (1-s(\text{t}_j)) \cdot \Delta \text{t}_j \geq T_\text{off,min}
+ E_{e,switch} = \sum_t s^{on}(t) \cdot c_{switch}
$$
- ---
+=== "Running Costs"
- **Effects per switch on** (when `effects_per_switch_on` specified):
+ Effects while equipment operates:
- $$\label{eq:onoff_switch_effects}
- E_{e,\text{switch}} = \sum_i \text{switch\_on}(\text{t}_i) \cdot c_{e,\text{switch}}
$$
-
- ---
-
- **Effects per running hour** (when `effects_per_running_hour` specified):
-
- $$\label{eq:onoff_running_effects}
- E_{e,\text{run}} = \sum_i s(\text{t}_i) \cdot \Delta \text{t}_i \cdot c_{e,\text{run}}
+ E_{e,run} = \sum_t s(t) \cdot \Delta t \cdot c_{run}
$$
- **Mathematical Patterns:** [State Transitions](../modeling-patterns/bounds-and-states.md), [Duration Tracking](../modeling-patterns/bounds-and-states.md)
-
-=== "Parameters"
+## Duration Constraints
- | Symbol | Python Parameter | Description | Default |
- |--------|------------------|-------------|---------|
- | $c_{e,\text{run}}$ | `effects_per_running_hour` | Ongoing effects while equipment operates | None |
- | $c_{e,\text{switch}}$ | `effects_per_switch_on` | Effects for each startup | None |
- | $\Delta \text{t}_i$ | - | Timestep duration (hours) | From system |
- | $H_\text{max}$ | `on_hours_max` | Maximum total operating hours per period | None |
- | $H_\text{min}$ | `on_hours_min` | Minimum total operating hours per period | None |
- | $N_\text{switch,max}$ | `switch_on_max` | Maximum number of startups per period | None |
- | $T_\text{off,min}$ | `consecutive_off_hours_min` | Minimum continuous shutdown duration | None |
- | $T_\text{on,max}$ | `consecutive_on_hours_max` | Maximum continuous operating duration | None |
- | $T_\text{on,min}$ | `consecutive_on_hours_min` | Minimum continuous operating duration | None |
- | - | `consecutive_off_hours_max` | Maximum continuous shutdown duration | None |
- | - | `force_switch_on` | Create switch variables even without max constraint | False |
+=== "Min Run Time"
-=== "Use Cases"
+ Once started, must run for minimum duration:
- ## Power Plant with Startup Costs
-
- ```python
- from flixopt import Flow, OnOffParameters
-
- generator = Flow(
- label='power_output',
- bus='electricity',
- size=100, # 100 MW
- on_off_parameters=OnOffParameters(
- effects_per_switch_on={'cost': 25000}, # €25k startup cost
- consecutive_on_hours_min=8, # Must run 8+ hours once started
- consecutive_off_hours_min=4, # Must stay off 4+ hours
- ),
- )
- ```
-
- **Variables:** `on[t]`, `switch_on[t]`, `switch_off[t]`, `consecutive_on_hours`, `consecutive_off_hours`
-
- **Constraints:** $\eqref{eq:onoff_transitions}$, $\eqref{eq:onoff_min_on}$ with 8h minimum, $\eqref{eq:onoff_min_off}$ with 4h minimum, $\eqref{eq:onoff_switch_effects}$ with €25k per startup
-
- ---
+ $$
+ \text{If } s^{on}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} s(j) \cdot \Delta t \geq T_{on}^{min}
+ $$
- ## Industrial Process with Cycling Limits
+=== "Min Off Time"
- ```python
- from flixopt import Flow, OnOffParameters
+ Once stopped, must stay off for minimum duration:
- batch_reactor = Flow(
- label='process_output',
- bus='production',
- size=50,
- on_off_parameters=OnOffParameters(
- effects_per_switch_on={'setup_cost': 1500},
- effects_per_running_hour={'utilities': 200},
- consecutive_on_hours_min=12, # 12h minimum batch
- switch_on_max=20, # Max 20 batches per period
- ),
- )
- ```
+ $$
+ \text{If } s^{off}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} (1-s(j)) \cdot \Delta t \geq T_{off}^{min}
+ $$
- **Variables:** `on[t]`, `switch_on[t]`, `switch_off[t]`, `switch_count`, `consecutive_on_hours`
+=== "Max Run Time"
- **Constraints:** $\eqref{eq:onoff_transitions}$, $\eqref{eq:onoff_switch_count}$ with max 20 startups, $\eqref{eq:onoff_min_on}$ with 12h minimum, $\eqref{eq:onoff_switch_effects}$, $\eqref{eq:onoff_running_effects}$
+ Cannot run continuously beyond limit:
- ---
+ $$
+ \sum_{j=t}^{t+k} s(j) \cdot \Delta t \leq T_{on}^{max}
+ $$
- ## HVAC with Operating Hour Limits
+=== "Total Hours"
- ```python
- from flixopt import Flow, OnOffParameters
+ Constrain total operating hours per period:
- chiller = Flow(
- label='cooling_output',
- bus='chilled_water',
- size=500, # 500 kW cooling
- on_off_parameters=OnOffParameters(
- on_hours_min=2000, # Minimum 2000h/year utilization
- on_hours_max=5000, # Maximum 5000h/year (maintenance limit)
- consecutive_on_hours_max=18, # Max 18h continuous operation
- ),
- )
- ```
+ $$
+ H^{min} \leq \sum_t s(t) \cdot \Delta t \leq H^{max}
+ $$
- **Variables:** `on[t]`, `on_hours_total`, `consecutive_on_hours`
+=== "Max Startups"
- **Constraints:** $\eqref{eq:onoff_total_hours}$ with 2000-5000h bounds, $\eqref{eq:onoff_max_on}$ with 18h maximum
+ Limit number of startups per period:
----
+ $$
+ \sum_t s^{on}(t) \leq N_{switch}^{max}
+ $$
-## Implementation
+## Variables
+
+| Symbol | Python Name | Description | When Created |
+|--------|-------------|-------------|--------------|
+| $s(t)$ | `on` | Binary on/off state | Always |
+| $s^{on}(t)$ | `switch_on` | Startup indicator | Switch effects or constraints |
+| $s^{off}(t)$ | `switch_off` | Shutdown indicator | Switch effects or constraints |
+
+## Parameters
+
+| Symbol | Python Name | Description | Default |
+|--------|-------------|-------------|---------|
+| $c_{switch}$ | `effects_per_switch_on` | Startup effects | None |
+| $c_{run}$ | `effects_per_running_hour` | Running effects | None |
+| $T_{on}^{min}$ | `consecutive_on_hours_min` | Min run time | None |
+| $T_{on}^{max}$ | `consecutive_on_hours_max` | Max run time | None |
+| $T_{off}^{min}$ | `consecutive_off_hours_min` | Min off time | None |
+| $T_{off}^{max}$ | `consecutive_off_hours_max` | Max off time | None |
+| $H^{min}$ | `on_hours_min` | Min total hours | None |
+| $H^{max}$ | `on_hours_max` | Max total hours | None |
+| $N_{switch}^{max}$ | `switch_on_max` | Max startups | None |
+
+## Usage Examples
+
+### Power Plant with Startup Costs
+
+```python
+generator = fx.Flow(
+ label='power',
+ bus=elec_bus,
+ size=100,
+ relative_minimum=0.4, # 40% min when ON
+ on_off_parameters=fx.OnOffParameters(
+ effects_per_switch_on={'costs': 25000}, # €25k startup
+ consecutive_on_hours_min=8, # Must run 8+ hours
+ consecutive_off_hours_min=4, # Must stay off 4+ hours
+ ),
+)
+```
+
+### Batch Process with Cycle Limits
+
+```python
+reactor = fx.Flow(
+ label='output',
+ bus=prod_bus,
+ size=50,
+ on_off_parameters=fx.OnOffParameters(
+ effects_per_switch_on={'costs': 1500},
+ effects_per_running_hour={'costs': 200},
+ consecutive_on_hours_min=12, # 12h batch
+ switch_on_max=20, # Max 20 batches
+ ),
+)
+```
+
+### HVAC with Operating Limits
+
+```python
+chiller = fx.Flow(
+ label='cooling',
+ bus=cool_bus,
+ size=500,
+ on_off_parameters=fx.OnOffParameters(
+ on_hours_min=2000, # Min 2000h/year
+ on_hours_max=5000, # Max 5000h/year
+ consecutive_on_hours_max=18, # Max 18h continuous
+ ),
+)
+```
+
+## Implementation Details
- **Feature Class:** [`OnOffParameters`][flixopt.interface.OnOffParameters]
- **Model Class:** [`OnOffModel`][flixopt.features.OnOffModel]
-- **Used by:** [`Flow`](../elements/Flow.md) · [`LinearConverter`](../elements/LinearConverter.md)
## See Also
-- **Elements:** [Flow](../elements/Flow.md) · [LinearConverter](../elements/LinearConverter.md)
-- **Features:** [InvestParameters](InvestParameters.md)
-- **Patterns:** [Bounds and States](../modeling-patterns/bounds-and-states.md)
-- **System-Level:** [Effects, Penalty & Objective](../effects-penalty-objective.md)
+- [Flow](../elements/Flow.md) — How on/off affects flow bounds
+- [InvestParameters](InvestParameters.md) — Combining with investment
+- [Effects & Objective](../effects-penalty-objective.md) — How costs are tracked
diff --git a/docs/user-guide/mathematical-notation/features/Piecewise.md b/docs/user-guide/mathematical-notation/features/Piecewise.md
index ada217c09..56c55d316 100644
--- a/docs/user-guide/mathematical-notation/features/Piecewise.md
+++ b/docs/user-guide/mathematical-notation/features/Piecewise.md
@@ -1,234 +1,210 @@
# Piecewise
-Piecewise enables modeling of non-linear relationships through piecewise linear approximations while maintaining problem linearity, consisting of a collection of Pieces that define valid ranges for variables.
+Piecewise linearization models non-linear relationships using connected linear segments — keeping the problem linear while capturing complex behavior.
-=== "Variables"
+!!! example "Real-world examples"
+ - **Part-load efficiency** — Boiler efficiency varies with load
+ - **Economies of scale** — Cost per kW decreases with size
+ - **Forbidden regions** — Turbine can't operate between 0-40%
- | Symbol | Python Name | Description | Domain | Created When |
- |--------|-------------|-------------|--------|--------------|
- | $\beta_k$ | `beta` (per Piece) | Binary variable indicating if piece $k$ is active | $\{0,1\}$ | Always (for each Piece) |
- | $\beta_\text{zero}$ | `zero_point` | Binary variable allowing all variables to be zero | $\{0,1\}$ | `zero_point=True` specified |
- | $\lambda_{0,k}$ | `lambda0` (per Piece) | Fraction of start point $\text{v}_{\text{start},k}$ that is active | $[0,1]$ | Always (for each Piece) |
- | $\lambda_{1,k}$ | `lambda1` (per Piece) | Fraction of end point $\text{v}_{\text{end},k}$ that is active | $[0,1]$ | Always (for each Piece) |
+## Core Concept: Linear Segments
-=== "Constraints"
+A piecewise function is defined by segments (pieces), each with start and end points:
- **Active piece definition** (always active for each piece):
+```
+ y
+ │ ╱─── Piece 3
+ │ ╱
+ │ ╱───── Piece 2
+ │ ╱
+ │╱──────── Piece 1
+ └───────────── x
+```
- $$\label{eq:active_piece}
- \beta_k = \lambda_{0,k} + \lambda_{1,k}
- $$
-
- Binary variable $\beta_k$ is 1 if piece $k$ is active (either $\lambda_{0,k}$ or $\lambda_{1,k}$ is non-zero), 0 otherwise.
+The variable can be anywhere along one of these segments.
- ---
+## Mathematical Formulation
- **Variable definition through piece** (always active for each variable):
+Each piece $k$ has:
- $$\label{eq:piece}
- v_k = \lambda_{0,k} \cdot \text{v}_{\text{start},k} + \lambda_{1,k} \cdot \text{v}_{\text{end},k}
- $$
+- Start point: $(x_k^{start}, y_k^{start})$
+- End point: $(x_k^{end}, y_k^{end})$
- The variable value is the weighted sum of the piece's start and end points.
+The value is a weighted combination:
- ---
+$$
+x = \lambda_0 \cdot x^{start} + \lambda_1 \cdot x^{end}
+$$
- **Single active piece** (when `zero_point=False` or not specified):
+$$
+y = \lambda_0 \cdot y^{start} + \lambda_1 \cdot y^{end}
+$$
- $$\label{eq:piecewise_in_pieces}
- \sum_{k=1}^K \beta_k = 1
- $$
+Where $\lambda_0, \lambda_1 \geq 0$ and $\lambda_0 + \lambda_1 = \beta_k$ (piece is active).
- Exactly one piece must be active. This ensures $v \in [\text{v}_{\text{start},k}, \text{v}_{\text{end},k}]$ for some $k$.
+=== "Single Piece Active"
- ---
+ Exactly one piece must be active:
- **Optional zero with single active piece** (when `zero_point=True`):
-
- $$\label{eq:piecewise_in_pieces_zero}
- \sum_{k=1}^K \beta_k = \beta_\text{zero}
+ $$
+ \sum_k \beta_k = 1
$$
- Either one piece is active ($\beta_\text{zero} = 1$) or all are inactive ($\beta_\text{zero} = 0$, forcing all $\lambda$ to zero). This allows $v \in \{0\} \cup [\text{v}_{\text{start},k}, \text{v}_{\text{end},k}]$.
-
- ---
+=== "With Zero Point"
- **Combined piecewise relationship** (when multiple variables share pieces):
+ Allow all variables to be zero (equipment off):
- $$\label{eq:piecewise_combined}
- \begin{align}
- v_1 &= \sum_{k=1}^K (\lambda_{0,k} \cdot \text{v}_{1,\text{start},k} + \lambda_{1,k} \cdot \text{v}_{1,\text{end},k}) \\
- v_2 &= \sum_{k=1}^K (\lambda_{0,k} \cdot \text{v}_{2,\text{start},k} + \lambda_{1,k} \cdot \text{v}_{2,\text{end},k})
- \end{align}
+ $$
+ \sum_k \beta_k = \beta_{zero}
$$
- Multiple variables share the same $\lambda$ and $\beta$ variables, creating coupled non-linear relationships.
-
- **Mathematical Patterns:** [SOS Type 2 Constraints](../modeling-patterns/bounds-and-states.md), [Piecewise Linear Approximation](../modeling-patterns/bounds-and-states.md)
-
-=== "Parameters"
+ - $\beta_{zero} = 0$: All off, $x = y = 0$
+ - $\beta_{zero} = 1$: One piece active
- | Symbol | Python Parameter | Description | Default |
- |--------|------------------|-------------|---------|
- | $K$ | - | Number of pieces | From `pieces` list length |
- | $\text{v}_{\text{end},k}$ | - | End point of piece $k$ | From `Piece.end` |
- | $\text{v}_{\text{start},k}$ | - | Start point of piece $k$ | From `Piece.start` |
- | - | `pieces` | List of Piece objects defining the linear segments | Required |
- | - | `zero_point` | Allow all variables to be zero | False |
+## Piece Patterns
-=== "Use Cases"
+=== "Continuous (Touching)"
- ## Continuous Efficiency Curve (Touching Pieces)
+ Pieces share boundary points — smooth function:
```python
- from flixopt import Piecewise, Piece
-
- efficiency_curve = Piecewise([
- Piece((0, 0), (25, 25)), # Low load: 0-25 MW
- Piece((25, 25), (75, 75)), # Medium load: 25-75 MW (touches at 25)
- Piece((75, 75), (100, 100)), # High load: 75-100 MW (touches at 75)
+ curve = fx.Piecewise([
+ fx.Piece((0, 0), (50, 45)), # Low load
+ fx.Piece((50, 45), (100, 90)), # High load (touches at 50)
])
```
- **Variables:** $\beta_1, \beta_2, \beta_3$ (piece indicators), $\lambda_{0,1}, \lambda_{1,1}, ..., \lambda_{0,3}, \lambda_{1,3}$ (lambda variables)
-
- **Constraints:** $\eqref{eq:active_piece}$ for each piece, $\eqref{eq:piece}$ defining $v$, $\eqref{eq:piecewise_in_pieces}$ ensuring exactly one piece active
-
- **Behavior:** Creates smooth continuous function without gaps, allowing operation anywhere from 0-100 MW.
+ Operation anywhere from 0-100.
- ---
+=== "Gap (Forbidden Region)"
- ## Forbidden Operating Range (Gap Between Pieces)
+ Non-contiguous pieces — forbidden operating range:
```python
- from flixopt import Piecewise, Piece
-
- turbine_operation = Piecewise([
- Piece((0, 0), (0, 0)), # Off state (point)
- Piece((40, 40), (100, 100)), # Operating range (gap: 0-40 forbidden)
+ curve = fx.Piecewise([
+ fx.Piece((0, 0), (0, 0)), # Off (point)
+ fx.Piece((40, 36), (100, 90)), # Operating (gap: 0-40 forbidden)
])
```
- **Variables:** $\beta_1, \beta_2$, $\lambda_{0,1}, \lambda_{1,1}, \lambda_{0,2}, \lambda_{1,2}$
-
- **Constraints:** $\eqref{eq:active_piece}$, $\eqref{eq:piece}$, $\eqref{eq:piecewise_in_pieces}$
-
- **Behavior:** Equipment must be either completely off (0) or operating between 40-100 MW. The range 0-40 MW is forbidden due to the gap.
+ Must be off (0) or operating (40-100).
- ---
+=== "Zero Point"
- ## Variable COP Heat Pump (Two Coupled Variables)
+ Explicitly allow zero without a zero piece:
```python
- from flixopt import LinearConverter, Flow, Piecewise, Piece, PiecewiseConversion
-
- # COP varies: 2.5 at low load to 4.0 at high load
- electricity_to_heat = Piecewise([
- Piece((0, 0), (50, 125)), # 0-50 kW elec → 0-125 kW heat (COP 2.5)
- Piece((50, 125), (100, 350)), # 50-100 kW elec → 125-350 kW heat (COP 3.5-4.5)
- ])
-
- heat_pump = LinearConverter(
- label='heat_pump',
- inputs=[Flow(label='electricity_in', bus='electricity', size=100)],
- outputs=[Flow(label='heat_out', bus='heating', size=350)],
- piecewise_conversion=PiecewiseConversion(
- origin_flow='electricity_in',
- piecewise_shares={'heat_out': electricity_to_heat},
- ),
- )
- ```
-
- **Variables:** Shared $\beta_1, \beta_2$, $\lambda_{0,1}, \lambda_{1,1}, \lambda_{0,2}, \lambda_{1,2}$ for both electricity and heat
-
- **Constraints:** $\eqref{eq:piecewise_combined}$ coupling electricity input to heat output with variable COP
-
- **Behavior:** Electricity input and heat output are coupled through shared lambda variables, modeling load-dependent COP.
-
- ---
-
- ## Optional Operation with Zero Point
-
- ```python
- from flixopt import Piecewise, Piece
-
- optional_operation = Piecewise(
+ curve = fx.Piecewise(
pieces=[
- Piece((10, 10), (50, 50)), # Low operating range
- Piece((50, 50), (100, 100)), # High operating range
+ fx.Piece((10, 9), (50, 45)),
+ fx.Piece((50, 45), (100, 90)),
],
- zero_point=True, # Allow complete shutdown
- )
- ```
-
- **Variables:** $\beta_1, \beta_2$, $\beta_\text{zero}$, $\lambda_{0,1}, \lambda_{1,1}, \lambda_{0,2}, \lambda_{1,2}$
-
- **Constraints:** $\eqref{eq:active_piece}$, $\eqref{eq:piece}$, $\eqref{eq:piecewise_in_pieces_zero}$ with zero point
-
- **Behavior:** Equipment can be completely off ($v=0$, $\beta_\text{zero}=0$) or operating in 10-100 range ($\beta_\text{zero}=1$, one piece active).
-
- ---
-
- ## Economies of Scale (Investment Costs)
-
- ```python
- from flixopt import InvestParameters, Piecewise, Piece, PiecewiseEffects
-
- # Cost per kWh decreases with scale
- battery_cost = InvestParameters(
- minimum_size=10,
- maximum_size=1000,
- piecewise_effects_of_investment=PiecewiseEffects(
- piecewise_origin=Piecewise([
- Piece((0, 0), (100, 100)), # Small
- Piece((100, 100), (500, 500)), # Medium
- Piece((500, 500), (1000, 1000)), # Large
- ]),
- piecewise_shares={
- 'cost': Piecewise([
- Piece((0, 0), (100, 80000)), # €800/kWh
- Piece((100, 80000), (500, 350000)), # €750-600/kWh
- Piece((500, 350000), (1000, 850000)), # €600-500/kWh (bulk discount)
- ])
- },
- ),
+ zero_point=True, # Can also be completely off
)
```
- **Variables:** Shared $\beta_k$, $\lambda_{0,k}$, $\lambda_{1,k}$ for size and cost
-
- **Constraints:** $\eqref{eq:piecewise_combined}$ coupling size to cost with decreasing unit cost
-
- **Behavior:** Investment size and total cost are coupled through piecewise relationship modeling economies of scale.
-
----
-
-## Piece Relationship Patterns
-
-### Touching Pieces (Continuous Function)
-Pieces that share boundary points create smooth, continuous functions without gaps or overlaps.
-**Use case:** Efficiency curves, performance maps
-
-### Gaps Between Pieces (Forbidden Regions)
-Non-contiguous pieces with gaps represent forbidden operating regions.
-**Use case:** Minimum load requirements, safety zones, equipment limitations
-
-### Overlapping Pieces (Flexible Operation)
-Pieces with overlapping domains provide optimization flexibility, allowing the solver to choose which segment to operate in.
-**Use case:** Multiple operating modes, flexible efficiency options
-
----
-
-## Implementation
+ Either off (0) or operating (10-100).
+
+## Variables
+
+| Symbol | Python Name | Description | When Created |
+|--------|-------------|-------------|--------------|
+| $\beta_k$ | `beta` | Piece $k$ active | Always |
+| $\lambda_{0,k}$ | `lambda0` | Weight on start point | Always |
+| $\lambda_{1,k}$ | `lambda1` | Weight on end point | Always |
+| $\beta_{zero}$ | `zero_point` | Allow zero | `zero_point=True` |
+
+## Parameters
+
+| Symbol | Python Name | Description |
+|--------|-------------|-------------|
+| $(x_k^{start}, y_k^{start})$ | `Piece.start` | Start point of piece $k$ |
+| $(x_k^{end}, y_k^{end})$ | `Piece.end` | End point of piece $k$ |
+| - | `zero_point` | Allow all variables = 0 |
+
+## Usage Examples
+
+### Variable COP Heat Pump
+
+```python
+# COP varies: 2.5 at low load, 4.0 at high load
+elec_to_heat = fx.Piecewise([
+ fx.Piece((0, 0), (50, 125)), # COP ~2.5
+ fx.Piece((50, 125), (100, 350)), # COP ~3.5-4.5
+])
+
+hp = fx.LinearConverter(
+ label='hp',
+ inputs=[fx.Flow(label='el', bus=elec_bus, size=100)],
+ outputs=[fx.Flow(label='heat', bus=heat_bus, size=350)],
+ piecewise_conversion=fx.PiecewiseConversion(
+ origin_flow='el',
+ piecewise_shares={'heat': elec_to_heat},
+ ),
+)
+```
+
+### Part-Load Efficiency Boiler
+
+```python
+# Efficiency: 80% at low load, 92% at high load
+gas_to_heat = fx.Piecewise([
+ fx.Piece((0, 0), (30, 24)), # 80% at 0-30%
+ fx.Piece((30, 24), (100, 92)), # 92% at 30-100%
+])
+
+boiler = fx.LinearConverter(
+ label='boiler',
+ inputs=[fx.Flow(label='gas', bus=gas_bus, size=100)],
+ outputs=[fx.Flow(label='heat', bus=heat_bus, size=92)],
+ piecewise_conversion=fx.PiecewiseConversion(
+ origin_flow='gas',
+ piecewise_shares={'heat': gas_to_heat},
+ ),
+)
+```
+
+### Economies of Scale (Investment)
+
+```python
+# Cost per kWh decreases with size
+battery = fx.InvestParameters(
+ minimum_size=10,
+ maximum_size=1000,
+ piecewise_effects_of_investment=fx.PiecewiseEffects(
+ piecewise_origin=fx.Piecewise([
+ fx.Piece((0, 0), (100, 100)),
+ fx.Piece((100, 100), (500, 500)),
+ fx.Piece((500, 500), (1000, 1000)),
+ ]),
+ piecewise_shares={
+ 'costs': fx.Piecewise([
+ fx.Piece((0, 0), (100, 80000)), # €800/kWh
+ fx.Piece((100, 80000), (500, 350000)), # €675/kWh avg
+ fx.Piece((500, 350000), (1000, 750000)), # €500/kWh
+ ])
+ },
+ ),
+)
+```
+
+### Forbidden Operating Region
+
+```python
+# Turbine: off or 40-100%, not in between
+turbine_curve = fx.Piecewise([
+ fx.Piece((0, 0), (0, 0)), # Off
+ fx.Piece((40, 40), (100, 100)), # Operating range
+])
+```
+
+## Implementation Details
- **Feature Class:** [`Piecewise`][flixopt.interface.Piecewise]
-- **Model Class:** [`PiecewiseModel`][flixopt.features.PiecewiseModel]
- **Helper Class:** [`Piece`][flixopt.interface.Piece]
-- **Used by:** [`LinearConverter`](../elements/LinearConverter.md) (via `PiecewiseConversion`) · [`InvestParameters`](InvestParameters.md) (via `PiecewiseEffects`)
+- **Model Class:** [`PiecewiseModel`][flixopt.features.PiecewiseModel]
## See Also
-- **Elements:** [LinearConverter](../elements/LinearConverter.md)
-- **Features:** [InvestParameters](InvestParameters.md)
-- **Patterns:** [Modeling Patterns](../modeling-patterns/index.md)
-- **System-Level:** [Effects, Penalty & Objective](../effects-penalty-objective.md)
+- [LinearConverter](../elements/LinearConverter.md) — Using piecewise conversion
+- [InvestParameters](InvestParameters.md) — Piecewise investment costs
From 04e9e1f2d53afa45096f388538a3684610528d44 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 22:00:48 +0100
Subject: [PATCH 50/79] Make OnOffParameters better
---
.../features/OnOffParameters.md | 81 +++++++++----------
1 file changed, 39 insertions(+), 42 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/features/OnOffParameters.md b/docs/user-guide/mathematical-notation/features/OnOffParameters.md
index d9b61b18e..e6be94ea7 100644
--- a/docs/user-guide/mathematical-notation/features/OnOffParameters.md
+++ b/docs/user-guide/mathematical-notation/features/OnOffParameters.md
@@ -1,30 +1,27 @@
# OnOffParameters
-OnOffParameters add binary on/off behavior — equipment that can be completely off or operating within its capacity range.
+OnOffParameters define a binary on/off state variable with associated constraints and effects.
!!! example "Real-world examples"
- **Power plant** — Startup costs, minimum run time
- **Batch reactor** — Must run complete cycles
- **Chiller** — Maximum operating hours per year
-## Core Concept: Binary State
+## Core Concept: Binary State Variable
-The on/off state $s(t) \in \{0, 1\}$ determines whether equipment operates:
+OnOffParameters create a binary state variable $s(t) \in \{0, 1\}$:
-- $s(t) = 0$: Off — flow rate must be zero
-- $s(t) = 1$: On — flow rate within capacity bounds
+- $s(t) = 0$: Off
+- $s(t) = 1$: On
-This modifies the flow bounds (see [Flow](../elements/Flow.md)):
-
-$$
-s(t) \cdot P \cdot p_{rel}^{min} \leq p(t) \leq s(t) \cdot P \cdot p_{rel}^{max}
-$$
+!!! note "Connection to continuous variables"
+ How this binary state connects to continuous variables (like flow rate) is defined where `on_off_parameters` is used. See [Flow](../elements/Flow.md) for how flows use the on/off state to modify their bounds.
## State Transitions
=== "Switch Detection"
- Track when equipment turns on/off:
+ Track when state changes:
$$
s^{on}(t) - s^{off}(t) = s(t) - s(t-1)
@@ -32,20 +29,20 @@ $$
Where:
- - $s^{on}(t) = 1$ when switching on (was off, now on)
- - $s^{off}(t) = 1$ when switching off (was on, now off)
+ - $s^{on}(t) = 1$ when switching on (0 → 1)
+ - $s^{off}(t) = 1$ when switching off (1 → 0)
-=== "Startup Costs"
+=== "Startup Effects"
- Effects incurred each time equipment starts:
+ Effects incurred each time state switches on:
$$
E_{e,switch} = \sum_t s^{on}(t) \cdot c_{switch}
$$
-=== "Running Costs"
+=== "Running Effects"
- Effects while equipment operates:
+ Effects while state is on:
$$
E_{e,run} = \sum_t s(t) \cdot \Delta t \cdot c_{run}
@@ -53,41 +50,41 @@ $$
## Duration Constraints
-=== "Min Run Time"
+=== "Min On Time"
- Once started, must run for minimum duration:
+ Once on, must stay on for minimum duration:
$$
- \text{If } s^{on}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} s(j) \cdot \Delta t \geq T_{on}^{min}
+ s^{on}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} s(j) \cdot \Delta t \geq T_{on}^{min}
$$
=== "Min Off Time"
- Once stopped, must stay off for minimum duration:
+ Once off, must stay off for minimum duration:
$$
- \text{If } s^{off}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} (1-s(j)) \cdot \Delta t \geq T_{off}^{min}
+ s^{off}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} (1-s(j)) \cdot \Delta t \geq T_{off}^{min}
$$
-=== "Max Run Time"
+=== "Max On Time"
- Cannot run continuously beyond limit:
+ Cannot stay on continuously beyond limit:
$$
\sum_{j=t}^{t+k} s(j) \cdot \Delta t \leq T_{on}^{max}
$$
-=== "Total Hours"
+=== "Total On Hours"
- Constrain total operating hours per period:
+ Constrain total on-time per period:
$$
H^{min} \leq \sum_t s(t) \cdot \Delta t \leq H^{max}
$$
-=== "Max Startups"
+=== "Max Switches"
- Limit number of startups per period:
+ Limit number of switch-ons per period:
$$
\sum_t s^{on}(t) \leq N_{switch}^{max}
@@ -97,23 +94,23 @@ $$
| Symbol | Python Name | Description | When Created |
|--------|-------------|-------------|--------------|
-| $s(t)$ | `on` | Binary on/off state | Always |
-| $s^{on}(t)$ | `switch_on` | Startup indicator | Switch effects or constraints |
-| $s^{off}(t)$ | `switch_off` | Shutdown indicator | Switch effects or constraints |
+| $s(t)$ | `on` | Binary state | Always |
+| $s^{on}(t)$ | `switch_on` | Switch-on indicator | Switch effects or constraints |
+| $s^{off}(t)$ | `switch_off` | Switch-off indicator | Switch effects or constraints |
## Parameters
| Symbol | Python Name | Description | Default |
|--------|-------------|-------------|---------|
-| $c_{switch}$ | `effects_per_switch_on` | Startup effects | None |
-| $c_{run}$ | `effects_per_running_hour` | Running effects | None |
-| $T_{on}^{min}$ | `consecutive_on_hours_min` | Min run time | None |
-| $T_{on}^{max}$ | `consecutive_on_hours_max` | Max run time | None |
-| $T_{off}^{min}$ | `consecutive_off_hours_min` | Min off time | None |
-| $T_{off}^{max}$ | `consecutive_off_hours_max` | Max off time | None |
-| $H^{min}$ | `on_hours_min` | Min total hours | None |
-| $H^{max}$ | `on_hours_max` | Max total hours | None |
-| $N_{switch}^{max}$ | `switch_on_max` | Max startups | None |
+| $c_{switch}$ | `effects_per_switch_on` | Effects per switch-on | None |
+| $c_{run}$ | `effects_per_running_hour` | Effects while on | None |
+| $T_{on}^{min}$ | `consecutive_on_hours_min` | Min consecutive on | None |
+| $T_{on}^{max}$ | `consecutive_on_hours_max` | Max consecutive on | None |
+| $T_{off}^{min}$ | `consecutive_off_hours_min` | Min consecutive off | None |
+| $T_{off}^{max}$ | `consecutive_off_hours_max` | Max consecutive off | None |
+| $H^{min}$ | `on_hours_min` | Min total on hours | None |
+| $H^{max}$ | `on_hours_max` | Max total on hours | None |
+| $N_{switch}^{max}$ | `switch_on_max` | Max switch-ons | None |
## Usage Examples
@@ -171,6 +168,6 @@ chiller = fx.Flow(
## See Also
-- [Flow](../elements/Flow.md) — How on/off affects flow bounds
+- [Flow](../elements/Flow.md) — How flows use on/off state
- [InvestParameters](InvestParameters.md) — Combining with investment
-- [Effects & Objective](../effects-penalty-objective.md) — How costs are tracked
+- [Effects & Objective](../effects-penalty-objective.md) — How effects are tracked
From 6cff2d7ee94c46d288030ebb074d99a6319bb3a5 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 22:07:53 +0100
Subject: [PATCH 51/79] Piecewise.md: - Replaced the useless ASCII diagram
with a more informative one showing: - Actual axis labels (input/output)
- Numeric values on axes - Two pieces with their connection point
labeled - Clear visual of how pieces connect at (50, 45) - Shows the
start/end points notation
---
.../features/Piecewise.md | 21 ++++++++++++-------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/features/Piecewise.md b/docs/user-guide/mathematical-notation/features/Piecewise.md
index 56c55d316..c59b46b2f 100644
--- a/docs/user-guide/mathematical-notation/features/Piecewise.md
+++ b/docs/user-guide/mathematical-notation/features/Piecewise.md
@@ -9,19 +9,24 @@ Piecewise linearization models non-linear relationships using connected linear s
## Core Concept: Linear Segments
-A piecewise function is defined by segments (pieces), each with start and end points:
+A piecewise function approximates a curve using linear segments (pieces):
```
- y
- │ ╱─── Piece 3
+ output
+ │
+ 90 ┤ ●━━━━━━━● (100, 90)
+ │ ╱
+ 70 ┤ ╱
+ │ ╱ Piece 2
+ 45 ┤ ● (50, 45)
│ ╱
- │ ╱───── Piece 2
- │ ╱
- │╱──────── Piece 1
- └───────────── x
+ 30 ┤ ╱
+ │ ╱ Piece 1
+ 0 ●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ input
+ 0 25 50 75 100
```
-The variable can be anywhere along one of these segments.
+Each piece is defined by a start point and end point. The optimizer chooses a point along exactly one piece.
## Mathematical Formulation
From a73c1819c07f47ac4c0da78eca1a2a820faf0b21 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 22:33:35 +0100
Subject: [PATCH 52/79] Add plotly chart
---
.../features/Piecewise.md | 49 +++++++++++++------
mkdocs.yml | 13 +++++
pyproject.toml | 2 +
3 files changed, 48 insertions(+), 16 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/features/Piecewise.md b/docs/user-guide/mathematical-notation/features/Piecewise.md
index c59b46b2f..6010a2553 100644
--- a/docs/user-guide/mathematical-notation/features/Piecewise.md
+++ b/docs/user-guide/mathematical-notation/features/Piecewise.md
@@ -9,24 +9,41 @@ Piecewise linearization models non-linear relationships using connected linear s
## Core Concept: Linear Segments
-A piecewise function approximates a curve using linear segments (pieces):
-
-```
- output
- │
- 90 ┤ ●━━━━━━━● (100, 90)
- │ ╱
- 70 ┤ ╱
- │ ╱ Piece 2
- 45 ┤ ● (50, 45)
- │ ╱
- 30 ┤ ╱
- │ ╱ Piece 1
- 0 ●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ input
- 0 25 50 75 100
+A piecewise function approximates a curve using linear segments (pieces). Each piece is defined by a start point and end point. The optimizer chooses a point along exactly one piece.
+
+```plotly
+{
+ "data": [
+ {
+ "x": [0, 50],
+ "y": [0, 45],
+ "mode": "lines+markers",
+ "name": "Piece 1",
+ "line": {"color": "#009688", "width": 3},
+ "marker": {"size": 10}
+ },
+ {
+ "x": [50, 100],
+ "y": [45, 90],
+ "mode": "lines+markers",
+ "name": "Piece 2",
+ "line": {"color": "#00bcd4", "width": 3},
+ "marker": {"size": 10}
+ }
+ ],
+ "layout": {
+ "xaxis": {"title": "Input", "range": [0, 105]},
+ "yaxis": {"title": "Output", "range": [0, 95]},
+ "showlegend": true,
+ "legend": {"x": 0.02, "y": 0.98},
+ "margin": {"l": 60, "r": 20, "t": 20, "b": 50},
+ "height": 300
+ }
+}
```
-Each piece is defined by a start point and end point. The optimizer chooses a point along exactly one piece.
+- **Piece 1**: from (0, 0) to (50, 45) — slope = 0.9
+- **Piece 2**: from (50, 45) to (100, 90) — slope = 0.9
## Mathematical Formulation
diff --git a/mkdocs.yml b/mkdocs.yml
index 34c2a8b5e..fde51cd38 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -178,6 +178,12 @@ markdown_extensions:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
+ - name: vegalite
+ class: vegalite
+ format: !!python/name:mkdocs_charts_plugin.fences.fence_vegalite
+ - name: plotly
+ class: mkdocs-plotly
+ format: !!python/name:mkdocs_plotly_plugin.fences.fence_plotly
# Enhanced content
- pymdownx.details
@@ -216,6 +222,10 @@ plugins:
- search:
separator: '[\s\u200b\-_,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])'
+ - charts
+
+ - plotly
+
- table-reader
- include-markdown
@@ -343,6 +353,9 @@ extra_javascript:
- javascripts/mathjax.js
- https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
- https://polyfill.io/v3/polyfill.min.js?features=es6
+ - https://cdn.jsdelivr.net/npm/vega@5
+ - https://cdn.jsdelivr.net/npm/vega-lite@5
+ - https://cdn.jsdelivr.net/npm/vega-embed@6
watch:
- flixopt
diff --git a/pyproject.toml b/pyproject.toml
index 0c6bcb951..cc227e24f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -104,6 +104,8 @@ docs = [
"mkdocs-gen-files==0.5.0",
"mkdocs-include-markdown-plugin==7.2.0",
"mkdocs-literate-nav==0.6.2",
+ "mkdocs-charts-plugin==0.0.13",
+ "mkdocs-plotly-plugin==0.1.3",
"markdown-include==0.8.1",
"pymdown-extensions==10.16.1",
"pygments==2.19.2",
From b0ef0ad81db441a93a71493f0dc04b0e6ca69ca4 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Tue, 25 Nov 2025 22:36:30 +0100
Subject: [PATCH 53/79] Add custom javascript
---
docs/javascripts/plotly-instant.js | 30 ++++++++++++++++++++++++++++++
mkdocs.yml | 1 +
2 files changed, 31 insertions(+)
create mode 100644 docs/javascripts/plotly-instant.js
diff --git a/docs/javascripts/plotly-instant.js b/docs/javascripts/plotly-instant.js
new file mode 100644
index 000000000..c6dd2766c
--- /dev/null
+++ b/docs/javascripts/plotly-instant.js
@@ -0,0 +1,30 @@
+// Re-initialize Plotly charts on MkDocs Material instant navigation
+document.addEventListener('DOMContentLoaded', function() {
+ initPlotlyCharts();
+});
+
+// Hook into Material's instant navigation
+if (typeof document$ !== 'undefined') {
+ document$.subscribe(function() {
+ initPlotlyCharts();
+ });
+}
+
+function initPlotlyCharts() {
+ const charts = document.querySelectorAll('div.mkdocs-plotly');
+ charts.forEach(function(chart) {
+ // Skip if already initialized (has children)
+ if (chart.children.length > 0) return;
+
+ try {
+ const plotData = JSON.parse(chart.textContent);
+ chart.textContent = '';
+ const data = plotData.data || [];
+ const layout = plotData.layout || {};
+ const config = plotData.config || {responsive: true};
+ Plotly.newPlot(chart, data, layout, config);
+ } catch (e) {
+ console.error('Failed to initialize Plotly chart:', e);
+ }
+ });
+}
diff --git a/mkdocs.yml b/mkdocs.yml
index fde51cd38..6aea85796 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -351,6 +351,7 @@ extra_css:
extra_javascript:
- javascripts/mathjax.js
+ - javascripts/plotly-instant.js
- https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
- https://polyfill.io/v3/polyfill.min.js?features=es6
- https://cdn.jsdelivr.net/npm/vega@5
From 4ff7a9af7cc38a2fe64e6761a1bbf7e0e16a3dfa Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Fri, 28 Nov 2025 16:38:20 +0100
Subject: [PATCH 54/79] Remove charts plugin
---
mkdocs.yml | 8 --------
pyproject.toml | 1 -
2 files changed, 9 deletions(-)
diff --git a/mkdocs.yml b/mkdocs.yml
index 6aea85796..25b3d7bb2 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -178,9 +178,6 @@ markdown_extensions:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
- - name: vegalite
- class: vegalite
- format: !!python/name:mkdocs_charts_plugin.fences.fence_vegalite
- name: plotly
class: mkdocs-plotly
format: !!python/name:mkdocs_plotly_plugin.fences.fence_plotly
@@ -222,8 +219,6 @@ plugins:
- search:
separator: '[\s\u200b\-_,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])'
- - charts
-
- plotly
- table-reader
@@ -354,9 +349,6 @@ extra_javascript:
- javascripts/plotly-instant.js
- https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
- https://polyfill.io/v3/polyfill.min.js?features=es6
- - https://cdn.jsdelivr.net/npm/vega@5
- - https://cdn.jsdelivr.net/npm/vega-lite@5
- - https://cdn.jsdelivr.net/npm/vega-embed@6
watch:
- flixopt
diff --git a/pyproject.toml b/pyproject.toml
index cc227e24f..b7c9a8feb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -104,7 +104,6 @@ docs = [
"mkdocs-gen-files==0.5.0",
"mkdocs-include-markdown-plugin==7.2.0",
"mkdocs-literate-nav==0.6.2",
- "mkdocs-charts-plugin==0.0.13",
"mkdocs-plotly-plugin==0.1.3",
"markdown-include==0.8.1",
"pymdown-extensions==10.16.1",
From 0e9f179592e7638231b905b5506c34b0b2b1e0bc Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Fri, 28 Nov 2025 16:42:49 +0100
Subject: [PATCH 55/79] Add missing docs file
---
docs/user-guide/results/index.md | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
create mode 100644 docs/user-guide/results/index.md
diff --git a/docs/user-guide/results/index.md b/docs/user-guide/results/index.md
new file mode 100644
index 000000000..92656010d
--- /dev/null
+++ b/docs/user-guide/results/index.md
@@ -0,0 +1,18 @@
+# Analyzing Results
+
+!!! note "Under Development"
+ This section is being expanded with detailed tutorials.
+
+Learn how to work with optimization results:
+
+- Accessing solution data
+- Plotting flows and states
+- Exporting to various formats
+- Comparing scenarios and periods
+
+## Getting Started
+
+For now, see:
+
+- **[Examples](../../examples/index.md)** - Result analysis patterns in working code
+- **[API Reference](../../api-reference/results.md)** - Results class documentation
From cdb1be5b5e9a971f36c67345356aa356edc428dc Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 01:31:37 +0100
Subject: [PATCH 56/79] Fix quick start
---
docs/home/quick-start.md | 29 ++++++++++++++---------------
1 file changed, 14 insertions(+), 15 deletions(-)
diff --git a/docs/home/quick-start.md b/docs/home/quick-start.md
index 701895d72..b0bdef7da 100644
--- a/docs/home/quick-start.md
+++ b/docs/home/quick-start.md
@@ -19,6 +19,7 @@ Let's create a simple energy system with a generator, demand, and battery storag
```python
import flixopt as fx
import numpy as np
+import pandas as pd
```
### 2. Define your time horizon
@@ -36,14 +37,14 @@ flow_system = fx.FlowSystem(timesteps)
# Define an effect to minimize (costs)
costs = fx.Effect('costs', 'EUR', 'Minimize total system costs', is_objective=True)
-system.add_effects(costs)
+flow_system.add_elements(costs)
```
### 4. Add Components
```python
# Electricity bus
-electricity_bus = fx.Bus('electricity', 'kW')
+electricity_bus = fx.Bus('electricity')
# Solar generator with time-varying output
solar_profile = np.array([0, 0, 0, 0, 0, 0, 0.2, 0.5, 0.8, 1.0,
@@ -54,7 +55,7 @@ solar = fx.Source(
'solar',
outputs=[fx.Flow(
'power',
- bus=electricity_bus,
+ bus='electricity',
size=100, # 100 kW capacity
relative_maximum=solar_profile
)
@@ -67,40 +68,39 @@ demand_profile = np.array([30, 25, 20, 20, 25, 35, 50, 70, 80, 75,
demand = fx.Sink('demand', inputs=[
fx.Flow('consumption',
- bus=electricity_bus,
+ bus='electricity',
size=1,
- fixed_relative_value=demand_profile)
+ fixed_relative_profile=demand_profile)
])
# Battery storage
battery = fx.Storage(
'battery',
- charging=fx.Flow('charge', bus=electricity_bus, size=50),
- discharging=fx.Flow('discharge', bus=electricity_bus, size=50),
+ charging=fx.Flow('charge', bus='electricity', size=50),
+ discharging=fx.Flow('discharge', bus='electricity', size=50),
capacity_in_flow_hours=100, # 100 kWh capacity
initial_charge_state=50, # Start at 50%
eta_charge=0.95,
eta_discharge=0.95,
- effects_per_flow_hour={costs: 0.01} # Small battery wear cost
)
# Add all components to system
-flow_system.add_components(solar, demand, battery)
+flow_system.add_elements(solar, demand, battery, electricity_bus)
```
### 5. Run Optimization
```python
# Create and run optimization
-calc = fx.Optimization('solar_battery_optimization', flow_system)
-calc.solve(fx.solvers.HighsSolver())
+optimization = fx.Optimization('solar_battery_optimization', flow_system)
+optimization.solve(fx.solvers.HighsSolver())
```
### 6. Save Results
```python
# This includes the modeled FlowSystem. SO you can restore both results and inputs
-calc.results.to_file()
+optimization.results.to_file()
```
## What's Next?
@@ -120,9 +120,8 @@ Most flixOpt projects follow this pattern:
2. **Create flow system** - Initialize with time series and effects
3. **Add buses** - Define connection points
4. **Add components** - Create generators, storage, converters, loads
-5. **Configure flows** - Set capacity, bounds, and cost parameters
-6. **Run calculation** - Solve the optimization
-7. **Save Results** - For later analysis. Or only extract needed data
+5. **Run optimization** - Solve the optimization
+6. **Save Results** - For later analysis. Or only extract needed data
## Tips
From 4a2aca7b693a30a436a4cc55e044fa0e1887be12 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 01:35:08 +0100
Subject: [PATCH 57/79] Delete model.md
---
docs/home/models.md | 59 ---------------------------------------------
mkdocs.yml | 1 -
2 files changed, 60 deletions(-)
delete mode 100644 docs/home/models.md
diff --git a/docs/home/models.md b/docs/home/models.md
deleted file mode 100644
index 8365c4023..000000000
--- a/docs/home/models.md
+++ /dev/null
@@ -1,59 +0,0 @@
-# Model Types
-
-flixOpt can model various energy and material flow systems.
-
-## Common Model Types
-
-### Dispatch Optimization
-
-Optimize operation with fixed capacities:
-
-- Generator and storage operation
-- Fixed or time-varying demand
-- Grid connection constraints
-- Typically short term time horizons
-
-### Capacity Expansion
-
-Investment and operational decisions combined:
-
-- Sizing with `InvestParameters`
-- Technology comparison (solar, wind, storage)
-- Annual time series or representative periods
-- Investment costs and constraints
-
-### Multi-Period Planning
-
-Sequential decisions across multiple periods:
-
-- Two-stage optimization
-- Evolving costs and demand
-- Long-term transformation pathways
-
-## Sector Coupling
-
-### Power-to-Heat
-- Heat pumps (`LinearConverter` with COP)
-- Thermal storage
-- District heating networks
-
-### Power-to-Gas
-- Electrolyzers
-- Hydrogen storage
-- Fuel cells
-
-### Combined Heat and Power
-- CHP units with heat/power ratios
-- Multiple demand profiles
-
-## Time Resolution
-
-- **Full resolution** - 8760 hours/year
-- **Representative periods** - Typical days/weeks
-- **Multi-hour timesteps** - Aggregated resolution
-
-## Getting Started
-
-1. Explore [Examples](../examples/index.md)
-2. Read [Core Concepts](../user-guide/core-concepts.md)
-3. Review [Mathematical Notation](../user-guide/mathematical-notation/index.md)
diff --git a/mkdocs.yml b/mkdocs.yml
index 25b3d7bb2..6f62d07aa 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -17,7 +17,6 @@ nav:
- Quick Start: home/quick-start.md
- About:
- Users: home/users.md
- - Models: home/models.md
- Citing: home/citing.md
- License: home/license.md
From 483b6ce24334d35cef35e30053354a5e592ae77b Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 01:41:15 +0100
Subject: [PATCH 58/79] Update citation
---
docs/home/citing.md | 12 +-----------
1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/docs/home/citing.md b/docs/home/citing.md
index 85026c2ba..6fd1a6020 100644
--- a/docs/home/citing.md
+++ b/docs/home/citing.md
@@ -4,17 +4,7 @@ If you use flixOpt in your research, please cite it.
## Citation
-When referencing flixOpt in academic publications, please use:
-
-```bibtex
-@software{flixopt,
- author = {flixOpt Contributors},
- title = {flixOpt: Energy and Material Flow Optimization Framework},
- url = {https://github.com/flixOpt/flixopt},
- version = {4.0.0},
- year = {2024}
-}
-```
+When referencing flixOpt in academic publications, please use look here: [flixopt citation](https://zenodo.org/records/17756895)
## Publications
From 540fc6e11d3dbd2a8c5f372de2d91dbcb9ee646f Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 01:42:25 +0100
Subject: [PATCH 59/79] Update license.md
---
docs/home/license.md | 17 -----------------
1 file changed, 17 deletions(-)
diff --git a/docs/home/license.md b/docs/home/license.md
index 107b382e5..d00755a0b 100644
--- a/docs/home/license.md
+++ b/docs/home/license.md
@@ -38,23 +38,6 @@ The MIT License is a permissive open-source license that allows you to:
✅ **Sublicense** under different terms
✅ **Use privately** without making your modifications public
-**Requirements:**
-
-- Include the copyright notice and license text in copies of the software
-- The software is provided "as is" without warranty
-
-## Third-Party Licenses
-
-flixOpt depends on several open-source libraries, each with their own licenses:
-
-- **NumPy** - BSD License
-- **Pandas** - BSD License
-- **Matplotlib** - PSF License
-- **HiGHS** - MIT License
-- **NetworkX** - BSD License
-
-Please refer to the individual packages for their specific license terms.
-
## Contributing
By contributing to flixOpt, you agree that your contributions will be licensed under the MIT License. See our [Contributing Guide](../contribute.md) for more information.
From 4b13595c03e62e3b827fedcf8177cc61dbbdd67c Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 01:47:02 +0100
Subject: [PATCH 60/79] Simplify faq, support and troubleshooting.md
---
docs/user-guide/faq.md | 63 ++-----
docs/user-guide/support.md | 136 ++-------------
docs/user-guide/troubleshooting.md | 270 +++--------------------------
3 files changed, 48 insertions(+), 421 deletions(-)
diff --git a/docs/user-guide/faq.md b/docs/user-guide/faq.md
index a012a7ce7..63994180d 100644
--- a/docs/user-guide/faq.md
+++ b/docs/user-guide/faq.md
@@ -1,39 +1,15 @@
# Frequently Asked Questions
-Common questions about using flixOpt.
+## What is flixOpt?
-## General
+flixOpt is a Python framework for modeling and optimizing energy and material flow systems. It handles both operational optimization (dispatch) and investment optimization (capacity expansion).
-### What is flixOpt?
-
-flixOpt is a Python framework for modeling and optimizing energy and material flow systems. It handles both operational optimization (dispatch) and investment optimization (capacity expansion planning).
-
-### What types of problems can flixOpt solve?
-
-flixOpt can solve:
-
-- Operational dispatch with fixed capacities
-- Capacity expansion planning with investment decisions
-- Multi-period planning with sequential investments
-- Combined operational and investment optimization
-- Multi-commodity systems with energy carriers
-- Stochastic optimization with scenarios
-
-### Which solvers does flixOpt support?
-
-flixOpt supports:
+## Which solvers does flixOpt support?
- **HiGHS** (default, included)
- **Gurobi** (commercial, academic licenses available)
-- we did support more, but saw no usage. Adding them back would be little effort
-
-### Is flixOpt free to use?
-
-Yes! flixOpt is released under the MIT License, which allows free use for commercial and academic purposes. However, some solvers like Gurobi and CPLEX require commercial licenses (though academic licenses are available).
-## Installation & Setup
-
-### How do I install flixOpt?
+## How do I install flixOpt?
```bash
pip install flixopt
@@ -44,34 +20,15 @@ For full features:
pip install "flixopt[full]"
```
-See [Installation](../home/installation.md) for details.
-
-### Do I need to install a solver separately?
-
-No! The HiGHS solver is included with flixOpt and works out of the box. Other solvers are optional.
-
-## Advanced Topics
+## Do I need to install a solver separately?
-### Can I add custom constraints?
+No. HiGHS is included and works out of the box.
-Yes! You can add custom constraints directly to the optimization model using linopy. See the Advanced Usage section.
+## Can I add custom constraints?
-### Does flixOpt support stochastic optimization?
+Yes. You can add custom constraints directly to the optimization model using linopy.
-Currently, you can optimize the expected value across scenarios. Further stochastic optimization is on the roadmap.
-
-## Troubleshooting
-
-### Where can I get help?
+## Where can I get help?
- Check [Troubleshooting](troubleshooting.md)
-- Search [GitHub Discussions](https://github.com/flixOpt/flixopt/discussions)
-- Open an [issue](https://github.com/flixOpt/flixopt/issues) if you found a bug
-- See [Support](support.md) for more resources
-
-## Not Finding Your Answer?
-
-- Browse the [User Guide](index.md)
-- Check the [Examples](../examples/index.md)
-- Review the [API Reference](../api-reference/)
-- Ask in [GitHub Discussions](https://github.com/flixOpt/flixopt/discussions)
+- Open an [issue on GitHub](https://github.com/flixOpt/flixopt/issues)
diff --git a/docs/user-guide/support.md b/docs/user-guide/support.md
index 7f8288601..5f26cdd24 100644
--- a/docs/user-guide/support.md
+++ b/docs/user-guide/support.md
@@ -1,133 +1,23 @@
# Support
-Get help with flixOpt through our community and resources.
+## Getting Help
-## Community Support
+**[GitHub Issues](https://github.com/flixOpt/flixopt/issues)** — Report bugs or ask questions
-### GitHub Discussions
+When opening an issue, include:
-The best place to ask questions and share ideas:
+- Minimal reproducible example
+- flixOpt version: `python -c "import flixopt; print(flixopt.__version__)"`
+- Python version and OS
+- Full error message
-**[Visit GitHub Discussions →](https://github.com/flixOpt/flixopt/discussions)**
+## Resources
-Use discussions for:
-
-- General questions about using flixOpt
-- Modeling advice and best practices
-- Sharing your projects and models
-- Feature requests and ideas
-- Connecting with other users
-
-### GitHub Issues
-
-For bug reports and specific problems:
-
-**[Open an Issue →](https://github.com/flixOpt/flixopt/issues)**
-
-When reporting an issue, please include:
-
-- **Minimal reproducible example** - Smallest code that demonstrates the problem
-- **flixOpt version** - Run `python -c "import flixopt; print(flixopt.__version__)"`
-- **Python version** - Run `python --version`
-- **Operating system** - Windows, macOS, Linux
-- **Full error message** - Copy the complete traceback
-
-## Documentation
-
-### User Guide
-
-Comprehensive guides and tutorials:
-
-- **[User Guide](index.md)** - Complete documentation
-- **[FAQ](faq.md)** - Frequently asked questions
-- **[Troubleshooting](troubleshooting.md)** - Common issues and solutions
-
-### Examples
-
-Working code examples for common scenarios:
-
-- **[Examples Gallery](../examples/index.md)** - Browse all examples
-- **[Minimal Example](../examples/00-Minimal Example.md)** - Simple starting point
-- **[Basic Example](../examples/01-Basic Example.md)** - Fundamental concepts
-- **[Complex Example](../examples/02-Complex Example.md)** - Advanced modeling
-
-### API Reference
-
-Detailed technical documentation:
-
-- **[API Reference](../api-reference/)** - Complete class and method documentation
-- **[Mathematical Notation](mathematical-notation/index.md)** - Mathematical formulation
-
-## Learning Resources
-
-### Getting Started
-
-New to flixOpt? Start here:
-
-1. **[Quick Start](../home/quick-start.md)** - 5-minute introduction
-2. **[Installation](../home/installation.md)** - Setup guide
-3. **[Core Concepts](core-concepts.md)** - Fundamental concepts
-4. **[Minimal Example](../examples/00-Minimal Example.md)** - First model
-
-### Video Tutorials
-
-*Coming soon: Video tutorials and walkthroughs*
-
-### Publications & Papers
-
-Research using flixOpt:
-
-- **[Citing flixOpt](../home/citing.md)** - How to cite in your work
-- *Coming soon: List of publications*
+- [FAQ](faq.md) — Common questions
+- [Troubleshooting](troubleshooting.md) — Common issues
+- [Examples](../examples/index.md) — Working code
+- [API Reference](../api-reference/index.md) — Technical docs
## Contributing
-Help improve flixOpt:
-
-### Ways to Contribute
-
-- **Report bugs** - Help us identify and fix issues
-- **Suggest features** - Share ideas for improvements
-- **Improve documentation** - Fix typos, clarify explanations
-- **Submit examples** - Share your models
-- **Write code** - Fix bugs or add features
-
-See our **[Contributing Guide](../contribute.md)** for details.
-
-### Development
-
-Want to contribute code?
-
-1. **Fork the repository** on GitHub
-2. **Clone your fork** locally
-3. **Install development dependencies:**
- ```bash
- pip install -e ".[full,dev]"
- ```
-4. **Make your changes** on a feature branch
-5. **Run tests** to ensure nothing breaks
-6. **Submit a pull request**
-
-## Stay Updated
-
-- **[Release Notes](../changelog.md)** - What's new in each version
-- **[Roadmap](../roadmap.md)** - Planned features
-
-## Response Time
-
-Please note:
-
-- **Community support** is provided on a best-effort basis by volunteers
-- **Response time** varies; please be patient
-- **Well-documented questions** get faster responses
-- **Minimal examples** greatly help in troubleshooting
-
-## Code of Conduct
-
-All community interactions should be:
-
-- Respectful and inclusive
-- Constructive and helpful
-- Professional and courteous
-
-We're here to help each other succeed with flixOpt!
+See our [Contributing Guide](../contribute.md) for how to help improve flixOpt.
diff --git a/docs/user-guide/troubleshooting.md b/docs/user-guide/troubleshooting.md
index be18223ad..2c89be8dc 100644
--- a/docs/user-guide/troubleshooting.md
+++ b/docs/user-guide/troubleshooting.md
@@ -1,281 +1,61 @@
# Troubleshooting
-Common issues and their solutions when working with flixOpt.
-
-## Installation Issues
-
-### Import Error: No module named 'flixopt'
-
-**Problem:** Python can't find the flixOpt package.
-
-**Solutions:**
-
-1. Verify installation:
- ```bash
- pip list | grep flixopt
- ```
-
-2. Install if missing:
- ```bash
- pip install flixopt
- ```
-
-3. Check you're using the correct Python environment:
- ```bash
- which python
- python --version
- ```
-
-### Solver Not Found
-
-**Problem:** Error message about missing solver.
-
-**Solutions:**
-
-1. For HiGHS (default), reinstall flixOpt:
- ```bash
- pip install --upgrade --force-reinstall flixopt
- ```
-
-2. For Gurobi/CPLEX, ensure the solver is installed and licensed
-3. Specify solver explicitly:
- ```python
- calc = fx.Optimization('model', flow_system, solver=fx.solvers.HighsSolver())
- ```
-
-## Modeling Issues
-
-### Infeasible Model
+## Infeasible Model
**Problem:** Solver reports the model is infeasible.
-**Diagnostic Steps:**
-
-1. **Enable penalty variables** to identify problematic constraints:
- ```python
- system.use_penalty_variables = True
- ```
-
-2. **Check balance constraints:**
- - Can supply meet demand at all timesteps?
- - Are there isolated buses with no input or output?
-
-3. **Verify capacity limits:**
- - Do components have sufficient size?
- - Are upper/lower bounds consistent?
-
-4. **Review storage constraints:**
- - Is initial charge state feasible?
- - Can storage charge/discharge meet requirements?
-
-5. **Check temporal constraints:**
- - Are minimum on/off times achievable?
- - Do ramp rate limits allow necessary changes?
-
-**Common Causes:**
+**Solutions:**
-- Demand exceeds total available capacity
-- Storage initial/final states incompatible
-- Over-constrained on/off requirements
-- Inconsistent flow bounds
+1. Check that supply can meet demand at all timesteps
+2. Verify capacity limits are sufficient
+3. Review storage initial/final states
-### Unbounded Model
+## Unbounded Model
**Problem:** Solver reports the model is unbounded.
**Solutions:**
-1. Add upper bounds to all decision variables
-2. Check that all flows have maximum limits
-3. Ensure investment parameters have maximum sizes
-4. Verify effect coefficients have correct signs
-
-### Unexpected Results
+1. Add upper bounds to all flows
+2. Ensure investment parameters have maximum sizes
+3. Verify effect coefficients have correct signs
-**Problem:** Model solves but results don't make sense.
+## Unexpected Results
**Debugging Steps:**
-1. **Enable logging:**
+1. Enable logging:
```python
from flixopt import CONFIG
CONFIG.exploring()
```
-2. **Start simple:**
- - Build a minimal model first
- - Add complexity incrementally
- - Verify each addition
-
-3. **Check units:**
- - Are all units consistent?
- - Do time series align with timesteps?
-
-4. **Visualize results:**
- - Plot flows over time
- - Check energy balances
- - Verify storage states
-
-5. **Validate input data:**
- - Check for NaN or infinite values
- - Ensure arrays have correct length
- - Verify parameter signs (costs should be positive)
+2. Start with a minimal model and add complexity incrementally
-## Performance Issues
+3. Check units are consistent
-### Slow Solve Times
+4. Visualize results to verify energy balances
-**Problem:** Optimization takes too long.
+## Slow Solve Times
**Solutions:**
-1. **Reduce model size:**
- - Use longer timesteps
- - Aggregate time periods
- - Remove unnecessary components
-
-2. **Simplify constraints:**
- - Relax tight bounds where possible
- - Remove redundant constraints
- - Use continuous instead of binary variables when appropriate
-
-3. **Use a better solver:**
- ```python
- calc = fx.Optimization('model', flow_system, solver=fx.solvers.GurobiSolver())
- ```
-
-4. **Set solver options:**
+1. Use longer timesteps or aggregate time periods
+2. Use Gurobi instead of HiGHS for large models
+3. Set solver options:
```python
- calc = fx.Optimization(
- 'model',
- flow_system,
- solver=fx.solvers.GurobiSolver(
- time_limit_seconds=3600,
- mip_gap=0.01, # 1% optimality gap
- extra_options={'Threads': 4}
- )
+ solver = fx.solvers.GurobiSolver(
+ time_limit_seconds=3600,
+ mip_gap=0.01
)
```
-5. **Enable presolve and cuts:**
- Most solvers have aggressive presolve options
-
-### Memory Issues
-
-**Problem:** Running out of memory.
-
-**Solutions:**
-
-1. Reduce time resolution
-2. Use sparse matrices (automatic in flixOpt)
-3. Process results in chunks
-4. Increase system RAM or use high-memory machine
-
-## Result Issues
-
-### Can't Access Results
-
-**Problem:** Error when accessing result attributes.
-
-**Solutions:**
-
-1. **Check solve status:**
- ```python
- print(calc.results.status)
- ```
-
-2. **Verify optimization completed:**
- ```python
- if calc.results.status == 'optimal':
- # Access results
- else:
- print(f"Solver status: {calc.results.status}")
- ```
-
-3. **Check component/flow names:**
- ```python
- print(calc.results.component_names)
- print(calc.results.get_component('name').flow_names)
- ```
-
-### Missing Time Series Data
-
-**Problem:** Some time series results are None or empty.
-
-**Solutions:**
-
-1. Verify the variable was created (check conditions in documentation)
-2. Check if the component/feature is active
-3. Ensure results object is from a completed solve
-
-## Numerical Issues
-
-### Scaling Problems
-
-**Problem:** Warning about poor numerical conditioning.
-
-**Solutions:**
-
-1. **Normalize units:**
- - Use MW instead of W
- - Use MWh instead of Wh
- - Keep coefficients between 1e-3 and 1e3
-
-2. **Check parameter magnitudes:**
- - Avoid very large or very small numbers
- - Scale costs and capacities appropriately
-
-3. **Set solver tolerances:**
- ```python
- solver_options={'FeasibilityTol': 1e-6, 'OptimalityTol': 1e-6}
- ```
-
-### Numerical Precision
-
-**Problem:** Results have unexpected precision errors.
-
-**Solutions:**
-
-1. Use appropriate tolerance when comparing values
-2. Round results for display: `round(value, 2)`
-3. Check solver numerical tolerances
-
-## Common Error Messages
-
-### `KeyError: 'component_name'`
-
-**Cause:** Trying to access a component that doesn't exist.
-
-**Solution:** Check spelling and verify component was added to system.
-
-### `ValueError: Array length mismatch`
-
-**Cause:** Time series length doesn't match model timesteps.
-
-**Solution:** Ensure all time series have length equal to `time_series.number_of_time_steps`.
-
-### `AttributeError: 'NoneType' object has no attribute`
-
-**Cause:** Accessing results before solving or on failed solve.
-
-**Solution:** Check solve status before accessing results.
-
## Getting Help
-If you've tried these solutions and still have issues:
+If you're stuck:
-1. **Search existing issues:** [GitHub Issues](https://github.com/flixOpt/flixopt/issues)
-2. **Ask the community:** [GitHub Discussions](https://github.com/flixOpt/flixopt/discussions)
-3. **Report a bug:** Open a new [issue](https://github.com/flixOpt/flixopt/issues/new) with:
+1. Search [GitHub Issues](https://github.com/flixOpt/flixopt/issues)
+2. Open a new issue with:
- Minimal reproducible example
- - flixOpt version
- - Python version
- - Operating system
+ - flixopt and Python version
- Full error message
-
-## Additional Resources
-
-- [FAQ](faq.md) - Frequently asked questions
-- [Support](support.md) - How to get help
-- [Examples](../examples/index.md) - Working code examples
-- [API Reference](../api-reference/) - Detailed documentation
From 58a27918a598be9cb04f33ab58c3ddd4dcb1332a Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 03:38:30 +0100
Subject: [PATCH 61/79] Remove old workflow
---
.github/workflows/python-app.yaml | 0
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 .github/workflows/python-app.yaml
diff --git a/.github/workflows/python-app.yaml b/.github/workflows/python-app.yaml
deleted file mode 100644
index e69de29bb..000000000
From 1aa74b2f15946e3a25f3ba8e4c0124d9e1eb615f Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 03:41:54 +0100
Subject: [PATCH 62/79] =?UTF-8?q?=20=201.=20Renamed=20OnOffParameters.md?=
=?UTF-8?q?=20=E2=86=92=20StatusParameters.md=20=20=202.=20Updated=20all?=
=?UTF-8?q?=20terminology:=20=20=20=20=20-=20on=5Foff=5Fparameters=20?=
=?UTF-8?q?=E2=86=92=20status=5Fparameters=20=20=20=20=20-=20OnOffParamete?=
=?UTF-8?q?rs=20=E2=86=92=20StatusParameters=20=20=20=20=20-=20effects=5Fp?=
=?UTF-8?q?er=5Fswitch=5Fon=20=E2=86=92=20effects=5Fper=5Fstartup=20=20=20?=
=?UTF-8?q?=20=20-=20effects=5Fper=5Frunning=5Fhour=20=E2=86=92=20effects?=
=?UTF-8?q?=5Fper=5Factive=5Fhour=20=20=20=20=20-=20consecutive=5Fon=5Fhou?=
=?UTF-8?q?rs=5Fmin=20=E2=86=92=20min=5Fuptime=20=20=20=20=20-=20consecuti?=
=?UTF-8?q?ve=5Fon=5Fhours=5Fmax=20=E2=86=92=20max=5Fuptime=20=20=20=20=20?=
=?UTF-8?q?-=20consecutive=5Foff=5Fhours=5Fmin=20=E2=86=92=20min=5Fdowntim?=
=?UTF-8?q?e=20=20=20=20=20-=20on=5Fhours=5Fmin/max=20=E2=86=92=20active?=
=?UTF-8?q?=5Fhours=5Fmin/max=20=20=20=20=20-=20switch=5Fon=5Fmax=20?=
=?UTF-8?q?=E2=86=92=20startup=5Flimit=20=20=20=20=20-=20switch=5Fon/switc?=
=?UTF-8?q?h=5Foff=20=E2=86=92=20startup/shutdown=20=20=20=20=20-=20"on/of?=
=?UTF-8?q?f"=20language=20=E2=86=92=20"active/inactive"=20language=20=20?=
=?UTF-8?q?=203.=20Updated=20references=20in=20Flow.md,=20LinearConverter.?=
=?UTF-8?q?md,=20and=20effects-penalty-objective.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../effects-penalty-objective.md | 8 +-
.../mathematical-notation/elements/Flow.md | 32 +-
.../elements/LinearConverter.md | 10 +-
.../features/OnOffParameters.md | 173 --------
.../features/StatusParameters.md | 376 ++++++------------
5 files changed, 141 insertions(+), 458 deletions(-)
delete mode 100644 docs/user-guide/mathematical-notation/features/OnOffParameters.md
diff --git a/docs/user-guide/mathematical-notation/effects-penalty-objective.md b/docs/user-guide/mathematical-notation/effects-penalty-objective.md
index 7ffc41470..6afd34663 100644
--- a/docs/user-guide/mathematical-notation/effects-penalty-objective.md
+++ b/docs/user-guide/mathematical-notation/effects-penalty-objective.md
@@ -133,8 +133,8 @@ Every FlixOpt model includes a special **Penalty Effect** $E_\Phi$ to:
import flixopt as fx
# Add penalty contributions just like any other effect
-on_off = fx.OnOffParameters(
- effects_per_switch_on={'Penalty': 1} # Add bias against switching on this component, without adding costs
+status = fx.StatusParameters(
+ effects_per_startup={'Penalty': 1} # Add bias against starting this component, without adding costs
)
```
@@ -227,8 +227,8 @@ Every FlixOpt model includes a special **Penalty Effect** $E_\Phi$ to:
import flixopt as fx
# Add penalty contributions just like any other effect
-on_off = fx.OnOffParameters(
- effects_per_switch_on={'Penalty': 1} # Add bias against switching on this component, without adding costs
+status = fx.StatusParameters(
+ effects_per_startup={'Penalty': 1} # Add bias against starting this component, without adding costs
)
```
diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md
index 6e54c7389..428b9119b 100644
--- a/docs/user-guide/mathematical-notation/elements/Flow.md
+++ b/docs/user-guide/mathematical-notation/elements/Flow.md
@@ -31,20 +31,20 @@ Every flow has two key quantities:
- $P = 100$, $p_{rel}^{min} = 0.3$, $p_{rel}^{max} = 1$
- Constraint: $30 \leq p(t) \leq 100$
-=== "Fixed Size + On/Off"
+=== "Fixed Size + Status"
- When `on_off_parameters` is specified, the flow can also be zero:
+ When `status_parameters` is specified, the flow can also be zero:
$$
s(t) \cdot P \cdot p_{rel}^{min}(t) \leq p(t) \leq s(t) \cdot P \cdot p_{rel}^{max}(t)
$$
- Where $s(t) \in \{0, 1\}$ is the binary on/off state.
+ Where $s(t) \in \{0, 1\}$ is the binary status.
- - When $s(t) = 0$: $p(t) = 0$ (off)
- - When $s(t) = 1$: $P \cdot p_{rel}^{min} \leq p(t) \leq P \cdot p_{rel}^{max}$ (on)
+ - When $s(t) = 0$: $p(t) = 0$ (inactive)
+ - When $s(t) = 1$: $P \cdot p_{rel}^{min} \leq p(t) \leq P \cdot p_{rel}^{max}$ (active)
- See [OnOffParameters](../features/OnOffParameters.md) for details.
+ See [StatusParameters](../features/StatusParameters.md) for details.
=== "Variable Size"
@@ -60,12 +60,12 @@ Every flow has two key quantities:
See [InvestParameters](../features/InvestParameters.md) for details.
-=== "Variable Size + On/Off"
+=== "Variable Size + Status"
!!! warning "Work in Progress"
This section needs review. The linearization constraints below may not be accurate.
- When both `size` is `InvestParameters` and `on_off_parameters` is set:
+ When both `size` is `InvestParameters` and `status_parameters` is set:
$$
P^{min} \leq P \leq P^{max}
@@ -140,9 +140,9 @@ This matters for costs: `effects_per_flow_hour` is cost per energy (€/MWh).
|--------|-------------|-------------|--------------|
| $p(t)$ | `flow_rate` | Flow rate at timestep $t$ | Always |
| $P$ | `size` | Capacity (variable) | `size` is `InvestParameters` |
-| $s(t)$ | `on_off_state` | Binary state | `on_off_parameters` set |
-| $s^{on}(t)$ | `switch_on` | Switch-on indicator | `on_off_parameters` set |
-| $s^{off}(t)$ | `switch_off` | Switch-off indicator | `on_off_parameters` set |
+| $s(t)$ | `status` | Binary status | `status_parameters` set |
+| $s^{start}(t)$ | `startup` | Startup indicator | `status_parameters` set |
+| $s^{stop}(t)$ | `shutdown` | Shutdown indicator | `status_parameters` set |
## Parameters
@@ -192,17 +192,17 @@ solar = fx.Flow(
)
```
-### With On/Off Operation
+### With Status Operation
```python
generator = fx.Flow(
label='power',
bus=electricity_bus,
size=50,
- relative_minimum=0.4, # 40% min when ON, but can be OFF
- on_off_parameters=fx.StatusParameters(
+ relative_minimum=0.4, # 40% min when active, but can be inactive
+ status_parameters=fx.StatusParameters(
effects_per_startup={'costs': 500},
- minimum_uptime=2,
+ min_uptime=2,
),
)
```
@@ -230,5 +230,5 @@ battery_flow = fx.Flow(
- [Bus](Bus.md) — Where flows connect
- [LinearConverter](LinearConverter.md) — Components using flows
-- [OnOffParameters](../features/OnOffParameters.md) — Binary operation
+- [StatusParameters](../features/StatusParameters.md) — Binary operation
- [InvestParameters](../features/InvestParameters.md) — Capacity optimization
diff --git a/docs/user-guide/mathematical-notation/elements/LinearConverter.md b/docs/user-guide/mathematical-notation/elements/LinearConverter.md
index 09d7ee67d..b4738254e 100644
--- a/docs/user-guide/mathematical-notation/elements/LinearConverter.md
+++ b/docs/user-guide/mathematical-notation/elements/LinearConverter.md
@@ -194,14 +194,14 @@ chp = fx.linear_converters.CHP(
inputs=[fx.Flow(label='fuel', bus=fuel_bus, size=100)],
outputs=[fx.Flow(label='el', bus=elec_bus, size=40, relative_minimum=0.4)],
conversion_factors=[{'fuel': 0.4, 'el': 1}],
- on_off_parameters=fx.OnOffParameters(
- effects_per_switch_on={'costs': 1000},
- consecutive_on_hours_min=4,
+ status_parameters=fx.StatusParameters(
+ effects_per_startup={'costs': 1000},
+ min_uptime=4,
),
)
```
- See [OnOffParameters](../features/OnOffParameters.md).
+ See [StatusParameters](../features/StatusParameters.md).
## Implementation Details
@@ -213,4 +213,4 @@ chp = fx.linear_converters.CHP(
- [Flow](Flow.md) — Input and output flows
- [Bus](Bus.md) — Where converters connect
- [Piecewise](../features/Piecewise.md) — Non-linear efficiency
-- [OnOffParameters](../features/OnOffParameters.md) — Binary operation
+- [StatusParameters](../features/StatusParameters.md) — Binary operation
diff --git a/docs/user-guide/mathematical-notation/features/OnOffParameters.md b/docs/user-guide/mathematical-notation/features/OnOffParameters.md
deleted file mode 100644
index e6be94ea7..000000000
--- a/docs/user-guide/mathematical-notation/features/OnOffParameters.md
+++ /dev/null
@@ -1,173 +0,0 @@
-# OnOffParameters
-
-OnOffParameters define a binary on/off state variable with associated constraints and effects.
-
-!!! example "Real-world examples"
- - **Power plant** — Startup costs, minimum run time
- - **Batch reactor** — Must run complete cycles
- - **Chiller** — Maximum operating hours per year
-
-## Core Concept: Binary State Variable
-
-OnOffParameters create a binary state variable $s(t) \in \{0, 1\}$:
-
-- $s(t) = 0$: Off
-- $s(t) = 1$: On
-
-!!! note "Connection to continuous variables"
- How this binary state connects to continuous variables (like flow rate) is defined where `on_off_parameters` is used. See [Flow](../elements/Flow.md) for how flows use the on/off state to modify their bounds.
-
-## State Transitions
-
-=== "Switch Detection"
-
- Track when state changes:
-
- $$
- s^{on}(t) - s^{off}(t) = s(t) - s(t-1)
- $$
-
- Where:
-
- - $s^{on}(t) = 1$ when switching on (0 → 1)
- - $s^{off}(t) = 1$ when switching off (1 → 0)
-
-=== "Startup Effects"
-
- Effects incurred each time state switches on:
-
- $$
- E_{e,switch} = \sum_t s^{on}(t) \cdot c_{switch}
- $$
-
-=== "Running Effects"
-
- Effects while state is on:
-
- $$
- E_{e,run} = \sum_t s(t) \cdot \Delta t \cdot c_{run}
- $$
-
-## Duration Constraints
-
-=== "Min On Time"
-
- Once on, must stay on for minimum duration:
-
- $$
- s^{on}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} s(j) \cdot \Delta t \geq T_{on}^{min}
- $$
-
-=== "Min Off Time"
-
- Once off, must stay off for minimum duration:
-
- $$
- s^{off}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} (1-s(j)) \cdot \Delta t \geq T_{off}^{min}
- $$
-
-=== "Max On Time"
-
- Cannot stay on continuously beyond limit:
-
- $$
- \sum_{j=t}^{t+k} s(j) \cdot \Delta t \leq T_{on}^{max}
- $$
-
-=== "Total On Hours"
-
- Constrain total on-time per period:
-
- $$
- H^{min} \leq \sum_t s(t) \cdot \Delta t \leq H^{max}
- $$
-
-=== "Max Switches"
-
- Limit number of switch-ons per period:
-
- $$
- \sum_t s^{on}(t) \leq N_{switch}^{max}
- $$
-
-## Variables
-
-| Symbol | Python Name | Description | When Created |
-|--------|-------------|-------------|--------------|
-| $s(t)$ | `on` | Binary state | Always |
-| $s^{on}(t)$ | `switch_on` | Switch-on indicator | Switch effects or constraints |
-| $s^{off}(t)$ | `switch_off` | Switch-off indicator | Switch effects or constraints |
-
-## Parameters
-
-| Symbol | Python Name | Description | Default |
-|--------|-------------|-------------|---------|
-| $c_{switch}$ | `effects_per_switch_on` | Effects per switch-on | None |
-| $c_{run}$ | `effects_per_running_hour` | Effects while on | None |
-| $T_{on}^{min}$ | `consecutive_on_hours_min` | Min consecutive on | None |
-| $T_{on}^{max}$ | `consecutive_on_hours_max` | Max consecutive on | None |
-| $T_{off}^{min}$ | `consecutive_off_hours_min` | Min consecutive off | None |
-| $T_{off}^{max}$ | `consecutive_off_hours_max` | Max consecutive off | None |
-| $H^{min}$ | `on_hours_min` | Min total on hours | None |
-| $H^{max}$ | `on_hours_max` | Max total on hours | None |
-| $N_{switch}^{max}$ | `switch_on_max` | Max switch-ons | None |
-
-## Usage Examples
-
-### Power Plant with Startup Costs
-
-```python
-generator = fx.Flow(
- label='power',
- bus=elec_bus,
- size=100,
- relative_minimum=0.4, # 40% min when ON
- on_off_parameters=fx.OnOffParameters(
- effects_per_switch_on={'costs': 25000}, # €25k startup
- consecutive_on_hours_min=8, # Must run 8+ hours
- consecutive_off_hours_min=4, # Must stay off 4+ hours
- ),
-)
-```
-
-### Batch Process with Cycle Limits
-
-```python
-reactor = fx.Flow(
- label='output',
- bus=prod_bus,
- size=50,
- on_off_parameters=fx.OnOffParameters(
- effects_per_switch_on={'costs': 1500},
- effects_per_running_hour={'costs': 200},
- consecutive_on_hours_min=12, # 12h batch
- switch_on_max=20, # Max 20 batches
- ),
-)
-```
-
-### HVAC with Operating Limits
-
-```python
-chiller = fx.Flow(
- label='cooling',
- bus=cool_bus,
- size=500,
- on_off_parameters=fx.OnOffParameters(
- on_hours_min=2000, # Min 2000h/year
- on_hours_max=5000, # Max 5000h/year
- consecutive_on_hours_max=18, # Max 18h continuous
- ),
-)
-```
-
-## Implementation Details
-
-- **Feature Class:** [`OnOffParameters`][flixopt.interface.OnOffParameters]
-- **Model Class:** [`OnOffModel`][flixopt.features.OnOffModel]
-
-## See Also
-
-- [Flow](../elements/Flow.md) — How flows use on/off state
-- [InvestParameters](InvestParameters.md) — Combining with investment
-- [Effects & Objective](../effects-penalty-objective.md) — How effects are tracked
diff --git a/docs/user-guide/mathematical-notation/features/StatusParameters.md b/docs/user-guide/mathematical-notation/features/StatusParameters.md
index 2ec34e3df..89d4fbd6c 100644
--- a/docs/user-guide/mathematical-notation/features/StatusParameters.md
+++ b/docs/user-guide/mathematical-notation/features/StatusParameters.md
@@ -1,317 +1,173 @@
# StatusParameters
-[`StatusParameters`][flixopt.interface.StatusParameters] model equipment that operates in discrete active/inactive states rather than continuous operation. This captures realistic operational constraints including startup costs, minimum run times, cycling limitations, and maintenance scheduling.
+StatusParameters define a binary status variable with operational constraints and effects.
-## Binary State Variable
+!!! example "Real-world examples"
+ - **Power plant** — Startup costs, minimum run time
+ - **Batch reactor** — Must run complete cycles
+ - **Chiller** — Maximum operating hours per year
-Equipment operation is modeled using a binary state variable:
+## Core Concept: Binary Status Variable
-$$\label{eq:status_state}
-s(t) \in \{0, 1\} \quad \forall t
-$$
+StatusParameters create a binary status variable $s(t) \in \{0, 1\}$:
-With:
+- $s(t) = 0$: Inactive
+- $s(t) = 1$: Active
-- $s(t) = 1$: equipment is operating (active state)
-- $s(t) = 0$: equipment is shutdown (inactive state)
+!!! note "Connection to continuous variables"
+ How this binary status connects to continuous variables (like flow rate) is defined where `status_parameters` is used. See [Flow](../elements/Flow.md) for how flows use the status to modify their bounds.
-This state variable controls the equipment's operational constraints and modifies flow bounds using the **bounds with state** pattern from [Bounds and States](../modeling-patterns/bounds-and-states.md#bounds-with-state).
+## State Transitions
----
+=== "Startup Detection"
-## State Transitions and Switching
+ Track when status changes from inactive to active:
-State transitions are tracked using switch variables (see [State Transitions](../modeling-patterns/state-transitions.md#binary-state-transitions)):
+ $$
+ s^{start}(t) - s^{stop}(t) = s(t) - s(t-1)
+ $$
-$$\label{eq:status_transitions}
-s^\text{startup}(t) - s^\text{shutdown}(t) = s(t) - s(t-1) \quad \forall t > 0
-$$
+ Where:
-$$\label{eq:status_switch_exclusivity}
-s^\text{startup}(t) + s^\text{shutdown}(t) \leq 1 \quad \forall t
-$$
+ - $s^{start}(t) = 1$ when starting up (0 → 1)
+ - $s^{stop}(t) = 1$ when shutting down (1 → 0)
-With:
+=== "Startup Effects"
-- $s^\text{startup}(t) \in \{0, 1\}$: equals 1 when switching from inactive to active (startup)
-- $s^\text{shutdown}(t) \in \{0, 1\}$: equals 1 when switching from active to inactive (shutdown)
+ Effects incurred each time equipment starts:
-**Behavior:**
-- Inactive → Active: $s^\text{startup}(t) = 1, s^\text{shutdown}(t) = 0$
-- Active → Inactive: $s^\text{startup}(t) = 0, s^\text{shutdown}(t) = 1$
-- No change: $s^\text{startup}(t) = 0, s^\text{shutdown}(t) = 0$
+ $$
+ E_{startup} = \sum_t s^{start}(t) \cdot c_{startup}
+ $$
----
+=== "Active Hour Effects"
-## Effects and Costs
+ Effects while status is active:
-### Startup Effects
+ $$
+ E_{active} = \sum_t s(t) \cdot \Delta t \cdot c_{active}
+ $$
-Effects incurred when equipment starts up:
+## Duration Constraints
-$$\label{eq:status_switch_effects}
-E_{e,\text{switch}} = \sum_{t} s^\text{startup}(t) \cdot \text{effect}_{e,\text{switch}}
-$$
+=== "Min Uptime"
-With:
+ Once active, must stay active for minimum duration:
-- $\text{effect}_{e,\text{switch}}$ being the effect value per startup event
+ $$
+ s^{start}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} s(j) \cdot \Delta t \geq T_{up}^{min}
+ $$
-**Examples:**
-- Startup fuel consumption
-- Wear and tear costs
-- Labor costs for startup procedures
-- Inrush power demands
+=== "Min Downtime"
----
+ Once inactive, must stay inactive for minimum duration:
-### Running Effects
+ $$
+ s^{stop}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} (1-s(j)) \cdot \Delta t \geq T_{down}^{min}
+ $$
-Effects incurred while equipment is operating:
+=== "Max Uptime"
-$$\label{eq:status_running_effects}
-E_{e,\text{run}} = \sum_{t} s(t) \cdot \Delta t \cdot \text{effect}_{e,\text{run}}
-$$
+ Cannot stay active continuously beyond limit:
-With:
+ $$
+ \sum_{j=t}^{t+k} s(j) \cdot \Delta t \leq T_{up}^{max}
+ $$
-- $\text{effect}_{e,\text{run}}$ being the effect rate per operating hour
-- $\Delta t$ being the time step duration
+=== "Active Hours"
-**Examples:**
-- Fixed operating and maintenance costs
-- Auxiliary power consumption
-- Consumable materials
-- Emissions while running
+ Constrain total active hours per period:
----
+ $$
+ H^{min} \leq \sum_t s(t) \cdot \Delta t \leq H^{max}
+ $$
-## Operating Hour Constraints
+=== "Startup Limit"
-### Total Operating Hours
+ Limit number of startups per period:
-Bounds on total operating time across the planning horizon:
+ $$
+ \sum_t s^{start}(t) \leq N_{start}^{max}
+ $$
-$$\label{eq:status_total_hours}
-h_\text{min} \leq \sum_{t} s(t) \cdot \Delta t \leq h_\text{max}
-$$
+## Variables
-With:
+| Symbol | Python Name | Description | When Created |
+|--------|-------------|-------------|--------------|
+| $s(t)$ | `status` | Binary status | Always |
+| $s^{start}(t)$ | `startup` | Startup indicator | Startup effects or constraints |
+| $s^{stop}(t)$ | `shutdown` | Shutdown indicator | Startup effects or constraints |
-- $h_\text{min}$ being the minimum total operating hours
-- $h_\text{max}$ being the maximum total operating hours
+## Parameters
-**Use cases:**
-- Minimum runtime requirements (contracts, maintenance)
-- Maximum runtime limits (fuel availability, permits, equipment life)
+| Symbol | Python Name | Description | Default |
+|--------|-------------|-------------|---------|
+| $c_{startup}$ | `effects_per_startup` | Effects per startup | None |
+| $c_{active}$ | `effects_per_active_hour` | Effects while active | None |
+| $T_{up}^{min}$ | `min_uptime` | Min consecutive uptime | None |
+| $T_{up}^{max}$ | `max_uptime` | Max consecutive uptime | None |
+| $T_{down}^{min}$ | `min_downtime` | Min consecutive downtime | None |
+| $T_{down}^{max}$ | `max_downtime` | Max consecutive downtime | None |
+| $H^{min}$ | `active_hours_min` | Min total active hours | None |
+| $H^{max}$ | `active_hours_max` | Max total active hours | None |
+| $N_{start}^{max}$ | `startup_limit` | Max startups | None |
----
-
-### Consecutive Operating Hours
-
-**Minimum Consecutive Uptime:**
-
-Enforces minimum runtime once started using duration tracking (see [Duration Tracking](../modeling-patterns/duration-tracking.md#minimum-duration-constraints)):
-
-$$\label{eq:status_min_uptime}
-d^\text{uptime}(t) \geq (s(t-1) - s(t)) \cdot h^\text{uptime}_\text{min} \quad \forall t > 0
-$$
-
-With:
-
-- $d^\text{uptime}(t)$ being the consecutive uptime duration at time $t$
-- $h^\text{uptime}_\text{min}$ being the minimum required uptime
-
-**Behavior:**
-- When shutting down at time $t$: enforces equipment was on for at least $h^\text{uptime}_\text{min}$ prior to the switch
-- Prevents short cycling and frequent startups
-
-**Maximum Consecutive Uptime:**
-
-Limits continuous operation before requiring shutdown:
-
-$$\label{eq:status_max_uptime}
-d^\text{uptime}(t) \leq h^\text{uptime}_\text{max} \quad \forall t
-$$
-
-**Use cases:**
-- Mandatory maintenance intervals
-- Process batch time limits
-- Thermal cycling requirements
-
----
-
-### Consecutive Shutdown Hours
-
-**Minimum Consecutive Downtime:**
-
-Enforces minimum shutdown duration before restarting:
-
-$$\label{eq:status_min_downtime}
-d^\text{downtime}(t) \geq (s(t) - s(t-1)) \cdot h^\text{downtime}_\text{min} \quad \forall t > 0
-$$
-
-With:
-
-- $d^\text{downtime}(t)$ being the consecutive downtime duration at time $t$
-- $h^\text{downtime}_\text{min}$ being the minimum required downtime
-
-**Use cases:**
-- Cooling periods
-- Maintenance requirements
-- Process stabilization
-
-**Maximum Consecutive Downtime:**
-
-Limits shutdown duration before mandatory restart:
-
-$$\label{eq:status_max_downtime}
-d^\text{downtime}(t) \leq h^\text{downtime}_\text{max} \quad \forall t
-$$
-
-**Use cases:**
-- Equipment preservation requirements
-- Process stability needs
-- Contractual minimum activity levels
-
----
-
-## Cycling Limits
-
-Maximum number of startups across the planning horizon:
-
-$$\label{eq:status_max_switches}
-\sum_{t} s^\text{startup}(t) \leq n_\text{max}
-$$
-
-With:
-
-- $n_\text{max}$ being the maximum allowed number of startups
-
-**Use cases:**
-- Preventing excessive equipment wear
-- Grid stability requirements
-- Operational complexity limits
-- Maintenance budget constraints
-
----
-
-## Integration with Flow Bounds
-
-StatusParameters modify flow rate bounds by coupling them to the active/inactive state.
-
-**Without StatusParameters** (continuous operation):
-$$
-P \cdot \text{rel}_\text{lower} \leq p(t) \leq P \cdot \text{rel}_\text{upper}
-$$
-
-**With StatusParameters** (binary operation):
-$$
-s(t) \cdot P \cdot \max(\varepsilon, \text{rel}_\text{lower}) \leq p(t) \leq s(t) \cdot P \cdot \text{rel}_\text{upper}
-$$
-
-Using the **bounds with state** pattern from [Bounds and States](../modeling-patterns/bounds-and-states.md#bounds-with-state).
-
-**Behavior:**
-- When $s(t) = 0$: flow is forced to zero
-- When $s(t) = 1$: flow follows normal bounds
-
----
-
-## Complete Formulation Summary
-
-For equipment with StatusParameters, the complete constraint system includes:
-
-1. **State variable:** $s(t) \in \{0, 1\}$
-2. **Switch tracking:** $s^\text{startup}(t) - s^\text{shutdown}(t) = s(t) - s(t-1)$
-3. **Switch exclusivity:** $s^\text{startup}(t) + s^\text{shutdown}(t) \leq 1$
-4. **Duration tracking:**
-
- - On-duration: $d^\text{uptime}(t)$ following duration tracking pattern
- - Off-duration: $d^\text{downtime}(t)$ following duration tracking pattern
-5. **Minimum uptime:** $d^\text{uptime}(t) \geq (s(t-1) - s(t)) \cdot h^\text{uptime}_\text{min}$
-6. **Maximum uptime:** $d^\text{uptime}(t) \leq h^\text{uptime}_\text{max}$
-7. **Minimum downtime:** $d^\text{downtime}(t) \geq (s(t) - s(t-1)) \cdot h^\text{downtime}_\text{min}$
-8. **Maximum downtime:** $d^\text{downtime}(t) \leq h^\text{downtime}_\text{max}$
-9. **Total hours:** $h_\text{min} \leq \sum_t s(t) \cdot \Delta t \leq h_\text{max}$
-10. **Cycling limit:** $\sum_t s^\text{startup}(t) \leq n_\text{max}$
-11. **Flow bounds:** $s(t) \cdot P \cdot \max(\varepsilon, \text{rel}_\text{lower}) \leq p(t) \leq s(t) \cdot P \cdot \text{rel}_\text{upper}$
-
----
-
-## Implementation
-
-**Python Class:** [`StatusParameters`][flixopt.interface.StatusParameters]
-
-**Key Parameters:**
-
-- `effects_per_startup`: Costs per startup event
-- `effects_per_active_hour`: Costs per hour of operation
-- `active_hours_min`, `active_hours_max`: Total runtime bounds
-- `min_uptime`, `max_uptime`: Consecutive runtime bounds
-- `min_downtime`, `max_downtime`: Consecutive shutdown bounds
-- `startup_limit`: Maximum number of startups
-- `force_startup_tracking`: Create switch variables even without limits (for tracking)
-
-See the [`StatusParameters`][flixopt.interface.StatusParameters] API documentation for complete parameter list and usage examples.
-
-**Mathematical Patterns Used:**
-- [State Transitions](../modeling-patterns/state-transitions.md#binary-state-transitions) - Switch tracking
-- [Duration Tracking](../modeling-patterns/duration-tracking.md) - Consecutive time constraints
-- [Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state) - Flow control
-
-**Used in:**
-- [`Flow`][flixopt.elements.Flow] - Active/inactive operation for flows
-- All components supporting discrete operational states
-
----
-
-## Examples
+## Usage Examples
### Power Plant with Startup Costs
-```python
-power_plant = StatusParameters(
- effects_per_startup={'startup_cost': 25000}, # €25k per startup
- effects_per_active_hour={'fixed_om': 125}, # €125/hour while running
- min_uptime=8, # Minimum 8-hour run
- min_downtime=4, # 4-hour cooling period
- active_hours_max=6000, # Annual limit
-)
-```
-### Batch Process with Cycling Limits
```python
-batch_reactor = StatusParameters(
- effects_per_startup={'setup_cost': 1500},
- min_uptime=12, # 12-hour minimum batch
- max_uptime=24, # 24-hour maximum batch
- min_downtime=6, # Cleaning time
- startup_limit=200, # Max 200 batches
+generator = fx.Flow(
+ label='power',
+ bus=elec_bus,
+ size=100,
+ relative_minimum=0.4, # 40% min when active
+ status_parameters=fx.StatusParameters(
+ effects_per_startup={'costs': 25000}, # €25k startup
+ min_uptime=8, # Must run 8+ hours
+ min_downtime=4, # Must stay off 4+ hours
+ ),
)
```
-### HVAC with Cycle Prevention
+### Batch Process with Cycle Limits
+
```python
-hvac = StatusParameters(
- effects_per_startup={'compressor_wear': 0.5},
- min_uptime=1, # Prevent short cycling
- min_downtime=0.5, # 30-min minimum off
- startup_limit=2000, # Limit compressor starts
+reactor = fx.Flow(
+ label='output',
+ bus=prod_bus,
+ size=50,
+ status_parameters=fx.StatusParameters(
+ effects_per_startup={'costs': 1500},
+ effects_per_active_hour={'costs': 200},
+ min_uptime=12, # 12h batch
+ startup_limit=20, # Max 20 batches
+ ),
)
```
-### Backup Generator with Testing Requirements
+### HVAC with Operating Limits
+
```python
-backup_gen = StatusParameters(
- effects_per_startup={'fuel_priming': 50}, # L diesel
- min_uptime=0.5, # 30-min test duration
- max_downtime=720, # Test every 30 days
- active_hours_min=26, # Weekly testing requirement
+chiller = fx.Flow(
+ label='cooling',
+ bus=cool_bus,
+ size=500,
+ status_parameters=fx.StatusParameters(
+ active_hours_min=2000, # Min 2000h/year
+ active_hours_max=5000, # Max 5000h/year
+ max_uptime=18, # Max 18h continuous
+ ),
)
```
----
+## Implementation Details
+
+- **Feature Class:** [`StatusParameters`][flixopt.interface.StatusParameters]
+- **Model Class:** [`StatusModel`][flixopt.features.StatusModel]
-## Notes
+## See Also
-**Time Series Boundary:** The final time period constraints for min_uptime/max and min_downtime/max are not enforced at the end of the planning horizon. This allows optimization to end with ongoing campaigns that may be shorter/longer than specified, as they extend beyond the modeled period.
+- [Flow](../elements/Flow.md) — How flows use status
+- [InvestParameters](InvestParameters.md) — Combining with investment
+- [Effects & Objective](../effects-penalty-objective.md) — How effects are tracked
From 205ad3463e8155e4ff4590b43b20092fdae61ead Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 03:48:05 +0100
Subject: [PATCH 63/79] Remove Modeling patterns from docs
---
.../user-guide/mathematical-notation/index.md | 2 -
.../modeling-patterns/bounds-and-states.md | 171 -------------
.../modeling-patterns/duration-tracking.md | 164 ------------
.../modeling-patterns/index.md | 54 ----
.../modeling-patterns/state-transitions.md | 235 ------------------
mkdocs.yml | 12 +-
6 files changed, 3 insertions(+), 635 deletions(-)
delete mode 100644 docs/user-guide/mathematical-notation/modeling-patterns/bounds-and-states.md
delete mode 100644 docs/user-guide/mathematical-notation/modeling-patterns/duration-tracking.md
delete mode 100644 docs/user-guide/mathematical-notation/modeling-patterns/index.md
delete mode 100644 docs/user-guide/mathematical-notation/modeling-patterns/state-transitions.md
diff --git a/docs/user-guide/mathematical-notation/index.md b/docs/user-guide/mathematical-notation/index.md
index ed2f2e7ba..1d34865a2 100644
--- a/docs/user-guide/mathematical-notation/index.md
+++ b/docs/user-guide/mathematical-notation/index.md
@@ -24,9 +24,7 @@ The documentation follows the same structure as Core Concepts:
Additional sections cover:
-- **[Features](features/InvestParameters.md)** — Investment decisions, on/off operation, piecewise linearization
- **[Dimensions](dimensions.md)** — Time, periods, and scenarios
-- **[Modeling Patterns](modeling-patterns/index.md)** — Internal implementation details (advanced)
## Notation Conventions
diff --git a/docs/user-guide/mathematical-notation/modeling-patterns/bounds-and-states.md b/docs/user-guide/mathematical-notation/modeling-patterns/bounds-and-states.md
deleted file mode 100644
index 18235e50d..000000000
--- a/docs/user-guide/mathematical-notation/modeling-patterns/bounds-and-states.md
+++ /dev/null
@@ -1,171 +0,0 @@
-# Bounds and States
-
-This document describes the mathematical formulations for variable bounding patterns used throughout FlixOpt. These patterns define how optimization variables are constrained, both with and without state control.
-
-## Basic Bounds
-
-The simplest bounding pattern constrains a variable between lower and upper bounds.
-
-$$\label{eq:basic_bounds}
-\text{lower} \leq v \leq \text{upper}
-$$
-
-With:
-
-- $v$ being the optimization variable
-- $\text{lower}$ being the lower bound (constant or time-dependent)
-- $\text{upper}$ being the upper bound (constant or time-dependent)
-
-**Implementation:** [`BoundingPatterns.basic_bounds()`][flixopt.modeling.BoundingPatterns.basic_bounds]
-
-**Used in:**
-- Storage charge state bounds (see [Storage](../elements/Storage.md))
-- Flow rate absolute bounds
-
----
-
-## Bounds with State
-
-When a variable should only be non-zero if a binary state variable is active (e.g., active/inactive operation, investment decisions), the bounds are controlled by the state:
-
-$$\label{eq:bounds_with_state}
-s \cdot \max(\varepsilon, \text{lower}) \leq v \leq s \cdot \text{upper}
-$$
-
-With:
-
-- $v$ being the optimization variable
-- $s \in \{0, 1\}$ being the binary state variable
-- $\text{lower}$ being the lower bound when active
-- $\text{upper}$ being the upper bound when active
-- $\varepsilon$ being a small positive number to ensure numerical stability
-
-**Behavior:**
-- When $s = 0$: variable is forced to zero ($0 \leq v \leq 0$)
-- When $s = 1$: variable can take values in $[\text{lower}, \text{upper}]$
-
-**Implementation:** [`BoundingPatterns.bounds_with_state()`][flixopt.modeling.BoundingPatterns.bounds_with_state]
-
-**Used in:**
-- Flow rates with active/inactive operation (see [StatusParameters](../features/StatusParameters.md))
-- Investment size decisions (see [InvestParameters](../features/InvestParameters.md))
-
----
-
-## Scaled Bounds
-
-When a variable's bounds depend on another variable (e.g., flow rate scaled by component size), scaled bounds are used:
-
-$$\label{eq:scaled_bounds}
-v_\text{scale} \cdot \text{rel}_\text{lower} \leq v \leq v_\text{scale} \cdot \text{rel}_\text{upper}
-$$
-
-With:
-
-- $v$ being the optimization variable (e.g., flow rate)
-- $v_\text{scale}$ being the scaling variable (e.g., component size)
-- $\text{rel}_\text{lower}$ being the relative lower bound factor (typically 0)
-- $\text{rel}_\text{upper}$ being the relative upper bound factor (typically 1)
-
-**Example:** Flow rate bounds
-- If $v_\text{scale} = P$ (flow size) and $\text{rel}_\text{upper} = 1$
-- Then: $0 \leq p(t_i) \leq P$ (see [Flow](../elements/Flow.md))
-
-**Implementation:** [`BoundingPatterns.scaled_bounds()`][flixopt.modeling.BoundingPatterns.scaled_bounds]
-
-**Used in:**
-- Flow rate constraints (see [Flow](../elements/Flow.md) equation 1)
-- Storage charge state constraints (see [Storage](../elements/Storage.md) equation 1)
-
----
-
-## Scaled Bounds with State
-
-Combining scaled bounds with binary state control requires a Big-M formulation to handle both the scaling and the active/inactive behavior:
-
-$$\label{eq:scaled_bounds_with_state_1}
-(s - 1) \cdot M_\text{misc} + v_\text{scale} \cdot \text{rel}_\text{lower} \leq v \leq v_\text{scale} \cdot \text{rel}_\text{upper}
-$$
-
-$$\label{eq:scaled_bounds_with_state_2}
-s \cdot M_\text{lower} \leq v \leq s \cdot M_\text{upper}
-$$
-
-With:
-
-- $v$ being the optimization variable
-- $v_\text{scale}$ being the scaling variable
-- $s \in \{0, 1\}$ being the binary state variable
-- $\text{rel}_\text{lower}$ being the relative lower bound factor
-- $\text{rel}_\text{upper}$ being the relative upper bound factor
-- $M_\text{misc} = v_\text{scale,max} \cdot \text{rel}_\text{lower}$
-- $M_\text{upper} = v_\text{scale,max} \cdot \text{rel}_\text{upper}$
-- $M_\text{lower} = \max(\varepsilon, v_\text{scale,min} \cdot \text{rel}_\text{lower})$
-
-Where $v_\text{scale,max}$ and $v_\text{scale,min}$ are the maximum and minimum possible values of the scaling variable.
-
-**Behavior:**
-- When $s = 0$: variable is forced to zero
-- When $s = 1$: variable follows scaled bounds $v_\text{scale} \cdot \text{rel}_\text{lower} \leq v \leq v_\text{scale} \cdot \text{rel}_\text{upper}$
-
-**Implementation:** [`BoundingPatterns.scaled_bounds_with_state()`][flixopt.modeling.BoundingPatterns.scaled_bounds_with_state]
-
-**Used in:**
-- Flow rates with active/inactive operation and investment sizing
-- Components combining [StatusParameters](../features/StatusParameters.md) and [InvestParameters](../features/InvestParameters.md)
-
----
-
-## Expression Tracking
-
-Sometimes it's necessary to create an auxiliary variable that equals an expression:
-
-$$\label{eq:expression_tracking}
-v_\text{tracker} = \text{expression}
-$$
-
-With optional bounds:
-
-$$\label{eq:expression_tracking_bounds}
-\text{lower} \leq v_\text{tracker} \leq \text{upper}
-$$
-
-With:
-
-- $v_\text{tracker}$ being the auxiliary tracking variable
-- $\text{expression}$ being a linear expression of other variables
-- $\text{lower}, \text{upper}$ being optional bounds on the tracker
-
-**Use cases:**
-- Creating named variables for complex expressions
-- Bounding intermediate results
-- Simplifying constraint formulations
-
-**Implementation:** [`ModelingPrimitives.expression_tracking_variable()`][flixopt.modeling.ModelingPrimitives.expression_tracking_variable]
-
----
-
-## Mutual Exclusivity
-
-When multiple binary variables should not be active simultaneously (at most one can be 1):
-
-$$\label{eq:mutual_exclusivity}
-\sum_{i} s_i(t) \leq \text{tolerance} \quad \forall t
-$$
-
-With:
-
-- $s_i(t) \in \{0, 1\}$ being binary state variables
-- $\text{tolerance}$ being the maximum number of simultaneously active states (typically 1)
-- $t$ being the time index
-
-**Use cases:**
-- Ensuring only one operating mode is active
-- Mutual exclusion of operation and maintenance states
-- Enforcing single-choice decisions
-
-**Implementation:** [`ModelingPrimitives.mutual_exclusivity_constraint()`][flixopt.modeling.ModelingPrimitives.mutual_exclusivity_constraint]
-
-**Used in:**
-- Operating mode selection
-- Piecewise linear function segments (see [Piecewise](../features/Piecewise.md))
diff --git a/docs/user-guide/mathematical-notation/modeling-patterns/duration-tracking.md b/docs/user-guide/mathematical-notation/modeling-patterns/duration-tracking.md
deleted file mode 100644
index 2d6f46ed1..000000000
--- a/docs/user-guide/mathematical-notation/modeling-patterns/duration-tracking.md
+++ /dev/null
@@ -1,164 +0,0 @@
-# Duration Tracking
-
-Duration tracking allows monitoring how long a binary state has been consecutively active. This is essential for modeling minimum run times, ramp-up periods, and similar time-dependent constraints.
-
-## Consecutive Duration Tracking
-
-For a binary state variable $s(t) \in \{0, 1\}$, the consecutive duration $d(t)$ tracks how long the state has been continuously active.
-
-### Duration Upper Bound
-
-The duration cannot exceed zero when the state is inactive:
-
-$$\label{eq:duration_upper}
-d(t) \leq s(t) \cdot M \quad \forall t
-$$
-
-With:
-
-- $d(t)$ being the duration variable (continuous, non-negative)
-- $s(t) \in \{0, 1\}$ being the binary state variable
-- $M$ being a sufficiently large constant (big-M)
-
-**Behavior:**
-- When $s(t) = 0$: forces $d(t) \leq 0$, thus $d(t) = 0$
-- When $s(t) = 1$: allows $d(t)$ to be positive
-
----
-
-### Duration Accumulation
-
-While the state is active, the duration increases by the time step size:
-
-$$\label{eq:duration_accumulation_upper}
-d(t+1) \leq d(t) + \Delta d(t) \quad \forall t
-$$
-
-$$\label{eq:duration_accumulation_lower}
-d(t+1) \geq d(t) + \Delta d(t) + (s(t+1) - 1) \cdot M \quad \forall t
-$$
-
-With:
-
-- $\Delta d(t)$ being the duration increment for time step $t$ (typically $\Delta t_i$ from the time series)
-- $M$ being a sufficiently large constant
-
-**Behavior:**
-- When $s(t+1) = 1$: both inequalities enforce $d(t+1) = d(t) + \Delta d(t)$
-- When $s(t+1) = 0$: only the upper bound applies, and $d(t+1) = 0$ (from equation $\eqref{eq:duration_upper}$)
-
----
-
-### Initial Duration
-
-The duration at the first time step depends on both the state and any previous duration:
-
-$$\label{eq:duration_initial}
-d(0) = (\Delta d(0) + d_\text{prev}) \cdot s(0)
-$$
-
-With:
-
-- $d_\text{prev}$ being the duration from before the optimization period
-- $\Delta d(0)$ being the duration increment for the first time step
-
-**Behavior:**
-- When $s(0) = 1$: duration continues from previous period
-- When $s(0) = 0$: duration resets to zero
-
----
-
-### Complete Formulation
-
-Combining all constraints:
-
-$$
-\begin{align}
-d(t) &\leq s(t) \cdot M && \forall t \label{eq:duration_complete_1} \\
-d(t+1) &\leq d(t) + \Delta d(t) && \forall t \label{eq:duration_complete_2} \\
-d(t+1) &\geq d(t) + \Delta d(t) + (s(t+1) - 1) \cdot M && \forall t \label{eq:duration_complete_3} \\
-d(0) &= (\Delta d(0) + d_\text{prev}) \cdot s(0) && \label{eq:duration_complete_4}
-\end{align}
-$$
-
----
-
-## Minimum Duration Constraints
-
-To enforce a minimum consecutive duration (e.g., minimum run time), an additional constraint links the duration to state changes:
-
-$$\label{eq:minimum_duration}
-d(t) \geq (s(t-1) - s(t)) \cdot d_\text{min}(t-1) \quad \forall t > 0
-$$
-
-With:
-
-- $d_\text{min}(t)$ being the required minimum duration at time $t$
-
-**Behavior:**
-- When shutting down ($s(t-1) = 1, s(t) = 0$): enforces $d(t-1) \geq d_\text{min}(t-1)$
-- This ensures the state was active for at least $d_\text{min}$ before turning off
-- When state is constant or turning on: constraint is non-binding
-
----
-
-## Implementation
-
-**Function:** [`ModelingPrimitives.consecutive_duration_tracking()`][flixopt.modeling.ModelingPrimitives.consecutive_duration_tracking]
-
-See the API documentation for complete parameter list and usage details.
-
----
-
-## Use Cases
-
-### Minimum Run Time
-
-Ensuring equipment runs for a minimum duration once started:
-
-```python
-# State: 1 when running, 0 when off
-# Require at least 2 hours of operation
-duration = modeling.consecutive_duration_tracking(
- state=on_state,
- duration_per_step=time_step_hours,
- minimum_duration=2.0
-)
-```
-
-### Ramp-Up Tracking
-
-Tracking time since startup for gradual ramp-up constraints:
-
-```python
-# Track startup duration
-startup_duration = modeling.consecutive_duration_tracking(
- state=on_state,
- duration_per_step=time_step_hours
-)
-# Constrain output based on startup duration
-# (additional constraints would link output to startup_duration)
-```
-
-### Cooldown Requirements
-
-Tracking time in a state before allowing transitions:
-
-```python
-# Track maintenance duration
-maintenance_duration = modeling.consecutive_duration_tracking(
- state=maintenance_state,
- duration_per_step=time_step_hours,
- minimum_duration=scheduled_maintenance_hours
-)
-```
-
----
-
-## Used In
-
-This pattern is used in:
-
-- [`StatusParameters`](../features/StatusParameters.md) - Minimum active/inactive times
-- Operating mode constraints with minimum durations
-- Startup/shutdown sequence modeling
diff --git a/docs/user-guide/mathematical-notation/modeling-patterns/index.md b/docs/user-guide/mathematical-notation/modeling-patterns/index.md
deleted file mode 100644
index ab347eb39..000000000
--- a/docs/user-guide/mathematical-notation/modeling-patterns/index.md
+++ /dev/null
@@ -1,54 +0,0 @@
-# Modeling Patterns
-
-This section documents the fundamental mathematical patterns used throughout FlixOpt for constructing optimization models. These patterns are implemented in `flixopt.modeling` and provide reusable building blocks for creating constraints.
-
-## Overview
-
-The modeling patterns are organized into three categories:
-
-1. **[Bounds and States](bounds-and-states.md)** - Variable bounding with optional state control
-2. **[Duration Tracking](duration-tracking.md)** - Tracking consecutive durations of states
-3. **[State Transitions](state-transitions.md)** - Modeling state changes and transitions
-
-## Pattern Categories
-
-### Bounding Patterns
-
-These patterns define how optimization variables are constrained within bounds:
-
-- **Basic Bounds** - Simple upper and lower bounds on variables
-- **Bounds with State** - Binary-controlled bounds (active/inactive states)
-- **Scaled Bounds** - Bounds dependent on another variable (e.g., size)
-- **Scaled Bounds with State** - Combination of scaling and binary control
-
-### Tracking Patterns
-
-These patterns track properties over time:
-
-- **Expression Tracking** - Creating auxiliary variables that track expressions
-- **Consecutive Duration Tracking** - Tracking how long a state has been active
-- **Mutual Exclusivity** - Ensuring only one of multiple options is active
-
-### Transition Patterns
-
-These patterns model changes between states:
-
-- **State Transitions** - Tracking switches between binary states (on→off, off→on)
-- **Continuous Transitions** - Linking continuous variable changes to switches
-- **Level Changes with Binaries** - Controlled increases/decreases in levels
-
-## Usage in Components
-
-These patterns are used throughout FlixOpt components:
-
-- [`Flow`][flixopt.elements.Flow] uses **scaled bounds with state** for flow rate constraints
-- [`Storage`][flixopt.components.Storage] uses **basic bounds** for charge state
-- [`StatusParameters`](../features/StatusParameters.md) uses **state transitions** for startup/shutdown
-- [`InvestParameters`](../features/InvestParameters.md) uses **bounds with state** for investment decisions
-
-## Implementation
-
-All patterns are implemented in [`flixopt.modeling`][flixopt.modeling] module:
-
-- [`ModelingPrimitives`][flixopt.modeling.ModelingPrimitives] - Core constraint patterns
-- [`BoundingPatterns`][flixopt.modeling.BoundingPatterns] - Specialized bounding patterns
diff --git a/docs/user-guide/mathematical-notation/modeling-patterns/state-transitions.md b/docs/user-guide/mathematical-notation/modeling-patterns/state-transitions.md
deleted file mode 100644
index cf6cfe736..000000000
--- a/docs/user-guide/mathematical-notation/modeling-patterns/state-transitions.md
+++ /dev/null
@@ -1,235 +0,0 @@
-# State Transitions
-
-State transition patterns model changes between discrete states and link them to continuous variables. These patterns are essential for modeling startup/shutdown events, switching behavior, and controlled changes in system operation.
-
-## Binary State Transitions
-
-For a binary state variable $s(t) \in \{0, 1\}$, state transitions track when the state switches on or off.
-
-### Switch Variables
-
-Two binary variables track the transitions:
-
-- $s^\text{on}(t) \in \{0, 1\}$: equals 1 when switching from off to on
-- $s^\text{off}(t) \in \{0, 1\}$: equals 1 when switching from on to off
-
-### Transition Tracking
-
-The state change equals the difference between switch-on and switch-off:
-
-$$\label{eq:state_transition}
-s^\text{on}(t) - s^\text{off}(t) = s(t) - s(t-1) \quad \forall t > 0
-$$
-
-$$\label{eq:state_transition_initial}
-s^\text{on}(0) - s^\text{off}(0) = s(0) - s_\text{prev}
-$$
-
-With:
-
-- $s(t)$ being the binary state variable
-- $s_\text{prev}$ being the state before the optimization period
-- $s^\text{on}(t), s^\text{off}(t)$ being the switch variables
-
-**Behavior:**
-- Off → On ($s(t-1)=0, s(t)=1$): $s^\text{on}(t)=1, s^\text{off}(t)=0$
-- On → Off ($s(t-1)=1, s(t)=0$): $s^\text{on}(t)=0, s^\text{off}(t)=1$
-- No change: $s^\text{on}(t)=0, s^\text{off}(t)=0$
-
----
-
-### Mutual Exclusivity of Switches
-
-A state cannot switch on and off simultaneously:
-
-$$\label{eq:switch_exclusivity}
-s^\text{on}(t) + s^\text{off}(t) \leq 1 \quad \forall t
-$$
-
-This ensures:
-
-- At most one switch event per time step
-- No simultaneous active/inactive switching
-
----
-
-### Complete State Transition Formulation
-
-$$
-\begin{align}
-s^\text{on}(t) - s^\text{off}(t) &= s(t) - s(t-1) && \forall t > 0 \label{eq:transition_complete_1} \\
-s^\text{on}(0) - s^\text{off}(0) &= s(0) - s_\text{prev} && \label{eq:transition_complete_2} \\
-s^\text{on}(t) + s^\text{off}(t) &\leq 1 && \forall t \label{eq:transition_complete_3} \\
-s^\text{on}(t), s^\text{off}(t) &\in \{0, 1\} && \forall t \label{eq:transition_complete_4}
-\end{align}
-$$
-
-**Implementation:** [`BoundingPatterns.state_transition_bounds()`][flixopt.modeling.BoundingPatterns.state_transition_bounds]
-
----
-
-## Continuous Transitions
-
-When a continuous variable should only change when certain switch events occur, continuous transition bounds link the variable changes to binary switches.
-
-### Change Bounds with Switches
-
-$$\label{eq:continuous_transition}
--\Delta v^\text{max} \cdot (s^\text{on}(t) + s^\text{off}(t)) \leq v(t) - v(t-1) \leq \Delta v^\text{max} \cdot (s^\text{on}(t) + s^\text{off}(t)) \quad \forall t > 0
-$$
-
-$$\label{eq:continuous_transition_initial}
--\Delta v^\text{max} \cdot (s^\text{on}(0) + s^\text{off}(0)) \leq v(0) - v_\text{prev} \leq \Delta v^\text{max} \cdot (s^\text{on}(0) + s^\text{off}(0))
-$$
-
-With:
-
-- $v(t)$ being the continuous variable
-- $v_\text{prev}$ being the value before the optimization period
-- $\Delta v^\text{max}$ being the maximum allowed change
-- $s^\text{on}(t), s^\text{off}(t) \in \{0, 1\}$ being switch binary variables
-
-**Behavior:**
-- When $s^\text{on}(t) = 0$ and $s^\text{off}(t) = 0$: forces $v(t) = v(t-1)$ (no change)
-- When $s^\text{on}(t) = 1$ or $s^\text{off}(t) = 1$: allows change up to $\pm \Delta v^\text{max}$
-
-**Implementation:** [`BoundingPatterns.continuous_transition_bounds()`][flixopt.modeling.BoundingPatterns.continuous_transition_bounds]
-
----
-
-## Level Changes with Binaries
-
-This pattern models a level variable that can increase or decrease, with changes controlled by binary variables. This is useful for inventory management, capacity adjustments, or gradual state changes.
-
-### Level Evolution
-
-The level evolves based on increases and decreases:
-
-$$\label{eq:level_initial}
-\ell(0) = \ell_\text{init} + \ell^\text{inc}(0) - \ell^\text{dec}(0)
-$$
-
-$$\label{eq:level_evolution}
-\ell(t) = \ell(t-1) + \ell^\text{inc}(t) - \ell^\text{dec}(t) \quad \forall t > 0
-$$
-
-With:
-
-- $\ell(t)$ being the level variable
-- $\ell_\text{init}$ being the initial level
-- $\ell^\text{inc}(t)$ being the increase in level at time $t$ (non-negative)
-- $\ell^\text{dec}(t)$ being the decrease in level at time $t$ (non-negative)
-
----
-
-### Change Bounds with Binary Control
-
-Changes are bounded and controlled by binary variables:
-
-$$\label{eq:increase_bound}
-\ell^\text{inc}(t) \leq \Delta \ell^\text{max} \cdot b^\text{inc}(t) \quad \forall t
-$$
-
-$$\label{eq:decrease_bound}
-\ell^\text{dec}(t) \leq \Delta \ell^\text{max} \cdot b^\text{dec}(t) \quad \forall t
-$$
-
-With:
-
-- $\Delta \ell^\text{max}$ being the maximum change per time step
-- $b^\text{inc}(t), b^\text{dec}(t) \in \{0, 1\}$ being binary control variables
-
----
-
-### Mutual Exclusivity of Changes
-
-Simultaneous increase and decrease are prevented:
-
-$$\label{eq:change_exclusivity}
-b^\text{inc}(t) + b^\text{dec}(t) \leq 1 \quad \forall t
-$$
-
-This ensures:
-
-- Level can only increase OR decrease (or stay constant) in each time step
-- No simultaneous contradictory changes
-
----
-
-### Complete Level Change Formulation
-
-$$
-\begin{align}
-\ell(0) &= \ell_\text{init} + \ell^\text{inc}(0) - \ell^\text{dec}(0) && \label{eq:level_complete_1} \\
-\ell(t) &= \ell(t-1) + \ell^\text{inc}(t) - \ell^\text{dec}(t) && \forall t > 0 \label{eq:level_complete_2} \\
-\ell^\text{inc}(t) &\leq \Delta \ell^\text{max} \cdot b^\text{inc}(t) && \forall t \label{eq:level_complete_3} \\
-\ell^\text{dec}(t) &\leq \Delta \ell^\text{max} \cdot b^\text{dec}(t) && \forall t \label{eq:level_complete_4} \\
-b^\text{inc}(t) + b^\text{dec}(t) &\leq 1 && \forall t \label{eq:level_complete_5} \\
-b^\text{inc}(t), b^\text{dec}(t) &\in \{0, 1\} && \forall t \label{eq:level_complete_6}
-\end{align}
-$$
-
-**Implementation:** [`BoundingPatterns.link_changes_to_level_with_binaries()`][flixopt.modeling.BoundingPatterns.link_changes_to_level_with_binaries]
-
----
-
-## Use Cases
-
-### Startup/Shutdown Costs
-
-Track startup and shutdown events to apply costs:
-
-```python
-# Create switch variables
-startup, shutdown = modeling.state_transition_bounds(
- state=on_state,
- previous_state=previous_on_state
-)
-
-# Apply costs to switches
-startup_cost = startup * startup_cost_per_event
-shutdown_cost = shutdown * shutdown_cost_per_event
-```
-
-### Limited Switching
-
-Restrict the number of state changes:
-
-```python
-# Track all switches
-startup, shutdown = modeling.state_transition_bounds(
- state=on_state
-)
-
-# Limit total switches
-model.add_constraint(
- (startup + shutdown).sum() <= max_switches
-)
-```
-
-### Gradual Capacity Changes
-
-Model systems where capacity can be incrementally adjusted:
-
-```python
-# Level represents installed capacity
-level_var, increase, decrease, inc_binary, dec_binary = \
- modeling.link_changes_to_level_with_binaries(
- initial_level=current_capacity,
- max_change=max_capacity_change_per_period
- )
-
-# Constrain total increases
-model.add_constraint(increase.sum() <= max_total_expansion)
-```
-
----
-
-## Used In
-
-These patterns are used in:
-
-- [`StatusParameters`](../features/StatusParameters.md) - Startup/shutdown tracking and costs
-- Operating mode switching with transition costs
-- Investment planning with staged capacity additions
-- Inventory management with controlled stock changes
diff --git a/mkdocs.yml b/mkdocs.yml
index 18aa44766..1a4d5842e 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -33,16 +33,10 @@ nav:
- LinearConverter: user-guide/mathematical-notation/elements/LinearConverter.md
- Storage: user-guide/mathematical-notation/elements/Storage.md
- Effects & Objective: user-guide/mathematical-notation/effects-penalty-objective.md
- - Features:
- - Investment: user-guide/mathematical-notation/features/InvestParameters.md
- - Status: user-guide/mathematical-notation/features/StatusParameters.md
- - Piecewise Linearization: user-guide/mathematical-notation/features/Piecewise.md
+ - Investment: user-guide/mathematical-notation/features/InvestParameters.md
+ - Status: user-guide/mathematical-notation/features/StatusParameters.md
+ - Piecewise: user-guide/mathematical-notation/features/Piecewise.md
- Dimensions: user-guide/mathematical-notation/dimensions.md
- - Modeling Patterns:
- - Overview: user-guide/mathematical-notation/modeling-patterns/index.md
- - Bounds and States: user-guide/mathematical-notation/modeling-patterns/bounds-and-states.md
- - Duration Tracking: user-guide/mathematical-notation/modeling-patterns/duration-tracking.md
- - State Transitions: user-guide/mathematical-notation/modeling-patterns/state-transitions.md
- Recipes: user-guide/recipes/index.md
- Support:
- FAQ: user-guide/faq.md
From e2c0dd1a1010cdb57838650475ea948069a81a64 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 03:54:23 +0100
Subject: [PATCH 64/79] Simplify docs
---
.../mathematical-notation/elements/Bus.md | 154 ++--------
.../mathematical-notation/elements/Flow.md | 279 ++++++------------
.../elements/LinearConverter.md | 224 +++++---------
.../mathematical-notation/elements/Storage.md | 226 +++++---------
.../features/InvestParameters.md | 252 ++++++----------
.../features/Piecewise.md | 258 +++++-----------
.../features/StatusParameters.md | 208 +++++--------
7 files changed, 518 insertions(+), 1083 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/elements/Bus.md b/docs/user-guide/mathematical-notation/elements/Bus.md
index 097aa1830..6fb5050e5 100644
--- a/docs/user-guide/mathematical-notation/elements/Bus.md
+++ b/docs/user-guide/mathematical-notation/elements/Bus.md
@@ -1,82 +1,31 @@
# Bus
-A Bus is a connection point where flows meet and must balance. Think of it as a junction in your system where energy or material from multiple sources combines and is distributed to multiple consumers.
+A Bus is where flows meet and must balance — inputs equal outputs at every timestep.
-!!! example "Real-world examples"
- - **Heat Bus** — where boiler output, heat pump output, and storage discharge meet building demand
- - **Electricity Bus** — where generators, grid imports, and battery discharge meet electrical loads
- - **Gas Bus** — connection point to the gas grid
+## Basic: Balance Equation
-## The Balance Equation
-
-The fundamental rule: **what goes in must equal what goes out**.
-
-=== "Without Excess (Strict)"
-
- $$
- \sum_{f \in \mathcal{F}_{in}} p_f(t) = \sum_{f \in \mathcal{F}_{out}} p_f(t)
- $$
-
- At every timestep $t$, the sum of all incoming flow rates must equal the sum of all outgoing flow rates. If this can't be satisfied, the model is infeasible.
-
-=== "With Excess (Soft)"
-
- When `excess_penalty_per_flow_hour` is set, slack variables allow imbalance:
-
- $$
- \sum_{f \in \mathcal{F}_{in}} p_f(t) + \phi_{in}(t) = \sum_{f \in \mathcal{F}_{out}} p_f(t) + \phi_{out}(t)
- $$
-
- Where:
-
- - $\phi_{in}(t)$ — "virtual supply" to cover shortages
- - $\phi_{out}(t)$ — "virtual demand" to absorb surplus
-
- Both are penalized in the objective:
-
- $$
- \Phi_b(t) = (\phi_{in}(t) + \phi_{out}(t)) \cdot \Delta t \cdot c_\phi
- $$
-
-!!! note "Direction matters"
- Flows have a defined direction. An *input* to the bus means energy/material flowing **into** the bus. An *output* means flowing **out of** the bus.
-
-!!! tip "Debugging with excess"
- If excess variables are non-zero in your solution, it means your system couldn't meet all constraints. Check:
-
- - Is demand too high for available capacity?
- - Are there timesteps where no supply is available?
- - Did you forget to connect a component?
-
-## Variables
-
-| Symbol | Python Name | Description | When Created |
-|--------|-------------|-------------|--------------|
-| $\phi_{in}(t)$ | `excess_input` | Virtual supply to cover shortages | `excess_penalty_per_flow_hour` is set |
-| $\phi_{out}(t)$ | `excess_output` | Virtual demand to absorb surplus | `excess_penalty_per_flow_hour` is set |
+$$
+\sum_{in} p(t) = \sum_{out} p(t)
+$$
-## Parameters
+```python
+heat_bus = fx.Bus(label='heat')
+# All flows connected to this bus must balance
+```
-| Symbol | Python Name | Description | Default |
-|--------|-------------|-------------|---------|
-| $c_\phi$ | `excess_penalty_per_flow_hour` | Penalty cost per unit imbalance | `1e5` |
-| $\mathcal{F}_{in}$ | - | Set of input flows | From connected flows |
-| $\mathcal{F}_{out}$ | - | Set of output flows | From connected flows |
+If balance can't be achieved → model is **infeasible**.
-## Usage Examples
+---
-### Strict Balance (No Imbalance Allowed)
+## With Excess Penalty
-```python
-electricity_bus = fx.Bus(
- label='electricity',
- excess_penalty_per_flow_hour=None # No slack allowed
-)
-```
+Allow imbalance for debugging or soft constraints:
-If balance can't be achieved, the solver returns infeasible.
+$$
+\sum_{in} p(t) + \phi_{in}(t) = \sum_{out} p(t) + \phi_{out}(t)
+$$
-### Balance with Penalty (Debugging/Soft Constraints)
+The slack variables $\phi$ are penalized: $(\phi_{in} + \phi_{out}) \cdot \Delta t \cdot c_\phi$
```python
heat_bus = fx.Bus(
@@ -85,65 +34,20 @@ heat_bus = fx.Bus(
)
```
-Imbalance is allowed but heavily penalized. Use this to:
-
-- Debug infeasible models
-- Model emergency scenarios
-- Allow small numerical tolerances
-
-### Time-Varying Penalty
-
-```python
-# Higher penalty during peak hours
-penalty_profile = [100, 100, 500, 500, 500, 100, 100, ...]
-
-material_bus = fx.Bus(
- label='material',
- excess_penalty_per_flow_hour=penalty_profile
-)
-```
-
-## How Buses Connect Components
-
-Buses don't exist in isolation — they connect components through flows:
-
-```
- ┌─────────┐
- gas_in ───────►│ Gas │
- │ Bus │
- └────┬────┘
- │ gas_out
- ▼
- ┌─────────┐
- │ Boiler │
- └────┬────┘
- │ heat_out
- ▼
- ┌─────────┐
- storage_out ──►│ Heat │◄─── heat_pump_out
- │ Bus │
- └────┬────┘
- │ demand_in
- ▼
- ┌─────────┐
- │ Demand │
- │ (Sink) │
- └─────────┘
-```
-
-Each arrow is a Flow. The Bus ensures that at every timestep:
+!!! tip "Debugging"
+ If excess is non-zero in results → your system couldn't meet demand. Check capacities and connections.
-$$
-heat\_pump\_out + boiler\_out + storage\_out = demand\_in
-$$
+---
-## Implementation Details
+## Reference
-- **Element Class:** [`Bus`][flixopt.elements.Bus]
-- **Model Class:** [`BusModel`][flixopt.elements.BusModel]
+| Variable | Description |
+|----------|-------------|
+| $\phi_{in}(t)$ | Virtual supply (covers shortages) |
+| $\phi_{out}(t)$ | Virtual demand (absorbs surplus) |
-## See Also
+| Parameter | Python | Default |
+|-----------|--------|---------|
+| Penalty | `excess_penalty_per_flow_hour` | `1e5` |
-- [Flow](Flow.md) — The flows that connect to buses
-- [Effects & Objective](../effects-penalty-objective.md) — How penalties affect the objective
-- [Core Concepts: Buses](../../core-concepts.md#buses-where-things-connect) — High-level overview
+**Classes:** [`Bus`][flixopt.elements.Bus], [`BusModel`][flixopt.elements.BusModel]
diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md
index 428b9119b..50896887a 100644
--- a/docs/user-guide/mathematical-notation/elements/Flow.md
+++ b/docs/user-guide/mathematical-notation/elements/Flow.md
@@ -1,234 +1,133 @@
# Flow
-A Flow represents the movement of energy or material between a component and a bus. It's the primary optimization variable — the solver decides how much flows at each timestep.
+A Flow is the primary optimization variable — the solver decides how much flows at each timestep.
-!!! example "Real-world examples"
- - Heat output from a boiler to the heat bus
- - Electricity import from the grid
- - Gas consumption by a CHP unit
- - Charging power into a battery
+## Basic: Bounded Flow Rate
-## Core Concept: Size and Flow Rate
+Every flow has a **size** $P$ (capacity) and a **flow rate** $p(t)$ (what the solver optimizes):
-Every flow has two key quantities:
-
-- **Size** ($P$) — The capacity or maximum possible flow rate
-- **Flow Rate** ($p(t)$) — The actual flow at each timestep (optimization variable)
-
-## Flow Rate Bounds
-
-=== "Fixed Size"
-
- The flow rate is bounded by the size:
-
- $$
- P \cdot p_{rel}^{min}(t) \leq p(t) \leq P \cdot p_{rel}^{max}(t)
- $$
-
- The flow **cannot be zero** if $p_{rel}^{min} > 0$.
-
- !!! example "100 kW boiler, min 30%"
- - $P = 100$, $p_{rel}^{min} = 0.3$, $p_{rel}^{max} = 1$
- - Constraint: $30 \leq p(t) \leq 100$
-
-=== "Fixed Size + Status"
-
- When `status_parameters` is specified, the flow can also be zero:
-
- $$
- s(t) \cdot P \cdot p_{rel}^{min}(t) \leq p(t) \leq s(t) \cdot P \cdot p_{rel}^{max}(t)
- $$
-
- Where $s(t) \in \{0, 1\}$ is the binary status.
-
- - When $s(t) = 0$: $p(t) = 0$ (inactive)
- - When $s(t) = 1$: $P \cdot p_{rel}^{min} \leq p(t) \leq P \cdot p_{rel}^{max}$ (active)
-
- See [StatusParameters](../features/StatusParameters.md) for details.
-
-=== "Variable Size"
-
- When `size` is `InvestParameters`, the capacity $P$ becomes a variable:
-
- $$
- P^{min} \leq P \leq P^{max}
- $$
-
- $$
- P \cdot p_{rel}^{min}(t) \leq p(t) \leq P \cdot p_{rel}^{max}(t)
- $$
-
- See [InvestParameters](../features/InvestParameters.md) for details.
-
-=== "Variable Size + Status"
-
- !!! warning "Work in Progress"
- This section needs review. The linearization constraints below may not be accurate.
-
- When both `size` is `InvestParameters` and `status_parameters` is set:
-
- $$
- P^{min} \leq P \leq P^{max}
- $$
-
- $$
- s(t) \cdot P \cdot p_{rel}^{min}(t) \leq p(t) \leq s(t) \cdot P \cdot p_{rel}^{max}(t)
- $$
+$$
+P \cdot p_{rel}^{min} \leq p(t) \leq P \cdot p_{rel}^{max}
+$$
- This creates a bilinear term $s(t) \cdot P$. flixOpt linearizes this using big-M constraints:
+```python
+# 100 kW boiler, minimum 30% when running
+heat = fx.Flow(label='heat', bus=heat_bus, size=100, relative_minimum=0.3)
+# → 30 ≤ p(t) ≤ 100
+```
- $$
- p(t) \leq P^{max} \cdot s(t)
- $$
+!!! warning "Cannot be zero"
+ With `relative_minimum > 0`, the flow cannot be zero. Use `status_parameters` to allow shutdown.
- $$
- p(t) \leq P - P^{min} \cdot (1 - s(t))
- $$
+---
- $$
- p(t) \geq P - P^{max} \cdot (1 - s(t))
- $$
+## Adding Features
=== "Fixed Profile"
- When `fixed_relative_profile` is set, the flow rate is fixed:
+ Lock the flow to a time series (demands, renewables):
- $$
- p(t) = P \cdot \pi(t)
- $$
+ $p(t) = P \cdot \pi(t)$
- No optimization freedom — used for demands and renewable generation.
+ ```python
+ demand = fx.Flow(
+ label='demand', bus=heat_bus, size=100,
+ fixed_relative_profile=[0.5, 0.8, 1.0, 0.6] # π(t)
+ )
+ ```
-## Flow Hours: Energy vs Power
+=== "On/Off Operation"
-**Flow rate** is power (kW, MW). **Flow hours** is energy (kWh, MWh):
+ Allow the flow to be zero with `status_parameters`:
-$$
-h_f(t) = p(t) \cdot \Delta t
-$$
-
-| Flow Rate | Timestep | Flow Hours |
-|-----------|----------|------------|
-| 100 kW | 1 hour | 100 kWh |
-| 100 kW | 15 min | 25 kWh |
+ $s(t) \cdot P \cdot p_{rel}^{min} \leq p(t) \leq s(t) \cdot P \cdot p_{rel}^{max}$
-This matters for costs: `effects_per_flow_hour` is cost per energy (€/MWh).
+ Where $s(t) \in \{0, 1\}$: inactive or active.
-## Additional Constraints
+ ```python
+ generator = fx.Flow(
+ label='power', bus=elec_bus, size=50,
+ relative_minimum=0.4,
+ status_parameters=fx.StatusParameters(
+ effects_per_startup={'costs': 500},
+ min_uptime=2,
+ ),
+ )
+ ```
-=== "Load Factor"
+ See [StatusParameters](../features/StatusParameters.md).
- Constrain average utilization (`load_factor_min/max`):
-
- $$
- \lambda_{min} \cdot P \cdot n_t \leq \sum_t p(t) \leq \lambda_{max} \cdot P \cdot n_t
- $$
+=== "Variable Size"
- Where $n_t$ is the number of timesteps.
+ Optimize the capacity with `InvestParameters`:
-=== "Flow Hours Limits"
+ $P^{min} \leq P \leq P^{max}$
- Constrain total energy (`flow_hours_min/max`):
+ ```python
+ battery = fx.Flow(
+ label='power', bus=elec_bus,
+ size=fx.InvestParameters(
+ minimum_size=0,
+ maximum_size=1000,
+ specific_effects={'costs': 100_000},
+ ),
+ )
+ ```
- $$
- h_{min} \leq \sum_t p(t) \cdot \Delta t \leq h_{max}
- $$
+ See [InvestParameters](../features/InvestParameters.md).
-## Variables
+=== "Flow Costs"
-| Symbol | Python Name | Description | When Created |
-|--------|-------------|-------------|--------------|
-| $p(t)$ | `flow_rate` | Flow rate at timestep $t$ | Always |
-| $P$ | `size` | Capacity (variable) | `size` is `InvestParameters` |
-| $s(t)$ | `status` | Binary status | `status_parameters` set |
-| $s^{start}(t)$ | `startup` | Startup indicator | `status_parameters` set |
-| $s^{stop}(t)$ | `shutdown` | Shutdown indicator | `status_parameters` set |
+ Add costs per energy (flow hours):
-## Parameters
+ ```python
+ gas = fx.Flow(
+ label='gas', bus=gas_bus, size=150,
+ effects_per_flow_hour={'costs': 50}, # €50/MWh
+ )
+ ```
-| Symbol | Python Name | Description | Default |
-|--------|-------------|-------------|---------|
-| $P$ | `size` | Flow capacity | Required |
-| $p_{rel}^{min}(t)$ | `relative_minimum` | Min as fraction of size | 0 |
-| $p_{rel}^{max}(t)$ | `relative_maximum` | Max as fraction of size | 1 |
-| $\pi(t)$ | `fixed_relative_profile` | Fixed profile | None |
-| $\lambda_{min}$ | `load_factor_min` | Min average utilization | None |
-| $\lambda_{max}$ | `load_factor_max` | Max average utilization | None |
-| $h_{min}$ | `flow_hours_min` | Min total energy | None |
-| $h_{max}$ | `flow_hours_max` | Max total energy | None |
+ Flow hours: $h(t) = p(t) \cdot \Delta t$
-## Usage Examples
+---
-### Basic Flow
+## Optional Constraints
-```python
-heat_output = fx.Flow(
- label='heat_out',
- bus=heat_bus,
- size=100, # 100 kW capacity
- relative_minimum=0.3, # Min 30 kW when operating
-)
-```
+=== "Load Factor"
-### Flow with Costs
+ Constrain average utilization:
-```python
-gas_input = fx.Flow(
- label='gas_in',
- bus=gas_bus,
- size=150,
- effects_per_flow_hour={'costs': 50}, # €50/MWh
-)
-```
+ $\lambda_{min} \leq \frac{\sum_t p(t)}{P \cdot n_t} \leq \lambda_{max}$
-### Fixed Profile (Solar)
+ ```python
+ fx.Flow(..., load_factor_min=0.5, load_factor_max=0.9)
+ ```
-```python
-solar = fx.Flow(
- label='solar',
- bus=electricity_bus,
- size=500, # 500 kW peak
- fixed_relative_profile=[0, 0.1, 0.4, 0.8, 0.9, 0.6, 0.2, 0],
-)
-```
+=== "Flow Hours"
-### With Status Operation
+ Constrain total energy:
-```python
-generator = fx.Flow(
- label='power',
- bus=electricity_bus,
- size=50,
- relative_minimum=0.4, # 40% min when active, but can be inactive
- status_parameters=fx.StatusParameters(
- effects_per_startup={'costs': 500},
- min_uptime=2,
- ),
-)
-```
+ $h_{min} \leq \sum_t p(t) \cdot \Delta t \leq h_{max}$
-### Investment Decision
+ ```python
+ fx.Flow(..., flow_hours_min=1000, flow_hours_max=5000)
+ ```
-```python
-battery_flow = fx.Flow(
- label='power',
- bus=electricity_bus,
- size=fx.InvestParameters(
- minimum_size=0,
- maximum_size=1000,
- specific_effects={'costs': 100_000},
- ),
-)
-```
+---
-## Implementation Details
+## Reference
-- **Element Class:** [`Flow`][flixopt.elements.Flow]
-- **Model Class:** [`FlowModel`][flixopt.elements.FlowModel]
+| Variable | Description |
+|----------|-------------|
+| $p(t)$ | Flow rate (optimized) |
+| $P$ | Size — fixed or variable with `InvestParameters` |
+| $s(t)$ | Binary status — with `status_parameters` |
-## See Also
+| Parameter | Python | Default |
+|-----------|--------|---------|
+| Capacity | `size` | required |
+| Min fraction | `relative_minimum` | 0 |
+| Max fraction | `relative_maximum` | 1 |
+| Fixed profile | `fixed_relative_profile` | None |
-- [Bus](Bus.md) — Where flows connect
-- [LinearConverter](LinearConverter.md) — Components using flows
-- [StatusParameters](../features/StatusParameters.md) — Binary operation
-- [InvestParameters](../features/InvestParameters.md) — Capacity optimization
+**Classes:** [`Flow`][flixopt.elements.Flow], [`FlowModel`][flixopt.elements.FlowModel]
diff --git a/docs/user-guide/mathematical-notation/elements/LinearConverter.md b/docs/user-guide/mathematical-notation/elements/LinearConverter.md
index b4738254e..e5a16cf3b 100644
--- a/docs/user-guide/mathematical-notation/elements/LinearConverter.md
+++ b/docs/user-guide/mathematical-notation/elements/LinearConverter.md
@@ -1,139 +1,76 @@
# LinearConverter
-A LinearConverter transforms inputs into outputs with defined conversion ratios. It's the workhorse for modeling equipment that converts energy or material.
+A LinearConverter transforms inputs into outputs with fixed ratios.
-!!! example "Real-world examples"
- - **Gas boiler** — gas → heat (η = 90%)
- - **Heat pump** — electricity → heat (COP = 3.5)
- - **CHP** — gas → electricity + heat
- - **Electrolyzer** — electricity → hydrogen
-
-## Core Concept: Conversion Factors
-
-The fundamental equation links inputs and outputs:
+## Basic: Conversion Equation
$$
-\sum_{f \in \mathcal{F}_{in}} a_f \cdot p_f(t) = \sum_{f \in \mathcal{F}_{out}} b_f \cdot p_f(t)
+\sum_{in} a_f \cdot p_f(t) = \sum_{out} b_f \cdot p_f(t)
$$
-The conversion factors $a_f$ and $b_f$ define the relationship.
-
-=== "Single Input/Output"
-
- For a boiler with 90% efficiency:
-
- $$
- 0.9 \cdot p_{gas}(t) = 1 \cdot p_{heat}(t)
- $$
-
- Or: $p_{heat}(t) = 0.9 \cdot p_{gas}(t)$
-
-=== "Multiple Outputs (CHP)"
-
- CHP produces electricity and heat from fuel:
-
- $$
- 0.35 \cdot p_{fuel}(t) = p_{el}(t)
- $$
-
- $$
- 0.50 \cdot p_{fuel}(t) = p_{th}(t)
- $$
-
- Total efficiency: 85%
-
-=== "COP > 1 (Heat Pump)"
-
- Heat pump with COP = 3.5:
-
- $$
- 3.5 \cdot p_{el}(t) = p_{th}(t)
- $$
-
- Factor > 1 because it extracts heat from environment.
-
-=== "Time-Varying"
-
- Efficiency can vary with time (e.g., COP depends on temperature):
-
- $$
- \eta(t) \cdot p_{in}(t) = p_{out}(t)
- $$
+=== "Boiler (η = 90%)"
-## Variables
+ $0.9 \cdot p_{gas}(t) = p_{heat}(t)$
-| Symbol | Python Name | Description | When Created |
-|--------|-------------|-------------|--------------|
-| $p_{in}(t)$ | (from input Flows) | Input flow rates | Always |
-| $p_{out}(t)$ | (from output Flows) | Output flow rates | Always |
+ ```python
+ boiler = fx.LinearConverter(
+ label='boiler',
+ inputs=[fx.Flow(label='gas', bus=gas_bus, size=111)],
+ outputs=[fx.Flow(label='heat', bus=heat_bus, size=100)],
+ conversion_factors=[{'gas': 0.9, 'heat': 1}],
+ )
+ ```
-The converter creates **constraints** linking flow variables, not new variables.
+=== "Heat Pump (COP = 3.5)"
-## Parameters
+ $3.5 \cdot p_{el}(t) = p_{heat}(t)$
-| Symbol | Python Name | Description |
-|--------|-------------|-------------|
-| $\mathcal{F}_{in}$ | `inputs` | List of input Flows |
-| $\mathcal{F}_{out}$ | `outputs` | List of output Flows |
-| $a_f$, $b_f$ | `conversion_factors` | Factor dictionaries |
+ ```python
+ hp = fx.LinearConverter(
+ label='hp',
+ inputs=[fx.Flow(label='el', bus=elec_bus, size=100)],
+ outputs=[fx.Flow(label='heat', bus=heat_bus, size=350)],
+ conversion_factors=[{'el': 3.5, 'heat': 1}],
+ )
+ ```
-## Usage Examples
+=== "CHP (35% el, 50% th)"
-### Gas Boiler
+ Two constraints linking fuel to outputs:
-```python
-boiler = fx.LinearConverter(
- label='boiler',
- inputs=[fx.Flow(label='gas', bus=gas_bus, size=111)],
- outputs=[fx.Flow(label='heat', bus=heat_bus, size=100)],
- conversion_factors=[{'gas': 0.9, 'heat': 1}],
-)
-```
-
-### CHP Unit
-
-```python
-chp = fx.LinearConverter(
- label='chp',
- inputs=[fx.Flow(label='fuel', bus=gas_bus, size=100)],
- outputs=[
- fx.Flow(label='el', bus=elec_bus, size=35),
- fx.Flow(label='heat', bus=heat_bus, size=50),
- ],
- conversion_factors=[
- {'fuel': 0.35, 'el': 1},
- {'fuel': 0.50, 'heat': 1},
- ],
-)
-```
+ ```python
+ chp = fx.LinearConverter(
+ label='chp',
+ inputs=[fx.Flow(label='fuel', bus=gas_bus, size=100)],
+ outputs=[
+ fx.Flow(label='el', bus=elec_bus, size=35),
+ fx.Flow(label='heat', bus=heat_bus, size=50),
+ ],
+ conversion_factors=[
+ {'fuel': 0.35, 'el': 1},
+ {'fuel': 0.50, 'heat': 1},
+ ],
+ )
+ ```
-### Heat Pump
+---
-```python
-hp = fx.LinearConverter(
- label='hp',
- inputs=[fx.Flow(label='el', bus=elec_bus, size=100)],
- outputs=[fx.Flow(label='heat', bus=heat_bus, size=350)],
- conversion_factors=[{'el': 3.5, 'heat': 1}],
-)
-```
+## Time-Varying Efficiency
-### Time-Varying COP
+Pass a list for time-dependent conversion:
```python
-cop = [3.0, 3.2, 3.5, 4.0, 3.8, ...] # Varies by timestep
+cop = [3.0, 3.2, 3.5, 4.0, 3.8, ...] # Varies with ambient temperature
hp = fx.LinearConverter(
- label='hp',
- inputs=[fx.Flow(label='el', bus=elec_bus, size=100)],
- outputs=[fx.Flow(label='heat', bus=heat_bus, size=400)],
+ ...,
conversion_factors=[{'el': cop, 'heat': 1}],
)
```
-## Specialized Components
+---
-flixOpt provides convenience classes:
+## Convenience Classes
```python
# Boiler
@@ -153,64 +90,59 @@ hp = fx.linear_converters.HeatPump(
# CHP
chp = fx.linear_converters.CHP(
label='chp', eta_el=0.35, eta_th=0.50,
- P_el=fx.Flow(label='el', bus=elec_bus, size=35),
- Q_th=fx.Flow(label='heat', bus=heat_bus, size=50),
- Q_fu=fx.Flow(label='fuel', bus=gas_bus, size=100),
+ P_el=fx.Flow(...), Q_th=fx.Flow(...), Q_fu=fx.Flow(...),
)
```
-## Advanced Features
+---
-=== "Piecewise Linear"
+## Adding Features
- For non-linear efficiency curves:
+=== "On/Off Operation"
```python
- from flixopt import Piecewise, Piece, PiecewiseConversion
-
- curve = Piecewise([
- Piece(start=(0, 0), end=(50, 40)), # 80% at low load
- Piece(start=(50, 40), end=(100, 90)), # 90% at high load
- ])
-
- boiler = fx.LinearConverter(
+ gen = fx.LinearConverter(
...,
- piecewise_conversion=PiecewiseConversion(
- origin_flow='gas',
- piecewise_shares={'heat': curve},
+ status_parameters=fx.StatusParameters(
+ effects_per_startup={'costs': 1000},
+ min_uptime=4,
),
)
```
- See [Piecewise](../features/Piecewise.md).
+ See [StatusParameters](../features/StatusParameters.md).
-=== "On/Off Operation"
+=== "Piecewise Efficiency"
- Add startup costs and minimum run times:
+ For non-linear efficiency curves:
```python
- gen = fx.LinearConverter(
- label='gen',
- inputs=[fx.Flow(label='fuel', bus=fuel_bus, size=100)],
- outputs=[fx.Flow(label='el', bus=elec_bus, size=40, relative_minimum=0.4)],
- conversion_factors=[{'fuel': 0.4, 'el': 1}],
- status_parameters=fx.StatusParameters(
- effects_per_startup={'costs': 1000},
- min_uptime=4,
+ curve = fx.Piecewise([
+ fx.Piece(start=(0, 0), end=(50, 40)), # 80% at low load
+ fx.Piece(start=(50, 40), end=(100, 90)), # 100% at high load
+ ])
+
+ boiler = fx.LinearConverter(
+ ...,
+ piecewise_conversion=fx.PiecewiseConversion(
+ origin_flow='gas',
+ piecewise_shares={'heat': curve},
),
)
```
- See [StatusParameters](../features/StatusParameters.md).
+ See [Piecewise](../features/Piecewise.md).
+
+---
-## Implementation Details
+## Reference
-- **Component Class:** [`LinearConverter`][flixopt.components.LinearConverter]
-- **Model Class:** [`LinearConverterModel`][flixopt.components.LinearConverterModel]
+The converter creates **constraints** linking flows, not new variables.
-## See Also
+| Parameter | Python | Description |
+|-----------|--------|-------------|
+| Inputs | `inputs` | List of input Flows |
+| Outputs | `outputs` | List of output Flows |
+| Ratios | `conversion_factors` | List of factor dicts |
-- [Flow](Flow.md) — Input and output flows
-- [Bus](Bus.md) — Where converters connect
-- [Piecewise](../features/Piecewise.md) — Non-linear efficiency
-- [StatusParameters](../features/StatusParameters.md) — Binary operation
+**Classes:** [`LinearConverter`][flixopt.components.LinearConverter], [`LinearConverterModel`][flixopt.components.LinearConverterModel]
diff --git a/docs/user-guide/mathematical-notation/elements/Storage.md b/docs/user-guide/mathematical-notation/elements/Storage.md
index 841dcebed..7dd5a3e32 100644
--- a/docs/user-guide/mathematical-notation/elements/Storage.md
+++ b/docs/user-guide/mathematical-notation/elements/Storage.md
@@ -1,185 +1,121 @@
# Storage
-A Storage component accumulates energy or material over time, allowing you to decouple when you produce from when you consume.
+A Storage accumulates energy over time — charge now, discharge later.
-!!! example "Real-world examples"
- - **Battery** — store electricity when cheap, discharge when expensive
- - **Thermal tank** — buffer heat production from demand
- - **Pumped hydro** — large-scale electricity storage
-
-## Core Concept: Charge State Over Time
-
-A storage has a **charge state** $c(t)$ that evolves based on charging and discharging:
+## Basic: Charge Dynamics
$$
-c(t_{i+1}) = c(t_i) \cdot (1 - \dot{c}_{loss})^{\Delta t} + p_{in}(t_i) \cdot \Delta t \cdot \eta_{in} - p_{out}(t_i) \cdot \Delta t / \eta_{out}
+c(t+1) = c(t) \cdot (1 - \dot{c}_{loss})^{\Delta t} + p_{in}(t) \cdot \Delta t \cdot \eta_{in} - p_{out}(t) \cdot \Delta t / \eta_{out}
$$
-Where:
+```python
+battery = fx.Storage(
+ label='battery',
+ charging=fx.Flow(label='charge', bus=elec_bus, size=50),
+ discharging=fx.Flow(label='discharge', bus=elec_bus, size=50),
+ capacity_in_flow_hours=200, # 200 kWh
+ eta_charge=0.95,
+ eta_discharge=0.95,
+)
+# Round-trip efficiency: 95% × 95% = 90.25%
+```
-| Symbol | Meaning |
-|--------|---------|
-| $c(t)$ | Charge state (energy stored) |
-| $p_{in}(t)$ | Charging power |
-| $p_{out}(t)$ | Discharging power |
-| $\eta_{in}$ | Charging efficiency |
-| $\eta_{out}$ | Discharging efficiency |
-| $\dot{c}_{loss}$ | Self-discharge rate per hour |
+---
## Charge State Bounds
$$
-C \cdot c_{rel}^{min}(t) \leq c(t) \leq C \cdot c_{rel}^{max}(t)
+C \cdot c_{rel}^{min} \leq c(t) \leq C \cdot c_{rel}^{max}
$$
-Where $C$ is the storage capacity.
-
-!!! example "Battery with 20-80% SOC range"
- $c_{rel}^{min} = 0.2$, $c_{rel}^{max} = 0.8$ → Effective range: 20-80% of capacity
+```python
+fx.Storage(...,
+ relative_minimum_charge_state=0.2, # Min 20% SOC
+ relative_maximum_charge_state=0.8, # Max 80% SOC
+)
+```
-## Initial and Final Conditions
+---
-=== "Fixed Initial"
+## Initial & Final Conditions
- $$
- c(t_0) = c_0
- $$
+=== "Fixed Start"
- Storage starts with a specified charge.
+ ```python
+ fx.Storage(..., initial_charge_state=100) # Start at 100 kWh
+ ```
=== "Cyclic"
- $$
- c(t_0) = c(t_{end})
- $$
+ Must end where it started (prevents "cheating"):
- Storage must end where it started. Use `initial_charge_state='equals_final'`.
-
- Prevents the optimizer from "cheating" by draining storage without replenishing.
+ ```python
+ fx.Storage(..., initial_charge_state='equals_final')
+ ```
=== "Final Bounds"
- $$
- c_{final}^{min} \leq c(t_{end}) \leq c_{final}^{max}
- $$
+ ```python
+ fx.Storage(...,
+ minimal_final_charge_state=50,
+ maximal_final_charge_state=150,
+ )
+ ```
- Constrain the final charge state.
+---
-## Variables
+## Adding Features
-| Symbol | Python Name | Description | When Created |
-|--------|-------------|-------------|--------------|
-| $c(t)$ | `charge_state` | Charge at timestep $t$ | Always |
-| $p_{in}(t)$ | (from `charging` Flow) | Charging power | Always |
-| $p_{out}(t)$ | (from `discharging` Flow) | Discharging power | Always |
-| $C$ | `size` | Capacity (variable) | `capacity_in_flow_hours` is `InvestParameters` |
+=== "Self-Discharge"
-## Parameters
+ ```python
+ tank = fx.Storage(...,
+ relative_loss_per_hour=0.02, # 2%/hour loss
+ )
+ ```
-| Symbol | Python Name | Description | Default |
-|--------|-------------|-------------|---------|
-| $C$ | `capacity_in_flow_hours` | Storage capacity | Required |
-| $\eta_{in}$ | `eta_charge` | Charging efficiency | 1.0 |
-| $\eta_{out}$ | `eta_discharge` | Discharging efficiency | 1.0 |
-| $\dot{c}_{loss}$ | `relative_loss_per_hour` | Self-discharge rate | 0 |
-| $c_0$ | `initial_charge_state` | Starting charge | 0 |
-| $c_{rel}^{min}(t)$ | `relative_minimum_charge_state` | Min SOC fraction | 0 |
-| $c_{rel}^{max}(t)$ | `relative_maximum_charge_state` | Max SOC fraction | 1 |
-| $c_{final}^{min}$ | `minimal_final_charge_state` | Min final charge | None |
-| $c_{final}^{max}$ | `maximal_final_charge_state` | Max final charge | None |
+=== "Variable Capacity"
-## Usage Examples
+ Optimize storage size:
-### Basic Battery
+ ```python
+ battery = fx.Storage(...,
+ capacity_in_flow_hours=fx.InvestParameters(
+ minimum_size=0,
+ maximum_size=1000,
+ specific_effects={'costs': 200}, # €/kWh
+ ),
+ )
+ ```
-```python
-battery = fx.Storage(
- label='battery',
- charging=fx.Flow(label='charge', bus=elec_bus, size=50),
- discharging=fx.Flow(label='discharge', bus=elec_bus, size=50),
- capacity_in_flow_hours=200, # 200 kWh
- initial_charge_state=100, # Start at 50% SOC
- eta_charge=0.95,
- eta_discharge=0.95,
-)
-```
+=== "Asymmetric Power"
-Round-trip efficiency: $0.95 \times 0.95 = 90.25\%$
+ Different charge/discharge rates:
-### Thermal Storage with Losses
+ ```python
+ fx.Storage(
+ charging=fx.Flow(..., size=100), # 100 MW pump
+ discharging=fx.Flow(..., size=120), # 120 MW turbine
+ ...
+ )
+ ```
-```python
-tank = fx.Storage(
- label='tank',
- charging=fx.Flow(label='in', bus=heat_bus, size=100),
- discharging=fx.Flow(label='out', bus=heat_bus, size=100),
- capacity_in_flow_hours=500,
- relative_loss_per_hour=0.02, # 2%/hour heat loss
- eta_charge=0.98,
- eta_discharge=0.98,
-)
-```
-
-### Cyclic (Typical Day)
-
-```python
-buffer = fx.Storage(
- label='buffer',
- charging=fx.Flow(label='in', bus=bus, size=100),
- discharging=fx.Flow(label='out', bus=bus, size=100),
- capacity_in_flow_hours=400,
- initial_charge_state='equals_final', # Must end where started
-)
-```
-
-### Investment Decision
-
-```python
-battery = fx.Storage(
- label='battery',
- charging=fx.Flow(label='charge', bus=elec_bus, size=100),
- discharging=fx.Flow(label='discharge', bus=elec_bus, size=100),
- capacity_in_flow_hours=fx.InvestParameters(
- minimum_size=0,
- maximum_size=1000,
- specific_effects={'costs': 200}, # €200/kWh
- ),
- eta_charge=0.92,
- eta_discharge=0.92,
-)
-```
-
-### Different Charge/Discharge Power
-
-```python
-pumped_hydro = fx.Storage(
- label='hydro',
- charging=fx.Flow(label='pump', bus=elec_bus, size=100), # 100 MW
- discharging=fx.Flow(label='turbine', bus=elec_bus, size=120), # 120 MW
- capacity_in_flow_hours=10000,
- eta_charge=0.85,
- eta_discharge=0.90,
-)
-```
-
-## Simultaneous Charge/Discharge
-
-By default, storage cannot charge and discharge simultaneously:
-
-```python
-storage = fx.Storage(
- ...,
- prevent_simultaneous_charge_and_discharge=True, # Default
-)
-```
+---
-## Implementation Details
+## Reference
-- **Component Class:** [`Storage`][flixopt.components.Storage]
-- **Model Class:** [`StorageModel`][flixopt.components.StorageModel]
+| Variable | Description |
+|----------|-------------|
+| $c(t)$ | Charge state |
+| $p_{in}(t)$ | Charging power (from `charging` flow) |
+| $p_{out}(t)$ | Discharging power (from `discharging` flow) |
-## See Also
+| Parameter | Python | Default |
+|-----------|--------|---------|
+| Capacity | `capacity_in_flow_hours` | required |
+| Charge efficiency | `eta_charge` | 1.0 |
+| Discharge efficiency | `eta_discharge` | 1.0 |
+| Self-discharge | `relative_loss_per_hour` | 0 |
+| Initial charge | `initial_charge_state` | 0 |
-- [Flow](Flow.md) — Charging and discharging flows
-- [Bus](Bus.md) — Where storage connects
-- [InvestParameters](../features/InvestParameters.md) — Capacity optimization
+**Classes:** [`Storage`][flixopt.components.Storage], [`StorageModel`][flixopt.components.StorageModel]
diff --git a/docs/user-guide/mathematical-notation/features/InvestParameters.md b/docs/user-guide/mathematical-notation/features/InvestParameters.md
index d3fd81a21..bf31332da 100644
--- a/docs/user-guide/mathematical-notation/features/InvestParameters.md
+++ b/docs/user-guide/mathematical-notation/features/InvestParameters.md
@@ -1,197 +1,131 @@
# InvestParameters
-InvestParameters enable investment decisions — should we build this? If so, how big?
+InvestParameters make capacity a decision variable — should we build this? How big?
-!!! example "Real-world examples"
- - **Solar PV** — Install 100 kW system or not (binary)
- - **Battery** — Choose capacity between 10-1000 kWh (continuous)
- - **Boiler replacement** — Upgrade or pay demolition costs
+## Basic: Size as Variable
-## Core Concept: Size as a Variable
-
-When `size` is an `InvestParameters`, the capacity $P$ becomes an optimization variable instead of a fixed parameter.
-
-=== "Binary (Fixed Size)"
-
- Build a predefined size or nothing:
-
- $$
- P = s_{inv} \cdot P_{fixed}
- $$
-
- Where $s_{inv} \in \{0, 1\}$ is the binary investment decision.
-
- - $s_{inv} = 0$: Don't invest, $P = 0$
- - $s_{inv} = 1$: Invest, $P = P_{fixed}$
-
-=== "Continuous (Size Range)"
-
- Choose size within bounds:
-
- $$
- s_{inv} \cdot P^{min} \leq P \leq s_{inv} \cdot P^{max}
- $$
-
- - $s_{inv} = 0$: Don't invest, $P = 0$
- - $s_{inv} = 1$: Invest, $P \in [P^{min}, P^{max}]$
-
-=== "Mandatory"
-
- Must invest, only choose size:
-
- $$
- P^{min} \leq P \leq P^{max}
- $$
-
- No binary variable — investment is required.
-
-## Investment Effects (Costs)
-
-=== "Fixed Effects"
-
- One-time costs if investing (permits, grid connection):
-
- $$
- E_{e,fix} = s_{inv} \cdot c_{fix}
- $$
+$$
+P^{min} \leq P \leq P^{max}
+$$
-=== "Specific Effects"
+```python
+battery = fx.Storage(
+ ...,
+ capacity_in_flow_hours=fx.InvestParameters(
+ minimum_size=10,
+ maximum_size=1000,
+ specific_effects={'costs': 600}, # €600/kWh
+ ),
+)
+```
- Costs proportional to size (€/kW):
+---
- $$
- E_{e,spec} = P \cdot c_{spec}
- $$
+## Investment Modes
-=== "Retirement Effects"
+=== "Continuous"
- Costs if NOT investing (demolition):
+ Choose size within range:
- $$
- E_{e,ret} = (1 - s_{inv}) \cdot c_{ret}
- $$
+ ```python
+ fx.InvestParameters(
+ minimum_size=10,
+ maximum_size=1000,
+ )
+ # → 10 ≤ P ≤ 1000
+ ```
-=== "Piecewise Effects"
+=== "Binary"
- Non-linear costs (economies of scale):
+ Fixed size or nothing:
- $$
- E_{e,pw} = f_{pw}(P)
- $$
+ ```python
+ fx.InvestParameters(
+ fixed_size=100, # 100 kW or 0
+ )
+ # → P ∈ {0, 100}
+ ```
- See [Piecewise](Piecewise.md) for details.
+=== "Optional"
-**Total investment effects:**
+ Can choose not to invest ($P = 0$ allowed):
-$$
-E_{e,inv} = E_{e,fix} + E_{e,spec} + E_{e,ret} + E_{e,pw}
-$$
+ ```python
+ fx.InvestParameters(
+ minimum_size=0, # Zero allowed
+ maximum_size=1000,
+ )
+ ```
-## Variables
+=== "Mandatory"
-| Symbol | Python Name | Description | When Created |
-|--------|-------------|-------------|--------------|
-| $P$ | `size` | Investment size | Always |
-| $s_{inv}$ | `invested` | Binary decision | `mandatory=False` |
+ Must invest (no binary variable):
-## Parameters
+ ```python
+ fx.InvestParameters(
+ minimum_size=50,
+ maximum_size=200,
+ mandatory=True,
+ )
+ ```
-| Symbol | Python Name | Description | Default |
-|--------|-------------|-------------|---------|
-| $P_{fixed}$ | `fixed_size` | Fixed size (binary decision) | None |
-| $P^{min}$ | `minimum_size` | Minimum size if investing | ε |
-| $P^{max}$ | `maximum_size` | Maximum size | Big M |
-| $c_{fix}$ | `effects_of_investment` | Fixed effects | None |
-| $c_{spec}$ | `effects_of_investment_per_size` | Per-unit effects | None |
-| $c_{ret}$ | `effects_of_retirement` | Effects if not investing | None |
-| - | `mandatory` | Force investment | False |
-| - | `piecewise_effects_of_investment` | Non-linear effects | None |
+---
-## Usage Examples
+## Investment Effects
-### Binary Investment (Solar)
+=== "Per-Size Cost"
-```python
-solar = fx.Flow(
- label='solar',
- bus=elec_bus,
- size=fx.InvestParameters(
- fixed_size=100, # 100 kW or nothing
- effects_of_investment={'costs': 25000}, # Fixed: €25k
- effects_of_investment_per_size={'costs': 1200}, # €1200/kW
- ),
-)
-```
+ Cost proportional to capacity (€/kW):
-### Continuous Sizing (Battery)
+ $E = P \cdot c_{spec}$
-```python
-battery = fx.Storage(
- ...,
- capacity_in_flow_hours=fx.InvestParameters(
- minimum_size=10, # At least 10 kWh
- maximum_size=1000, # At most 1 MWh
- effects_of_investment={'costs': 5000}, # Grid connection
- effects_of_investment_per_size={'costs': 600}, # €600/kWh
- ),
-)
-```
+ ```python
+ fx.InvestParameters(
+ specific_effects={'costs': 1200}, # €1200/kW
+ )
+ ```
-### With Retirement Costs
+=== "Fixed Cost"
-```python
-boiler = fx.LinearConverter(
- ...,
- inputs=[fx.Flow(
- label='gas',
- bus=gas_bus,
- size=fx.InvestParameters(
- minimum_size=50,
- maximum_size=200,
- effects_of_investment_per_size={'costs': 400},
- effects_of_retirement={'costs': 8000}, # Demolition if not replaced
- ),
- )],
- ...
-)
-```
+ One-time cost if investing:
-### Mandatory Investment
+ $E = s_{inv} \cdot c_{fix}$
-```python
-upgrade = fx.Flow(
- label='grid',
- bus=elec_bus,
- size=fx.InvestParameters(
- minimum_size=100,
- maximum_size=500,
- mandatory=True, # Must invest
- effects_of_investment_per_size={'costs': 1000},
- ),
-)
-```
+ ```python
+ fx.InvestParameters(
+ effects_of_investment={'costs': 25000}, # €25k
+ )
+ ```
-## Cost Annualization
+=== "Retirement Cost"
-Investment costs must be annualized to match the optimization time horizon:
+ Cost if NOT investing:
-$$
-c_{annual} = \frac{c_{capital} \cdot r}{1 - (1 + r)^{-n}}
-$$
+ $E = (1 - s_{inv}) \cdot c_{ret}$
-Where $r$ is the discount rate and $n$ is the lifetime (years).
+ ```python
+ fx.InvestParameters(
+ effects_of_retirement={'costs': 8000}, # Demolition
+ )
+ ```
-!!! example "€1M equipment, 20 years, 5% discount"
- $c_{annual} = \frac{1{,}000{,}000 \cdot 0.05}{1 - 1.05^{-20}} \approx €80{,}243/year$
+---
-## Implementation Details
+## Reference
-- **Feature Class:** [`InvestParameters`][flixopt.interface.InvestParameters]
-- **Model Class:** [`InvestmentModel`][flixopt.features.InvestmentModel]
+| Variable | Description |
+|----------|-------------|
+| $P$ | Investment size |
+| $s_{inv}$ | Binary decision (0=no, 1=yes) |
-## See Also
+| Parameter | Python | Description |
+|-----------|--------|-------------|
+| Min size | `minimum_size` | Lower bound |
+| Max size | `maximum_size` | Upper bound |
+| Fixed size | `fixed_size` | Binary: this or nothing |
+| Per-size cost | `specific_effects` | €/unit |
+| Fixed cost | `effects_of_investment` | One-time if investing |
+| Retirement | `effects_of_retirement` | Cost if not investing |
+| Force invest | `mandatory` | No binary variable |
-- [Flow](../elements/Flow.md) — Using investment in flows
-- [Storage](../elements/Storage.md) — Storage capacity investment
-- [Piecewise](Piecewise.md) — Non-linear cost structures
-- [Effects & Objective](../effects-penalty-objective.md) — How costs are tracked
+**Classes:** [`InvestParameters`][flixopt.interface.InvestParameters], [`InvestmentModel`][flixopt.features.InvestmentModel]
diff --git a/docs/user-guide/mathematical-notation/features/Piecewise.md b/docs/user-guide/mathematical-notation/features/Piecewise.md
index 6010a2553..90f8504ae 100644
--- a/docs/user-guide/mathematical-notation/features/Piecewise.md
+++ b/docs/user-guide/mathematical-notation/features/Piecewise.md
@@ -1,232 +1,124 @@
# Piecewise
-Piecewise linearization models non-linear relationships using connected linear segments — keeping the problem linear while capturing complex behavior.
+Piecewise linearization models non-linear relationships with connected linear segments.
-!!! example "Real-world examples"
- - **Part-load efficiency** — Boiler efficiency varies with load
- - **Economies of scale** — Cost per kW decreases with size
- - **Forbidden regions** — Turbine can't operate between 0-40%
-
-## Core Concept: Linear Segments
-
-A piecewise function approximates a curve using linear segments (pieces). Each piece is defined by a start point and end point. The optimizer chooses a point along exactly one piece.
+## Basic: Linear Segments
```plotly
{
"data": [
- {
- "x": [0, 50],
- "y": [0, 45],
- "mode": "lines+markers",
- "name": "Piece 1",
- "line": {"color": "#009688", "width": 3},
- "marker": {"size": 10}
- },
- {
- "x": [50, 100],
- "y": [45, 90],
- "mode": "lines+markers",
- "name": "Piece 2",
- "line": {"color": "#00bcd4", "width": 3},
- "marker": {"size": 10}
- }
+ {"x": [0, 50], "y": [0, 45], "mode": "lines+markers", "name": "Piece 1", "line": {"color": "#009688", "width": 3}, "marker": {"size": 10}},
+ {"x": [50, 100], "y": [45, 90], "mode": "lines+markers", "name": "Piece 2", "line": {"color": "#00bcd4", "width": 3}, "marker": {"size": 10}}
],
- "layout": {
- "xaxis": {"title": "Input", "range": [0, 105]},
- "yaxis": {"title": "Output", "range": [0, 95]},
- "showlegend": true,
- "legend": {"x": 0.02, "y": 0.98},
- "margin": {"l": 60, "r": 20, "t": 20, "b": 50},
- "height": 300
- }
+ "layout": {"xaxis": {"title": "Input"}, "yaxis": {"title": "Output"}, "height": 250, "margin": {"t": 20}}
}
```
-- **Piece 1**: from (0, 0) to (50, 45) — slope = 0.9
-- **Piece 2**: from (50, 45) to (100, 90) — slope = 0.9
-
-## Mathematical Formulation
-
-Each piece $k$ has:
-
-- Start point: $(x_k^{start}, y_k^{start})$
-- End point: $(x_k^{end}, y_k^{end})$
-
-The value is a weighted combination:
-
-$$
-x = \lambda_0 \cdot x^{start} + \lambda_1 \cdot x^{end}
-$$
-
-$$
-y = \lambda_0 \cdot y^{start} + \lambda_1 \cdot y^{end}
-$$
-
-Where $\lambda_0, \lambda_1 \geq 0$ and $\lambda_0 + \lambda_1 = \beta_k$ (piece is active).
-
-=== "Single Piece Active"
+```python
+curve = fx.Piecewise([
+ fx.Piece((0, 0), (50, 45)), # Slope 0.9
+ fx.Piece((50, 45), (100, 90)), # Slope 0.9
+])
+```
- Exactly one piece must be active:
+The optimizer picks a point along exactly one segment.
- $$
- \sum_k \beta_k = 1
- $$
+---
-=== "With Zero Point"
+## Use Cases
- Allow all variables to be zero (equipment off):
+=== "Part-Load Efficiency"
- $$
- \sum_k \beta_k = \beta_{zero}
- $$
+ Boiler: 80% at low load, 92% at high load:
- - $\beta_{zero} = 0$: All off, $x = y = 0$
- - $\beta_{zero} = 1$: One piece active
+ ```python
+ gas_to_heat = fx.Piecewise([
+ fx.Piece((0, 0), (30, 24)), # 80%
+ fx.Piece((30, 24), (100, 92)), # 92%
+ ])
-## Piece Patterns
+ boiler = fx.LinearConverter(
+ ...,
+ piecewise_conversion=fx.PiecewiseConversion(
+ origin_flow='gas',
+ piecewise_shares={'heat': gas_to_heat},
+ ),
+ )
+ ```
-=== "Continuous (Touching)"
+=== "Variable COP"
- Pieces share boundary points — smooth function:
+ Heat pump COP varies with load:
```python
- curve = fx.Piecewise([
- fx.Piece((0, 0), (50, 45)), # Low load
- fx.Piece((50, 45), (100, 90)), # High load (touches at 50)
+ elec_to_heat = fx.Piecewise([
+ fx.Piece((0, 0), (50, 125)), # COP ~2.5
+ fx.Piece((50, 125), (100, 350)), # COP ~4.5
])
- ```
- Operation anywhere from 0-100.
+ hp = fx.LinearConverter(
+ ...,
+ piecewise_conversion=fx.PiecewiseConversion(
+ origin_flow='el',
+ piecewise_shares={'heat': elec_to_heat},
+ ),
+ )
+ ```
-=== "Gap (Forbidden Region)"
+=== "Forbidden Region"
- Non-contiguous pieces — forbidden operating range:
+ Turbine: off or 40-100%, nothing in between:
```python
curve = fx.Piecewise([
- fx.Piece((0, 0), (0, 0)), # Off (point)
- fx.Piece((40, 36), (100, 90)), # Operating (gap: 0-40 forbidden)
+ fx.Piece((0, 0), (0, 0)), # Off (point)
+ fx.Piece((40, 40), (100, 100)), # Operating
])
```
- Must be off (0) or operating (40-100).
-
-=== "Zero Point"
+=== "Economies of Scale"
- Explicitly allow zero without a zero piece:
+ Investment cost decreases with size:
```python
- curve = fx.Piecewise(
- pieces=[
- fx.Piece((10, 9), (50, 45)),
- fx.Piece((50, 45), (100, 90)),
- ],
- zero_point=True, # Can also be completely off
+ fx.InvestParameters(
+ piecewise_effects_of_investment=fx.PiecewiseEffects(
+ piecewise_origin=fx.Piecewise([...]),
+ piecewise_shares={'costs': fx.Piecewise([
+ fx.Piece((0, 0), (100, 80000)), # €800/kWh
+ fx.Piece((100, 80000), (500, 350000)), # €675/kWh
+ ])},
+ ),
)
```
- Either off (0) or operating (10-100).
+---
-## Variables
+## Zero Point
-| Symbol | Python Name | Description | When Created |
-|--------|-------------|-------------|--------------|
-| $\beta_k$ | `beta` | Piece $k$ active | Always |
-| $\lambda_{0,k}$ | `lambda0` | Weight on start point | Always |
-| $\lambda_{1,k}$ | `lambda1` | Weight on end point | Always |
-| $\beta_{zero}$ | `zero_point` | Allow zero | `zero_point=True` |
-
-## Parameters
-
-| Symbol | Python Name | Description |
-|--------|-------------|-------------|
-| $(x_k^{start}, y_k^{start})$ | `Piece.start` | Start point of piece $k$ |
-| $(x_k^{end}, y_k^{end})$ | `Piece.end` | End point of piece $k$ |
-| - | `zero_point` | Allow all variables = 0 |
-
-## Usage Examples
-
-### Variable COP Heat Pump
-
-```python
-# COP varies: 2.5 at low load, 4.0 at high load
-elec_to_heat = fx.Piecewise([
- fx.Piece((0, 0), (50, 125)), # COP ~2.5
- fx.Piece((50, 125), (100, 350)), # COP ~3.5-4.5
-])
-
-hp = fx.LinearConverter(
- label='hp',
- inputs=[fx.Flow(label='el', bus=elec_bus, size=100)],
- outputs=[fx.Flow(label='heat', bus=heat_bus, size=350)],
- piecewise_conversion=fx.PiecewiseConversion(
- origin_flow='el',
- piecewise_shares={'heat': elec_to_heat},
- ),
-)
-```
-
-### Part-Load Efficiency Boiler
+Allow equipment to be completely off without a zero piece:
```python
-# Efficiency: 80% at low load, 92% at high load
-gas_to_heat = fx.Piecewise([
- fx.Piece((0, 0), (30, 24)), # 80% at 0-30%
- fx.Piece((30, 24), (100, 92)), # 92% at 30-100%
-])
-
-boiler = fx.LinearConverter(
- label='boiler',
- inputs=[fx.Flow(label='gas', bus=gas_bus, size=100)],
- outputs=[fx.Flow(label='heat', bus=heat_bus, size=92)],
- piecewise_conversion=fx.PiecewiseConversion(
- origin_flow='gas',
- piecewise_shares={'heat': gas_to_heat},
- ),
+curve = fx.Piecewise(
+ pieces=[
+ fx.Piece((10, 9), (100, 90)), # Operating range 10-100
+ ],
+ zero_point=True, # Can also be off (0, 0)
)
```
-### Economies of Scale (Investment)
-
-```python
-# Cost per kWh decreases with size
-battery = fx.InvestParameters(
- minimum_size=10,
- maximum_size=1000,
- piecewise_effects_of_investment=fx.PiecewiseEffects(
- piecewise_origin=fx.Piecewise([
- fx.Piece((0, 0), (100, 100)),
- fx.Piece((100, 100), (500, 500)),
- fx.Piece((500, 500), (1000, 1000)),
- ]),
- piecewise_shares={
- 'costs': fx.Piecewise([
- fx.Piece((0, 0), (100, 80000)), # €800/kWh
- fx.Piece((100, 80000), (500, 350000)), # €675/kWh avg
- fx.Piece((500, 350000), (1000, 750000)), # €500/kWh
- ])
- },
- ),
-)
-```
-
-### Forbidden Operating Region
-
-```python
-# Turbine: off or 40-100%, not in between
-turbine_curve = fx.Piecewise([
- fx.Piece((0, 0), (0, 0)), # Off
- fx.Piece((40, 40), (100, 100)), # Operating range
-])
-```
+---
-## Implementation Details
+## Reference
-- **Feature Class:** [`Piecewise`][flixopt.interface.Piecewise]
-- **Helper Class:** [`Piece`][flixopt.interface.Piece]
-- **Model Class:** [`PiecewiseModel`][flixopt.features.PiecewiseModel]
+| Variable | Description |
+|----------|-------------|
+| $\beta_k$ | Piece $k$ is active |
+| $\lambda_0, \lambda_1$ | Weights on start/end points |
-## See Also
+| Parameter | Description |
+|-----------|-------------|
+| `Piece(start, end)` | Define segment from start to end point |
+| `zero_point` | Allow all variables = 0 |
-- [LinearConverter](../elements/LinearConverter.md) — Using piecewise conversion
-- [InvestParameters](InvestParameters.md) — Piecewise investment costs
+**Classes:** [`Piecewise`][flixopt.interface.Piecewise], [`Piece`][flixopt.interface.Piece]
diff --git a/docs/user-guide/mathematical-notation/features/StatusParameters.md b/docs/user-guide/mathematical-notation/features/StatusParameters.md
index 89d4fbd6c..67d9ba3a2 100644
--- a/docs/user-guide/mathematical-notation/features/StatusParameters.md
+++ b/docs/user-guide/mathematical-notation/features/StatusParameters.md
@@ -1,173 +1,111 @@
# StatusParameters
-StatusParameters define a binary status variable with operational constraints and effects.
+StatusParameters add on/off behavior to flows — startup costs, minimum run times, cycling limits.
-!!! example "Real-world examples"
- - **Power plant** — Startup costs, minimum run time
- - **Batch reactor** — Must run complete cycles
- - **Chiller** — Maximum operating hours per year
+## Basic: Binary Status
-## Core Concept: Binary Status Variable
+A status variable $s(t) \in \{0, 1\}$ controls whether equipment is active:
-StatusParameters create a binary status variable $s(t) \in \{0, 1\}$:
-
-- $s(t) = 0$: Inactive
-- $s(t) = 1$: Active
-
-!!! note "Connection to continuous variables"
- How this binary status connects to continuous variables (like flow rate) is defined where `status_parameters` is used. See [Flow](../elements/Flow.md) for how flows use the status to modify their bounds.
-
-## State Transitions
+```python
+generator = fx.Flow(
+ label='power', bus=elec_bus, size=100,
+ relative_minimum=0.4, # 40% min when ON
+ status_parameters=fx.StatusParameters(
+ effects_per_startup={'costs': 25000}, # €25k per startup
+ ),
+)
+```
-=== "Startup Detection"
+When $s(t) = 0$: flow is zero. When $s(t) = 1$: flow bounds apply.
- Track when status changes from inactive to active:
+---
- $$
- s^{start}(t) - s^{stop}(t) = s(t) - s(t-1)
- $$
+## Startup Tracking
- Where:
+Detect transitions: $s^{start}(t) - s^{stop}(t) = s(t) - s(t-1)$
- - $s^{start}(t) = 1$ when starting up (0 → 1)
- - $s^{stop}(t) = 1$ when shutting down (1 → 0)
+=== "Startup Costs"
-=== "Startup Effects"
+ ```python
+ fx.StatusParameters(
+ effects_per_startup={'costs': 25000},
+ )
+ ```
- Effects incurred each time equipment starts:
+=== "Running Costs"
- $$
- E_{startup} = \sum_t s^{start}(t) \cdot c_{startup}
- $$
+ ```python
+ fx.StatusParameters(
+ effects_per_active_hour={'costs': 100}, # €/h while on
+ )
+ ```
-=== "Active Hour Effects"
+=== "Startup Limit"
- Effects while status is active:
+ ```python
+ fx.StatusParameters(
+ startup_limit=20, # Max 20 starts per period
+ )
+ ```
- $$
- E_{active} = \sum_t s(t) \cdot \Delta t \cdot c_{active}
- $$
+---
## Duration Constraints
=== "Min Uptime"
- Once active, must stay active for minimum duration:
+ Once on, must stay on:
- $$
- s^{start}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} s(j) \cdot \Delta t \geq T_{up}^{min}
- $$
+ ```python
+ fx.StatusParameters(min_uptime=8) # 8 hours minimum
+ ```
=== "Min Downtime"
- Once inactive, must stay inactive for minimum duration:
+ Once off, must stay off:
- $$
- s^{stop}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} (1-s(j)) \cdot \Delta t \geq T_{down}^{min}
- $$
+ ```python
+ fx.StatusParameters(min_downtime=4) # 4 hours cooling
+ ```
=== "Max Uptime"
- Cannot stay active continuously beyond limit:
-
- $$
- \sum_{j=t}^{t+k} s(j) \cdot \Delta t \leq T_{up}^{max}
- $$
-
-=== "Active Hours"
-
- Constrain total active hours per period:
-
- $$
- H^{min} \leq \sum_t s(t) \cdot \Delta t \leq H^{max}
- $$
-
-=== "Startup Limit"
-
- Limit number of startups per period:
-
- $$
- \sum_t s^{start}(t) \leq N_{start}^{max}
- $$
-
-## Variables
-
-| Symbol | Python Name | Description | When Created |
-|--------|-------------|-------------|--------------|
-| $s(t)$ | `status` | Binary status | Always |
-| $s^{start}(t)$ | `startup` | Startup indicator | Startup effects or constraints |
-| $s^{stop}(t)$ | `shutdown` | Shutdown indicator | Startup effects or constraints |
+ Force shutdown after limit:
-## Parameters
+ ```python
+ fx.StatusParameters(max_uptime=18) # Max 18h continuous
+ ```
-| Symbol | Python Name | Description | Default |
-|--------|-------------|-------------|---------|
-| $c_{startup}$ | `effects_per_startup` | Effects per startup | None |
-| $c_{active}$ | `effects_per_active_hour` | Effects while active | None |
-| $T_{up}^{min}$ | `min_uptime` | Min consecutive uptime | None |
-| $T_{up}^{max}$ | `max_uptime` | Max consecutive uptime | None |
-| $T_{down}^{min}$ | `min_downtime` | Min consecutive downtime | None |
-| $T_{down}^{max}$ | `max_downtime` | Max consecutive downtime | None |
-| $H^{min}$ | `active_hours_min` | Min total active hours | None |
-| $H^{max}$ | `active_hours_max` | Max total active hours | None |
-| $N_{start}^{max}$ | `startup_limit` | Max startups | None |
+=== "Total Hours"
-## Usage Examples
+ Limit total operating hours:
-### Power Plant with Startup Costs
+ ```python
+ fx.StatusParameters(
+ active_hours_min=2000,
+ active_hours_max=5000,
+ )
+ ```
-```python
-generator = fx.Flow(
- label='power',
- bus=elec_bus,
- size=100,
- relative_minimum=0.4, # 40% min when active
- status_parameters=fx.StatusParameters(
- effects_per_startup={'costs': 25000}, # €25k startup
- min_uptime=8, # Must run 8+ hours
- min_downtime=4, # Must stay off 4+ hours
- ),
-)
-```
-
-### Batch Process with Cycle Limits
-
-```python
-reactor = fx.Flow(
- label='output',
- bus=prod_bus,
- size=50,
- status_parameters=fx.StatusParameters(
- effects_per_startup={'costs': 1500},
- effects_per_active_hour={'costs': 200},
- min_uptime=12, # 12h batch
- startup_limit=20, # Max 20 batches
- ),
-)
-```
-
-### HVAC with Operating Limits
-
-```python
-chiller = fx.Flow(
- label='cooling',
- bus=cool_bus,
- size=500,
- status_parameters=fx.StatusParameters(
- active_hours_min=2000, # Min 2000h/year
- active_hours_max=5000, # Max 5000h/year
- max_uptime=18, # Max 18h continuous
- ),
-)
-```
+---
-## Implementation Details
+## Reference
-- **Feature Class:** [`StatusParameters`][flixopt.interface.StatusParameters]
-- **Model Class:** [`StatusModel`][flixopt.features.StatusModel]
+| Variable | Description |
+|----------|-------------|
+| $s(t)$ | Binary status (0=off, 1=on) |
+| $s^{start}(t)$ | Startup indicator |
+| $s^{stop}(t)$ | Shutdown indicator |
-## See Also
+| Parameter | Python | Description |
+|-----------|--------|-------------|
+| Startup cost | `effects_per_startup` | Per-startup effects |
+| Running cost | `effects_per_active_hour` | Per-hour effects |
+| Min on | `min_uptime` | Min consecutive hours on |
+| Min off | `min_downtime` | Min consecutive hours off |
+| Max on | `max_uptime` | Max consecutive hours on |
+| Total min | `active_hours_min` | Min total hours |
+| Total max | `active_hours_max` | Max total hours |
+| Max starts | `startup_limit` | Max startups per period |
-- [Flow](../elements/Flow.md) — How flows use status
-- [InvestParameters](InvestParameters.md) — Combining with investment
-- [Effects & Objective](../effects-penalty-objective.md) — How effects are tracked
+**Classes:** [`StatusParameters`][flixopt.interface.StatusParameters], [`StatusModel`][flixopt.features.StatusModel]
From 5d75395c46bf4c70c6b4e0732df34fb8a06ea251 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 03:57:28 +0100
Subject: [PATCH 65/79] Improve LinearConverter.md
---
.../mathematical-notation/elements/LinearConverter.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/elements/LinearConverter.md b/docs/user-guide/mathematical-notation/elements/LinearConverter.md
index e5a16cf3b..961ae0a7d 100644
--- a/docs/user-guide/mathematical-notation/elements/LinearConverter.md
+++ b/docs/user-guide/mathematical-notation/elements/LinearConverter.md
@@ -60,7 +60,7 @@ $$
Pass a list for time-dependent conversion:
```python
-cop = [3.0, 3.2, 3.5, 4.0, 3.8, ...] # Varies with ambient temperature
+cop = np.array([3.0, 3.2, 3.5, 4.0, 3.8, ...]) # Varies with ambient temperature
hp = fx.LinearConverter(
...,
@@ -98,7 +98,9 @@ chp = fx.linear_converters.CHP(
## Adding Features
-=== "On/Off Operation"
+=== "Status"
+
+ A component is active when any of its flows is non-zero. Add startup costs, minimum run times:
```python
gen = fx.LinearConverter(
From 80e681dc005099fdbba5adf8b268da987ba01fb8 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 04:00:45 +0100
Subject: [PATCH 66/79] Improve Flow.md
---
.../mathematical-notation/elements/Flow.md | 33 ++++++++++---------
1 file changed, 17 insertions(+), 16 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md
index 50896887a..8bf4f8817 100644
--- a/docs/user-guide/mathematical-notation/elements/Flow.md
+++ b/docs/user-guide/mathematical-notation/elements/Flow.md
@@ -23,20 +23,7 @@ heat = fx.Flow(label='heat', bus=heat_bus, size=100, relative_minimum=0.3)
## Adding Features
-=== "Fixed Profile"
-
- Lock the flow to a time series (demands, renewables):
-
- $p(t) = P \cdot \pi(t)$
-
- ```python
- demand = fx.Flow(
- label='demand', bus=heat_bus, size=100,
- fixed_relative_profile=[0.5, 0.8, 1.0, 0.6] # π(t)
- )
- ```
-
-=== "On/Off Operation"
+=== "Status"
Allow the flow to be zero with `status_parameters`:
@@ -76,9 +63,9 @@ heat = fx.Flow(label='heat', bus=heat_bus, size=100, relative_minimum=0.3)
See [InvestParameters](../features/InvestParameters.md).
-=== "Flow Costs"
+=== "Flow Effects"
- Add costs per energy (flow hours):
+ Add effects per energy (flow hours) moved:
```python
gas = fx.Flow(
@@ -89,6 +76,20 @@ heat = fx.Flow(label='heat', bus=heat_bus, size=100, relative_minimum=0.3)
Flow hours: $h(t) = p(t) \cdot \Delta t$
+
+=== "Fixed Profile"
+
+ Lock the flow to a time series (demands, renewables):
+
+ $p(t) = P \cdot \pi(t)$
+
+ ```python
+ demand = fx.Flow(
+ label='demand', bus=heat_bus, size=100,
+ fixed_relative_profile=[0.5, 0.8, 1.0, 0.6] # π(t)
+ )
+ ```
+
---
## Optional Constraints
From 671c2264ffce710845c3ec658d6d365a499778be Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 04:05:31 +0100
Subject: [PATCH 67/79] Improve effects-penalty-objective.md
---
.../effects-penalty-objective.md | 508 ++++--------------
1 file changed, 90 insertions(+), 418 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/effects-penalty-objective.md b/docs/user-guide/mathematical-notation/effects-penalty-objective.md
index 6afd34663..4933fe114 100644
--- a/docs/user-guide/mathematical-notation/effects-penalty-objective.md
+++ b/docs/user-guide/mathematical-notation/effects-penalty-objective.md
@@ -1,491 +1,163 @@
# Effects & Objective
-Effects are how you track and optimize metrics in your system. One effect is your **objective** (what you minimize), while others can be **constraints** or just tracked for reporting.
+Effects track metrics (costs, CO₂, energy) and define what you optimize.
-!!! example "Common effects"
- - **Costs** — minimize total costs (objective)
- - **CO₂ emissions** — track or constrain to meet targets
- - **Primary energy** — report for efficiency analysis
- - **Peak power** — constrain maximum grid import
+## Basic: Defining Effects
-## Core Concept: Aggregating Contributions
-
-Every element in your model can contribute to effects. These contributions are aggregated into totals:
-
-$$
-E_e = \sum_{l \in \mathcal{L}} s_{l \rightarrow e}
-$$
-
-Where $s_{l \rightarrow e}$ is the share from element $l$ to effect $e$.
+```python
+costs = fx.Effect(label='costs', unit='€', is_objective=True)
+co2 = fx.Effect(label='co2', unit='kg')
-## The Objective Function
+flow_system.add_elements(costs, co2)
+```
-flixOpt minimizes one effect (plus any penalties):
+One effect is the **objective** (minimized). Others are tracked or constrained.
-$$
-\min \quad E_\Omega + \Phi
-$$
+---
-Where $E_\Omega$ is the objective effect and $\Phi$ is the penalty from bus violations.
+## Temporal vs Periodic
-## Two Types of Effects
+Effects have two components with different time behavior:
=== "Temporal (Operational)"
- Accumulated over timesteps:
+ Accumulated over timesteps — fuel costs, emissions, energy use:
- $$
- E_{e,temp} = \sum_t s_{l \rightarrow e}(t) \cdot \Delta t
- $$
+ $E_{temp} = \sum_t s(t) \cdot \Delta t$
- !!! example "Fuel costs"
- ```python
- gas_flow = fx.Flow(
- label='gas', bus=gas_bus, size=100,
- effects_per_flow_hour={'costs': 50}, # €50/MWh
- )
- ```
- Contribution: $50 \cdot p(t) \cdot \Delta t$
+ ```python
+ gas = fx.Flow(
+ ...,
+ effects_per_flow_hour={'costs': 50}, # €50/MWh
+ )
+ ```
=== "Periodic (Investment)"
- Time-independent — incurred once:
-
- $$
- E_{e,per} = \sum_{inv} P \cdot c_{inv}
- $$
+ Time-independent — incurred once per period:
- !!! example "Battery investment"
- ```python
- capacity=fx.InvestParameters(
- maximum_size=1000,
- specific_effects={'costs': 200}, # €200/kWh
- )
- ```
- Contribution: $200 \cdot C$
+ $E_{per} = P \cdot c_{inv}$
-=== "Total"
-
- $$
- E_e = E_{e,per} + E_{e,temp}
- $$
-
-## Cross-Effects: Linking Metrics
-
-Effects can contribute to each other. This enables carbon pricing, multi-criteria optimization, and complex cost structures.
-
-!!! example "Carbon pricing"
- CO₂ emissions cost €80/tonne:
```python
- co2 = fx.Effect(label='co2', unit='kg')
-
- costs = fx.Effect(
- label='costs',
- unit='€',
- is_objective=True,
- share_from_temporal={'co2': 0.08}, # €0.08/kg = €80/tonne
+ fx.InvestParameters(
+ specific_effects={'costs': 200}, # €200/kW
)
```
- Now CO₂ emissions automatically contribute to costs.
-
-## Effect Constraints
-
-Besides optimizing one effect, you can constrain others:
-
-### Total Limit
-
-```python
-co2 = fx.Effect(
- label='co2',
- unit='kg',
- maximum_total=100_000, # Max 100 tonnes total
-)
-```
-
-$$
-E_{co2,total} \leq 100{,}000
-$$
-
-### Per-Timestep Limit
-
-```python
-peak_power = fx.Effect(
- label='peak_power',
- unit='kW',
- maximum_per_hour=500, # Max 500 kW at any timestep
-)
-```
-
-$$
-E_{peak}(t) \leq 500 \quad \forall t
-$$
-
-### Investment Budget
-Every FlixOpt model includes a special **Penalty Effect** $E_\Phi$ to:
-
-- Prevent infeasible problems
-- Allow introducing a bias without influencing effects, simplifying results analysis
-
-**Key Feature:** Penalty is implemented as a standard Effect (labeled `Penalty`), so you can **add penalty contributions anywhere effects are used**:
-
-```python
-import flixopt as fx
-
-# Add penalty contributions just like any other effect
-status = fx.StatusParameters(
- effects_per_startup={'Penalty': 1} # Add bias against starting this component, without adding costs
-)
-```
-
-```python
-capex = fx.Effect(
- label='capex',
- unit='€',
- maximum_periodic=5_000_000, # €5M investment budget
-)
-```
-**Optionally Define Custom Penalty:**
-Users can define their own Penalty effect with custom properties (unit, constraints, etc.):
+=== "Total"
-$$
-E_{capex,periodic} \leq 5{,}000{,}000
-```python
-# Define custom penalty effect (must use fx.PENALTY_EFFECT_LABEL)
-custom_penalty = fx.Effect(
- fx.PENALTY_EFFECT_LABEL, # Always use this constant: 'Penalty'
- unit='€',
- description='Penalty costs for constraint violations',
- maximum_total=1e6, # Limit total penalty for debugging
-)
-flow_system.add_elements(custom_penalty)
-```
+ $E = E_{per} + E_{temp}$
-If not user-defined, the Penalty effect is automatically created during modeling with default settings.
-
-**Periodic penalty shares** (time-independent):
-$$ \label{eq:Penalty_periodic}
-E_{\Phi, \text{per}} = \sum_{l \in \mathcal{L}} s_{l \rightarrow \Phi,\text{per}}
-$$
-
-**Temporal penalty shares** (time-dependent):
-$$ \label{eq:Penalty_temporal}
-E_{\Phi, \text{temp}}(\text{t}_{i}) = \sum_{l \in \mathcal{L}} s_{l \rightarrow \Phi, \text{temp}}(\text{t}_i)
-$$
-
-**Total penalty** (combining both domains):
-$$ \label{eq:Penalty_total}
-E_{\Phi} = E_{\Phi,\text{per}} + \sum_{\text{t}_i \in \mathcal{T}} E_{\Phi, \text{temp}}(\text{t}_{i})
-$$
-
-## Variables
-
-| Symbol | Python Name | Description | When Created |
-|--------|-------------|-------------|--------------|
-| $E_{e,temp}(t)$ | `(temporal)\|total` | Temporal effect at $t$ | Always |
-| $E_{e,per}$ | `(periodic)\|total` | Periodic effect | Always |
-| $E_e$ | `total` | Total effect | Always |
-| $\Phi$ | `penalty` | Sum of penalties | Always |
-- $\mathcal{L}$ is the set of all elements
-- $\mathcal{T}$ is the set of all timesteps
-- $s_{l \rightarrow \Phi, \text{per}}$ is the periodic penalty share from element $l$
-- $s_{l \rightarrow \Phi, \text{temp}}(\text{t}_i)$ is the temporal penalty share from element $l$ at timestep $\text{t}_i$
-
-## Parameters
-**Primary usage:** Penalties occur in [Buses](elements/Bus.md) via the `excess_penalty_per_flow_hour` parameter, which allows nodal imbalances at a high cost, and in time series aggregation to allow period flexibility.
-
-**Key properties:**
-- Penalty shares are added via `add_share_to_effects(name, expressions={fx.PENALTY_EFFECT_LABEL: ...}, target='temporal'/'periodic')`
-- Like other effects, penalty can be constrained (e.g., `maximum_total` for debugging)
-- Results include breakdown: temporal, periodic, and total penalty contributions
-- Penalty is always added to the objective function (cannot be disabled)
-- Access via `flow_system.effects.penalty_effect` or `flow_system.effects[fx.PENALTY_EFFECT_LABEL]`
-- **Scenario weighting**: Penalty is weighted identically to the objective effect—see [Time + Scenario](#time--scenario) for details
-
-| Symbol | Python Name | Description |
-|--------|-------------|-------------|
-| - | `is_objective` | Minimize this effect |
-| - | `is_standard` | Allow shorthand syntax |
-| $E_e^{max}$ | `maximum_total` | Upper bound on total |
-| $E_e^{min}$ | `minimum_total` | Lower bound on total |
-| $E_{e,temp}^{max}(t)$ | `maximum_per_hour` | Upper bound per timestep |
-| $E_{e,per}^{max}$ | `maximum_periodic` | Upper bound on periodic |
-| $r_{x \rightarrow e}$ | `share_from_temporal` | Cross-effect factor (temporal) |
-| $r_{x \rightarrow e}$ | `share_from_periodic` | Cross-effect factor (periodic) |
---
-## Penalty
+## Dimensions & Weights
-Every FlixOpt model includes a special **Penalty Effect** $E_\Phi$ to:
+Effects aggregate across dimensions with weights:
-- Prevent infeasible problems
-- Allow introducing a bias without influencing effects, simplifying results analysis
+=== "Single Period"
-**Key Feature:** Penalty is implemented as a standard Effect (labeled `Penalty`), so you can **add penalty contributions anywhere effects are used**:
+ $$\min \quad E_{per} + \sum_t E_{temp}(t)$$
-```python
-import flixopt as fx
-
-# Add penalty contributions just like any other effect
-status = fx.StatusParameters(
- effects_per_startup={'Penalty': 1} # Add bias against starting this component, without adding costs
-)
-```
-
-**Optionally Define Custom Penalty:**
-Users can define their own Penalty effect with custom properties (unit, constraints, etc.):
+=== "With Scenarios"
-```python
-# Define custom penalty effect (must use fx.PENALTY_EFFECT_LABEL)
-custom_penalty = fx.Effect(
- fx.PENALTY_EFFECT_LABEL, # Always use this constant: 'Penalty'
- unit='€',
- description='Penalty costs for constraint violations',
- maximum_total=1e6, # Limit total penalty for debugging
-)
-flow_system.add_elements(custom_penalty)
-```
+ Scenarios represent uncertainty (weather, prices). Temporal effects are scenario-specific, periodic effects are shared:
-If not user-defined, the Penalty effect is automatically created during modeling with default settings.
+ $$\min \quad E_{per} + \sum_s w_s \cdot \sum_t E_{temp}(t, s)$$
-**Periodic penalty shares** (time-independent):
-$$ \label{eq:Penalty_periodic}
-E_{\Phi, \text{per}} = \sum_{l \in \mathcal{L}} s_{l \rightarrow \Phi,\text{per}}
-$$
+ Investment decided once, operations vary by scenario.
-**Temporal penalty shares** (time-dependent):
-$$ \label{eq:Penalty_temporal}
-E_{\Phi, \text{temp}}(\text{t}_{i}) = \sum_{l \in \mathcal{L}} s_{l \rightarrow \Phi, \text{temp}}(\text{t}_i)
-$$
+=== "With Periods"
-**Total penalty** (combining both domains):
-$$ \label{eq:Penalty_total}
-E_{\Phi} = E_{\Phi,\text{per}} + \sum_{\text{t}_i \in \mathcal{T}} E_{\Phi, \text{temp}}(\text{t}_{i})
-$$
+ Periods represent sequential time blocks (years). Each has independent effects:
-Where:
+ $$\min \quad \sum_y w_y \cdot \left( E_{per}(y) + \sum_t E_{temp}(t, y) \right)$$
-- $\mathcal{L}$ is the set of all elements
-- $\mathcal{T}$ is the set of all timesteps
-- $s_{l \rightarrow \Phi, \text{per}}$ is the periodic penalty share from element $l$
-- $s_{l \rightarrow \Phi, \text{temp}}(\text{t}_i)$ is the temporal penalty share from element $l$ at timestep $\text{t}_i$
+=== "Full (Periods + Scenarios)"
-**Primary usage:** Penalties occur in [Buses](elements/Bus.md) via the `excess_penalty_per_flow_hour` parameter, which allows nodal imbalances at a high cost, and in time series aggregation to allow period flexibility.
+ $$\min \quad \sum_y w_y \cdot \left( E_{per}(y) + \sum_s w_s \cdot \sum_t E_{temp}(t, y, s) \right)$$
-**Key properties:**
-- Penalty shares are added via `add_share_to_effects(name, expressions={fx.PENALTY_EFFECT_LABEL: ...}, target='temporal'/'periodic')`
-- Like other effects, penalty can be constrained (e.g., `maximum_total` for debugging)
-- Results include breakdown: temporal, periodic, and total penalty contributions
-- Penalty is always added to the objective function (cannot be disabled)
-- Access via `flow_system.effects.penalty_effect` or `flow_system.effects[fx.PENALTY_EFFECT_LABEL]`
-- **Scenario weighting**: Penalty is weighted identically to the objective effect—see [Time + Scenario](#time--scenario) for details
+ - $w_y$ — period weight (e.g., discount factor)
+ - $w_s$ — scenario weight (e.g., probability)
---
-## Objective Function
-
-The optimization objective minimizes the chosen effect plus the penalty effect:
+## Constraints on Effects
-$$ \label{eq:Objective}
-\min \left( E_{\Omega} + E_{\Phi} \right)
-$$
+=== "Total Limit"
-Where:
+ ```python
+ co2 = fx.Effect(label='co2', unit='kg', maximum_total=100_000)
+ ```
-- $E_{\Omega}$ is the chosen **objective effect** (see $\eqref{eq:Effect_Total}$)
-- $E_{\Phi}$ is the [penalty effect](#penalty) (see $\eqref{eq:Penalty_total}$)
+ $E_{co2} \leq 100{,}000$
-One effect must be designated as the objective via `is_objective=True`. The penalty effect is automatically created and always added to the objective.
+=== "Per-Timestep Limit"
+ ```python
+ peak = fx.Effect(label='peak', unit='kW', maximum_per_hour=500)
+ ```
-## Usage Examples
+ $E_{peak}(t) \leq 500 \quad \forall t$
-### Basic Cost Minimization
-- $E_{\Omega}$ is the chosen **objective effect** (see $\eqref{eq:Effect_Total}$)
-- $E_{\Phi}$ is the [penalty effect](#penalty) (see $\eqref{eq:Penalty_total}$)
+=== "Periodic Limit"
-```python
-costs = fx.Effect(
- label='costs',
- unit='€',
- is_objective=True,
- is_standard=True, # Allows shorthand: effects_per_flow_hour={'costs': 50}
-)
-One effect must be designated as the objective via `is_objective=True`. The penalty effect is automatically created and always added to the objective.
-
-# Elements contribute via effects_per_flow_hour
-gas_flow = fx.Flow(
- label='gas',
- bus=gas_bus,
- size=100,
- effects_per_flow_hour={'costs': 50},
-)
-```
+ ```python
+ capex = fx.Effect(label='capex', unit='€', maximum_periodic=1_000_000)
+ ```
-### Cost + CO₂ Constraint (ε-Constraint Method)
+ $E_{capex,per} \leq 1{,}000{,}000$
-```python
-costs = fx.Effect(label='costs', unit='€', is_objective=True)
-co2 = fx.Effect(label='co2', unit='kg', maximum_total=50_000)
-
-# Generator contributes to both
-generator_flow = fx.Flow(
- label='power',
- bus=electricity_bus,
- size=100,
- effects_per_flow_hour={
- 'costs': 45, # €45/MWh
- 'co2': 0.4, # 0.4 kg/kWh = 400 kg/MWh
- },
-)
-```
+---
-Minimize costs while staying under 50 tonnes CO₂.
+## Cross-Effects
-### Carbon Pricing (Weighted Sum Method)
+Effects can contribute to each other (e.g., carbon pricing):
```python
co2 = fx.Effect(label='co2', unit='kg')
costs = fx.Effect(
- label='costs',
- unit='€',
- is_objective=True,
- share_from_temporal={'co2': 0.08}, # €80/tonne
-)
-
-# Generator only specifies CO₂
-generator_flow = fx.Flow(
- label='power',
- bus=electricity_bus,
- size=100,
- effects_per_flow_hour={'co2': 0.4},
+ label='costs', unit='€', is_objective=True,
+ share_from_temporal={'co2': 0.08}, # €0.08/kg = €80/tonne
)
```
-$$
-\min \quad E_{\Omega} + E_{\Phi} = \sum_{\text{t}_i \in \mathcal{T}} E_{\Omega,\text{temp}}(\text{t}_i) + E_{\Omega,\text{per}} + E_{\Phi,\text{per}} + \sum_{\text{t}_i \in \mathcal{T}} E_{\Phi,\text{temp}}(\text{t}_i)
-$$
-
-CO₂ automatically converted to costs. No need to specify cost contribution separately.
-Where:
-- Temporal effects sum over time: $\sum_{\text{t}_i} E_{\Omega,\text{temp}}(\text{t}_i)$ and $\sum_{\text{t}_i} E_{\Phi,\text{temp}}(\text{t}_i)$
-- Periodic effects are constant: $E_{\Omega,\text{per}}$ and $E_{\Phi,\text{per}}$
-
-### Peak Power Constraint
-
-```python
-peak = fx.Effect(
- label='grid_peak',
- unit='kW',
- maximum_per_hour=500,
-)
-grid_import = fx.Flow(
- label='grid',
- bus=electricity_bus,
- size=1000,
- effects_per_flow_hour={'grid_peak': 1}, # 1 kW per kW flow
-)
-```
-$$
-\min \quad \sum_{s \in \mathcal{S}} w_s \cdot \left( E_{\Omega}(s) + E_{\Phi}(s) \right)
-$$
+CO₂ emissions automatically add to costs.
-Grid import limited to 500 kW at any timestep.
-Where:
+---
-- $\mathcal{S}$ is the set of scenarios
-- $w_s$ is the weight for scenario $s$ (typically scenario probability)
-- Periodic effects are **shared across scenarios**: $E_{\Omega,\text{per}}$ and $E_{\Phi,\text{per}}$ (same for all $s$)
-- Temporal effects are **scenario-specific**: $E_{\Omega,\text{temp}}(s) = \sum_{\text{t}_i} E_{\Omega,\text{temp}}(\text{t}_i, s)$ and $E_{\Phi,\text{temp}}(s) = \sum_{\text{t}_i} E_{\Phi,\text{temp}}(\text{t}_i, s)$
+## Penalty Effect
-### Investment Budget
-**Interpretation:**
-- Investment decisions (periodic) made once, used across all scenarios
-- Operations (temporal) differ by scenario
-- Objective balances expected value across scenarios
-- **Both $E_{\Omega}$ (objective effect) and $E_{\Phi}$ (penalty) are weighted identically by $w_s$**
+A built-in `Penalty` effect prevents infeasibility and allows soft biases:
```python
-capex = fx.Effect(label='capex', unit='€', maximum_periodic=1_000_000)
-opex = fx.Effect(label='opex', unit='€')
-total_costs = fx.Effect(label='total', unit='€', is_objective=True)
-
-# Link them
-total_costs.share_from_periodic = {'capex': 1}
-total_costs.share_from_temporal = {'opex': 1}
-
-# Battery investment
-battery = fx.Storage(
- ...,
- capacity_in_flow_hours=InvestParameters(
- maximum_size=1000,
- specific_effects={'capex': 500}, # €500/kWh
- ),
-)
-```
-$$
-\min \quad \sum_{y \in \mathcal{Y}} w_y \cdot \left( E_{\Omega}(y) + E_{\Phi}(y) \right)
-$$
-
-Optimize total costs while respecting €1M investment budget.
-Where:
-
-- $\mathcal{Y}$ is the set of periods (e.g., years)
-- $w_y$ is the weight for period $y$ (typically annual discount factor)
-- Each period $y$ has **independent** periodic and temporal effects (including penalty)
-- Each period $y$ has **independent** investment and operational decisions
-- **Both $E_{\Omega}$ (objective effect) and $E_{\Phi}$ (penalty) are weighted identically by $w_y$**
-
-## Multi-Dimensional Effects
-
-When using periods and scenarios (see [Dimensions](dimensions.md)), effects aggregate with weights:
-
-$$
-\min \sum_{periods} w_{period} \cdot \sum_{scenarios} w_{scenario} \cdot E(period, scenario)
-\min \quad \sum_{y \in \mathcal{Y}} \left[ w_y \cdot \left( E_{\Omega,\text{per}}(y) + E_{\Phi,\text{per}}(y) \right) + \sum_{s \in \mathcal{S}} w_{y,s} \cdot \left( E_{\Omega,\text{temp}}(y,s) + E_{\Phi,\text{temp}}(y,s) \right) \right]
-$$
+# Bias against startups without affecting cost tracking
+fx.StatusParameters(effects_per_startup={'Penalty': 1})
-**Key principle:** Investment decisions (periodic effects) are shared across scenarios within a period, while operational effects are scenario-specific.
-Where:
-
-- $\mathcal{S}$ is the set of scenarios
-- $\mathcal{Y}$ is the set of periods
-- $w_y$ is the period weight (for periodic effects)
-- $w_{y,s}$ is the combined period-scenario weight (for temporal effects)
-- **Periodic effects** $E_{\Omega,\text{per}}(y)$ and $E_{\Phi,\text{per}}(y)$ are period-specific but **scenario-independent**
-- **Temporal effects** $E_{\Omega,\text{temp}}(y,s) = \sum_{\text{t}_i} E_{\Omega,\text{temp}}(\text{t}_i, y, s)$ and $E_{\Phi,\text{temp}}(y,s) = \sum_{\text{t}_i} E_{\Phi,\text{temp}}(\text{t}_i, y, s)$ are **fully indexed**
-
-**Key Principle:**
-- Scenarios and periods are **operationally independent** (no energy/resource exchange)
-- Coupled **only through the weighted objective function**
-- **Periodic effects within a period are shared across all scenarios** (investment made once per period)
-- **Temporal effects are independent per scenario** (different operations under different conditions)
-- **Both $E_{\Omega}$ (objective effect) and $E_{\Phi}$ (penalty) use identical weighting** ($w_y$ for periodic, $w_{y,s}$ for temporal)
-
----
+# Bus imbalance penalty
+fx.Bus(label='heat', excess_penalty_per_flow_hour=1e5)
+```
-## Implementation Details
-
-- **Element Class:** [`Effect`][flixopt.effects.Effect]
-- **Model Class:** [`EffectModel`][flixopt.effects.EffectModel]
-- **Collection Class:** [`EffectCollection`][flixopt.effects.EffectCollection]
-| Concept | Formulation | Time Dependency | Dimension Indexing |
-|---------|-------------|-----------------|-------------------|
-| **Temporal share** | $s_{l \rightarrow e, \text{temp}}(\text{t}_i)$ | Time-dependent | $(t, y, s)$ when present |
-| **Periodic share** | $s_{l \rightarrow e, \text{per}}$ | Time-independent | $(y)$ when periods present |
-| **Total temporal effect** | $E_{e,\text{temp},\text{tot}} = \sum_{\text{t}_i} E_{e,\text{temp}}(\text{t}_i)$ | Sum over time | Depends on dimensions |
-| **Total periodic effect** | $E_{e,\text{per}}$ | Constant | $(y)$ when periods present |
-| **Total effect** | $E_e = E_{e,\text{per}} + E_{e,\text{temp},\text{tot}}$ | Combined | Depends on dimensions |
-| **Penalty effect** | $E_\Phi = E_{\Phi,\text{per}} + E_{\Phi,\text{temp},\text{tot}}$ | Combined (same as effects) | **Weighted identically to objective effect** |
-| **Objective** | $\min(E_{\Omega} + E_{\Phi})$ | With weights when multi-dimensional | See formulations above |
+The objective is always: $\min \quad E_{objective} + E_{penalty}$
---
-## See Also
-
-- [Bus](elements/Bus.md) — Penalty contributions from balance violations
-- [Flow](elements/Flow.md) — Temporal contributions via `effects_per_flow_hour`
-- [InvestParameters](features/InvestParameters.md) — Periodic contributions from investments
-- [Dimensions](dimensions.md) — Multi-period and scenario handling
-- [Core Concepts: Effects](../core-concepts.md#effects-what-youre-tracking) — High-level overview
+## Reference
+
+| Component | Description |
+|-----------|-------------|
+| $E_{temp}(t)$ | Temporal effect at timestep $t$ |
+| $E_{per}$ | Periodic effect (time-independent) |
+| $E$ | Total = periodic + sum of temporal |
+| $w_s$ | Scenario weight |
+| $w_y$ | Period weight |
+
+| Parameter | Python | Description |
+|-----------|--------|-------------|
+| Objective | `is_objective=True` | Minimize this effect |
+| Total limit | `maximum_total` | Upper bound on total |
+| Timestep limit | `maximum_per_hour` | Upper bound per timestep |
+| Periodic limit | `maximum_periodic` | Upper bound on periodic |
+| Cross-effect | `share_from_temporal` | Link from other effect |
+
+**Classes:** [`Effect`][flixopt.effects.Effect], [`EffectCollection`][flixopt.effects.EffectCollection]
From 4c1ed4553033ef928644de13f6aa1058afb3691b Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 04:08:10 +0100
Subject: [PATCH 68/79] Improve InvestParameters.md
---
.../features/InvestParameters.md | 20 ++++++-------------
1 file changed, 6 insertions(+), 14 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/features/InvestParameters.md b/docs/user-guide/mathematical-notation/features/InvestParameters.md
index bf31332da..b0610531d 100644
--- a/docs/user-guide/mathematical-notation/features/InvestParameters.md
+++ b/docs/user-guide/mathematical-notation/features/InvestParameters.md
@@ -23,16 +23,18 @@ battery = fx.Storage(
## Investment Modes
+By default, investment is **optional** — the optimizer can choose $P = 0$ (don't invest).
+
=== "Continuous"
- Choose size within range:
+ Choose size within range (or zero):
```python
fx.InvestParameters(
minimum_size=10,
maximum_size=1000,
)
- # → 10 ≤ P ≤ 1000
+ # → P = 0 OR 10 ≤ P ≤ 1000
```
=== "Binary"
@@ -46,20 +48,9 @@ battery = fx.Storage(
# → P ∈ {0, 100}
```
-=== "Optional"
-
- Can choose not to invest ($P = 0$ allowed):
-
- ```python
- fx.InvestParameters(
- minimum_size=0, # Zero allowed
- maximum_size=1000,
- )
- ```
-
=== "Mandatory"
- Must invest (no binary variable):
+ Force investment with `mandatory=True` — zero not allowed:
```python
fx.InvestParameters(
@@ -67,6 +58,7 @@ battery = fx.Storage(
maximum_size=200,
mandatory=True,
)
+ # → 50 ≤ P ≤ 200 (no zero option)
```
---
From dc7432ea8da95fef776e210373e66796f8c056f6 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 04:11:31 +0100
Subject: [PATCH 69/79] Add durtaion constraints
---
.../features/StatusParameters.md | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/features/StatusParameters.md b/docs/user-guide/mathematical-notation/features/StatusParameters.md
index 67d9ba3a2..822874a93 100644
--- a/docs/user-guide/mathematical-notation/features/StatusParameters.md
+++ b/docs/user-guide/mathematical-notation/features/StatusParameters.md
@@ -54,7 +54,9 @@ Detect transitions: $s^{start}(t) - s^{stop}(t) = s(t) - s(t-1)$
=== "Min Uptime"
- Once on, must stay on:
+ Once on, must stay on for minimum duration:
+
+ $s^{start}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} s(j) \geq T_{up}^{min}$
```python
fx.StatusParameters(min_uptime=8) # 8 hours minimum
@@ -62,7 +64,9 @@ Detect transitions: $s^{start}(t) - s^{stop}(t) = s(t) - s(t-1)$
=== "Min Downtime"
- Once off, must stay off:
+ Once off, must stay off for minimum duration:
+
+ $s^{stop}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} (1 - s(j)) \geq T_{down}^{min}$
```python
fx.StatusParameters(min_downtime=4) # 4 hours cooling
@@ -72,13 +76,17 @@ Detect transitions: $s^{start}(t) - s^{stop}(t) = s(t) - s(t-1)$
Force shutdown after limit:
+ $\sum_{j=t-k}^{t} s(j) \leq T_{up}^{max}$
+
```python
fx.StatusParameters(max_uptime=18) # Max 18h continuous
```
=== "Total Hours"
- Limit total operating hours:
+ Limit total operating hours per period:
+
+ $H^{min} \leq \sum_t s(t) \cdot \Delta t \leq H^{max}$
```python
fx.StatusParameters(
From 31592e2e2f6bb33231fd6ed82501762bb0018183 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 04:29:49 +0100
Subject: [PATCH 70/79] Update Piecewise stuff
---
.../elements/LinearConverter.md | 27 +--
.../features/InvestParameters.md | 26 +++
.../features/Piecewise.md | 167 ++++++++++--------
3 files changed, 134 insertions(+), 86 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/elements/LinearConverter.md b/docs/user-guide/mathematical-notation/elements/LinearConverter.md
index 961ae0a7d..9b0f704a8 100644
--- a/docs/user-guide/mathematical-notation/elements/LinearConverter.md
+++ b/docs/user-guide/mathematical-notation/elements/LinearConverter.md
@@ -114,22 +114,23 @@ chp = fx.linear_converters.CHP(
See [StatusParameters](../features/StatusParameters.md).
-=== "Piecewise Efficiency"
+=== "Piecewise Conversion"
- For non-linear efficiency curves:
+ For variable efficiency — all flows change together based on operating point:
```python
- curve = fx.Piecewise([
- fx.Piece(start=(0, 0), end=(50, 40)), # 80% at low load
- fx.Piece(start=(50, 40), end=(100, 90)), # 100% at high load
- ])
-
- boiler = fx.LinearConverter(
- ...,
- piecewise_conversion=fx.PiecewiseConversion(
- origin_flow='gas',
- piecewise_shares={'heat': curve},
- ),
+ chp = fx.LinearConverter(
+ label='CHP',
+ inputs=[fx.Flow('fuel', bus=gas_bus)],
+ outputs=[
+ fx.Flow('el', bus=elec_bus, size=60),
+ fx.Flow('heat', bus=heat_bus),
+ ],
+ piecewise_conversion=fx.PiecewiseConversion({
+ 'el': fx.Piecewise([fx.Piece(5, 30), fx.Piece(40, 60)]),
+ 'heat': fx.Piecewise([fx.Piece(6, 35), fx.Piece(45, 100)]),
+ 'fuel': fx.Piecewise([fx.Piece(12, 70), fx.Piece(90, 200)]),
+ }),
)
```
diff --git a/docs/user-guide/mathematical-notation/features/InvestParameters.md b/docs/user-guide/mathematical-notation/features/InvestParameters.md
index b0610531d..f32b897d5 100644
--- a/docs/user-guide/mathematical-notation/features/InvestParameters.md
+++ b/docs/user-guide/mathematical-notation/features/InvestParameters.md
@@ -101,6 +101,31 @@ By default, investment is **optional** — the optimizer can choose $P = 0$ (don
)
```
+=== "Piecewise Cost"
+
+ Non-linear cost curves (e.g., economies of scale):
+
+ $E = f_{piecewise}(P)$
+
+ ```python
+ fx.InvestParameters(
+ piecewise_effects_of_investment=fx.PiecewiseEffects(
+ piecewise_origin=fx.Piecewise([
+ fx.Piece(0, 100),
+ fx.Piece(100, 500),
+ ]),
+ piecewise_shares={
+ 'costs': fx.Piecewise([
+ fx.Piece(0, 80_000), # €800/kW for 0-100
+ fx.Piece(80_000, 280_000), # €500/kW for 100-500
+ ])
+ },
+ ),
+ )
+ ```
+
+ See [Piecewise](Piecewise.md) for details on the formulation.
+
---
## Reference
@@ -118,6 +143,7 @@ By default, investment is **optional** — the optimizer can choose $P = 0$ (don
| Per-size cost | `specific_effects` | €/unit |
| Fixed cost | `effects_of_investment` | One-time if investing |
| Retirement | `effects_of_retirement` | Cost if not investing |
+| Piecewise cost | `piecewise_effects_of_investment` | Non-linear cost curve |
| Force invest | `mandatory` | No binary variable |
**Classes:** [`InvestParameters`][flixopt.interface.InvestParameters], [`InvestmentModel`][flixopt.features.InvestmentModel]
diff --git a/docs/user-guide/mathematical-notation/features/Piecewise.md b/docs/user-guide/mathematical-notation/features/Piecewise.md
index 90f8504ae..298d53fb7 100644
--- a/docs/user-guide/mathematical-notation/features/Piecewise.md
+++ b/docs/user-guide/mathematical-notation/features/Piecewise.md
@@ -1,111 +1,130 @@
# Piecewise
-Piecewise linearization models non-linear relationships with connected linear segments.
+Piecewise linearization approximates non-linear relationships using connected linear segments.
-## Basic: Linear Segments
+## Mathematical Formulation
-```plotly
-{
- "data": [
- {"x": [0, 50], "y": [0, 45], "mode": "lines+markers", "name": "Piece 1", "line": {"color": "#009688", "width": 3}, "marker": {"size": 10}},
- {"x": [50, 100], "y": [45, 90], "mode": "lines+markers", "name": "Piece 2", "line": {"color": "#00bcd4", "width": 3}, "marker": {"size": 10}}
- ],
- "layout": {"xaxis": {"title": "Input"}, "yaxis": {"title": "Output"}, "height": 250, "margin": {"t": 20}}
-}
-```
+A piecewise linear function with $n$ segments uses SOS2 (Special Ordered Set Type 2) variables:
-```python
-curve = fx.Piecewise([
- fx.Piece((0, 0), (50, 45)), # Slope 0.9
- fx.Piece((50, 45), (100, 90)), # Slope 0.9
-])
-```
+$$
+x = \sum_{i=0}^{n} \lambda_i \cdot x_i \quad \text{and} \quad y = \sum_{i=0}^{n} \lambda_i \cdot y_i
+$$
-The optimizer picks a point along exactly one segment.
+Where:
+
+- $(x_i, y_i)$ are the breakpoints defining the segments
+- $\lambda_i \geq 0$ are interpolation weights with $\sum_i \lambda_i = 1$
+- At most two adjacent $\lambda_i$ can be non-zero (SOS2 constraint)
+
+This ensures $y$ follows the piecewise linear path defined by the breakpoints.
---
-## Use Cases
+## Building Blocks
-=== "Part-Load Efficiency"
+=== "Piece"
- Boiler: 80% at low load, 92% at high load:
+ A linear segment from start to end value:
```python
- gas_to_heat = fx.Piecewise([
- fx.Piece((0, 0), (30, 24)), # 80%
- fx.Piece((30, 24), (100, 92)), # 92%
- ])
+ fx.Piece(start=10, end=50) # Linear from 10 to 50
+ ```
- boiler = fx.LinearConverter(
- ...,
- piecewise_conversion=fx.PiecewiseConversion(
- origin_flow='gas',
- piecewise_shares={'heat': gas_to_heat},
- ),
+ Values can be time-varying:
+
+ ```python
+ fx.Piece(
+ start=np.linspace(5, 6, n_timesteps),
+ end=np.linspace(30, 35, n_timesteps)
)
```
-=== "Variable COP"
+=== "Piecewise"
- Heat pump COP varies with load:
+ Multiple connected pieces forming a piecewise linear function:
```python
- elec_to_heat = fx.Piecewise([
- fx.Piece((0, 0), (50, 125)), # COP ~2.5
- fx.Piece((50, 125), (100, 350)), # COP ~4.5
+ fx.Piecewise([
+ fx.Piece(0, 30), # Segment 1: 0 → 30
+ fx.Piece(30, 60), # Segment 2: 30 → 60
])
+ ```
- hp = fx.LinearConverter(
- ...,
- piecewise_conversion=fx.PiecewiseConversion(
- origin_flow='el',
- piecewise_shares={'heat': elec_to_heat},
- ),
+=== "PiecewiseConversion"
+
+ Synchronizes multiple flows — all interpolate at the same relative position:
+
+ ```python
+ fx.PiecewiseConversion({
+ 'input_flow': fx.Piecewise([...]),
+ 'output_flow': fx.Piecewise([...]),
+ })
+ ```
+
+ All piecewise functions must have the same number of segments.
+
+=== "PiecewiseEffects"
+
+ Maps a size/capacity variable to effects (costs, emissions):
+
+ ```python
+ fx.PiecewiseEffects(
+ piecewise_origin=fx.Piecewise([...]), # Size segments
+ piecewise_shares={'costs': fx.Piecewise([...])}, # Effect segments
)
```
-=== "Forbidden Region"
+---
+
+## Usage
+
+=== "Variable Efficiency"
- Turbine: off or 40-100%, nothing in between:
+ Converter efficiency that varies with load:
```python
- curve = fx.Piecewise([
- fx.Piece((0, 0), (0, 0)), # Off (point)
- fx.Piece((40, 40), (100, 100)), # Operating
- ])
+ chp = fx.LinearConverter(
+ ...,
+ piecewise_conversion=fx.PiecewiseConversion({
+ 'el': fx.Piecewise([fx.Piece(5, 30), fx.Piece(40, 60)]),
+ 'heat': fx.Piecewise([fx.Piece(6, 35), fx.Piece(45, 100)]),
+ 'fuel': fx.Piecewise([fx.Piece(12, 70), fx.Piece(90, 200)]),
+ }),
+ )
```
=== "Economies of Scale"
- Investment cost decreases with size:
+ Investment cost per unit decreases with size:
```python
fx.InvestParameters(
piecewise_effects_of_investment=fx.PiecewiseEffects(
- piecewise_origin=fx.Piecewise([...]),
- piecewise_shares={'costs': fx.Piecewise([
- fx.Piece((0, 0), (100, 80000)), # €800/kWh
- fx.Piece((100, 80000), (500, 350000)), # €675/kWh
- ])},
+ piecewise_origin=fx.Piecewise([
+ fx.Piece(0, 100),
+ fx.Piece(100, 500),
+ ]),
+ piecewise_shares={
+ 'costs': fx.Piecewise([
+ fx.Piece(0, 80_000),
+ fx.Piece(80_000, 280_000),
+ ])
+ },
),
)
```
----
+=== "Forbidden Operating Region"
-## Zero Point
+ Equipment cannot operate in certain ranges:
-Allow equipment to be completely off without a zero piece:
-
-```python
-curve = fx.Piecewise(
- pieces=[
- fx.Piece((10, 9), (100, 90)), # Operating range 10-100
- ],
- zero_point=True, # Can also be off (0, 0)
-)
-```
+ ```python
+ fx.PiecewiseConversion({
+ 'fuel': fx.Piecewise([fx.Piece(0, 0), fx.Piece(40, 100)]),
+ 'power': fx.Piecewise([fx.Piece(0, 0), fx.Piece(35, 95)]),
+ })
+ # Either off (0,0) or operating above 40%
+ ```
---
@@ -113,12 +132,14 @@ curve = fx.Piecewise(
| Variable | Description |
|----------|-------------|
-| $\beta_k$ | Piece $k$ is active |
-| $\lambda_0, \lambda_1$ | Weights on start/end points |
+| $\lambda_i$ | SOS2 interpolation weight for breakpoint $i$ |
+| $(x_i, y_i)$ | Breakpoint coordinates |
-| Parameter | Description |
-|-----------|-------------|
-| `Piece(start, end)` | Define segment from start to end point |
-| `zero_point` | Allow all variables = 0 |
+| Class | Description |
+|-------|-------------|
+| `Piece(start, end)` | Linear segment |
+| `Piecewise([pieces])` | Collection of segments |
+| `PiecewiseConversion({flow: piecewise})` | Synchronized multi-flow conversion |
+| `PiecewiseEffects(origin, shares)` | Size-to-effect mapping |
-**Classes:** [`Piecewise`][flixopt.interface.Piecewise], [`Piece`][flixopt.interface.Piece]
+**Classes:** [`Piecewise`][flixopt.interface.Piecewise], [`Piece`][flixopt.interface.Piece], [`PiecewiseConversion`][flixopt.interface.PiecewiseConversion], [`PiecewiseEffects`][flixopt.interface.PiecewiseEffects]
From 134fa57b9c789d6ca9b006a8798e2b7e2cb4fa6c Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 04:32:42 +0100
Subject: [PATCH 71/79] Update Piecewise stuff
---
.../features/Piecewise.md | 30 ++++++++++++-------
1 file changed, 20 insertions(+), 10 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/features/Piecewise.md b/docs/user-guide/mathematical-notation/features/Piecewise.md
index 298d53fb7..b3995994d 100644
--- a/docs/user-guide/mathematical-notation/features/Piecewise.md
+++ b/docs/user-guide/mathematical-notation/features/Piecewise.md
@@ -4,19 +4,28 @@ Piecewise linearization approximates non-linear relationships using connected li
## Mathematical Formulation
-A piecewise linear function with $n$ segments uses SOS2 (Special Ordered Set Type 2) variables:
+A piecewise linear function with $n$ segments uses per-segment interpolation:
$$
-x = \sum_{i=0}^{n} \lambda_i \cdot x_i \quad \text{and} \quad y = \sum_{i=0}^{n} \lambda_i \cdot y_i
+x = \sum_{i=1}^{n} \left( \lambda_i^0 \cdot x_i^{start} + \lambda_i^1 \cdot x_i^{end} \right)
$$
-Where:
+Each segment $i$ has:
-- $(x_i, y_i)$ are the breakpoints defining the segments
-- $\lambda_i \geq 0$ are interpolation weights with $\sum_i \lambda_i = 1$
-- At most two adjacent $\lambda_i$ can be non-zero (SOS2 constraint)
+- $s_i \in \{0, 1\}$ — binary indicating if segment is active
+- $\lambda_i^0, \lambda_i^1 \geq 0$ — interpolation weights for segment endpoints
-This ensures $y$ follows the piecewise linear path defined by the breakpoints.
+Constraints ensure valid interpolation:
+
+$$
+\lambda_i^0 + \lambda_i^1 = s_i \quad \forall i
+$$
+
+$$
+\sum_{i=1}^{n} s_i \leq 1
+$$
+
+When segment $i$ is active ($s_i = 1$), the lambdas interpolate between $x_i^{start}$ and $x_i^{end}$. When inactive ($s_i = 0$), both lambdas are zero.
---
@@ -41,7 +50,7 @@ This ensures $y$ follows the piecewise linear path defined by the breakpoints.
=== "Piecewise"
- Multiple connected pieces forming a piecewise linear function:
+ Multiple segments forming a piecewise linear function:
```python
fx.Piecewise([
@@ -132,8 +141,9 @@ This ensures $y$ follows the piecewise linear path defined by the breakpoints.
| Variable | Description |
|----------|-------------|
-| $\lambda_i$ | SOS2 interpolation weight for breakpoint $i$ |
-| $(x_i, y_i)$ | Breakpoint coordinates |
+| $s_i$ | Binary: segment $i$ is active |
+| $\lambda_i^0$ | Interpolation weight for segment start |
+| $\lambda_i^1$ | Interpolation weight for segment end |
| Class | Description |
|-------|-------------|
From f8147b5ce97c6d1ed212c1097e39f99f4d4999bc Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 04:35:28 +0100
Subject: [PATCH 72/79] Update Piecewise stuff
---
docs/user-guide/mathematical-notation/features/Piecewise.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/docs/user-guide/mathematical-notation/features/Piecewise.md b/docs/user-guide/mathematical-notation/features/Piecewise.md
index b3995994d..fda04eeae 100644
--- a/docs/user-guide/mathematical-notation/features/Piecewise.md
+++ b/docs/user-guide/mathematical-notation/features/Piecewise.md
@@ -27,6 +27,9 @@ $$
When segment $i$ is active ($s_i = 1$), the lambdas interpolate between $x_i^{start}$ and $x_i^{end}$. When inactive ($s_i = 0$), both lambdas are zero.
+!!! note "Implementation Note"
+ This formulation is an explicit binary reformulation of SOS2 (Special Ordered Set Type 2) constraints. It produces identical results but uses more variables. We will migrate to native SOS2 constraints once [linopy](https://github.com/PyPSA/linopy) supports them.
+
---
## Building Blocks
From 199c077ddaee18f1d43cabee78166ee56b5c4629 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 04:44:11 +0100
Subject: [PATCH 73/79] Combine effects and dimensions into one tab
---
README.md | 2 +-
.../mathematical-notation/dimensions.md | 316 ------------------
.../effects-and-dimensions.md | 248 ++++++++++++++
.../effects-penalty-objective.md | 163 ---------
.../user-guide/mathematical-notation/index.md | 8 +-
flixopt/effects.py | 4 +-
mkdocs.yml | 3 +-
7 files changed, 254 insertions(+), 490 deletions(-)
delete mode 100644 docs/user-guide/mathematical-notation/dimensions.md
create mode 100644 docs/user-guide/mathematical-notation/effects-and-dimensions.md
delete mode 100644 docs/user-guide/mathematical-notation/effects-penalty-objective.md
diff --git a/README.md b/README.md
index 6d049819d..339a40b41 100644
--- a/README.md
+++ b/README.md
@@ -96,7 +96,7 @@ boiler = fx.Boiler("Boiler", eta=0.9, ...)
### Key Features
**Multi-criteria optimization:** Model costs, emissions, resource use - any custom metric. Optimize single objectives or use weighted combinations and ε-constraints.
-→ [Effects documentation](https://flixopt.github.io/flixopt/latest/user-guide/mathematical-notation/effects-penalty-objective/)
+→ [Effects documentation](https://flixopt.github.io/flixopt/latest/user-guide/mathematical-notation/effects-and-dimensions/)
**Performance at any scale:** Choose optimization modes without changing your model - Optimization, SegmentedOptimization, or ClusteredOptimization (using [TSAM](https://github.com/FZJ-IEK3-VSA/tsam)).
→ [Optimization modes](https://flixopt.github.io/flixopt/latest/api-reference/optimization/)
diff --git a/docs/user-guide/mathematical-notation/dimensions.md b/docs/user-guide/mathematical-notation/dimensions.md
deleted file mode 100644
index 2a526e19d..000000000
--- a/docs/user-guide/mathematical-notation/dimensions.md
+++ /dev/null
@@ -1,316 +0,0 @@
-# Dimensions
-
-FlixOpt's `FlowSystem` supports multiple dimensions for modeling optimization problems. Understanding these dimensions is crucial for interpreting the mathematical formulations presented in this documentation.
-
-## The Three Dimensions
-
-FlixOpt models can have up to three dimensions:
-
-1. **Time (`time`)** - **MANDATORY**
- - Represents the temporal evolution of the system
- - Defined via `pd.DatetimeIndex`
- - Must contain at least 2 timesteps
- - All optimization variables and constraints evolve over time
-2. **Period (`period`)** - **OPTIONAL**
- - Represents independent planning periods (e.g., years 2020, 2021, 2022)
- - Defined via `pd.Index` with integer values
- - Used for multi-period optimization such as investment planning across years
- - Each period is independent with its own time series
-3. **Scenario (`scenario`)** - **OPTIONAL**
- - Represents alternative futures or uncertainty realizations (e.g., "Base Case", "High Demand")
- - Defined via `pd.Index` with any labels
- - Scenarios within the same period share the same time dimension
- - Used for stochastic optimization or scenario comparison
-
----
-
-## Dimensional Structure
-
-**Coordinate System:**
-
-```python
-FlowSystemDimensions = Literal['time', 'period', 'scenario']
-
-coords = {
- 'time': pd.DatetimeIndex, # Always present
- 'period': pd.Index | None, # Optional
- 'scenario': pd.Index | None # Optional
-}
-```
-
-**Example:**
-```python
-import pandas as pd
-import numpy as np
-import flixopt as fx
-
-timesteps = pd.date_range('2020-01-01', periods=24, freq='h')
-scenarios = pd.Index(['Base Case', 'High Demand'])
-periods = pd.Index([2020, 2021, 2022])
-
-flow_system = fx.FlowSystem(
- timesteps=timesteps,
- periods=periods,
- scenarios=scenarios,
- scenario_weights=np.array([0.5, 0.5]) # Scenario weights
-)
-```
-
-This creates a system with:
-- 24 time steps per scenario per period
-- 2 scenarios with equal weights (0.5 each)
-- 3 periods (years)
-- **Total decision space:** 24 × 2 × 3 = 144 time-scenario-period combinations
-
----
-
-## Independence of Formulations
-
-**All mathematical formulations in this documentation are independent of whether periods or scenarios are present.**
-
-The equations shown throughout this documentation (for [Flow](elements/Flow.md), [Storage](elements/Storage.md), [Bus](elements/Bus.md), etc.) are written with only the time index $\text{t}_i$. When periods and/or scenarios are added, **the same equations apply** - they are simply expanded to additional dimensions.
-
-### How Dimensions Expand Formulations
-
-**Flow rate bounds** (from [Flow](elements/Flow.md)):
-
-$$
-\text{P} \cdot \text{p}^{\text{L}}_{\text{rel}}(\text{t}_{i}) \leq p(\text{t}_{i}) \leq \text{P} \cdot \text{p}^{\text{U}}_{\text{rel}}(\text{t}_{i})
-$$
-
-This equation remains valid regardless of dimensions:
-
-| Dimensions Present | Variable Indexing | Interpretation |
-|-------------------|-------------------|----------------|
-| Time only | $p(\text{t}_i)$ | Flow rate at time $\text{t}_i$ |
-| Time + Scenario | $p(\text{t}_i, s)$ | Flow rate at time $\text{t}_i$ in scenario $s$ |
-| Time + Period | $p(\text{t}_i, y)$ | Flow rate at time $\text{t}_i$ in period $y$ |
-| Time + Period + Scenario | $p(\text{t}_i, y, s)$ | Flow rate at time $\text{t}_i$ in period $y$, scenario $s$ |
-
-**The mathematical relationship remains identical** - only the indexing expands.
-
----
-
-## Independence Between Scenarios and Periods
-
-**There is no interconnection between scenarios and periods, except for shared investment decisions within a period.**
-
-### Scenario Independence
-
-Scenarios within a period are **operationally independent**:
-
-- Each scenario has its own operational variables: $p(\text{t}_i, s_1)$ and $p(\text{t}_i, s_2)$ are independent
-- Scenarios cannot exchange energy, information, or resources
-- Storage states are separate: $c(\text{t}_i, s_1) \neq c(\text{t}_i, s_2)$
-- Binary states (active/inactive) are independent: $s(\text{t}_i, s_1)$ vs $s(\text{t}_i, s_2)$
-
-Scenarios are connected **only through the objective function** via weights:
-
-$$
-\min \quad \sum_{s \in \mathcal{S}} w_s \cdot \text{Objective}_s
-$$
-
-Where:
-- $\mathcal{S}$ is the set of scenarios
-- $w_s$ is the weight for scenario $s$
-- The optimizer balances performance across scenarios according to their weights
-- **Both the objective effect and Penalty effect are weighted by $w_s$** (see [Penalty weighting](effects-penalty-objective.md#penalty))
-
-### Period Independence
-
-Periods are **completely independent** optimization problems:
-
-- Each period has separate operational variables
-- Each period has separate investment decisions
-- No temporal coupling between periods (e.g., storage state at end of period $y$ does not affect period $y+1$)
-- Periods cannot exchange resources or information
-
-Periods are connected **only through weighted aggregation** in the objective:
-
-$$
-\min \quad \sum_{y \in \mathcal{Y}} w_y \cdot \text{Objective}_y
-$$
-
-Where **both the objective effect and Penalty effect are weighted by $w_y$** (see [Penalty weighting](effects-penalty-objective.md#penalty))
-
-### Shared Periodic Decisions: The Exception
-
-**Investment decisions (sizes) can be shared across all scenarios:**
-
-By default, sizes (e.g., Storage capacity, Thermal power, ...) are **scenario-independent** but **flow_rates are scenario-specific**.
-
-**Example - Flow with investment:**
-
-$$
-v_\text{invest}(y) = s_\text{invest}(y) \cdot \text{size}_\text{fixed} \quad \text{(one decision per period)}
-$$
-
-$$
-p(\text{t}_i, y, s) \leq v_\text{invest}(y) \cdot \text{rel}_\text{upper} \quad \forall s \in \mathcal{S} \quad \text{(same capacity for all scenarios)}
-$$
-
-**Interpretation:**
-- "We decide once in period $y$ how much capacity to build" (periodic decision)
-- "This capacity is then operated differently in each scenario $s$ within period $y$" (temporal decisions)
-- "Periodic effects (investment) are incurred once per period, temporal effects (operational) are weighted across scenarios"
-
-This reflects real-world investment under uncertainty: you build capacity once (periodic/investment decision), but it operates under different conditions (temporal/operational decisions per scenario).
-
-**Mathematical Flexibility:**
-
-Variables can be either scenario-independent or scenario-specific:
-
-| Variable Type | Scenario-Independent | Scenario-Specific |
-|---------------|---------------------|-------------------|
-| **Sizes** (e.g., $\text{P}$) | $\text{P}(y)$ - Single value per period | $\text{P}(y, s)$ - Different per scenario |
-| **Flow rates** (e.g., $p(\text{t}_i)$) | $p(\text{t}_i, y)$ - Same across scenarios | $p(\text{t}_i, y, s)$ - Different per scenario |
-
-**Use Cases:**
-
-*Investment problems (with InvestParameters):*
-- **Sizes shared** (default): Investment under uncertainty - build capacity that performs well across all scenarios
-- **Sizes vary**: Scenario-specific capacity planning where different investments can be made for each future
-- **Selected sizes shared**: Mix of shared critical infrastructure and scenario-specific optional/flexible capacity
-
-*Dispatch problems (fixed sizes, no investments):*
-- **Flow rates shared**: Robust dispatch - find a single operational strategy that works across all forecast scenarios (e.g., day-ahead unit commitment under demand/weather uncertainty)
-- **Flow rates vary** (default): Scenario-adaptive dispatch - optimize operations for each scenario's specific conditions (demand, weather, prices)
-
-For implementation details on controlling scenario independence, see the [`FlowSystem`][flixopt.flow_system.FlowSystem] API reference.
-
----
-
-## Dimensional Impact on Objective Function
-
-The objective function aggregates effects across all dimensions with weights:
-
-### Time Only
-$$
-\min \quad \sum_{\text{t}_i \in \mathcal{T}} \sum_{e \in \mathcal{E}} s_{e}(\text{t}_i)
-$$
-
-### Time + Scenario
-$$
-\min \quad \sum_{s \in \mathcal{S}} w_s \cdot \left( \sum_{\text{t}_i \in \mathcal{T}} \sum_{e \in \mathcal{E}} s_{e}(\text{t}_i, s) \right)
-$$
-
-### Time + Period
-$$
-\min \quad \sum_{y \in \mathcal{Y}} w_y \cdot \left( \sum_{\text{t}_i \in \mathcal{T}} \sum_{e \in \mathcal{E}} s_{e}(\text{t}_i, y) \right)
-$$
-
-### Time + Period + Scenario (Full Multi-Dimensional)
-$$
-\min \quad \sum_{y \in \mathcal{Y}} \sum_{s \in \mathcal{S}} w_{y,s} \cdot \left( \sum_{\text{t}_i \in \mathcal{T}} \sum_{e \in \mathcal{E}} s_{e}(\text{t}_i, y, s) \right)
-$$
-
-Where:
-- $\mathcal{T}$ is the set of time steps
-- $\mathcal{E}$ is the set of effects (including the Penalty effect $E_\Phi$)
-- $\mathcal{S}$ is the set of scenarios
-- $\mathcal{Y}$ is the set of periods
-- $s_{e}(\cdots)$ are the effect contributions (costs, emissions, etc.)
-- $w_s, w_y, w_{y,s}$ are the dimension weights
-- **Penalty effect is weighted identically to other effects**
-
-**See [Effects, Penalty & Objective](effects-penalty-objective.md) for complete formulations including:**
-- How temporal and periodic effects expand with dimensions
-- Detailed objective function for each dimensional case
-- Periodic (investment) vs temporal (operational) effect handling
-- Explicit Penalty weighting formulations
-
----
-
-## Weights
-
-Weights determine the relative importance of scenarios and periods in the objective function.
-
-### Scenario Weights
-
-You provide scenario weights explicitly via the `scenario_weights` parameter:
-
-```python
-flow_system = fx.FlowSystem(
- timesteps=timesteps,
- scenarios=scenarios,
- scenario_weights=np.array([0.3, 0.7]) # Scenario probabilities
-)
-```
-
-**Default:** If not specified, all scenarios have equal weight (normalized to sum to 1).
-
-### Period Weights
-
-Period weights are **automatically computed** from the period index (similar to how `hours_per_timestep` is computed from the time index):
-
-```python
-# Period weights are computed from the differences between period values
-periods = pd.Index([2020, 2025, 2030, 2035])
-# → period_weights = [5, 5, 5, 5] (representing 5-year intervals)
-
-flow_system = fx.FlowSystem(
- timesteps=timesteps,
- periods=periods,
- # No need to specify period weights - they're computed automatically
-)
-```
-
-**How period weights are computed:**
-- For periods `[2020, 2025, 2030, 2035]`, the weights are `[5, 5, 5, 5]` (the interval sizes)
-- This ensures that when you use `.sel()` to select a subset of periods, the weights are correctly recalculated
-- You can specify `weight_of_last_period` if the last period weight cannot be inferred from the index
-
-### Combined Weights
-
-When both periods and scenarios are present, the combined `weights` array (accessible via `flow_system.model.objective_weights`) is computed as:
-
-$$
-w_{y,s} = w_y \times \frac{w_s}{\sum_{s \in \mathcal{S}} w_s}
-$$
-
-Where:
-- $w_y$ are the period weights (computed from period index)
-- $w_s$ are the scenario weights (user-specified)
-- $\mathcal{S}$ is the set of all scenarios
-- The scenario weights are normalized to sum to 1 before multiplication
-
-**Example:**
-```python
-periods = pd.Index([2020, 2030, 2040]) # → period_weights = [10, 10, 10]
-scenarios = pd.Index(['Base', 'High'])
-scenario_weights = np.array([0.6, 0.4])
-
-flow_system = fx.FlowSystem(
- timesteps=timesteps,
- periods=periods,
- scenarios=scenarios,
- scenario_weights=scenario_weights
-)
-
-# Combined weights shape: (3 periods, 2 scenarios)
-# [[6.0, 4.0], # 2020: 10 × [0.6, 0.4]
-# [6.0, 4.0], # 2030: 10 × [0.6, 0.4]
-# [6.0, 4.0]] # 2040: 10 × [0.6, 0.4]
-```
-
-**Normalization:** Set `normalize_weights=False` in `Optimization` to turn off the normalization.
-
----
-
-## Summary Table
-
-| Dimension | Required? | Independence | Typical Use Case |
-|-----------|-----------|--------------|------------------|
-| **time** | ✅ Yes | Variables evolve over time via constraints (e.g., storage balance) | All optimization problems |
-| **scenario** | ❌ No | Fully independent operations; shared investments within period | Uncertainty modeling, risk assessment |
-| **period** | ❌ No | Fully independent; no coupling between periods | Multi-year planning, long-term investment |
-
-**Key Principle:** All constraints and formulations operate **within** each (period, scenario) combination independently. Only the objective function couples them via weighted aggregation.
-
----
-
-## See Also
-
-- [Effects, Penalty & Objective](effects-penalty-objective.md) - How dimensions affect the objective function
-- [InvestParameters](features/InvestParameters.md) - Investment decisions across scenarios
-- [FlowSystem API][flixopt.flow_system.FlowSystem] - Creating multi-dimensional systems
diff --git a/docs/user-guide/mathematical-notation/effects-and-dimensions.md b/docs/user-guide/mathematical-notation/effects-and-dimensions.md
new file mode 100644
index 000000000..3370bd21b
--- /dev/null
+++ b/docs/user-guide/mathematical-notation/effects-and-dimensions.md
@@ -0,0 +1,248 @@
+# Effects & Dimensions
+
+Effects track metrics (costs, CO₂, energy). Dimensions define the structure over which effects aggregate.
+
+## Defining Effects
+
+```python
+costs = fx.Effect(label='costs', unit='€', is_objective=True)
+co2 = fx.Effect(label='co2', unit='kg')
+
+flow_system.add_elements(costs, co2)
+```
+
+One effect is the **objective** (minimized). Others are tracked or constrained.
+
+---
+
+## Effect Types
+
+=== "Temporal"
+
+ Accumulated over timesteps — operational costs, emissions, energy - per timestep:
+
+ $E_{temp}(t) = \text{flow}(t) \cdot c \cdot \Delta t$
+
+ ```python
+ fx.Flow(..., effects_per_flow_hour={'costs': 50}) # €50/MWh
+ ```
+
+=== "Periodic"
+
+ Time-independent — investment costs, fixed fees - per period:
+
+ $E_{per} = P \cdot c_{inv}$
+
+ ```python
+ fx.InvestParameters(specific_effects={'costs': 200}) # €200/kW
+ ```
+
+=== "Total"
+
+ Sum of periodic and temporal components.
+
+---
+
+## Dimensions
+
+The model operates across three dimensions:
+
+=== "Timesteps"
+
+ The basic time resolution — always required:
+
+ ```python
+ timesteps = pd.date_range('2024-01-01', periods=8760, freq='h')
+ ```
+
+ All variables and constraints are indexed by time. Temporal effects sum over timesteps.
+
+=== "Scenarios"
+
+ Represent uncertainty (weather, prices). Operations vary per scenario, investments are shared:
+
+ ```python
+ fx.Optimization(
+ flow_system,
+ time_series_data={
+ 'sunny_year': sunny_data,
+ 'cloudy_year': cloudy_data,
+ },
+ scenario_weights={'sunny_year': 0.7, 'cloudy_year': 0.3},
+ )
+ ```
+
+ Scenarios are independent — no energy or information exchange between them.
+
+=== "Periods"
+
+ Sequential time blocks (years) for multi-period planning:
+
+ ```python
+ fx.SegmentedTimeSeriesCollection(
+ time_series_data={'2025': data_2025, '2030': data_2030},
+ )
+ ```
+
+ Periods are independent — each has its own investment decisions.
+
+---
+
+## Objective Function
+
+The objective aggregates effects across all dimensions with weights:
+
+=== "Basic"
+
+ Single period, no scenarios:
+
+ $$\min \quad E_{per} + \sum_t E_{temp}(t)$$
+
+=== "With Scenarios"
+
+ Investment decided once, operations weighted by probability:
+
+ $$\min \quad E_{per} + \sum_s w_s \cdot \sum_t E_{temp}(t, s)$$
+
+ - $w_s$ — scenario weight (probability)
+
+=== "With Periods"
+
+ Multi-year planning with discounting:
+
+ $$\min \quad \sum_y w_y \cdot \left( E_{per}(y) + \sum_t E_{temp}(t, y) \right)$$
+
+ - $w_y$ — period weight (duration or discount factor)
+
+=== "Full"
+
+ Periods × Scenarios:
+
+ $$\min \quad \sum_y w_y \cdot \left( E_{per}(y) + \sum_s w_s \cdot \sum_t E_{temp}(t, y, s) \right)$$
+
+The penalty effect is always included: $\min \quad E_{objective} + E_{penalty}$
+
+---
+
+## Weights
+
+=== "Scenario Weights"
+
+ Provided explicitly — typically probabilities:
+
+ ```python
+ scenario_weights={'base': 0.6, 'high_demand': 0.4}
+ ```
+
+ Default: equal weights, normalized to sum to 1.
+
+=== "Period Weights"
+
+ Computed automatically from period index (interval sizes):
+
+ ```python
+ periods = pd.Index([2020, 2025, 2030])
+ # → weights: [5, 5, 5] (5-year intervals)
+ ```
+
+=== "Combined"
+
+ When both present:
+
+ $w_{y,s} = w_y \cdot w_s$
+
+---
+
+## Constraints on Effects
+
+=== "Total Limit"
+
+ Bound on aggregated effect:
+
+ ```python
+ fx.Effect(label='co2', unit='kg', maximum_total=100_000)
+ ```
+
+=== "Per-Timestep Limit"
+
+ Bound at each timestep:
+
+ ```python
+ fx.Effect(label='peak', unit='kW', maximum_per_hour=500)
+ ```
+
+=== "Periodic Limit"
+
+ Bound on periodic component:
+
+ ```python
+ fx.Effect(label='capex', unit='€', maximum_periodic=1_000_000)
+ ```
+
+---
+
+## Cross-Effects
+
+Effects can contribute to each other (e.g., carbon pricing):
+
+```python
+co2 = fx.Effect(label='co2', unit='kg')
+
+costs = fx.Effect(
+ label='costs', unit='€', is_objective=True,
+ share_from_temporal={'co2': 0.08}, # €80/tonne
+)
+```
+
+---
+
+## Penalty Effect
+
+A built-in `Penalty` effect enables soft constraints and prevents infeasibility:
+
+```python
+fx.StatusParameters(effects_per_startup={'Penalty': 1})
+fx.Bus(label='heat', excess_penalty_per_flow_hour=1e5)
+```
+
+Penalty is weighted identically to the objective effect across all dimensions.
+
+---
+
+## Shared vs Independent Decisions
+
+=== "Investments (Sizes)"
+
+ By default, investment decisions are **shared across scenarios** within a period:
+
+ - Build capacity once → operate differently per scenario
+ - Reflects real-world investment under uncertainty
+
+ $$P(y) \quad \text{(one decision per period, used in all scenarios)}$$
+
+=== "Operations (Flows)"
+
+ By default, operational decisions are **independent per scenario**:
+
+ $$p(t, y, s) \quad \text{(different for each scenario)}$$
+
+---
+
+## Reference
+
+| Variable | Description |
+|----------|-------------|
+| $E_{temp}(t)$ | Temporal effect at timestep $t$ |
+| $E_{per}$ | Periodic effect |
+| $w_s$ | Scenario weight |
+| $w_y$ | Period weight |
+
+| Parameter | Python | Description |
+|-----------|--------|-------------|
+| Objective | `is_objective=True` | Minimize this effect |
+| Total limit | `maximum_total` | Upper bound on total |
+| Timestep limit | `maximum_per_hour` | Upper bound per timestep |
+| Periodic limit | `maximum_periodic` | Upper bound on periodic |
+| Cross-effect | `share_from_temporal` | Link from other effect |
+
+**Classes:** [`Effect`][flixopt.effects.Effect], [`EffectCollection`][flixopt.effects.EffectCollection]
diff --git a/docs/user-guide/mathematical-notation/effects-penalty-objective.md b/docs/user-guide/mathematical-notation/effects-penalty-objective.md
deleted file mode 100644
index 4933fe114..000000000
--- a/docs/user-guide/mathematical-notation/effects-penalty-objective.md
+++ /dev/null
@@ -1,163 +0,0 @@
-# Effects & Objective
-
-Effects track metrics (costs, CO₂, energy) and define what you optimize.
-
-## Basic: Defining Effects
-
-```python
-costs = fx.Effect(label='costs', unit='€', is_objective=True)
-co2 = fx.Effect(label='co2', unit='kg')
-
-flow_system.add_elements(costs, co2)
-```
-
-One effect is the **objective** (minimized). Others are tracked or constrained.
-
----
-
-## Temporal vs Periodic
-
-Effects have two components with different time behavior:
-
-=== "Temporal (Operational)"
-
- Accumulated over timesteps — fuel costs, emissions, energy use:
-
- $E_{temp} = \sum_t s(t) \cdot \Delta t$
-
- ```python
- gas = fx.Flow(
- ...,
- effects_per_flow_hour={'costs': 50}, # €50/MWh
- )
- ```
-
-=== "Periodic (Investment)"
-
- Time-independent — incurred once per period:
-
- $E_{per} = P \cdot c_{inv}$
-
- ```python
- fx.InvestParameters(
- specific_effects={'costs': 200}, # €200/kW
- )
- ```
-
-=== "Total"
-
- $E = E_{per} + E_{temp}$
-
----
-
-## Dimensions & Weights
-
-Effects aggregate across dimensions with weights:
-
-=== "Single Period"
-
- $$\min \quad E_{per} + \sum_t E_{temp}(t)$$
-
-=== "With Scenarios"
-
- Scenarios represent uncertainty (weather, prices). Temporal effects are scenario-specific, periodic effects are shared:
-
- $$\min \quad E_{per} + \sum_s w_s \cdot \sum_t E_{temp}(t, s)$$
-
- Investment decided once, operations vary by scenario.
-
-=== "With Periods"
-
- Periods represent sequential time blocks (years). Each has independent effects:
-
- $$\min \quad \sum_y w_y \cdot \left( E_{per}(y) + \sum_t E_{temp}(t, y) \right)$$
-
-=== "Full (Periods + Scenarios)"
-
- $$\min \quad \sum_y w_y \cdot \left( E_{per}(y) + \sum_s w_s \cdot \sum_t E_{temp}(t, y, s) \right)$$
-
- - $w_y$ — period weight (e.g., discount factor)
- - $w_s$ — scenario weight (e.g., probability)
-
----
-
-## Constraints on Effects
-
-=== "Total Limit"
-
- ```python
- co2 = fx.Effect(label='co2', unit='kg', maximum_total=100_000)
- ```
-
- $E_{co2} \leq 100{,}000$
-
-=== "Per-Timestep Limit"
-
- ```python
- peak = fx.Effect(label='peak', unit='kW', maximum_per_hour=500)
- ```
-
- $E_{peak}(t) \leq 500 \quad \forall t$
-
-=== "Periodic Limit"
-
- ```python
- capex = fx.Effect(label='capex', unit='€', maximum_periodic=1_000_000)
- ```
-
- $E_{capex,per} \leq 1{,}000{,}000$
-
----
-
-## Cross-Effects
-
-Effects can contribute to each other (e.g., carbon pricing):
-
-```python
-co2 = fx.Effect(label='co2', unit='kg')
-
-costs = fx.Effect(
- label='costs', unit='€', is_objective=True,
- share_from_temporal={'co2': 0.08}, # €0.08/kg = €80/tonne
-)
-```
-
-CO₂ emissions automatically add to costs.
-
----
-
-## Penalty Effect
-
-A built-in `Penalty` effect prevents infeasibility and allows soft biases:
-
-```python
-# Bias against startups without affecting cost tracking
-fx.StatusParameters(effects_per_startup={'Penalty': 1})
-
-# Bus imbalance penalty
-fx.Bus(label='heat', excess_penalty_per_flow_hour=1e5)
-```
-
-The objective is always: $\min \quad E_{objective} + E_{penalty}$
-
----
-
-## Reference
-
-| Component | Description |
-|-----------|-------------|
-| $E_{temp}(t)$ | Temporal effect at timestep $t$ |
-| $E_{per}$ | Periodic effect (time-independent) |
-| $E$ | Total = periodic + sum of temporal |
-| $w_s$ | Scenario weight |
-| $w_y$ | Period weight |
-
-| Parameter | Python | Description |
-|-----------|--------|-------------|
-| Objective | `is_objective=True` | Minimize this effect |
-| Total limit | `maximum_total` | Upper bound on total |
-| Timestep limit | `maximum_per_hour` | Upper bound per timestep |
-| Periodic limit | `maximum_periodic` | Upper bound on periodic |
-| Cross-effect | `share_from_temporal` | Link from other effect |
-
-**Classes:** [`Effect`][flixopt.effects.Effect], [`EffectCollection`][flixopt.effects.EffectCollection]
diff --git a/docs/user-guide/mathematical-notation/index.md b/docs/user-guide/mathematical-notation/index.md
index 1d34865a2..95e21db5e 100644
--- a/docs/user-guide/mathematical-notation/index.md
+++ b/docs/user-guide/mathematical-notation/index.md
@@ -20,11 +20,7 @@ The documentation follows the same structure as Core Concepts:
| **Flows** — what moves | [Flow](elements/Flow.md) — capacity bounds, load factors, profiles |
| **Converters** — transform things | [LinearConverter](elements/LinearConverter.md) — conversion ratios |
| **Storages** — save for later | [Storage](elements/Storage.md) — charge dynamics, efficiency losses |
-| **Effects** — what you track | [Effects & Objective](effects-penalty-objective.md) — cost aggregation, constraints |
-
-Additional sections cover:
-
-- **[Dimensions](dimensions.md)** — Time, periods, and scenarios
+| **Effects** — what you track | [Effects & Dimensions](effects-and-dimensions.md) — objectives, costs, scenarios, periods |
## Notation Conventions
@@ -108,4 +104,4 @@ Start with the element that's most relevant to your question:
- **Why is my component not running?** → [Flow](elements/Flow.md) (capacity bounds)
- **How does storage charge/discharge?** → [Storage](elements/Storage.md) (charge dynamics)
- **How are efficiencies handled?** → [LinearConverter](elements/LinearConverter.md) (conversion)
-- **How are costs calculated?** → [Effects & Objective](effects-penalty-objective.md)
+- **How are costs calculated?** → [Effects & Dimensions](effects-and-dimensions.md)
diff --git a/flixopt/effects.py b/flixopt/effects.py
index 5ffc9630a..804fe0376 100644
--- a/flixopt/effects.py
+++ b/flixopt/effects.py
@@ -42,7 +42,7 @@ class Effect(Element):
pricing), and flexible constraint formulation.
Mathematical Formulation:
- See
+ See
Args:
label: The label of the Element. Used to identify it in the FlowSystem.
@@ -310,7 +310,7 @@ class EffectModel(ElementModel):
and effect bounds.
Mathematical Formulation:
- See
+ See
"""
element: Effect # Type hint
diff --git a/mkdocs.yml b/mkdocs.yml
index 1a4d5842e..8fb6765ae 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -32,11 +32,10 @@ nav:
- Flow: user-guide/mathematical-notation/elements/Flow.md
- LinearConverter: user-guide/mathematical-notation/elements/LinearConverter.md
- Storage: user-guide/mathematical-notation/elements/Storage.md
- - Effects & Objective: user-guide/mathematical-notation/effects-penalty-objective.md
+ - Effects & Dimensions: user-guide/mathematical-notation/effects-and-dimensions.md
- Investment: user-guide/mathematical-notation/features/InvestParameters.md
- Status: user-guide/mathematical-notation/features/StatusParameters.md
- Piecewise: user-guide/mathematical-notation/features/Piecewise.md
- - Dimensions: user-guide/mathematical-notation/dimensions.md
- Recipes: user-guide/recipes/index.md
- Support:
- FAQ: user-guide/faq.md
From 5caabf7ee6dd2438d86bc1386d5f63e3007ede7b Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 04:48:43 +0100
Subject: [PATCH 74/79] The dimension examples now correctly show how to assign
them to FlowSystem using pd.Index:
---
.../effects-and-dimensions.md | 20 +++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/effects-and-dimensions.md b/docs/user-guide/mathematical-notation/effects-and-dimensions.md
index 3370bd21b..12144fa4b 100644
--- a/docs/user-guide/mathematical-notation/effects-and-dimensions.md
+++ b/docs/user-guide/mathematical-notation/effects-and-dimensions.md
@@ -52,7 +52,9 @@ The model operates across three dimensions:
The basic time resolution — always required:
```python
- timesteps = pd.date_range('2024-01-01', periods=8760, freq='h')
+ flow_system = fx.FlowSystem(
+ timesteps=pd.date_range('2024-01-01', periods=8760, freq='h'),
+ )
```
All variables and constraints are indexed by time. Temporal effects sum over timesteps.
@@ -62,13 +64,10 @@ The model operates across three dimensions:
Represent uncertainty (weather, prices). Operations vary per scenario, investments are shared:
```python
- fx.Optimization(
- flow_system,
- time_series_data={
- 'sunny_year': sunny_data,
- 'cloudy_year': cloudy_data,
- },
- scenario_weights={'sunny_year': 0.7, 'cloudy_year': 0.3},
+ flow_system = fx.FlowSystem(
+ timesteps=pd.date_range('2024-01-01', periods=8760, freq='h'),
+ scenarios=pd.Index(['sunny_year', 'cloudy_year']),
+ scenario_weights=[0.7, 0.3],
)
```
@@ -79,8 +78,9 @@ The model operates across three dimensions:
Sequential time blocks (years) for multi-period planning:
```python
- fx.SegmentedTimeSeriesCollection(
- time_series_data={'2025': data_2025, '2030': data_2030},
+ flow_system = fx.FlowSystem(
+ timesteps=pd.date_range('2024-01-01', periods=8760, freq='h'),
+ periods=pd.Index([2025, 2030]),
)
```
From cb950f561ecc3918cd5f9765cd3b417c659a1e0a Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 04:54:00 +0100
Subject: [PATCH 75/79] Update effects-and-dimensions.md
---
.../effects-and-dimensions.md | 188 ++++++++++++++++--
1 file changed, 174 insertions(+), 14 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/effects-and-dimensions.md b/docs/user-guide/mathematical-notation/effects-and-dimensions.md
index 12144fa4b..e88209ff2 100644
--- a/docs/user-guide/mathematical-notation/effects-and-dimensions.md
+++ b/docs/user-guide/mathematical-notation/effects-and-dimensions.md
@@ -19,9 +19,9 @@ One effect is the **objective** (minimized). Others are tracked or constrained.
=== "Temporal"
- Accumulated over timesteps — operational costs, emissions, energy - per timestep:
+ Accumulated over timesteps — operational costs, emissions, energy:
- $E_{temp}(t) = \text{flow}(t) \cdot c \cdot \Delta t$
+ $E_{temp}(t) = \text{value}(t) \cdot c \cdot \Delta t$
```python
fx.Flow(..., effects_per_flow_hour={'costs': 50}) # €50/MWh
@@ -29,12 +29,12 @@ One effect is the **objective** (minimized). Others are tracked or constrained.
=== "Periodic"
- Time-independent — investment costs, fixed fees - per period:
+ Time-independent — investment costs, fixed fees:
$E_{per} = P \cdot c_{inv}$
```python
- fx.InvestParameters(specific_effects={'costs': 200}) # €200/kW
+ fx.InvestParameters(effects_of_investment_per_size={'costs': 200}) # €200/kW
```
=== "Total"
@@ -43,6 +43,46 @@ One effect is the **objective** (minimized). Others are tracked or constrained.
---
+## Where Effects Are Contributed
+
+=== "Flow"
+
+ ```python
+ fx.Flow(
+ effects_per_flow_hour={'costs': 50, 'co2': 0.2}, # Per MWh
+ )
+ ```
+
+=== "Status"
+
+ ```python
+ fx.StatusParameters(
+ effects_per_startup={'costs': 1000}, # Per startup event
+ effects_per_active_hour={'costs': 10}, # Per hour while running
+ )
+ ```
+
+=== "Investment"
+
+ ```python
+ fx.InvestParameters(
+ effects_of_investment={'costs': 50000}, # Fixed if investing
+ effects_of_investment_per_size={'costs': 800}, # Per kW installed
+ effects_of_retirement={'costs': 10000}, # If NOT investing
+ )
+ ```
+
+=== "Bus"
+
+ ```python
+ fx.Bus(
+ excess_penalty_per_flow_hour=1e6, # Penalty for excess
+ shortage_penalty_per_flow_hour=1e6, # Penalty for shortage
+ )
+ ```
+
+---
+
## Dimensions
The model operates across three dimensions:
@@ -131,7 +171,7 @@ The penalty effect is always included: $\min \quad E_{objective} + E_{penalty}$
Provided explicitly — typically probabilities:
```python
- scenario_weights={'base': 0.6, 'high_demand': 0.4}
+ scenario_weights=[0.6, 0.4]
```
Default: equal weights, normalized to sum to 1.
@@ -157,7 +197,7 @@ The penalty effect is always included: $\min \quad E_{objective} + E_{penalty}$
=== "Total Limit"
- Bound on aggregated effect:
+ Bound on aggregated effect (temporal + periodic) per period:
```python
fx.Effect(label='co2', unit='kg', maximum_total=100_000)
@@ -173,12 +213,28 @@ The penalty effect is always included: $\min \quad E_{objective} + E_{penalty}$
=== "Periodic Limit"
- Bound on periodic component:
+ Bound on periodic component only:
```python
fx.Effect(label='capex', unit='€', maximum_periodic=1_000_000)
```
+=== "Temporal Limit"
+
+ Bound on temporal component only:
+
+ ```python
+ fx.Effect(label='opex', unit='€', maximum_temporal=500_000)
+ ```
+
+=== "Over All Periods"
+
+ Bound across all periods (weighted sum):
+
+ ```python
+ fx.Effect(label='co2', unit='kg', maximum_over_periods=1_000_000)
+ ```
+
---
## Cross-Effects
@@ -228,6 +284,110 @@ Penalty is weighted identically to the objective effect across all dimensions.
---
+## Use Cases
+
+=== "Carbon Budget"
+
+ Limit total CO₂ emissions across all years:
+
+ ```python
+ co2 = fx.Effect(
+ label='co2', unit='kg',
+ maximum_over_periods=1_000_000, # 1000 tonnes total
+ )
+
+ # Contribute emissions from gas consumption
+ gas_flow = fx.Flow(
+ label='gas', bus=gas_bus,
+ effects_per_flow_hour={'co2': 0.2}, # 0.2 kg/kWh
+ )
+ ```
+
+=== "Investment Budget"
+
+ Cap annual investment spending:
+
+ ```python
+ capex = fx.Effect(
+ label='capex', unit='€',
+ maximum_periodic=5_000_000, # €5M per period
+ )
+
+ battery = fx.Storage(
+ ...,
+ capacity=fx.InvestParameters(
+ effects_of_investment_per_size={'capex': 600}, # €600/kWh
+ ),
+ )
+ ```
+
+=== "Peak Demand Charge"
+
+ Track and limit peak power:
+
+ ```python
+ peak = fx.Effect(
+ label='peak', unit='kW',
+ maximum_per_hour=1000, # Grid connection limit
+ )
+
+ grid_import = fx.Flow(
+ label='import', bus=elec_bus,
+ effects_per_flow_hour={'peak': 1}, # Track instantaneous power
+ )
+ ```
+
+=== "Carbon Pricing"
+
+ Add CO₂ cost to objective automatically:
+
+ ```python
+ co2 = fx.Effect(label='co2', unit='kg')
+
+ costs = fx.Effect(
+ label='costs', unit='€', is_objective=True,
+ share_from_temporal={'co2': 0.08}, # €80/tonne carbon price
+ )
+
+ # Now any CO₂ contribution automatically adds to costs
+ ```
+
+=== "Land Use Constraint"
+
+ Limit total land area for installations:
+
+ ```python
+ land = fx.Effect(
+ label='land', unit='m²',
+ maximum_periodic=50_000, # 5 hectares max
+ )
+
+ pv = fx.Source(
+ ...,
+ output=fx.Flow(
+ ...,
+ invest_parameters=fx.InvestParameters(
+ effects_of_investment_per_size={'land': 5}, # 5 m²/kWp
+ ),
+ ),
+ )
+ ```
+
+=== "Multi-Criteria Optimization"
+
+ Track multiple objectives, optimize one:
+
+ ```python
+ costs = fx.Effect(label='costs', unit='€', is_objective=True)
+ co2 = fx.Effect(label='co2', unit='kg')
+ primary_energy = fx.Effect(label='PE', unit='kWh')
+
+ # All are tracked, costs is minimized
+ # Use maximum_total on co2 for ε-constraint method
+ ```
+
+---
+
## Reference
| Variable | Description |
@@ -237,12 +397,12 @@ Penalty is weighted identically to the objective effect across all dimensions.
| $w_s$ | Scenario weight |
| $w_y$ | Period weight |
-| Parameter | Python | Description |
-|-----------|--------|-------------|
-| Objective | `is_objective=True` | Minimize this effect |
-| Total limit | `maximum_total` | Upper bound on total |
-| Timestep limit | `maximum_per_hour` | Upper bound per timestep |
-| Periodic limit | `maximum_periodic` | Upper bound on periodic |
-| Cross-effect | `share_from_temporal` | Link from other effect |
+| Constraint | Python | Scope |
+|-----------|--------|-------|
+| Total limit | `maximum_total` | Per period |
+| Timestep limit | `maximum_per_hour` | Each timestep |
+| Periodic limit | `maximum_periodic` | Per period (periodic only) |
+| Temporal limit | `maximum_temporal` | Per period (temporal only) |
+| Global limit | `maximum_over_periods` | Across all periods |
**Classes:** [`Effect`][flixopt.effects.Effect], [`EffectCollection`][flixopt.effects.EffectCollection]
From d88adc9283a1137c571c44bf5588fbaff4c69f5e Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 04:57:34 +0100
Subject: [PATCH 76/79] Update effects-and-dimensions.md
---
.../user-guide/mathematical-notation/effects-and-dimensions.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/user-guide/mathematical-notation/effects-and-dimensions.md b/docs/user-guide/mathematical-notation/effects-and-dimensions.md
index e88209ff2..3f6afea36 100644
--- a/docs/user-guide/mathematical-notation/effects-and-dimensions.md
+++ b/docs/user-guide/mathematical-notation/effects-and-dimensions.md
@@ -21,7 +21,8 @@ One effect is the **objective** (minimized). Others are tracked or constrained.
Accumulated over timesteps — operational costs, emissions, energy:
- $E_{temp}(t) = \text{value}(t) \cdot c \cdot \Delta t$
+ - Per flow hour: $E(t) = p(t) \cdot c \cdot \Delta t$
+ - Per event (startup): $E(t) = s^{start}(t) \cdot c$
```python
fx.Flow(..., effects_per_flow_hour={'costs': 50}) # €50/MWh
From dbfefac22949c965107b4c5aa147a2567c2ff782 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 05:09:20 +0100
Subject: [PATCH 77/79] updated all reference tables across all Mathematical
Notation pages to be consistent
---
.../effects-and-dimensions.md | 18 ++++++++-----
.../mathematical-notation/elements/Bus.md | 15 +++++------
.../mathematical-notation/elements/Flow.md | 21 +++++++--------
.../elements/LinearConverter.md | 10 +++----
.../mathematical-notation/elements/Storage.md | 25 +++++++++--------
.../features/InvestParameters.md | 24 +++++++----------
.../features/Piecewise.md | 21 +++++++--------
.../features/StatusParameters.md | 27 ++++++++-----------
8 files changed, 74 insertions(+), 87 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/effects-and-dimensions.md b/docs/user-guide/mathematical-notation/effects-and-dimensions.md
index 3f6afea36..cd9e7d4d5 100644
--- a/docs/user-guide/mathematical-notation/effects-and-dimensions.md
+++ b/docs/user-guide/mathematical-notation/effects-and-dimensions.md
@@ -391,12 +391,18 @@ Penalty is weighted identically to the objective effect across all dimensions.
## Reference
-| Variable | Description |
-|----------|-------------|
-| $E_{temp}(t)$ | Temporal effect at timestep $t$ |
-| $E_{per}$ | Periodic effect |
-| $w_s$ | Scenario weight |
-| $w_y$ | Period weight |
+| Symbol | Type | Description |
+|--------|------|-------------|
+| $E_{temp}(t)$ | $\mathbb{R}$ | Temporal effect at timestep $t$ |
+| $E_{per}$ | $\mathbb{R}$ | Periodic effect (per period) |
+| $E$ | $\mathbb{R}$ | Total effect ($E_{per} + \sum_t E_{temp}(t)$) |
+| $w_s$ | $[0, 1]$ | Scenario weight (probability) |
+| $w_y$ | $\mathbb{R}_{> 0}$ | Period weight (duration/discount) |
+| $p(t)$ | $\mathbb{R}_{\geq 0}$ | Flow rate at timestep $t$ |
+| $s^{start}(t)$ | $\{0, 1\}$ | Startup indicator |
+| $P$ | $\mathbb{R}_{\geq 0}$ | Investment size |
+| $c$ | $\mathbb{R}$ | Effect coefficient |
+| $\Delta t$ | $\mathbb{R}_{> 0}$ | Timestep duration (hours) |
| Constraint | Python | Scope |
|-----------|--------|-------|
diff --git a/docs/user-guide/mathematical-notation/elements/Bus.md b/docs/user-guide/mathematical-notation/elements/Bus.md
index 6fb5050e5..7fc1f87a3 100644
--- a/docs/user-guide/mathematical-notation/elements/Bus.md
+++ b/docs/user-guide/mathematical-notation/elements/Bus.md
@@ -41,13 +41,12 @@ heat_bus = fx.Bus(
## Reference
-| Variable | Description |
-|----------|-------------|
-| $\phi_{in}(t)$ | Virtual supply (covers shortages) |
-| $\phi_{out}(t)$ | Virtual demand (absorbs surplus) |
-
-| Parameter | Python | Default |
-|-----------|--------|---------|
-| Penalty | `excess_penalty_per_flow_hour` | `1e5` |
+| Symbol | Type | Description |
+|--------|------|-------------|
+| $p(t)$ | $\mathbb{R}_{\geq 0}$ | Flow rate of connected flows |
+| $\phi_{in}(t)$ | $\mathbb{R}_{\geq 0}$ | Slack: virtual supply (covers shortages) |
+| $\phi_{out}(t)$ | $\mathbb{R}_{\geq 0}$ | Slack: virtual demand (absorbs surplus) |
+| $c_\phi$ | $\mathbb{R}_{\geq 0}$ | Penalty factor (`excess_penalty_per_flow_hour`) |
+| $\Delta t$ | $\mathbb{R}_{> 0}$ | Timestep duration (hours) |
**Classes:** [`Bus`][flixopt.elements.Bus], [`BusModel`][flixopt.elements.BusModel]
diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md
index 8bf4f8817..91bf0589c 100644
--- a/docs/user-guide/mathematical-notation/elements/Flow.md
+++ b/docs/user-guide/mathematical-notation/elements/Flow.md
@@ -118,17 +118,14 @@ heat = fx.Flow(label='heat', bus=heat_bus, size=100, relative_minimum=0.3)
## Reference
-| Variable | Description |
-|----------|-------------|
-| $p(t)$ | Flow rate (optimized) |
-| $P$ | Size — fixed or variable with `InvestParameters` |
-| $s(t)$ | Binary status — with `status_parameters` |
-
-| Parameter | Python | Default |
-|-----------|--------|---------|
-| Capacity | `size` | required |
-| Min fraction | `relative_minimum` | 0 |
-| Max fraction | `relative_maximum` | 1 |
-| Fixed profile | `fixed_relative_profile` | None |
+| Symbol | Type | Description |
+|--------|------|-------------|
+| $p(t)$ | $\mathbb{R}_{\geq 0}$ | Flow rate at timestep $t$ |
+| $P$ | $\mathbb{R}_{\geq 0}$ | Size (capacity) — fixed or optimized |
+| $s(t)$ | $\{0, 1\}$ | Binary status (with `status_parameters`) |
+| $p_{rel}^{min}$ | $[0, 1]$ | Minimum relative flow (`relative_minimum`) |
+| $p_{rel}^{max}$ | $[0, 1]$ | Maximum relative flow (`relative_maximum`) |
+| $\pi(t)$ | $[0, 1]$ | Fixed profile (`fixed_relative_profile`) |
+| $\Delta t$ | $\mathbb{R}_{> 0}$ | Timestep duration (hours) |
**Classes:** [`Flow`][flixopt.elements.Flow], [`FlowModel`][flixopt.elements.FlowModel]
diff --git a/docs/user-guide/mathematical-notation/elements/LinearConverter.md b/docs/user-guide/mathematical-notation/elements/LinearConverter.md
index 9b0f704a8..915537d60 100644
--- a/docs/user-guide/mathematical-notation/elements/LinearConverter.md
+++ b/docs/user-guide/mathematical-notation/elements/LinearConverter.md
@@ -142,10 +142,10 @@ chp = fx.linear_converters.CHP(
The converter creates **constraints** linking flows, not new variables.
-| Parameter | Python | Description |
-|-----------|--------|-------------|
-| Inputs | `inputs` | List of input Flows |
-| Outputs | `outputs` | List of output Flows |
-| Ratios | `conversion_factors` | List of factor dicts |
+| Symbol | Type | Description |
+|--------|------|-------------|
+| $p_f(t)$ | $\mathbb{R}_{\geq 0}$ | Flow rate of flow $f$ at timestep $t$ |
+| $a_f$ | $\mathbb{R}$ | Conversion factor for input flow $f$ |
+| $b_f$ | $\mathbb{R}$ | Conversion factor for output flow $f$ |
**Classes:** [`LinearConverter`][flixopt.components.LinearConverter], [`LinearConverterModel`][flixopt.components.LinearConverterModel]
diff --git a/docs/user-guide/mathematical-notation/elements/Storage.md b/docs/user-guide/mathematical-notation/elements/Storage.md
index 7dd5a3e32..35f687f80 100644
--- a/docs/user-guide/mathematical-notation/elements/Storage.md
+++ b/docs/user-guide/mathematical-notation/elements/Storage.md
@@ -104,18 +104,17 @@ fx.Storage(...,
## Reference
-| Variable | Description |
-|----------|-------------|
-| $c(t)$ | Charge state |
-| $p_{in}(t)$ | Charging power (from `charging` flow) |
-| $p_{out}(t)$ | Discharging power (from `discharging` flow) |
-
-| Parameter | Python | Default |
-|-----------|--------|---------|
-| Capacity | `capacity_in_flow_hours` | required |
-| Charge efficiency | `eta_charge` | 1.0 |
-| Discharge efficiency | `eta_discharge` | 1.0 |
-| Self-discharge | `relative_loss_per_hour` | 0 |
-| Initial charge | `initial_charge_state` | 0 |
+| Symbol | Type | Description |
+|--------|------|-------------|
+| $c(t)$ | $\mathbb{R}_{\geq 0}$ | Charge state at timestep $t$ |
+| $C$ | $\mathbb{R}_{\geq 0}$ | Capacity (`capacity_in_flow_hours`) |
+| $p_{in}(t)$ | $\mathbb{R}_{\geq 0}$ | Charging power (from `charging` flow) |
+| $p_{out}(t)$ | $\mathbb{R}_{\geq 0}$ | Discharging power (from `discharging` flow) |
+| $\eta_{in}$ | $[0, 1]$ | Charge efficiency (`eta_charge`) |
+| $\eta_{out}$ | $[0, 1]$ | Discharge efficiency (`eta_discharge`) |
+| $\dot{c}_{loss}$ | $[0, 1]$ | Self-discharge rate (`relative_loss_per_hour`) |
+| $c_{rel}^{min}$ | $[0, 1]$ | Min charge state (`relative_minimum_charge_state`) |
+| $c_{rel}^{max}$ | $[0, 1]$ | Max charge state (`relative_maximum_charge_state`) |
+| $\Delta t$ | $\mathbb{R}_{> 0}$ | Timestep duration (hours) |
**Classes:** [`Storage`][flixopt.components.Storage], [`StorageModel`][flixopt.components.StorageModel]
diff --git a/docs/user-guide/mathematical-notation/features/InvestParameters.md b/docs/user-guide/mathematical-notation/features/InvestParameters.md
index f32b897d5..b6e1afe6b 100644
--- a/docs/user-guide/mathematical-notation/features/InvestParameters.md
+++ b/docs/user-guide/mathematical-notation/features/InvestParameters.md
@@ -130,20 +130,14 @@ By default, investment is **optional** — the optimizer can choose $P = 0$ (don
## Reference
-| Variable | Description |
-|----------|-------------|
-| $P$ | Investment size |
-| $s_{inv}$ | Binary decision (0=no, 1=yes) |
-
-| Parameter | Python | Description |
-|-----------|--------|-------------|
-| Min size | `minimum_size` | Lower bound |
-| Max size | `maximum_size` | Upper bound |
-| Fixed size | `fixed_size` | Binary: this or nothing |
-| Per-size cost | `specific_effects` | €/unit |
-| Fixed cost | `effects_of_investment` | One-time if investing |
-| Retirement | `effects_of_retirement` | Cost if not investing |
-| Piecewise cost | `piecewise_effects_of_investment` | Non-linear cost curve |
-| Force invest | `mandatory` | No binary variable |
+| Symbol | Type | Description |
+|--------|------|-------------|
+| $P$ | $\mathbb{R}_{\geq 0}$ | Investment size (capacity) |
+| $s_{inv}$ | $\{0, 1\}$ | Binary investment decision (0=no, 1=yes) |
+| $P^{min}$ | $\mathbb{R}_{\geq 0}$ | Minimum size (`minimum_size`) |
+| $P^{max}$ | $\mathbb{R}_{\geq 0}$ | Maximum size (`maximum_size`) |
+| $c_{spec}$ | $\mathbb{R}$ | Per-size effect (`effects_of_investment_per_size`) |
+| $c_{fix}$ | $\mathbb{R}$ | Fixed effect (`effects_of_investment`) |
+| $c_{ret}$ | $\mathbb{R}$ | Retirement effect (`effects_of_retirement`) |
**Classes:** [`InvestParameters`][flixopt.interface.InvestParameters], [`InvestmentModel`][flixopt.features.InvestmentModel]
diff --git a/docs/user-guide/mathematical-notation/features/Piecewise.md b/docs/user-guide/mathematical-notation/features/Piecewise.md
index fda04eeae..da6405b52 100644
--- a/docs/user-guide/mathematical-notation/features/Piecewise.md
+++ b/docs/user-guide/mathematical-notation/features/Piecewise.md
@@ -142,17 +142,14 @@ When segment $i$ is active ($s_i = 1$), the lambdas interpolate between $x_i^{st
## Reference
-| Variable | Description |
-|----------|-------------|
-| $s_i$ | Binary: segment $i$ is active |
-| $\lambda_i^0$ | Interpolation weight for segment start |
-| $\lambda_i^1$ | Interpolation weight for segment end |
-
-| Class | Description |
-|-------|-------------|
-| `Piece(start, end)` | Linear segment |
-| `Piecewise([pieces])` | Collection of segments |
-| `PiecewiseConversion({flow: piecewise})` | Synchronized multi-flow conversion |
-| `PiecewiseEffects(origin, shares)` | Size-to-effect mapping |
+| Symbol | Type | Description |
+|--------|------|-------------|
+| $x$ | $\mathbb{R}$ | Interpolated variable value |
+| $s_i$ | $\{0, 1\}$ | Binary: segment $i$ is active |
+| $\lambda_i^0$ | $[0, 1]$ | Interpolation weight for segment start |
+| $\lambda_i^1$ | $[0, 1]$ | Interpolation weight for segment end |
+| $x_i^{start}$ | $\mathbb{R}$ | Start value of segment $i$ |
+| $x_i^{end}$ | $\mathbb{R}$ | End value of segment $i$ |
+| $n$ | $\mathbb{Z}_{> 0}$ | Number of segments |
**Classes:** [`Piecewise`][flixopt.interface.Piecewise], [`Piece`][flixopt.interface.Piece], [`PiecewiseConversion`][flixopt.interface.PiecewiseConversion], [`PiecewiseEffects`][flixopt.interface.PiecewiseEffects]
diff --git a/docs/user-guide/mathematical-notation/features/StatusParameters.md b/docs/user-guide/mathematical-notation/features/StatusParameters.md
index 822874a93..29b7fbfba 100644
--- a/docs/user-guide/mathematical-notation/features/StatusParameters.md
+++ b/docs/user-guide/mathematical-notation/features/StatusParameters.md
@@ -99,21 +99,16 @@ Detect transitions: $s^{start}(t) - s^{stop}(t) = s(t) - s(t-1)$
## Reference
-| Variable | Description |
-|----------|-------------|
-| $s(t)$ | Binary status (0=off, 1=on) |
-| $s^{start}(t)$ | Startup indicator |
-| $s^{stop}(t)$ | Shutdown indicator |
-
-| Parameter | Python | Description |
-|-----------|--------|-------------|
-| Startup cost | `effects_per_startup` | Per-startup effects |
-| Running cost | `effects_per_active_hour` | Per-hour effects |
-| Min on | `min_uptime` | Min consecutive hours on |
-| Min off | `min_downtime` | Min consecutive hours off |
-| Max on | `max_uptime` | Max consecutive hours on |
-| Total min | `active_hours_min` | Min total hours |
-| Total max | `active_hours_max` | Max total hours |
-| Max starts | `startup_limit` | Max startups per period |
+| Symbol | Type | Description |
+|--------|------|-------------|
+| $s(t)$ | $\{0, 1\}$ | Binary status (0=off, 1=on) |
+| $s^{start}(t)$ | $\{0, 1\}$ | Startup indicator |
+| $s^{stop}(t)$ | $\{0, 1\}$ | Shutdown indicator |
+| $T_{up}^{min}$ | $\mathbb{Z}_{\geq 0}$ | Min uptime (`min_uptime`) |
+| $T_{up}^{max}$ | $\mathbb{Z}_{\geq 0}$ | Max uptime (`max_uptime`) |
+| $T_{down}^{min}$ | $\mathbb{Z}_{\geq 0}$ | Min downtime (`min_downtime`) |
+| $H^{min}$ | $\mathbb{R}_{\geq 0}$ | Min total active hours (`active_hours_min`) |
+| $H^{max}$ | $\mathbb{R}_{\geq 0}$ | Max total active hours (`active_hours_max`) |
+| $\Delta t$ | $\mathbb{R}_{> 0}$ | Timestep duration (hours) |
**Classes:** [`StatusParameters`][flixopt.interface.StatusParameters], [`StatusModel`][flixopt.features.StatusModel]
From 7fe0a2b96e88fc402dc87afc88c26c8734dc1b2a Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 05:12:42 +0100
Subject: [PATCH 78/79] updated all reference tables across all Mathematical
Notation pages to be consistent
---
.../mathematical-notation/effects-and-dimensions.md | 2 +-
docs/user-guide/mathematical-notation/elements/Flow.md | 6 +++---
.../mathematical-notation/elements/Storage.md | 10 +++++-----
.../mathematical-notation/features/Piecewise.md | 4 ++--
.../mathematical-notation/features/StatusParameters.md | 6 +++---
5 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/effects-and-dimensions.md b/docs/user-guide/mathematical-notation/effects-and-dimensions.md
index cd9e7d4d5..011fc810e 100644
--- a/docs/user-guide/mathematical-notation/effects-and-dimensions.md
+++ b/docs/user-guide/mathematical-notation/effects-and-dimensions.md
@@ -396,7 +396,7 @@ Penalty is weighted identically to the objective effect across all dimensions.
| $E_{temp}(t)$ | $\mathbb{R}$ | Temporal effect at timestep $t$ |
| $E_{per}$ | $\mathbb{R}$ | Periodic effect (per period) |
| $E$ | $\mathbb{R}$ | Total effect ($E_{per} + \sum_t E_{temp}(t)$) |
-| $w_s$ | $[0, 1]$ | Scenario weight (probability) |
+| $w_s$ | $\mathbb{R}_{\geq 0}$ | Scenario weight (probability) |
| $w_y$ | $\mathbb{R}_{> 0}$ | Period weight (duration/discount) |
| $p(t)$ | $\mathbb{R}_{\geq 0}$ | Flow rate at timestep $t$ |
| $s^{start}(t)$ | $\{0, 1\}$ | Startup indicator |
diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md
index 91bf0589c..4f5f9dcf3 100644
--- a/docs/user-guide/mathematical-notation/elements/Flow.md
+++ b/docs/user-guide/mathematical-notation/elements/Flow.md
@@ -123,9 +123,9 @@ heat = fx.Flow(label='heat', bus=heat_bus, size=100, relative_minimum=0.3)
| $p(t)$ | $\mathbb{R}_{\geq 0}$ | Flow rate at timestep $t$ |
| $P$ | $\mathbb{R}_{\geq 0}$ | Size (capacity) — fixed or optimized |
| $s(t)$ | $\{0, 1\}$ | Binary status (with `status_parameters`) |
-| $p_{rel}^{min}$ | $[0, 1]$ | Minimum relative flow (`relative_minimum`) |
-| $p_{rel}^{max}$ | $[0, 1]$ | Maximum relative flow (`relative_maximum`) |
-| $\pi(t)$ | $[0, 1]$ | Fixed profile (`fixed_relative_profile`) |
+| $p_{rel}^{min}$ | $\mathbb{R}_{\geq 0}$ | Minimum relative flow (`relative_minimum`) |
+| $p_{rel}^{max}$ | $\mathbb{R}_{\geq 0}$ | Maximum relative flow (`relative_maximum`) |
+| $\pi(t)$ | $\mathbb{R}_{\geq 0}$ | Fixed profile (`fixed_relative_profile`) |
| $\Delta t$ | $\mathbb{R}_{> 0}$ | Timestep duration (hours) |
**Classes:** [`Flow`][flixopt.elements.Flow], [`FlowModel`][flixopt.elements.FlowModel]
diff --git a/docs/user-guide/mathematical-notation/elements/Storage.md b/docs/user-guide/mathematical-notation/elements/Storage.md
index 35f687f80..808fefaed 100644
--- a/docs/user-guide/mathematical-notation/elements/Storage.md
+++ b/docs/user-guide/mathematical-notation/elements/Storage.md
@@ -110,11 +110,11 @@ fx.Storage(...,
| $C$ | $\mathbb{R}_{\geq 0}$ | Capacity (`capacity_in_flow_hours`) |
| $p_{in}(t)$ | $\mathbb{R}_{\geq 0}$ | Charging power (from `charging` flow) |
| $p_{out}(t)$ | $\mathbb{R}_{\geq 0}$ | Discharging power (from `discharging` flow) |
-| $\eta_{in}$ | $[0, 1]$ | Charge efficiency (`eta_charge`) |
-| $\eta_{out}$ | $[0, 1]$ | Discharge efficiency (`eta_discharge`) |
-| $\dot{c}_{loss}$ | $[0, 1]$ | Self-discharge rate (`relative_loss_per_hour`) |
-| $c_{rel}^{min}$ | $[0, 1]$ | Min charge state (`relative_minimum_charge_state`) |
-| $c_{rel}^{max}$ | $[0, 1]$ | Max charge state (`relative_maximum_charge_state`) |
+| $\eta_{in}$ | $\mathbb{R}_{\geq 0}$ | Charge efficiency (`eta_charge`) |
+| $\eta_{out}$ | $\mathbb{R}_{\geq 0}$ | Discharge efficiency (`eta_discharge`) |
+| $\dot{c}_{loss}$ | $\mathbb{R}_{\geq 0}$ | Self-discharge rate (`relative_loss_per_hour`) |
+| $c_{rel}^{min}$ | $\mathbb{R}_{\geq 0}$ | Min charge state (`relative_minimum_charge_state`) |
+| $c_{rel}^{max}$ | $\mathbb{R}_{\geq 0}$ | Max charge state (`relative_maximum_charge_state`) |
| $\Delta t$ | $\mathbb{R}_{> 0}$ | Timestep duration (hours) |
**Classes:** [`Storage`][flixopt.components.Storage], [`StorageModel`][flixopt.components.StorageModel]
diff --git a/docs/user-guide/mathematical-notation/features/Piecewise.md b/docs/user-guide/mathematical-notation/features/Piecewise.md
index da6405b52..185a9026a 100644
--- a/docs/user-guide/mathematical-notation/features/Piecewise.md
+++ b/docs/user-guide/mathematical-notation/features/Piecewise.md
@@ -146,8 +146,8 @@ When segment $i$ is active ($s_i = 1$), the lambdas interpolate between $x_i^{st
|--------|------|-------------|
| $x$ | $\mathbb{R}$ | Interpolated variable value |
| $s_i$ | $\{0, 1\}$ | Binary: segment $i$ is active |
-| $\lambda_i^0$ | $[0, 1]$ | Interpolation weight for segment start |
-| $\lambda_i^1$ | $[0, 1]$ | Interpolation weight for segment end |
+| $\lambda_i^0$ | $\mathbb{R}_{\geq 0}$ | Interpolation weight for segment start |
+| $\lambda_i^1$ | $\mathbb{R}_{\geq 0}$ | Interpolation weight for segment end |
| $x_i^{start}$ | $\mathbb{R}$ | Start value of segment $i$ |
| $x_i^{end}$ | $\mathbb{R}$ | End value of segment $i$ |
| $n$ | $\mathbb{Z}_{> 0}$ | Number of segments |
diff --git a/docs/user-guide/mathematical-notation/features/StatusParameters.md b/docs/user-guide/mathematical-notation/features/StatusParameters.md
index 29b7fbfba..7b4c08f72 100644
--- a/docs/user-guide/mathematical-notation/features/StatusParameters.md
+++ b/docs/user-guide/mathematical-notation/features/StatusParameters.md
@@ -104,9 +104,9 @@ Detect transitions: $s^{start}(t) - s^{stop}(t) = s(t) - s(t-1)$
| $s(t)$ | $\{0, 1\}$ | Binary status (0=off, 1=on) |
| $s^{start}(t)$ | $\{0, 1\}$ | Startup indicator |
| $s^{stop}(t)$ | $\{0, 1\}$ | Shutdown indicator |
-| $T_{up}^{min}$ | $\mathbb{Z}_{\geq 0}$ | Min uptime (`min_uptime`) |
-| $T_{up}^{max}$ | $\mathbb{Z}_{\geq 0}$ | Max uptime (`max_uptime`) |
-| $T_{down}^{min}$ | $\mathbb{Z}_{\geq 0}$ | Min downtime (`min_downtime`) |
+| $T_{up}^{min}$ | $\mathbb{R}_{\geq 0}$ | Min uptime in hours (`min_uptime`) |
+| $T_{up}^{max}$ | $\mathbb{R}_{\geq 0}$ | Max uptime in hours (`max_uptime`) |
+| $T_{down}^{min}$ | $\mathbb{R}_{\geq 0}$ | Min downtime in hours (`min_downtime`) |
| $H^{min}$ | $\mathbb{R}_{\geq 0}$ | Min total active hours (`active_hours_min`) |
| $H^{max}$ | $\mathbb{R}_{\geq 0}$ | Max total active hours (`active_hours_max`) |
| $\Delta t$ | $\mathbb{R}_{> 0}$ | Timestep duration (hours) |
From ef0c1c8023c2c477b8ea2d7224228aa2a3b9eea0 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Sun, 30 Nov 2025 05:16:03 +0100
Subject: [PATCH 79/79] updated all reference tables across all Mathematical
Notation pages to be consistent
---
docs/user-guide/mathematical-notation/features/Piecewise.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/user-guide/mathematical-notation/features/Piecewise.md b/docs/user-guide/mathematical-notation/features/Piecewise.md
index 185a9026a..da6405b52 100644
--- a/docs/user-guide/mathematical-notation/features/Piecewise.md
+++ b/docs/user-guide/mathematical-notation/features/Piecewise.md
@@ -146,8 +146,8 @@ When segment $i$ is active ($s_i = 1$), the lambdas interpolate between $x_i^{st
|--------|------|-------------|
| $x$ | $\mathbb{R}$ | Interpolated variable value |
| $s_i$ | $\{0, 1\}$ | Binary: segment $i$ is active |
-| $\lambda_i^0$ | $\mathbb{R}_{\geq 0}$ | Interpolation weight for segment start |
-| $\lambda_i^1$ | $\mathbb{R}_{\geq 0}$ | Interpolation weight for segment end |
+| $\lambda_i^0$ | $[0, 1]$ | Interpolation weight for segment start |
+| $\lambda_i^1$ | $[0, 1]$ | Interpolation weight for segment end |
| $x_i^{start}$ | $\mathbb{R}$ | Start value of segment $i$ |
| $x_i^{end}$ | $\mathbb{R}$ | End value of segment $i$ |
| $n$ | $\mathbb{Z}_{> 0}$ | Number of segments |