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/CHANGELOG.md b/CHANGELOG.md
index 7e181ee9a..914ef2666 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] - ????-??-??
@@ -49,11 +49,11 @@ If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOp
---
-## [Unreleased] - ????-??-??
+Until here -->
-**Summary**: Renamed OnOff terminology to Status terminology for better alignment with PyPSA and unit commitment standards. **All deprecated items from v4.x have been removed.**
+## [Upcoming]
-### โจ Added
+**Summary**: Renamed OnOff terminology to Status terminology for better alignment with PyPSA and unit commitment standards. **All deprecated items from v4.x have been removed.**
### ๐ฅ Breaking Changes
@@ -212,22 +212,14 @@ A partial backwards compatibility wrapper would be misleading, so we opted for a
- Flow parameters: `Q_fu` โ use `fuel_flow`, `P_el` โ use `electrical_flow`, `Q_th` โ use `thermal_flow`, `Q_ab` โ use `heat_source_flow`
- Efficiency parameters: `eta` โ use `thermal_efficiency`, `eta_th` โ use `thermal_efficiency`, `eta_el` โ use `electrical_efficiency`, `COP` โ use `cop`
-### ๐ Fixed
-
-### ๐ Security
-
-### ๐ฆ Dependencies
### ๐ Docs
+- Improve documentation from the ground up
-### ๐ท Development
-
-### ๐ง Known Issues
+This is not yet publicly released!
---
-Until here -->
-
## [4.3.5] - 2025-11-29
**Summary**: Fix zenodo again
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/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/citing.md b/docs/home/citing.md
new file mode 100644
index 000000000..6fd1a6020
--- /dev/null
+++ b/docs/home/citing.md
@@ -0,0 +1,29 @@
+# Citing flixOpt
+
+If you use flixOpt in your research, please cite it.
+
+## Citation
+
+When referencing flixOpt in academic publications, please use look here: [flixopt citation](https://zenodo.org/records/17756895)
+
+## 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*
+
+## Contributing Back
+
+If flixOpt helped your research:
+
+- Share your model as an example
+- Report issues or contribute code
+- Improve documentation
+
+See the [Contributing Guide](../contribute.md).
+
+## License
+
+flixOpt is released under the MIT License. See [License](license.md) for details.
diff --git a/docs/home/installation.md b/docs/home/installation.md
new file mode 100644
index 000000000..afb24172b
--- /dev/null
+++ b/docs/home/installation.md
@@ -0,0 +1,91 @@
+# Installation
+
+This guide covers installing flixOpt and its dependencies.
+
+
+## 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,docs]"
+```
+
+## 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
+
+## Verification
+
+Verify your installation by running:
+
+```python
+import flixopt
+print(flixopt.__version__)
+```
+
+## Logging Configuration
+
+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()
+```
+
+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
+
+- 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..d00755a0b
--- /dev/null
+++ b/docs/home/license.md
@@ -0,0 +1,43 @@
+# 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
+
+## 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/quick-start.md b/docs/home/quick-start.md
new file mode 100644
index 000000000..b0bdef7da
--- /dev/null
+++ b/docs/home/quick-start.md
@@ -0,0 +1,132 @@
+# 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[full]"
+```
+
+## 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
+import pandas as pd
+```
+
+### 2. Define your time horizon
+
+```python
+# 24h period with hourly timesteps
+timesteps = pd.date_range('2024-01-01', periods=24, freq='h')
+```
+
+### 2. Set Up the Flow System
+
+```python
+# Create the flow system
+flow_system = fx.FlowSystem(timesteps)
+
+# Define an effect to minimize (costs)
+costs = fx.Effect('costs', 'EUR', 'Minimize total system costs', is_objective=True)
+flow_system.add_elements(costs)
+```
+
+### 4. Add Components
+
+```python
+# Electricity bus
+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,
+ 1.0, 0.9, 0.8, 0.7, 0.5, 0.3, 0.1, 0,
+ 0, 0, 0, 0, 0, 0])
+
+solar = fx.Source(
+ 'solar',
+ outputs=[fx.Flow(
+ 'power',
+ bus='electricity',
+ size=100, # 100 kW capacity
+ relative_maximum=solar_profile
+ )
+])
+
+# 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.Sink('demand', inputs=[
+ fx.Flow('consumption',
+ bus='electricity',
+ size=1,
+ fixed_relative_profile=demand_profile)
+])
+
+# Battery storage
+battery = fx.Storage(
+ 'battery',
+ 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,
+)
+
+# Add all components to system
+flow_system.add_elements(solar, demand, battery, electricity_bus)
+```
+
+### 5. Run Optimization
+
+```python
+# Create and run optimization
+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
+optimization.results.to_file()
+```
+
+## 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. **Run optimization** - Solve the optimization
+6. **Save Results** - For later analysis. Or only extract needed data
+
+## 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..d27f99576
--- /dev/null
+++ b/docs/home/users.md
@@ -0,0 +1,27 @@
+# Who Uses flixOpt?
+
+flixOpt is developed and used primarily in academic research for energy system optimization.
+
+## Primary Users
+
+- **Researchers** - Energy system modeling and optimization studies
+- **Students** - Master's and PhD thesis projects
+- **Engineers** - Feasibility studies and system planning
+
+## Typical Applications
+
+- Dispatch optimization with renewable integration
+- Capacity expansion planning
+- Battery and thermal storage sizing
+- District heating network optimization
+- Combined heat and power (CHP) systems
+- Multi-energy systems and sector coupling
+
+## Get Involved
+
+Using flixOpt in your research? Consider:
+
+- [Citing flixOpt](citing.md) in your publications
+- Sharing your model as an example
+- Contributing to the codebase
+- Joining [discussions](https://github.com/flixOpt/flixopt/discussions)
diff --git a/docs/index.md b/docs/index.md
index 3467bb394..70fd15bf4 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,8 +1,5 @@
---
title: Home
-hide:
- - navigation
- - toc
---
@@ -16,7 +13,7 @@ hide:
Model, optimize, and analyze complex energy systems with a powerful Python framework designed for flexibility and performance.
+
+- :rocket: **[Getting Started](home/installation/)**
+
+ ---
+
+ New to FlixOpt? Start here with installation and your first model
+
+- :bulb: **[Examples Gallery](examples/)**
+
+ ---
+
+ Explore real-world examples from simple to complex systems
+
+- :books: **[API Reference](api-reference/)**
+
+ ---
+
+ Detailed documentation of all classes, methods and parameters
+
+- :book: **[Recipes](user-guide/recipes/)**
+
+ ---
+
+ Common patterns and best practices for modeling energy systems
+
+- :material-math-integral: **[Mathematical Notation](user-guide/mathematical-notation/)**
+
+ ---
+
+ Understand the mathematical formulations behind the framework
+
+- :material-road: **[Roadmap](roadmap/)**
+
+ ---
+
+ See what's coming next and contribute to the future of FlixOpt
+
## ๐๏ธ Framework Architecture
@@ -77,43 +82,31 @@ hide:
## :material-account-group: Community & Support
-
-
-
-
-:fontawesome-brands-github:{ .feature-icon }
+
-### GitHub
+- :fontawesome-brands-github: **GitHub**
-Report issues, request features, and contribute to the codebase
+ ---
-[Visit Repository โ](https://github.com/flixOpt/flixopt){target="_blank" rel="noopener noreferrer"}
+ Report issues, request features, and contribute to the codebase
-
-
-
+ [Visit Repository โ](https://github.com/flixOpt/flixopt){target="_blank" rel="noopener noreferrer"}
-:material-forum:{ .feature-icon }
+- :material-forum: **Discussions**
-### Discussions
+ ---
-Ask questions and share your projects with the community
+ Ask questions and share your projects with the community
-[Join Discussion โ](https://github.com/flixOpt/flixopt/discussions){target="_blank" rel="noopener noreferrer"}
-
-
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/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/stylesheets/extra.css b/docs/stylesheets/extra.css
index 2992267b6..78946b9ad 100644
--- a/docs/stylesheets/extra.css
+++ b/docs/stylesheets/extra.css
@@ -505,12 +505,16 @@
Home Page Inline Styles (moved from docs/index.md)
========================================================================= */
+/* Center home page content when navigation is hidden */
+/* Remove this rule as it conflicts with TOC layout */
+
.hero-section {
text-align: center;
padding: 4rem 2rem 3rem 2rem;
background: linear-gradient(135deg, rgba(0, 150, 136, 0.1) 0%, rgba(0, 121, 107, 0.1) 100%);
border-radius: 1rem;
- margin-bottom: 3rem;
+ margin: 0 auto 3rem auto;
+ max-width: 1200px;
}
.hero-section h1 {
@@ -558,47 +562,6 @@
margin-top: 2rem;
}
-.feature-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
- gap: 2rem;
- margin: 3rem 0;
-}
-
-.feature-card {
- padding: 2rem;
- border-radius: 0.75rem;
- background: var(--md-code-bg-color);
- border: 1px solid var(--md-default-fg-color--lightest);
- transition: all 0.3s ease;
- text-align: center;
-}
-
-.feature-card:hover {
- transform: translateY(-4px);
- box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
- border-color: var(--md-primary-fg-color);
-}
-
-.feature-icon {
- font-size: 3rem;
- margin-bottom: 1rem;
- display: block;
-}
-
-.feature-card h3 {
- margin-top: 0;
- margin-bottom: 0.5rem;
- font-size: 1.25rem;
-}
-
-.feature-card p {
- color: var(--md-default-fg-color--light);
- margin: 0;
- font-size: 0.95rem;
- line-height: 1.6;
-}
-
.stats-banner {
display: flex;
justify-content: space-around;
@@ -631,45 +594,14 @@
}
.architecture-section {
- margin: 4rem 0;
+ margin: 4rem auto;
padding: 2rem;
background: var(--md-code-bg-color);
border-radius: 0.75rem;
+ max-width: 1200px;
}
-.quick-links {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
- gap: 1.5rem;
- margin: 3rem 0;
-}
-
-.quick-link-card {
- padding: 1.5rem;
- border-left: 4px solid var(--md-primary-fg-color);
- background: var(--md-code-bg-color);
- border-radius: 0.5rem;
- transition: all 0.2s ease;
- text-decoration: none;
- display: block;
-}
-
-.quick-link-card:hover {
- background: var(--md-default-fg-color--lightest);
- transform: translateX(4px);
-}
-.quick-link-card h3 {
- margin: 0 0 0.5rem 0;
- font-size: 1.1rem;
- color: var(--md-primary-fg-color);
-}
-
-.quick-link-card p {
- margin: 0;
- color: var(--md-default-fg-color--light);
- font-size: 0.9rem;
-}
@media screen and (max-width: 768px) {
.hero-section h1 {
@@ -685,10 +617,6 @@
align-items: stretch;
}
- .feature-grid {
- grid-template-columns: 1fr;
- }
-
.stats-banner {
flex-direction: column;
}
@@ -839,27 +767,26 @@ button:focus-visible {
Footer Alignment Fix
========================================================================= */
-/* Align footer with content width */
-.md-footer-meta__inner,
-.md-footer__inner {
- max-width: 1300px;
- margin: 0 auto;
- padding-left: 1.2rem;
- padding-right: 1.2rem;
+/* Hide the social media footer section */
+.md-footer-meta {
+ display: none;
+}
+
+/* Footer navigation content matches page width */
+.md-footer .md-grid {
+ max-width: 1300px !important;
+ padding-left: 1.2rem !important;
+ padding-right: 1.2rem !important;
}
@media screen and (min-width: 76.25em) {
- .md-footer-meta__inner,
- .md-footer__inner {
- padding-left: 1rem;
- padding-right: 1rem;
+ .md-footer .md-grid {
+ padding-left: 1rem !important;
}
}
@media screen and (min-width: 100em) {
- .md-footer-meta__inner,
- .md-footer__inner {
- padding-left: 2rem;
- padding-right: 2rem;
+ .md-footer .md-grid {
+ padding-left: 2rem !important;
}
}
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/core-concepts.md b/docs/user-guide/core-concepts.md
index d63f10f27..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, active/inactive status 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.
diff --git a/docs/user-guide/faq.md b/docs/user-guide/faq.md
new file mode 100644
index 000000000..63994180d
--- /dev/null
+++ b/docs/user-guide/faq.md
@@ -0,0 +1,34 @@
+# Frequently Asked Questions
+
+## 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).
+
+## Which solvers does flixOpt support?
+
+- **HiGHS** (default, included)
+- **Gurobi** (commercial, academic licenses available)
+
+## How do I install flixOpt?
+
+```bash
+pip install flixopt
+```
+
+For full features:
+```bash
+pip install "flixopt[full]"
+```
+
+## Do I need to install a solver separately?
+
+No. HiGHS is included and works out of the box.
+
+## Can I add custom constraints?
+
+Yes. You can add custom constraints directly to the optimization model using linopy.
+
+## Where can I get help?
+
+- Check [Troubleshooting](troubleshooting.md)
+- Open an [issue on GitHub](https://github.com/flixOpt/flixopt/issues)
diff --git a/docs/user-guide/index.md b/docs/user-guide/index.md
new file mode 100644
index 000000000..bfb288ea4
--- /dev/null
+++ b/docs/user-guide/index.md
@@ -0,0 +1,83 @@
+# User Guide
+
+Welcome to the flixOpt User Guide! This guide will help you master energy and material flow optimization with flixOpt.
+
+## What is flixOpt?
+
+flixOpt is a comprehensive framework for modeling and optimizing energy and material flow systems. It supports:
+
+- **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
+
+## Key Features
+
+
+
+## Learning Path
+
+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
+
+### Getting Started
+
+- [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
+
+### Reference
+
+- [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
+
+### Help
+
+- [FAQ](faq.md) - Frequently asked questions
+- [Troubleshooting](troubleshooting.md) - Common issues and solutions
+- [Community](support.md) - Get help from the community
+
+## Use Cases
+
+flixOpt handles any flow-based optimization problem:
+
+**Energy Systems**: Power dispatch, CHP optimization, renewable integration, battery storage, district heating
+
+**Industrial Applications**: Process optimization, multi-commodity networks, supply chains, resource allocation
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..011fc810e
--- /dev/null
+++ b/docs/user-guide/mathematical-notation/effects-and-dimensions.md
@@ -0,0 +1,415 @@
+# 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 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
+ ```
+
+=== "Periodic"
+
+ Time-independent โ investment costs, fixed fees:
+
+ $E_{per} = P \cdot c_{inv}$
+
+ ```python
+ fx.InvestParameters(effects_of_investment_per_size={'costs': 200}) # โฌ200/kW
+ ```
+
+=== "Total"
+
+ Sum of periodic and temporal components.
+
+---
+
+## 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:
+
+=== "Timesteps"
+
+ The basic time resolution โ always required:
+
+ ```python
+ 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.
+
+=== "Scenarios"
+
+ Represent uncertainty (weather, prices). Operations vary per scenario, investments are shared:
+
+ ```python
+ 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],
+ )
+ ```
+
+ Scenarios are independent โ no energy or information exchange between them.
+
+=== "Periods"
+
+ Sequential time blocks (years) for multi-period planning:
+
+ ```python
+ flow_system = fx.FlowSystem(
+ timesteps=pd.date_range('2024-01-01', periods=8760, freq='h'),
+ periods=pd.Index([2025, 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=[0.6, 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 (temporal + periodic) per period:
+
+ ```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 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
+
+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)}$$
+
+---
+
+## 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
+
+| 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$ | $\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 |
+| $P$ | $\mathbb{R}_{\geq 0}$ | Investment size |
+| $c$ | $\mathbb{R}$ | Effect coefficient |
+| $\Delta t$ | $\mathbb{R}_{> 0}$ | Timestep duration (hours) |
+
+| 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]
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 fd8a97a1d..000000000
--- a/docs/user-guide/mathematical-notation/effects-penalty-objective.md
+++ /dev/null
@@ -1,337 +0,0 @@
-# Effects, Penalty & Objective
-
-## Effects
-
-[`Effects`][flixopt.effects.Effect] are used to quantify system-wide impacts like costs, emissions, or resource consumption. These arise from **shares** contributed by **Elements** such as [Flows](elements/Flow.md), [Storage](elements/Storage.md), and other components.
-
-**Example:**
-
-[`Flows`][flixopt.elements.Flow] have an attribute `effects_per_flow_hour` that defines the effect contribution per flow-hour:
-
-- Costs (โฌ/kWh)
-- Emissions (kg COโ/kWh)
-- Primary energy consumption (kWh_primary/kWh)
-
-Effects are categorized into two domains:
-
-1. **Temporal effects** - Time-dependent contributions (e.g., operational costs, hourly emissions)
-2. **Periodic effects** - Time-independent contributions (e.g., investment costs, fixed annual fees)
-
-### Multi-Dimensional Effects
-
-**The formulations below are written with time index $\text{t}_i$ only, but automatically expand when periods and/or scenarios are present.**
-
-When the FlowSystem has additional dimensions (see [Dimensions](dimensions.md)):
-
-- **Temporal effects** are indexed by all present dimensions: $E_{e,\text{temp}}(\text{t}_i, y, s)$
-- **Periodic effects** are indexed by period only (scenario-independent within a period): $E_{e,\text{per}}(y)$
-- Effects are aggregated with dimension weights in the objective function
-
-For complete details on how dimensions affect effects and the objective, see [Dimensions](dimensions.md).
-
----
-
-## Effect Formulation
-
-### Shares from Elements
-
-Each element $l$ contributes shares to effect $e$ in both temporal and periodic domains:
-
-**Periodic shares** (time-independent):
-$$ \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}
-$$
-
-**Temporal shares** (time-dependent):
-$$ \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:
-
-- $\text{t}_i$ is the time step
-- $\mathcal{V}_l$ is the set of all optimization variables of element $l$
-- $\mathcal{V}_{l, \text{per}}$ is the subset of periodic (investment-related) variables
-- $\mathcal{V}_{l, \text{temp}}$ is the subset of temporal (operational) variables
-- $v$ is an optimization variable
-- $v(\text{t}_i)$ is the variable value at timestep $\text{t}_i$
-- $\text{a}_{v \rightarrow e}$ is the effect factor (e.g., โฌ/kW for investment, โฌ/kWh for operation)
-- $s_{l \rightarrow e, \text{per}}$ is the periodic share of element $l$ to effect $e$
-- $s_{l \rightarrow e, \text{temp}}(\text{t}_i)$ is the temporal share of element $l$ to effect $e$
-
-**Examples:**
-- **Periodic share**: Investment cost = $\text{size} \cdot \text{specific\_cost}$ (โฌ/kW)
-- **Temporal share**: Operational cost = $\text{flow\_rate}(\text{t}_i) \cdot \text{price}(\text{t}_i)$ (โฌ/kWh)
-
----
-
-### Cross-Effect Contributions
-
-Effects can contribute shares to other effects, enabling relationships like carbon pricing or resource accounting.
-
-An effect $x$ can contribute to another effect $e \in \mathcal{E}\backslash x$ via conversion factors:
-
-**Example:** COโ emissions (kg) โ Monetary costs (โฌ)
-- Effect $x$: "COโ emissions" (unit: kg)
-- Effect $e$: "costs" (unit: โฌ)
-- Factor $\text{r}_{x \rightarrow e}$: COโ price (โฌ/kg)
-
-**Note:** Circular references must be avoided.
-
-### Total Effect Calculation
-
-**Periodic effects** aggregate element shares and cross-effect contributions:
-
-$$ \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}}
-$$
-
-**Temporal effects** at each timestep:
-
-$$ \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)
-$$
-
-**Total temporal effects** (sum over all timesteps):
-
-$$\label{eq:Effect_temporal_total}
-E_{e,\text{temp},\text{tot}} = \sum_{i=1}^n E_{e,\text{temp}}(\text{t}_{i})
-$$
-
-**Total effect** (combining both domains):
-
-$$ \label{eq:Effect_Total}
-E_{e} = E_{e,\text{per}} + E_{e,\text{temp},\text{tot}}
-$$
-
-Where:
-
-- $\mathcal{L}$ is the set of all elements in the FlowSystem
-- $\mathcal{E}$ is the set of all effects
-- $\text{r}_{x \rightarrow e, \text{per}}$ is the periodic conversion factor from effect $x$ to effect $e$
-- $\text{r}_{x \rightarrow e, \text{temp}}(\text{t}_i)$ is the temporal conversion factor
-
----
-
-### Constraining Effects
-
-Effects can be bounded to enforce limits on costs, emissions, or other impacts:
-
-**Total bounds** (apply to $E_{e,\text{per}}$, $E_{e,\text{temp},\text{tot}}$, or $E_e$):
-
-$$ \label{eq:Bounds_Total}
-E^\text{L} \leq E \leq E^\text{U}
-$$
-
-**Temporal bounds per timestep:**
-
-$$ \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)
-$$
-
-**Implementation:** See [`Effect`][flixopt.effects.Effect] parameters:
-- `minimum_temporal`, `maximum_temporal` - Total temporal bounds
-- `minimum_per_hour`, `maximum_per_hour` - Hourly temporal bounds
-- `minimum_periodic`, `maximum_periodic` - Periodic bounds
-- `minimum_total`, `maximum_total` - Combined total bounds
-
----
-
-## Penalty
-
-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
-on_off = fx.OnOffParameters(
- effects_per_switch_on={'Penalty': 1} # Add bias against switching on this component, without adding costs
-)
-```
-
-**Optionally Define Custom Penalty:**
-Users can define their own Penalty effect with custom properties (unit, constraints, etc.):
-
-```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)
-```
-
-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})
-$$
-
-Where:
-
-- $\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$
-
-**Primary usage:** Penalties occur in [Buses](elements/Bus.md) via the `imbalance_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
-
----
-
-## Objective Function
-
-The optimization objective minimizes the chosen effect plus the penalty effect:
-
-$$ \label{eq:Objective}
-\min \left( E_{\Omega} + E_{\Phi} \right)
-$$
-
-Where:
-
-- $E_{\Omega}$ is the chosen **objective effect** (see $\eqref{eq:Effect_Total}$)
-- $E_{\Phi}$ is the [penalty effect](#penalty) (see $\eqref{eq:Penalty_total}$)
-
-One effect must be designated as the objective via `is_objective=True`. The penalty effect is automatically created and always added to the objective.
-
-### Multi-Criteria Optimization
-
-This formulation supports multiple optimization approaches:
-
-**1. Weighted Sum Method**
-- The objective effect can incorporate other effects via cross-effect factors
-- Example: Minimize costs while including carbon pricing: $\text{CO}_2 \rightarrow \text{costs}$
-
-**2. ฮต-Constraint Method**
-- Optimize one effect while constraining others
-- Example: Minimize costs subject to $\text{CO}_2 \leq 1000$ kg
-
----
-
-## Objective with Multiple Dimensions
-
-When the FlowSystem includes **periods** and/or **scenarios** (see [Dimensions](dimensions.md)), the objective aggregates effects across all dimensions using weights.
-
-### Time Only (Base Case)
-
-$$
-\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)
-$$
-
-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}}$
-
----
-
-### Time + Scenario
-
-$$
-\min \quad \sum_{s \in \mathcal{S}} w_s \cdot \left( E_{\Omega}(s) + E_{\Phi}(s) \right)
-$$
-
-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)$
-
-**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$**
-
----
-
-### Time + Period
-
-$$
-\min \quad \sum_{y \in \mathcal{Y}} w_y \cdot \left( E_{\Omega}(y) + E_{\Phi}(y) \right)
-$$
-
-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$**
-
----
-
-### Time + Period + Scenario (Full Multi-Dimensional)
-
-$$
-\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]
-$$
-
-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)
-
----
-
-## Summary
-
-| 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 |
-
----
-
-## See Also
-
-- [Dimensions](dimensions.md) - Complete explanation of multi-dimensional modeling
-- [Flow](elements/Flow.md) - Temporal effect contributions via `effects_per_flow_hour`
-- [InvestParameters](features/InvestParameters.md) - Periodic effect contributions via investment
-- [Effect API][flixopt.effects.Effect] - Implementation details and parameters
diff --git a/docs/user-guide/mathematical-notation/elements/Bus.md b/docs/user-guide/mathematical-notation/elements/Bus.md
index 5028e8ef7..464381fe8 100644
--- a/docs/user-guide/mathematical-notation/elements/Bus.md
+++ b/docs/user-guide/mathematical-notation/elements/Bus.md
@@ -1,49 +1,52 @@
-A Bus is a simple nodal balance between its incoming and outgoing flow rates.
+# Bus
-$$ \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)
-$$
-
-Optionally, a Bus can have an `imbalance_penalty_per_flow_hour` parameter, which allows to penalize the balance for missing or excess flow-rates.
-This is useful as it handles a possible infeasibility gently.
+A Bus is where flows meet and must balance โ inputs equal outputs at every timestep.
-This changes the balance to
+## Basic: Balance Equation
-$$ \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)
$$
-
-The penalty term is defined as
-
-$$ \label{eq:bus_penalty}
- s_{b \rightarrow \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) ]
+\sum_{in} p(t) = \sum_{out} p(t)
$$
-With:
+```python
+heat_bus = fx.Bus(label='heat')
+# All flows connected to this bus must balance
+```
-- $\mathcal{F}_\text{in}$ and $\mathcal{F}_\text{out}$ being the set of all incoming and outgoing flows
-- $p_{f_\text{in}}(\text{t}_i)$ and $p_{f_\text{out}}(\text{t}_i)$ being the flow-rate at time $\text{t}_i$ for flow $f_\text{in}$ and $f_\text{out}$, respectively
-- $\phi_\text{in}(\text{t}_i)$ and $\phi_\text{out}(\text{t}_i)$ being the virtual supply and virtual demand at time $\text{t}_i$, respectively
-- $\text{t}_i$ being the time step
-- $s_{b \rightarrow \Phi}(\text{t}_i)$ being the penalty term
-- $\text a_{b \rightarrow \Phi}(\text{t}_i)$ being the penalty coefficient (`imbalance_penalty_per_flow_hour`)
+If balance can't be achieved โ model is **infeasible**.
---
-## Implementation
+## With Imbalance Penalty
+
+Allow imbalance for debugging or soft constraints:
-**Python Class:** [`Bus`][flixopt.elements.Bus]
+$$
+\sum_{in} p(t) + \phi_{in}(t) = \sum_{out} p(t) + \phi_{out}(t)
+$$
+
+The slack variables $\phi$ are penalized: $(\phi_{in} + \phi_{out}) \cdot \Delta t \cdot c_\phi$
-See the API documentation for implementation details and usage examples.
+```python
+heat_bus = fx.Bus(
+ label='heat',
+ imbalance_penalty_per_flow_hour=1e5 # High penalty for imbalance
+)
+```
+
+!!! tip "Debugging"
+ If you see a `virtual_demand` or `virtual_supply` and its non zero in results โ your system couldn't meet demand. Check capacities and connections.
---
-## See Also
+## Reference
+
+| 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 (`imbalance_penalty_per_flow_hour`) |
+| $\Delta t$ | $\mathbb{R}_{> 0}$ | Timestep duration (hours) |
-- [Flow](../elements/Flow.md) - Definition of flow rates in the balance
-- [Effects, Penalty & Objective](../effects-penalty-objective.md) - How penalties are included in the objective function
-- [Modeling Patterns](../modeling-patterns/index.md) - Mathematical building blocks
+**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 2cc2e3b6a..4f5f9dcf3 100644
--- a/docs/user-guide/mathematical-notation/elements/Flow.md
+++ b/docs/user-guide/mathematical-notation/elements/Flow.md
@@ -1,65 +1,131 @@
# Flow
-The flow_rate is the main optimization variable of the Flow. It's limited by the size of the Flow and relative bounds \eqref{eq:flow_rate}.
+A Flow is the primary optimization variable โ the solver decides how much flows at each timestep.
-$$ \label{eq:flow_rate}
- \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})
-$$
-
-With:
+## Basic: Bounded Flow Rate
-- $\text P$ being the size of the Flow
-- $p(\text{t}_{i})$ being the flow-rate at time $\text{t}_{i}$
-- $\text p^{\text{L}}_{\text{rel}}(\text{t}_{i})$ being the relative lower bound (typically 0)
-- $\text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$ being the relative upper bound (typically 1)
-
-With $\text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) = 0$ and $\text p^{\text{U}}_{\text{rel}}(\text{t}_{i}) = 1$,
-equation \eqref{eq:flow_rate} simplifies to
+Every flow has a **size** $P$ (capacity) and a **flow rate** $p(t)$ (what the solver optimizes):
$$
- 0 \leq p(\text{t}_{i}) \leq \text P
+P \cdot p_{rel}^{min} \leq p(t) \leq P \cdot p_{rel}^{max}
$$
+```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
+```
-This mathematical formulation can be extended by using [StatusParameters](../features/StatusParameters.md)
-to define the active/inactive state of the Flow, or by using [InvestParameters](../features/InvestParameters.md)
-to change the size of the Flow from a constant to an optimization variable.
+!!! warning "Cannot be zero"
+ With `relative_minimum > 0`, the flow cannot be zero. Use `status_parameters` to allow shutdown.
---
-## Mathematical Patterns Used
+## Adding Features
+
+=== "Status"
+
+ Allow the flow to be zero with `status_parameters`:
+
+ $s(t) \cdot P \cdot p_{rel}^{min} \leq p(t) \leq s(t) \cdot P \cdot p_{rel}^{max}$
+
+ Where $s(t) \in \{0, 1\}$: inactive or active.
+
+ ```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,
+ ),
+ )
+ ```
+
+ See [StatusParameters](../features/StatusParameters.md).
+
+=== "Variable Size"
+
+ Optimize the capacity with `InvestParameters`:
+
+ $P^{min} \leq P \leq P^{max}$
+
+ ```python
+ battery = fx.Flow(
+ label='power', bus=elec_bus,
+ size=fx.InvestParameters(
+ minimum_size=0,
+ maximum_size=1000,
+ specific_effects={'costs': 100_000},
+ ),
+ )
+ ```
-Flow formulation uses the following modeling patterns:
+ See [InvestParameters](../features/InvestParameters.md).
-- **[Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)** - Basic flow rate bounds (equation $\eqref{eq:flow_rate}$)
-- **[Scaled Bounds with State](../modeling-patterns/bounds-and-states.md#scaled-bounds-with-state)** - When combined with [StatusParameters](../features/StatusParameters.md)
-- **[Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state)** - Investment decisions with [InvestParameters](../features/InvestParameters.md)
+=== "Flow Effects"
+
+ Add effects per energy (flow hours) moved:
+
+ ```python
+ gas = fx.Flow(
+ label='gas', bus=gas_bus, size=150,
+ effects_per_flow_hour={'costs': 50}, # โฌ50/MWh
+ )
+ ```
+
+ 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)
+ )
+ ```
---
-## Implementation
+## Optional Constraints
+
+=== "Load Factor"
+
+ Constrain average utilization:
-**Python Class:** [`Flow`][flixopt.elements.Flow]
+ $\lambda_{min} \leq \frac{\sum_t p(t)}{P \cdot n_t} \leq \lambda_{max}$
-**Key Parameters:**
+ ```python
+ fx.Flow(..., load_factor_min=0.5, load_factor_max=0.9)
+ ```
-- `size`: Flow size $\text{P}$ (can be fixed or variable with InvestParameters)
-- `relative_minimum`, `relative_maximum`: Relative bounds $\text{p}^{\text{L}}_{\text{rel}}, \text{p}^{\text{U}}_{\text{rel}}$
-- `effects_per_flow_hour`: Operational effects (costs, emissions, etc.)
-- `invest_parameters`: Optional investment modeling (see [InvestParameters](../features/InvestParameters.md))
-- `status_parameters`: Optional active/inactive operation (see [StatusParameters](../features/StatusParameters.md))
+=== "Flow Hours"
-See the [`Flow`][flixopt.elements.Flow] API documentation for complete parameter list and usage examples.
+ Constrain total energy:
+
+ $h_{min} \leq \sum_t p(t) \cdot \Delta t \leq h_{max}$
+
+ ```python
+ fx.Flow(..., flow_hours_min=1000, flow_hours_max=5000)
+ ```
---
-## See Also
+## Reference
+
+| 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}$ | $\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) |
-- [StatusParameters](../features/StatusParameters.md) - Binary active/inactive operation
-- [InvestParameters](../features/InvestParameters.md) - Variable flow sizing
-- [Bus](../elements/Bus.md) - Flow balance constraints
-- [LinearConverter](../elements/LinearConverter.md) - Flow ratio constraints
-- [Storage](../elements/Storage.md) - Flow integration over time
-- [Modeling Patterns](../modeling-patterns/index.md) - Mathematical building blocks
+**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 b007aa7f5..915537d60 100644
--- a/docs/user-guide/mathematical-notation/elements/LinearConverter.md
+++ b/docs/user-guide/mathematical-notation/elements/LinearConverter.md
@@ -1,50 +1,151 @@
-[`LinearConverters`][flixopt.components.LinearConverter] define a ratio between incoming and outgoing [Flows](../elements/Flow.md).
+# LinearConverter
-$$ \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)
+A LinearConverter transforms inputs into outputs with fixed ratios.
+
+## Basic: Conversion Equation
+
+$$
+\sum_{in} a_f \cdot p_f(t) = \sum_{out} b_f \cdot p_f(t)
$$
-With:
+=== "Boiler (ฮท = 90%)"
+
+ $0.9 \cdot p_{gas}(t) = p_{heat}(t)$
+
+ ```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}],
+ )
+ ```
+
+=== "Heat Pump (COP = 3.5)"
+
+ $3.5 \cdot p_{el}(t) = p_{heat}(t)$
+
+ ```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}],
+ )
+ ```
+
+=== "CHP (35% el, 50% th)"
+
+ Two constraints linking fuel to outputs:
+
+ ```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},
+ ],
+ )
+ ```
-- $\mathcal F_{in}$ and $\mathcal F_{out}$ being the set of all incoming and outgoing flows
-- $p_{f_\text{in}}(\text{t}_i)$ and $p_{f_\text{out}}(\text{t}_i)$ being the flow-rate at time $\text{t}_i$ for flow $f_\text{in}$ and $f_\text{out}$, respectively
-- $\text a_{f_\text{in}}(\text{t}_i)$ and $\text b_{f_\text{out}}(\text{t}_i)$ being the ratio of the flow-rate at time $\text{t}_i$ for flow $f_\text{in}$ and $f_\text{out}$, respectively
+---
-With one incoming **Flow** and one outgoing **Flow**, this can be simplified to:
+## Time-Varying 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)
-$$
+Pass a list for time-dependent conversion:
+
+```python
+cop = np.array([3.0, 3.2, 3.5, 4.0, 3.8, ...]) # Varies with ambient temperature
-where $\text a$ can be interpreted as the conversion efficiency of the **LinearConverter**.
+hp = fx.LinearConverter(
+ ...,
+ conversion_factors=[{'el': cop, 'heat': 1}],
+)
+```
-#### Piecewise Conversion factors
-The conversion efficiency can be defined as a piecewise linear approximation. See [Piecewise](../features/Piecewise.md) for more details.
+---
+
+## Convenience Classes
+
+```python
+# Boiler
+boiler = fx.linear_converters.Boiler(
+ 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),
+)
+
+# 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
+chp = fx.linear_converters.CHP(
+ label='chp', eta_el=0.35, eta_th=0.50,
+ P_el=fx.Flow(...), Q_th=fx.Flow(...), Q_fu=fx.Flow(...),
+)
+```
---
-## Implementation
+## Adding Features
+
+=== "Status"
-**Python Class:** [`LinearConverter`][flixopt.components.LinearConverter]
+ A component is active when any of its flows is non-zero. Add startup costs, minimum run times:
-**Specialized Linear Converters:**
+ ```python
+ gen = fx.LinearConverter(
+ ...,
+ status_parameters=fx.StatusParameters(
+ effects_per_startup={'costs': 1000},
+ min_uptime=4,
+ ),
+ )
+ ```
-FlixOpt provides specialized linear converter classes for common applications:
+ See [StatusParameters](../features/StatusParameters.md).
-- **[`HeatPump`][flixopt.linear_converters.HeatPump]** - Coefficient of Performance (COP) based conversion
-- **[`Power2Heat`][flixopt.linear_converters.Power2Heat]** - Electric heating with efficiency โค 1
-- **[`CHP`][flixopt.linear_converters.CHP]** - Combined heat and power generation
-- **[`Boiler`][flixopt.linear_converters.Boiler]** - Fuel to heat conversion
+=== "Piecewise Conversion"
-These classes handle the mathematical formulation automatically based on physical relationships.
+ For variable efficiency โ all flows change together based on operating point:
-See the API documentation for implementation details and usage examples.
+ ```python
+ 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)]),
+ }),
+ )
+ ```
+
+ See [Piecewise](../features/Piecewise.md).
---
-## See Also
+## Reference
+
+The converter creates **constraints** linking flows, not new variables.
+
+| 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$ |
-- [Flow](../elements/Flow.md) - Definition of flow rates
-- [Piecewise](../features/Piecewise.md) - Non-linear conversion efficiency modeling
-- [InvestParameters](../features/InvestParameters.md) - Variable converter sizing
-- [Modeling Patterns](../modeling-patterns/index.md) - Mathematical building blocks
+**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 9ecd4d570..808fefaed 100644
--- a/docs/user-guide/mathematical-notation/elements/Storage.md
+++ b/docs/user-guide/mathematical-notation/elements/Storage.md
@@ -1,81 +1,120 @@
-# Storages
-**Storages** have one incoming and one outgoing **[Flow](../elements/Flow.md)** with a charging and discharging efficiency.
-A storage has a state of charge $c(\text{t}_i)$ which is limited by its `size` $\text C$ and relative bounds $\eqref{eq:Storage_Bounds}$.
-
-$$ \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})
-$$
+# Storage
-Where:
+A Storage accumulates energy over time โ charge now, discharge later.
-- $\text C$ is the size of the storage
-- $c(\text{t}_i)$ is the state of charge at time $\text{t}_i$
-- $\text c^{\text{L}}_{\text{rel}}(\text t_{i})$ is the relative lower bound (typically 0)
-- $\text c^{\text{U}}_{\text{rel}}(\text t_{i})$ is the relative upper bound (typically 1)
+## Basic: Charge Dynamics
-With $\text c^{\text{L}}_{\text{rel}}(\text t_{i}) = 0$ and $\text c^{\text{U}}_{\text{rel}}(\text t_{i}) = 1$,
-Equation $\eqref{eq:Storage_Bounds}$ simplifies to
+$$
+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}
+$$
-$$ 0 \leq c(\text t_{i}) \leq \text C $$
+```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%
+```
-The state of charge $c(\text{t}_i)$ decreases by a fraction of the prior state of charge. The belonging parameter
-$ \dot{ \text c}_\text{rel, loss}(\text{t}_i)$ expresses the "loss fraction per hour". The storage balance from $\text{t}_i$ to $\text t_{i+1}$ is
+---
+
+## Charge State Bounds
$$
-\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}} \\
- &\quad + p_{f_\text{in}}(\text{t}_i) \cdot \Delta \text{t}_i \cdot \eta_\text{in}(\text{t}_i) \\
- &\quad - p_{f_\text{out}}(\text{t}_i) \cdot \Delta \text{t}_i \cdot \eta_\text{out}(\text{t}_i)
- \tag{3}
-\end{align*}
+C \cdot c_{rel}^{min} \leq c(t) \leq C \cdot c_{rel}^{max}
$$
-Where:
-
-- $c(\text{t}_{i+1})$ is the state of charge at time $\text{t}_{i+1}$
-- $c(\text{t}_{i})$ is the state of charge at time $\text{t}_{i}$
-- $\dot{\text{c}}_\text{rel,loss}(\text{t}_i)$ is the relative loss rate (self-discharge) per hour
-- $\Delta \text{t}_{i}$ is the time step duration in hours
-- $p_{f_\text{in}}(\text{t}_i)$ is the input flow rate at time $\text{t}_i$
-- $\eta_\text{in}(\text{t}_i)$ is the charging efficiency at time $\text{t}_i$
-- $p_{f_\text{out}}(\text{t}_i)$ is the output flow rate at time $\text{t}_i$
-- $\eta_\text{out}(\text{t}_i)$ is the discharging efficiency at time $\text{t}_i$
+```python
+fx.Storage(...,
+ relative_minimum_charge_state=0.2, # Min 20% SOC
+ relative_maximum_charge_state=0.8, # Max 80% SOC
+)
+```
---
-## Mathematical Patterns Used
+## Initial & Final Conditions
+
+=== "Fixed Start"
-Storage formulation uses the following modeling patterns:
+ ```python
+ fx.Storage(..., initial_charge_state=100) # Start at 100 kWh
+ ```
-- **[Basic Bounds](../modeling-patterns/bounds-and-states.md#basic-bounds)** - For charge state bounds (equation $\eqref{eq:Storage_Bounds}$)
-- **[Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)** - For flow rate bounds relative to storage size
+=== "Cyclic"
-When combined with investment parameters, storage can use:
+ Must end where it started (prevents "cheating"):
-- **[Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state)** - Investment decisions (see [InvestParameters](../features/InvestParameters.md))
+ ```python
+ fx.Storage(..., initial_charge_state='equals_final')
+ ```
+
+=== "Final Bounds"
+
+ ```python
+ fx.Storage(...,
+ minimal_final_charge_state=50,
+ maximal_final_charge_state=150,
+ )
+ ```
---
-## Implementation
+## Adding Features
-**Python Class:** [`Storage`][flixopt.components.Storage]
+=== "Self-Discharge"
-**Key Parameters:**
+ ```python
+ tank = fx.Storage(...,
+ relative_loss_per_hour=0.02, # 2%/hour loss
+ )
+ ```
-- `capacity_in_flow_hours`: Storage capacity $\text{C}$
-- `relative_loss_per_hour`: Self-discharge rate $\dot{\text{c}}_\text{rel,loss}$
-- `initial_charge_state`: Initial charge $c(\text{t}_0)$
-- `minimal_final_charge_state`, `maximal_final_charge_state`: Final charge bounds $c(\text{t}_\text{end})$ (optional)
-- `eta_charge`, `eta_discharge`: Charging/discharging efficiencies $\eta_\text{in}, \eta_\text{out}$
+=== "Variable Capacity"
-See the [`Storage`][flixopt.components.Storage] API documentation for complete parameter list and usage examples.
+ Optimize storage size:
----
+ ```python
+ battery = fx.Storage(...,
+ capacity_in_flow_hours=fx.InvestParameters(
+ minimum_size=0,
+ maximum_size=1000,
+ specific_effects={'costs': 200}, # โฌ/kWh
+ ),
+ )
+ ```
+
+=== "Asymmetric Power"
-## See Also
+ Different charge/discharge rates:
+
+ ```python
+ fx.Storage(
+ charging=fx.Flow(..., size=100), # 100 MW pump
+ discharging=fx.Flow(..., size=120), # 120 MW turbine
+ ...
+ )
+ ```
+
+---
-- [Flow](../elements/Flow.md) - Input and output flow definitions
-- [InvestParameters](../features/InvestParameters.md) - Variable storage sizing
-- [Modeling Patterns](../modeling-patterns/index.md) - Mathematical building blocks
+## Reference
+
+| 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}$ | $\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/InvestParameters.md b/docs/user-guide/mathematical-notation/features/InvestParameters.md
index d998039c0..b6e1afe6b 100644
--- a/docs/user-guide/mathematical-notation/features/InvestParameters.md
+++ b/docs/user-guide/mathematical-notation/features/InvestParameters.md
@@ -1,310 +1,143 @@
# InvestParameters
-[`InvestParameters`][flixopt.interface.InvestParameters] model investment decisions in optimization problems, enabling both binary (invest/don't invest) and continuous sizing choices with comprehensive cost modeling.
+InvestParameters make capacity a decision variable โ should we build this? How big?
-## Investment Decision Types
+## Basic: Size as Variable
-FlixOpt supports two main types of investment decisions:
-
-### Binary Investment
-
-Fixed-size investment creating a yes/no decision (e.g., install a 100 kW generator):
-
-$$\label{eq:invest_binary}
-v_\text{invest} = s_\text{invest} \cdot \text{size}_\text{fixed}
-$$
-
-With:
-
-- $v_\text{invest}$ being the resulting investment size
-- $s_\text{invest} \in \{0, 1\}$ being the binary investment decision
-- $\text{size}_\text{fixed}$ being the predefined component size
-
-**Behavior:**
-- $s_\text{invest} = 0$: no investment ($v_\text{invest} = 0$)
-- $s_\text{invest} = 1$: invest at fixed size ($v_\text{invest} = \text{size}_\text{fixed}$)
-
----
-
-### Continuous Sizing
-
-Variable-size investment with bounds (e.g., battery capacity from 10-1000 kWh):
-
-$$\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}
-$$
-
-With:
-
-- $v_\text{invest}$ being the investment size variable (continuous)
-- $s_\text{invest} \in \{0, 1\}$ being the binary investment decision
-- $\text{size}_\text{min}$ being the minimum investment size (if investing)
-- $\text{size}_\text{max}$ being the maximum investment size
-
-**Behavior:**
-- $s_\text{invest} = 0$: no investment ($v_\text{invest} = 0$)
-- $s_\text{invest} = 1$: invest with size in $[\text{size}_\text{min}, \text{size}_\text{max}]$
-
-This uses the **bounds with state** pattern described in [Bounds and States](../modeling-patterns/bounds-and-states.md#bounds-with-state).
-
----
-
-### Optional vs. Mandatory Investment
-
-The `mandatory` parameter controls whether investment is required:
-
-**Optional Investment** (`mandatory=False`, default):
-$$\label{eq:invest_optional}
-s_\text{invest} \in \{0, 1\}
-$$
-
-The optimization can freely choose to invest or not.
-
-**Mandatory Investment** (`mandatory=True`):
-$$\label{eq:invest_mandatory}
-s_\text{invest} = 1
-$$
-
-The investment must occur (useful for mandatory upgrades or replacements).
-
----
-
-## Effect Modeling
-
-Investment effects (costs, emissions, etc.) are modeled using three components:
-
-### Fixed Effects
-
-One-time effects incurred if investment is made, independent of size:
-
-$$\label{eq:invest_fixed_effects}
-E_{e,\text{fix}} = s_\text{invest} \cdot \text{fix}_e
$$
-
-With:
-
-- $E_{e,\text{fix}}$ being the fixed contribution to effect $e$
-- $\text{fix}_e$ being the fixed effect value (e.g., fixed installation cost)
-
-**Examples:**
-- Fixed installation costs (permits, grid connection)
-- One-time environmental impacts (land preparation)
-- Fixed labor or administrative costs
-
----
-
-### Specific Effects
-
-Effects proportional to investment size (per-unit costs):
-
-$$\label{eq:invest_specific_effects}
-E_{e,\text{spec}} = v_\text{invest} \cdot \text{spec}_e
+P^{min} \leq P \leq P^{max}
$$
-With:
-
-- $E_{e,\text{spec}}$ being the size-dependent contribution to effect $e$
-- $\text{spec}_e$ being the specific effect value per unit size (e.g., โฌ/kW)
-
-**Examples:**
-- Equipment costs (โฌ/kW)
-- Material requirements (kg steel/kW)
-- Recurring costs (โฌ/kW/year maintenance)
-
----
-
-### Piecewise Effects
-
-Non-linear effect relationships using piecewise linear approximations:
-
-$$\label{eq:invest_piecewise_effects}
-E_{e,\text{pw}} = \sum_{k=1}^{K} \lambda_k \cdot r_{e,k}
-$$
-
-Subject to:
-$$
-v_\text{invest} = \sum_{k=1}^{K} \lambda_k \cdot v_k
-$$
-
-With:
-
-- $E_{e,\text{pw}}$ being the piecewise contribution to effect $e$
-- $\lambda_k$ being the piecewise lambda variables (see [Piecewise](../features/Piecewise.md))
-- $r_{e,k}$ being the effect rate at piece $k$
-- $v_k$ being the size points defining the pieces
-
-**Use cases:**
-- Economies of scale (bulk discounts)
-- Technology learning curves
-- Threshold effects (capacity tiers with different costs)
-
-See [Piecewise](../features/Piecewise.md) for detailed mathematical formulation.
+```python
+battery = fx.Storage(
+ ...,
+ capacity_in_flow_hours=fx.InvestParameters(
+ minimum_size=10,
+ maximum_size=1000,
+ specific_effects={'costs': 600}, # โฌ600/kWh
+ ),
+)
+```
---
-### Retirement Effects
-
-Effects incurred if investment is NOT made (when retiring/not replacing existing equipment):
+## Investment Modes
-$$\label{eq:invest_retirement_effects}
-E_{e,\text{retirement}} = (1 - s_\text{invest}) \cdot \text{retirement}_e
-$$
+By default, investment is **optional** โ the optimizer can choose $P = 0$ (don't invest).
-With:
+=== "Continuous"
-- $E_{e,\text{retirement}}$ being the retirement contribution to effect $e$
-- $\text{retirement}_e$ being the retirement effect value
+ Choose size within range (or zero):
-**Behavior:**
-- $s_\text{invest} = 0$: retirement effects are incurred
-- $s_\text{invest} = 1$: no retirement effects
+ ```python
+ fx.InvestParameters(
+ minimum_size=10,
+ maximum_size=1000,
+ )
+ # โ P = 0 OR 10 โค P โค 1000
+ ```
-**Examples:**
-- Demolition or disposal costs
-- Decommissioning expenses
-- Contractual penalties for not investing
-- Opportunity costs or lost revenues
+=== "Binary"
----
+ Fixed size or nothing:
-### Total Investment Effects
+ ```python
+ fx.InvestParameters(
+ fixed_size=100, # 100 kW or 0
+ )
+ # โ P โ {0, 100}
+ ```
-The total contribution to effect $e$ from an investment is:
+=== "Mandatory"
-$$\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}}
-$$
+ Force investment with `mandatory=True` โ zero not allowed:
-Effects integrate into the overall system effects as described in [Effects, Penalty & Objective](../effects-penalty-objective.md).
+ ```python
+ fx.InvestParameters(
+ minimum_size=50,
+ maximum_size=200,
+ mandatory=True,
+ )
+ # โ 50 โค P โค 200 (no zero option)
+ ```
---
-## Integration with Components
+## Investment Effects
-Investment parameters modify component sizing:
+=== "Per-Size Cost"
-### Without Investment
-Component size is a fixed parameter:
-$$
-\text{size} = \text{size}_\text{nominal}
-$$
+ Cost proportional to capacity (โฌ/kW):
-### With Investment
-Component size becomes a variable:
-$$
-\text{size} = v_\text{invest}
-$$
+ $E = P \cdot c_{spec}$
-This size variable then appears in component constraints. For example, flow rate bounds become:
+ ```python
+ fx.InvestParameters(
+ specific_effects={'costs': 1200}, # โฌ1200/kW
+ )
+ ```
-$$
-v_\text{invest} \cdot \text{rel}_\text{lower} \leq p(t) \leq v_\text{invest} \cdot \text{rel}_\text{upper}
-$$
+=== "Fixed Cost"
-Using the **scaled bounds** pattern from [Bounds and States](../modeling-patterns/bounds-and-states.md#scaled-bounds).
+ One-time cost if investing:
----
+ $E = s_{inv} \cdot c_{fix}$
-## Cost Annualization
+ ```python
+ fx.InvestParameters(
+ effects_of_investment={'costs': 25000}, # โฌ25k
+ )
+ ```
-**Important:** All investment cost values must be properly weighted to match the optimization model's time horizon.
+=== "Retirement Cost"
-For long-term investments, costs should be annualized:
+ Cost if NOT investing:
-$$\label{eq:annualization}
-\text{cost}_\text{annual} = \frac{\text{cost}_\text{capital} \cdot r}{1 - (1 + r)^{-n}}
-$$
-
-With:
-
-- $\text{cost}_\text{capital}$ being the upfront investment cost
-- $r$ being the discount rate
-- $n$ being the equipment lifetime in 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}
-$$
+ $E = (1 - s_{inv}) \cdot c_{ret}$
----
-
-## Implementation
+ ```python
+ fx.InvestParameters(
+ effects_of_retirement={'costs': 8000}, # Demolition
+ )
+ ```
-**Python Class:** [`InvestParameters`][flixopt.interface.InvestParameters]
+=== "Piecewise Cost"
-**Key Parameters:**
+ Non-linear cost curves (e.g., economies of scale):
-- `fixed_size`: For binary investments (mutually exclusive with continuous sizing)
-- `minimum_size`, `maximum_size`: For continuous sizing
-- `mandatory`: Whether investment is required (default: `False`)
-- `effects_of_investment`: Fixed effects incurred when investing (replaces deprecated `fix_effects`)
-- `effects_of_investment_per_size`: Per-unit effects proportional to size (replaces deprecated `specific_effects`)
-- `piecewise_effects_of_investment`: Non-linear effect modeling (replaces deprecated `piecewise_effects`)
-- `effects_of_retirement`: Effects for not investing (replaces deprecated `divest_effects`)
+ $E = f_{piecewise}(P)$
-See the [`InvestParameters`][flixopt.interface.InvestParameters] API documentation for complete parameter list and usage examples.
+ ```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
+ ])
+ },
+ ),
+ )
+ ```
-**Used in:**
-- [`Flow`][flixopt.elements.Flow] - Flexible capacity decisions
-- [`Storage`][flixopt.components.Storage] - Storage sizing optimization
-- [`LinearConverter`][flixopt.components.LinearConverter] - Converter capacity planning
-- All components supporting investment decisions
+ See [Piecewise](Piecewise.md) for details on the formulation.
---
-## Examples
+## Reference
-### Binary Investment (Solar Panels)
-```python
-solar_investment = InvestParameters(
- fixed_size=100, # 100 kW system
- mandatory=False, # Optional investment (default)
- effects_of_investment={'cost': 25000}, # Installation costs
- effects_of_investment_per_size={'cost': 1200}, # โฌ1200/kW
-)
-```
+| 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`) |
-### Continuous Sizing (Battery)
-```python
-battery_investment = InvestParameters(
- minimum_size=10, # kWh
- maximum_size=1000,
- mandatory=False, # Optional investment (default)
- effects_of_investment={'cost': 5000}, # Grid connection
- effects_of_investment_per_size={'cost': 600}, # โฌ600/kWh
-)
-```
-
-### With Retirement Costs (Replacement)
-```python
-boiler_replacement = InvestParameters(
- minimum_size=50, # kW
- maximum_size=200,
- mandatory=False, # Optional investment (default)
- effects_of_investment={'cost': 15000},
- effects_of_investment_per_size={'cost': 400},
- effects_of_retirement={'cost': 8000}, # Demolition if not replaced
-)
-```
-
-### Economies of Scale (Piecewise)
-```python
-battery_investment = InvestParameters(
- minimum_size=10,
- maximum_size=1000,
- piecewise_effects_of_investment=PiecewiseEffects(
- piecewise_origin=Piecewise([
- Piece(0, 100), # Small
- Piece(100, 500), # Medium
- Piece(500, 1000), # Large
- ]),
- piecewise_shares={
- 'cost': Piecewise([
- Piece(800, 750), # โฌ800-750/kWh
- Piece(750, 600), # โฌ750-600/kWh
- Piece(600, 500), # โฌ600-500/kWh (bulk discount)
- ])
- },
- ),
-)
-```
+**Classes:** [`InvestParameters`][flixopt.interface.InvestParameters], [`InvestmentModel`][flixopt.features.InvestmentModel]
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 e69de29bb..000000000
diff --git a/docs/user-guide/mathematical-notation/features/Piecewise.md b/docs/user-guide/mathematical-notation/features/Piecewise.md
index 688ac8cea..da6405b52 100644
--- a/docs/user-guide/mathematical-notation/features/Piecewise.md
+++ b/docs/user-guide/mathematical-notation/features/Piecewise.md
@@ -1,49 +1,155 @@
# Piecewise
-A Piecewise is a collection of [`Pieces`][flixopt.interface.Piece], which each define a valid range for a variable $v$
+Piecewise linearization approximates non-linear relationships using connected linear segments.
+
+## Mathematical Formulation
+
+A piecewise linear function with $n$ segments uses per-segment interpolation:
-$$ \label{eq:active_piece}
- \beta_\text{k} = \lambda_\text{0, k} + \lambda_\text{1, k}
$$
+x = \sum_{i=1}^{n} \left( \lambda_i^0 \cdot x_i^{start} + \lambda_i^1 \cdot x_i^{end} \right)
+$$
+
+Each segment $i$ has:
-$$ \label{eq:piece}
- v_\text{k} = \lambda_\text{0, k} * \text{v}_{\text{start,k}} + \lambda_\text{1,k} * \text{v}_{\text{end,k}}
+- $s_i \in \{0, 1\}$ โ binary indicating if segment is active
+- $\lambda_i^0, \lambda_i^1 \geq 0$ โ interpolation weights for segment endpoints
+
+Constraints ensure valid interpolation:
+
+$$
+\lambda_i^0 + \lambda_i^1 = s_i \quad \forall i
$$
-$$ \label{eq:piecewise_in_pieces}
-\sum_{k=1}^k \beta_{k} = 1
+$$
+\sum_{i=1}^{n} s_i \leq 1
$$
-With:
+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.
-- $v$: The variable to be defined by the Piecewise
-- $\text{v}_{\text{start,k}}$: the start point of the piece for variable $v$
-- $\text{v}_{\text{end,k}}$: the end point of the piece for variable $v$
-- $\beta_\text{k} \in \{0, 1\}$: defining wether the Piece $k$ is active
-- $\lambda_\text{0,k} \in [0, 1]$: A variable defining the fraction of $\text{v}_{\text{start,k}}$ that is active
-- $\lambda_\text{1,k} \in [0, 1]$: A variable defining the fraction of $\text{v}_{\text{end,k}}$ that is active
+!!! 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.
-Which can also be described as $v \in 0 \cup [\text{v}_\text{start}, \text{v}_\text{end}]$.
+---
-Instead of \eqref{eq:piecewise_in_pieces}, the following constraint is used to also allow all variables to be zero:
+## Building Blocks
-$$ \label{eq:piecewise_in_pieces_zero}
-\sum_{k=1}^k \beta_{k} = \beta_\text{zero}
-$$
+=== "Piece"
+
+ A linear segment from start to end value:
+
+ ```python
+ fx.Piece(start=10, end=50) # Linear from 10 to 50
+ ```
+
+ Values can be time-varying:
+
+ ```python
+ fx.Piece(
+ start=np.linspace(5, 6, n_timesteps),
+ end=np.linspace(30, 35, n_timesteps)
+ )
+ ```
+
+=== "Piecewise"
+
+ Multiple segments forming a piecewise linear function:
+
+ ```python
+ fx.Piecewise([
+ fx.Piece(0, 30), # Segment 1: 0 โ 30
+ fx.Piece(30, 60), # Segment 2: 30 โ 60
+ ])
+ ```
+
+=== "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
+ )
+ ```
+
+---
+
+## Usage
+
+=== "Variable Efficiency"
-With:
+ Converter efficiency that varies with load:
-- $\beta_\text{zero} \in \{0, 1\}$.
+ ```python
+ 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)]),
+ }),
+ )
+ ```
-Which can also be described as $v \in \{0\} \cup [\text{v}_{\text{start_k}}, \text{v}_{\text{end_k}}]$
+=== "Economies of Scale"
+ Investment cost per unit decreases with size:
-## Combining multiple Piecewises
+ ```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),
+ fx.Piece(80_000, 280_000),
+ ])
+ },
+ ),
+ )
+ ```
-Piecewise allows representing non-linear relationships.
-This is a powerful technique in linear optimization to model non-linear behaviors while maintaining the problem's linearity.
+=== "Forbidden Operating Region"
-Therefore, each Piecewise must have the same number of Pieces $k$.
+ Equipment cannot operate in certain ranges:
-The variables described in [Piecewise](#piecewise) are created for each Piece, but nor for each Piecewise.
-Rather, \eqref{eq:piece} is the only constraint that is created for each Piecewise, using the start and endpoints $\text{v}_{\text{start,k}}$ and $\text{v}_{\text{end,k}}$ of each Piece for the corresponding variable $v$
+ ```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%
+ ```
+
+---
+
+## Reference
+
+| 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 2ec34e3df..7b4c08f72 100644
--- a/docs/user-guide/mathematical-notation/features/StatusParameters.md
+++ b/docs/user-guide/mathematical-notation/features/StatusParameters.md
@@ -1,317 +1,114 @@
# 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 add on/off behavior to flows โ startup costs, minimum run times, cycling limits.
-## Binary State Variable
+## Basic: Binary Status
-Equipment operation is modeled using a binary state variable:
+A status variable $s(t) \in \{0, 1\}$ controls whether equipment is active:
-$$\label{eq:status_state}
-s(t) \in \{0, 1\} \quad \forall t
-$$
-
-With:
-
-- $s(t) = 1$: equipment is operating (active state)
-- $s(t) = 0$: equipment is shutdown (inactive state)
-
-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 and Switching
-
-State transitions are tracked using switch variables (see [State Transitions](../modeling-patterns/state-transitions.md#binary-state-transitions)):
-
-$$\label{eq:status_transitions}
-s^\text{startup}(t) - s^\text{shutdown}(t) = s(t) - s(t-1) \quad \forall t > 0
-$$
-
-$$\label{eq:status_switch_exclusivity}
-s^\text{startup}(t) + s^\text{shutdown}(t) \leq 1 \quad \forall t
-$$
-
-With:
-
-- $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)
-
-**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$
-
----
-
-## Effects and Costs
-
-### Startup Effects
-
-Effects incurred when equipment starts up:
-
-$$\label{eq:status_switch_effects}
-E_{e,\text{switch}} = \sum_{t} s^\text{startup}(t) \cdot \text{effect}_{e,\text{switch}}
-$$
-
-With:
-
-- $\text{effect}_{e,\text{switch}}$ being the effect value per startup event
-
-**Examples:**
-- Startup fuel consumption
-- Wear and tear costs
-- Labor costs for startup procedures
-- Inrush power demands
-
----
-
-### Running Effects
-
-Effects incurred while equipment is operating:
-
-$$\label{eq:status_running_effects}
-E_{e,\text{run}} = \sum_{t} s(t) \cdot \Delta t \cdot \text{effect}_{e,\text{run}}
-$$
-
-With:
-
-- $\text{effect}_{e,\text{run}}$ being the effect rate per operating hour
-- $\Delta t$ being the time step duration
-
-**Examples:**
-- Fixed operating and maintenance costs
-- Auxiliary power consumption
-- Consumable materials
-- Emissions while running
-
----
-
-## Operating Hour Constraints
-
-### Total Operating Hours
-
-Bounds on total operating time across the planning horizon:
-
-$$\label{eq:status_total_hours}
-h_\text{min} \leq \sum_{t} s(t) \cdot \Delta t \leq h_\text{max}
-$$
-
-With:
-
-- $h_\text{min}$ being the minimum total operating hours
-- $h_\text{max}$ being the maximum total operating hours
-
-**Use cases:**
-- Minimum runtime requirements (contracts, maintenance)
-- Maximum runtime limits (fuel availability, permits, equipment life)
-
----
-
-### 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
-$$
+```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
+ ),
+)
+```
-**Use cases:**
-- Equipment preservation requirements
-- Process stability needs
-- Contractual minimum activity levels
+When $s(t) = 0$: flow is zero. When $s(t) = 1$: flow bounds apply.
---
-## Cycling Limits
+## Startup Tracking
-Maximum number of startups across the planning horizon:
+Detect transitions: $s^{start}(t) - s^{stop}(t) = s(t) - s(t-1)$
-$$\label{eq:status_max_switches}
-\sum_{t} s^\text{startup}(t) \leq n_\text{max}
-$$
+=== "Startup Costs"
-With:
+ ```python
+ fx.StatusParameters(
+ effects_per_startup={'costs': 25000},
+ )
+ ```
-- $n_\text{max}$ being the maximum allowed number of startups
+=== "Running Costs"
-**Use cases:**
-- Preventing excessive equipment wear
-- Grid stability requirements
-- Operational complexity limits
-- Maintenance budget constraints
+ ```python
+ fx.StatusParameters(
+ effects_per_active_hour={'costs': 100}, # โฌ/h while on
+ )
+ ```
----
-
-## 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).
+=== "Startup Limit"
-**Behavior:**
-- When $s(t) = 0$: flow is forced to zero
-- When $s(t) = 1$: flow follows normal bounds
+ ```python
+ fx.StatusParameters(
+ startup_limit=20, # Max 20 starts per period
+ )
+ ```
---
-## Complete Formulation Summary
+## Duration Constraints
-For equipment with StatusParameters, the complete constraint system includes:
+=== "Min Uptime"
-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:**
+ Once on, must stay on for minimum duration:
- - 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}$
+ $s^{start}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} s(j) \geq T_{up}^{min}$
----
-
-## Implementation
+ ```python
+ fx.StatusParameters(min_uptime=8) # 8 hours minimum
+ ```
-**Python Class:** [`StatusParameters`][flixopt.interface.StatusParameters]
+=== "Min Downtime"
-**Key Parameters:**
+ Once off, must stay off for minimum duration:
-- `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)
+ $s^{stop}(t) = 1 \Rightarrow \sum_{j=t}^{t+k} (1 - s(j)) \geq T_{down}^{min}$
-See the [`StatusParameters`][flixopt.interface.StatusParameters] API documentation for complete parameter list and usage examples.
+ ```python
+ fx.StatusParameters(min_downtime=4) # 4 hours cooling
+ ```
-**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
+=== "Max Uptime"
-**Used in:**
-- [`Flow`][flixopt.elements.Flow] - Active/inactive operation for flows
-- All components supporting discrete operational states
+ Force shutdown after limit:
----
+ $\sum_{j=t-k}^{t} s(j) \leq T_{up}^{max}$
-## Examples
+ ```python
+ fx.StatusParameters(max_uptime=18) # Max 18h continuous
+ ```
-### 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
-)
-```
+=== "Total Hours"
-### 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
-)
-```
+ Limit total operating hours per period:
-### HVAC with Cycle Prevention
-```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
-)
-```
+ $H^{min} \leq \sum_t s(t) \cdot \Delta t \leq H^{max}$
-### Backup Generator with Testing Requirements
-```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
-)
-```
+ ```python
+ fx.StatusParameters(
+ active_hours_min=2000,
+ active_hours_max=5000,
+ )
+ ```
---
-## Notes
-
-**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.
+## Reference
+
+| 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{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) |
+
+**Classes:** [`StatusParameters`][flixopt.interface.StatusParameters], [`StatusModel`][flixopt.features.StatusModel]
diff --git a/docs/user-guide/mathematical-notation/index.md b/docs/user-guide/mathematical-notation/index.md
index 4512820f3..95e21db5e 100644
--- a/docs/user-guide/mathematical-notation/index.md
+++ b/docs/user-guide/mathematical-notation/index.md
@@ -1,123 +1,107 @@
-
# 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
-
-FlixOpt uses the following naming conventions:
-
-- 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$)
+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.
-## Dimensions and Time Steps
+!!! tip "When to read this"
+ You don't need this section to use flixOpt effectively. It's here for:
-FlixOpt supports multi-dimensional optimization with up to three dimensions: **time** (mandatory), **period** (optional), and **scenario** (optional).
+ - Understanding exactly what the solver is optimizing
+ - Debugging unexpected model behavior
+ - Extending flixOpt with custom constraints
+ - Academic work requiring formal notation
-**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.
+## Structure
-For complete details on dimensions, their relationships, and influence on formulations, see **[Dimensions](dimensions.md)**.
+The documentation follows the same structure as Core Concepts:
-### Time Steps
+| 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 & Dimensions](effects-and-dimensions.md) โ objectives, costs, scenarios, periods |
-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
-- [StatusParameters](features/StatusParameters.md) - Binary active/inactive operation
-- [Piecewise](features/Piecewise.md) - Piecewise linear approximations
+At its core, flixOpt solves:
-**User API:** When you pass `invest_parameters` or `status_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** | [StatusParameters](features/StatusParameters.md) | [`StatusParameters`][flixopt.interface.StatusParameters] |
-| **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] |
-| `StatusParameters` | [StatusParameters](features/StatusParameters.md) | [`StatusParameters`][flixopt.interface.StatusParameters] |
-| `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 & Dimensions](effects-and-dimensions.md)
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/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/docs/user-guide/optimization/index.md b/docs/user-guide/optimization/index.md
new file mode 100644
index 000000000..7010acfc5
--- /dev/null
+++ b/docs/user-guide/optimization/index.md
@@ -0,0 +1,195 @@
+# Running Optimizations
+
+This section covers how to run optimizations in flixOpt, including different optimization modes and solver configuration.
+
+## Optimization Modes
+
+flixOpt provides three optimization modes to handle different problem sizes and requirements:
+
+### Optimization (Full)
+
+[`Optimization`][flixopt.optimization.Optimization] solves the entire problem at once.
+
+```python
+import flixopt as fx
+
+optimization = fx.Optimization('my_model', flow_system)
+optimization.solve(fx.solvers.HighsSolver())
+```
+
+**Best for:**
+
+- Small to medium problems
+- When you need the globally optimal solution
+- Problems without time-coupling simplifications
+
+### SegmentedOptimization
+
+[`SegmentedOptimization`][flixopt.optimization.SegmentedOptimization] splits the time horizon into segments and solves them sequentially.
+
+```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())
+```
+
+**Best for:**
+
+- Large problems that don't fit in memory
+- Long time horizons (weeks, months)
+- Problems where decisions are mostly local in time
+
+**Trade-offs:**
+
+- Faster solve times
+- May miss globally optimal solutions
+- Overlap helps maintain solution quality at segment boundaries
+
+### ClusteredOptimization
+
+[`ClusteredOptimization`][flixopt.optimization.ClusteredOptimization] uses time series aggregation to reduce problem size by identifying representative periods.
+
+```python
+clustering_params = fx.ClusteringParameters(
+ n_periods=8, # Number of typical periods
+ hours_per_period=24 # Hours per typical period
+)
+
+optimization = fx.ClusteredOptimization(
+ 'clustered_model',
+ flow_system,
+ clustering_params
+)
+optimization.solve(fx.solvers.HighsSolver())
+```
+
+**Best for:**
+
+- Investment planning problems
+- Year-long optimizations
+- When computational speed is critical
+
+**Trade-offs:**
+
+- Much faster solve times
+- Approximates the full problem
+- Best when patterns repeat (e.g., typical days)
+
+## Choosing an Optimization Mode
+
+| Mode | Problem Size | Solve Time | Solution Quality |
+|------|-------------|------------|------------------|
+| `Optimization` | Small-Medium | Slow | Optimal |
+| `SegmentedOptimization` | Large | Medium | Near-optimal |
+| `ClusteredOptimization` | Very Large | Fast | Approximate |
+
+## Solver Configuration
+
+### Available 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 (included by default). Use Gurobi/CPLEX for large models or when speed matters.
+
+### Solver Options
+
+```python
+# Basic usage with defaults
+optimization.solve(fx.solvers.HighsSolver())
+
+# With custom options
+optimization.solve(
+ fx.solvers.GurobiSolver(
+ time_limit_seconds=3600,
+ mip_gap=0.01,
+ extra_options={
+ 'Threads': 4,
+ 'Presolve': 2
+ }
+ )
+)
+```
+
+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
+- Use `ClusteredOptimization` for long horizons
+- Remove unnecessary components
+- Simplify constraint formulations
+
+### Solver Tuning
+
+- Enable presolve and cuts
+- Adjust optimality tolerances for faster (approximate) solutions
+- Use parallel threads when available
+
+### Problem Formulation
+
+- Avoid unnecessary binary variables
+- Use continuous investment sizes when possible
+- Tighten variable bounds
+- Remove redundant constraints
+
+## Debugging
+
+### Infeasibility
+
+If your model has no feasible solution:
+
+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.
+
+### Unexpected Results
+
+If solutions don't match expectations:
+
+1. Verify input data (units, scales)
+2. Enable logging: `fx.CONFIG.exploring()`
+3. Visualize intermediate results
+4. Start with a simpler model
+5. Check constraint formulations
+
+## Next Steps
+
+- 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
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
diff --git a/docs/user-guide/support.md b/docs/user-guide/support.md
new file mode 100644
index 000000000..5f26cdd24
--- /dev/null
+++ b/docs/user-guide/support.md
@@ -0,0 +1,23 @@
+# Support
+
+## Getting Help
+
+**[GitHub Issues](https://github.com/flixOpt/flixopt/issues)** โ Report bugs or ask questions
+
+When opening an issue, include:
+
+- Minimal reproducible example
+- flixOpt version: `python -c "import flixopt; print(flixopt.__version__)"`
+- Python version and OS
+- Full error message
+
+## Resources
+
+- [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
+
+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
new file mode 100644
index 000000000..2c89be8dc
--- /dev/null
+++ b/docs/user-guide/troubleshooting.md
@@ -0,0 +1,61 @@
+# Troubleshooting
+
+## Infeasible Model
+
+**Problem:** Solver reports the model is infeasible.
+
+**Solutions:**
+
+1. Check that supply can meet demand at all timesteps
+2. Verify capacity limits are sufficient
+3. Review storage initial/final states
+
+## Unbounded Model
+
+**Problem:** Solver reports the model is unbounded.
+
+**Solutions:**
+
+1. Add upper bounds to all flows
+2. Ensure investment parameters have maximum sizes
+3. Verify effect coefficients have correct signs
+
+## Unexpected Results
+
+**Debugging Steps:**
+
+1. Enable logging:
+ ```python
+ from flixopt import CONFIG
+ CONFIG.exploring()
+ ```
+
+2. Start with a minimal model and add complexity incrementally
+
+3. Check units are consistent
+
+4. Visualize results to verify energy balances
+
+## Slow Solve Times
+
+**Solutions:**
+
+1. Use longer timesteps or aggregate time periods
+2. Use Gurobi instead of HiGHS for large models
+3. Set solver options:
+ ```python
+ solver = fx.solvers.GurobiSolver(
+ time_limit_seconds=3600,
+ mip_gap=0.01
+ )
+ ```
+
+## Getting Help
+
+If you're stuck:
+
+1. Search [GitHub Issues](https://github.com/flixOpt/flixopt/issues)
+2. Open a new issue with:
+ - Minimal reproducible example
+ - flixopt and Python version
+ - Full error message
diff --git a/flixopt/components.py b/flixopt/components.py
index 2d04586cc..0cfed39eb 100644
--- a/flixopt/components.py
+++ b/flixopt/components.py
@@ -41,8 +41,7 @@ class LinearConverter(Component):
behavior approximated through piecewise linear segments.
Mathematical Formulation:
- See the complete mathematical model in the documentation:
- [LinearConverter](../user-guide/mathematical-notation/elements/LinearConverter.md)
+ See
Args:
label: The label of the Element. Used to identify it in the FlowSystem.
@@ -260,18 +259,7 @@ class Storage(Component):
and investment-optimized storage systems with comprehensive techno-economic modeling.
Mathematical Formulation:
- See the complete mathematical model in the documentation:
- [Storage](../user-guide/mathematical-notation/elements/Storage.md)
-
- - Equation (1): Charge state bounds
- - Equation (3): Storage balance (charge state evolution)
-
- Variable Mapping:
- - ``capacity_in_flow_hours`` โ C (storage capacity)
- - ``charge_state`` โ c(t_i) (state of charge at time t_i)
- - ``relative_loss_per_hour`` โ ฤ_rel,loss (self-discharge rate)
- - ``eta_charge`` โ ฮท_in (charging efficiency)
- - ``eta_discharge`` โ ฮท_out (discharging efficiency)
+ See
Args:
label: Element identifier used in the FlowSystem.
@@ -779,6 +767,16 @@ def create_transmission_equation(self, name: str, in_flow: Flow, out_flow: Flow)
class LinearConverterModel(ComponentModel):
+ """Mathematical model implementation for LinearConverter components.
+
+ Creates optimization constraints for linear conversion relationships between
+ input and output flows, supporting both simple conversion factors and piecewise
+ non-linear approximations.
+
+ Mathematical Formulation:
+ See
+ """
+
element: LinearConverter
def __init__(self, model: FlowSystemModel, element: LinearConverter):
@@ -827,7 +825,14 @@ def _do_modeling(self):
class StorageModel(ComponentModel):
- """Submodel of Storage"""
+ """Mathematical model implementation for Storage components.
+
+ Creates optimization variables and constraints for charge state tracking,
+ storage balance equations, and optional investment sizing.
+
+ Mathematical Formulation:
+ See
+ """
element: Storage
diff --git a/flixopt/effects.py b/flixopt/effects.py
index 9df7c2ce5..5dd53258f 100644
--- a/flixopt/effects.py
+++ b/flixopt/effects.py
@@ -32,16 +32,15 @@
@register_class_for_io
class Effect(Element):
- """
- Represents system-wide impacts like costs, emissions, resource consumption, or other effects.
+ """Represents system-wide impacts like costs, emissions, or resource consumption.
- Effects capture the broader impacts of system operation and investment decisions beyond
- the primary energy/material flows. Each Effect accumulates contributions from Components,
- Flows, and other system elements. One Effect is typically chosen as the optimization
- objective, while others can serve as constraints or tracking metrics.
+ Effects quantify impacts aggregating contributions from Elements across the FlowSystem.
+ One Effect serves as the optimization objective, while others can be constrained or tracked.
+ Supports operational and investment contributions, cross-effect relationships (e.g., carbon
+ pricing), and flexible constraint formulation.
- Effects support comprehensive modeling including operational and investment contributions,
- cross-effect relationships (e.g., carbon pricing), and flexible constraint formulation.
+ Mathematical Formulation:
+ See
Args:
label: The label of the Element. Used to identify it in the FlowSystem.
@@ -302,6 +301,16 @@ def _plausibility_checks(self) -> None:
class EffectModel(ElementModel):
+ """Mathematical model implementation for Effects.
+
+ Creates optimization variables and constraints for effect aggregation,
+ including periodic and temporal tracking, cross-effect contributions,
+ and effect bounds.
+
+ Mathematical Formulation:
+ See
+ """
+
element: Effect # Type hint
def __init__(self, model: FlowSystemModel, element: Effect):
diff --git a/flixopt/elements.py b/flixopt/elements.py
index 9ca938b62..74ed7bde4 100644
--- a/flixopt/elements.py
+++ b/flixopt/elements.py
@@ -190,8 +190,7 @@ class Bus(Element):
or material flows between different Components.
Mathematical Formulation:
- See the complete mathematical model in the documentation:
- [Bus](../user-guide/mathematical-notation/elements/Bus.md)
+ See
Args:
label: The label of the Element. Used to identify it in the FlowSystem.
@@ -331,8 +330,7 @@ class Flow(Element):
- **StatusParameters**: Used for `status_parameters` when flow has discrete states
Mathematical Formulation:
- See the complete mathematical model in the documentation:
- [Flow](../user-guide/mathematical-notation/elements/Flow.md)
+ See
Args:
label: Unique flow identifier within its component.
@@ -595,6 +593,15 @@ def _format_invest_params(self, params: InvestParameters) -> str:
class FlowModel(ElementModel):
+ """Mathematical model implementation for Flow elements.
+
+ Creates optimization variables and constraints for flow rate bounds,
+ flow-hours tracking, and load factors.
+
+ Mathematical Formulation:
+ See
+ """
+
element: Flow # Type hint
def __init__(self, model: FlowSystemModel, element: Flow):
@@ -858,6 +865,15 @@ def previous_status(self) -> xr.DataArray | None:
class BusModel(ElementModel):
+ """Mathematical model implementation for Bus elements.
+
+ Creates optimization variables and constraints for nodal balance equations,
+ and optional excess/deficit variables with penalty costs.
+
+ Mathematical Formulation:
+ See
+ """
+
element: Bus # Type hint
def __init__(self, model: FlowSystemModel, element: Bus):
diff --git a/flixopt/features.py b/flixopt/features.py
index 653a2fb92..cd9e07151 100644
--- a/flixopt/features.py
+++ b/flixopt/features.py
@@ -24,16 +24,19 @@
class InvestmentModel(Submodel):
- """
- This feature model is used to model the investment of a variable.
- It applies the corresponding bounds to the variable and the active/inactive state of the variable.
+ """Mathematical model implementation for investment decisions.
+
+ Creates optimization variables and constraints for investment sizing decisions,
+ supporting both binary and continuous sizing with comprehensive effect modeling.
+
+ Mathematical Formulation:
+ See
Args:
model: The optimization model instance
label_of_element: The label of the parent (Element). Used to construct the full label of the model.
parameters: The parameters of the feature model.
label_of_model: The label of the model. This is needed to construct the full label of the model.
-
"""
parameters: InvestParameters
@@ -147,7 +150,14 @@ def invested(self) -> linopy.Variable | None:
class StatusModel(Submodel):
- """Status model for equipment with binary active/inactive states"""
+ """Mathematical model implementation for binary status.
+
+ Creates optimization variables and constraints for binary status modeling,
+ state transitions, duration tracking, and operational effects.
+
+ Mathematical Formulation:
+ See
+ """
def __init__(
self,
@@ -385,6 +395,15 @@ def _do_modeling(self):
class PiecewiseModel(Submodel):
+ """Mathematical model implementation for piecewise linear approximations.
+
+ Creates optimization variables and constraints for piecewise linear relationships,
+ including lambda variables, piece activation binaries, and coupling constraints.
+
+ Mathematical Formulation:
+ See
+ """
+
def __init__(
self,
model: FlowSystemModel,
diff --git a/flixopt/interface.py b/flixopt/interface.py
index 30db4876f..7995d5e78 100644
--- a/flixopt/interface.py
+++ b/flixopt/interface.py
@@ -82,8 +82,14 @@ def transform_data(self, name_prefix: str = '') -> None:
@register_class_for_io
class Piecewise(Interface):
- """
- Define a Piecewise, consisting of a list of Pieces.
+ """Define piecewise linear approximations for modeling non-linear relationships.
+
+ Enables modeling of non-linear relationships through piecewise linear segments
+ while maintaining problem linearity. Consists of a collection of Pieces that
+ define valid ranges for variables.
+
+ Mathematical Formulation:
+ See
Args:
pieces: list of Piece objects defining the linear segments. The arrangement
@@ -706,8 +712,7 @@ class InvestParameters(Interface):
- **Divestment Effects**: Penalties for not investing (demolition, opportunity costs)
Mathematical Formulation:
- See the complete mathematical model in the documentation:
- [InvestParameters](../user-guide/mathematical-notation/features/InvestParameters.md)
+ See
Args:
fixed_size: Creates binary decision at this exact size. None allows continuous sizing.
@@ -1028,8 +1033,7 @@ class StatusParameters(Interface):
- **Process Equipment**: Compressors, pumps with operational constraints
Mathematical Formulation:
- See the complete mathematical model in the documentation:
- [StatusParameters](../user-guide/mathematical-notation/features/StatusParameters.md)
+ See
Args:
effects_per_startup: Costs or impacts incurred for each transition from
diff --git a/mkdocs.yml b/mkdocs.yml
index 7e86d9720..8fb6765ae 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -7,37 +7,60 @@ 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
+ - Home:
+ - Home: index.md
+ - Getting Started:
+ - Installation: home/installation.md
+ - Quick Start: home/quick-start.md
+ - About:
+ - Users: home/users.md
+ - Citing: home/citing.md
+ - License: home/license.md
+
- User Guide:
- - Getting Started: getting-started.md
+ - Overview: user-guide/index.md
- Core Concepts: user-guide/core-concepts.md
- - Migration to v3.0.0: user-guide/migration-guide-v3.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
- - 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
- - StatusParameters: user-guide/mathematical-notation/features/StatusParameters.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
+ - 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 & 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
- Recipes: user-guide/recipes/index.md
- - Roadmap: roadmap.md
- - Examples: examples/
- - Contribute: contribute.md
+ - Support:
+ - FAQ: user-guide/faq.md
+ - Troubleshooting: user-guide/troubleshooting.md
+ - Community: user-guide/support.md
+ - Migration & Updates:
+ - Migration Guide v3: user-guide/migration-guide-v3.md
+ - Release Notes: changelog.md
+ - Roadmap: roadmap.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-Optimization 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
@@ -128,7 +151,7 @@ markdown_extensions:
- toc:
permalink: true
permalink_title: Anchor link to this section
- toc_depth: 2
+ toc_depth: 3
title: On this page
# Code blocks
@@ -147,6 +170,9 @@ markdown_extensions:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
+ - name: plotly
+ class: mkdocs-plotly
+ format: !!python/name:mkdocs_plotly_plugin.fences.fence_plotly
# Enhanced content
- pymdownx.details
@@ -185,6 +211,8 @@ plugins:
- search:
separator: '[\s\u200b\-_,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])'
+ - plotly
+
- table-reader
- include-markdown
@@ -310,6 +338,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
diff --git a/pyproject.toml b/pyproject.toml
index 258b0ab7f..206283767 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -104,6 +104,7 @@ docs = [
"mkdocs-gen-files==0.5.0",
"mkdocs-include-markdown-plugin==7.2.0",
"mkdocs-literate-nav==0.6.2",
+ "mkdocs-plotly-plugin==0.1.3",
"markdown-include==0.8.1",
"pymdown-extensions==10.16.1",
"pygments==2.19.2",
diff --git a/scripts/extract_changelog.py b/scripts/extract_changelog.py
deleted file mode 100644
index 44790fec6..000000000
--- a/scripts/extract_changelog.py
+++ /dev/null
@@ -1,151 +0,0 @@
-#!/usr/bin/env python3
-"""
-Extract individual releases from CHANGELOG.md to docs/changelog/
-Simple script to create one file per release.
-"""
-
-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]*?)(?=