Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
843f996
Scenarios: Prepare DataModel (#213)
FBumann Mar 31, 2025
8c4a45b
Feature/scenarios Transform data and update type hints (#215)
FBumann Apr 1, 2025
9905790
Feature/scenarios effects (#216)
FBumann Apr 2, 2025
24af8c6
ruff check and format
FBumann Apr 2, 2025
3792523
Fix coords in constraints and variables
FBumann Apr 2, 2025
dcc86f4
Feature/scenarios results (#220)
FBumann Apr 5, 2025
bd1d2b6
ruff check and format
FBumann Apr 5, 2025
28a46dc
Feature/scenarios dims order (#219)
FBumann Apr 5, 2025
479c1eb
Bugfix main results
FBumann Apr 9, 2025
a611672
Remove code duplicate
FBumann Apr 9, 2025
9a8724e
Feature/scenarios invest (#227)
FBumann Apr 9, 2025
39f9e4b
Feature/scenarios weights (#228)
FBumann Apr 9, 2025
75cb399
Feature/scenarios tests pandas (#229)
FBumann Apr 9, 2025
26bc447
ruff check
FBumann Apr 9, 2025
0241d1d
Merge branch 'main' into scenarios/main
FBumann Apr 11, 2025
6eeea72
Bugfix plausibility in Storage
FBumann Apr 11, 2025
ecf64d2
Bugfix check in Storage Model
FBumann Apr 11, 2025
091ab71
Improve example
FBumann Apr 11, 2025
0a7e336
ruff check
FBumann Apr 11, 2025
da60aa2
Merge branch 'main' into scenarios/main
FBumann Apr 11, 2025
8b5d2fc
Simplifying the investment with scenarios, by a simpler approach
FBumann Apr 14, 2025
f28db64
Simplifying the investment with scenarios
FBumann Apr 14, 2025
8696430
Bugfix in scenario selection
FBumann Apr 14, 2025
2d3f0ad
ruff check
FBumann Apr 14, 2025
c730b87
Scenarios/io (#244)
FBumann Apr 15, 2025
50bb559
Scenarios/testing (#246)
FBumann Apr 15, 2025
9ea8fba
ruff check
FBumann Apr 15, 2025
7f4fddb
Bugfix in _create_bounds_for_scenarios()
FBumann Apr 15, 2025
e2192da
exclude super long test
FBumann Apr 16, 2025
4ad86d5
exclude super long test - fix
FBumann Apr 16, 2025
967174c
Bugfix plot_node_balance_pie()
FBumann Apr 17, 2025
d24b5e7
Scenarios/fixes (#252)
FBumann Apr 22, 2025
a3c7d47
Scenarios/filter (#253)
FBumann Apr 22, 2025
0977c1f
Scenarios/drop suffix (#251)
FBumann Apr 22, 2025
5cf6e0e
Scenarios/bar plot (#254)
FBumann Apr 22, 2025
4cfa27f
Bugfix plotting
FBumann Apr 22, 2025
16fd74c
Fix example_calculation_types.py
FBumann Apr 22, 2025
67d1716
Scenarios/fixes (#255)
FBumann Apr 24, 2025
b968027
Scenarios/effects (#256)
FBumann Apr 25, 2025
9f2f38b
Scenarios/datasets results (#257)
FBumann Apr 28, 2025
dbfb1b5
ruff check
FBumann Apr 28, 2025
6738d34
ruff check
FBumann Apr 28, 2025
c64d12e
Scenarios/deprecation (#258)
FBumann Apr 28, 2025
0499497
Bugfix in plausibility_check: Index 0
FBumann Apr 28, 2025
ee00577
Set bargap to 0 in stacked bars
FBumann Apr 29, 2025
a11ed92
Ensure the size is always properly indexed in results.
FBumann Apr 29, 2025
2400244
ruff check
FBumann Apr 29, 2025
0f9b30a
BUGFIX in extract data, that causes coords in linopy to be incorrect …
FBumann May 5, 2025
26e89a9
Improve yaml formatting for model documentation (#259)
FBumann May 5, 2025
c0cbaae
Make the size/capacity a TimeSeries (#260)
FBumann May 5, 2025
67ebfbb
Scenarios/plot network (#262)
FBumann May 13, 2025
9edd1fa
Update deploy-docs.yaml:
FBumann May 13, 2025
d3c0c48
Bugfix DataConverter and add tests (#263)
FBumann May 13, 2025
e6e680c
Fix doc deployment to not publish on non stable releases
FBumann May 13, 2025
3d89b74
Remove unused code
FBumann May 15, 2025
cc772a4
Remove legend placing for better auto placing in plotly
FBumann May 16, 2025
d92349e
Fix plotly dependency
FBumann May 16, 2025
5c2900a
Improve validation when adding new effects
FBumann May 19, 2025
8e2e949
Merge branch 'main' into scenarios/main
FBumann Jun 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
55 changes: 55 additions & 0 deletions docs/release-notes/v2.2.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Release v2.2.0

**Release Date:** YYYY-MM-DD

## What's New

### Scenarios
Scenarios are a new feature of flixopt. They can be used to model uncertainties in the flow system, such as:
* Different demand profiles
* Different price forecasts
* Different weather conditions
* Different climate conditions
The might also be used to model an evolving system with multiple investment periods. Each **scenario** might be a new year, a new month, or a new day, with a different set of investment decisions to take.

The weighted sum of the total objective effect of each scenario is used as the objective of the optimization.

#### Investments and scenarios
Scenarios allow for more flexibility in investment decisions.
You can decide to allow different investment decisions for each scenario, or to allow a single investment decision for a subset of all scnarios, while not allowing for an invest in others.
This enables the following use cases:
* Find the best investment decision for each scenario individually
* Find the best overall investment decision for possible scenarios (robust decision-making)
* Find the best overall investment decision for a subset of all scenarios

The last one might be useful if you want to model a system with multiple investment periods, where one investment decision is made for more than one scenario.
This might occur when scenarios represent years or months, while an investment decision influences the system for multiple years or months.


## Other new features
* Balanced storage - Storage charging and discharging sizes can now be forced to be equal in when optimizing their size.
* Feature 2 - Description

## Improvements

* Improvement 1 - Description
* Improvement 2 - Description

## Bug Fixes

* Fixed issue with X
* Resolved problem with Y

## Breaking Changes

* Change 1 - Migration instructions
* Change 2 - Migration instructions

## Deprecations

* Feature X will be removed in v{next_version}

## Dependencies

* Added dependency X v1.2.3
* Updated dependency Y to v2.0.0
115 changes: 115 additions & 0 deletions docs/user-guide/Mathematical Notation/Investment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Investments

## Current state
$$
\beta_{\text{invest}} \cdot \text{max}(\epsilon, \text V^{\text L}) \leq V \leq \beta_{\text{invest}} \cdot \text V^{\text U}
$$
With:
- $V$ = size
- $V^{\text L}$ = minimum size
- $V^{\text U}$ = maximum size
- $\epsilon$ = epsilon, a small number (such as $1e^{-5}$)
- $\beta_{invest} \in {0,1}$ = wether the size is invested or not

_Please edit the use cases as needed_
## Quickfix 1: Optimize the single best size overall
### Single variable
This is already possible and should be, as this is a needed use case
An additional factor to when the size is actually available might me practical (Which indicates the (fixed) time of investment)
## Math
$$
V(p) = V * a(p)
$$
with:
- $V$ = size
- $a(p)$ = factor for availlability per period

Factor $a(p)$ is simply multiplied with relative minimum or maximum(t). This is already possible by doing this yourself.
Effectively, the relative minimum or maximum are altered before using the same constraiints as before.
THis might lead to some issues regariding minimum_load factor, or others, as the size is not 0 in a scenario where the component cant produce.
**Therefore this might not be the best choice. See (#Variable per Scenario)

## Variable per Scenario
- **size** and **invest** as a variable per period $V(s)$ and $\beta_{invest}(s)$
- with scenario $s \in S$

### Usecase 1: Optimize the size for each Scenario independently
Restrictions are seperatly for each scenario
No changes needed. This could be the default behaviour.

### Usecase 2: Optimize ONE size for ALL scenarios
The size is the same globally, but not a scalar, but a variable per scenario $V(s)$
#### 2a: The same size in all scenarios
$$
V(s) = V(s') \quad \forall s,s' \in S
$$

With:
- $V(s)$ and $V(s')$ = size
- $S$ = set of scenarios

#### 2b: The same size, but can be 0 prior to the first increment
- Find the Optimal time of investment.
- Force an investment in a certain scenario (parameter optional as a list/array ob booleans)
- Combine optional and minimum/maximum size to force an investment inside a range if scenarios

$$
\beta_{\text{invest}}(s) \leq \beta_{\text{invest}}(s+1) \quad \forall s \in \{1,2,\ldots,S-1\}
$$

$$
V(s') - V(s) \leq M \cdot (2 - \beta_{\text{invest}}(s) - \beta_{\text{invest}}(s')) \quad \forall s, s' \in S
$$
$$
V(s') - V(s) \geq M \cdot (2 - \beta_{\text{invest}}(s) - \beta_{\text{invest}}(s')) \quad \forall s, s' \in S
$$

This could be the default behaviour. (which would be consistent with other variables)


### Switch

$$
\begin{aligned}
& \text{SWITCH}_s \in \{0,1\} \quad \forall s \in \{1,2,\ldots,S\} \\
& \sum_{s=1}^{S} \text{SWITCH}_s = 1 \\
& \beta_{\text{invest}}(s) = \sum_{s'=1}^{s} \text{SWITCH}_{s'} \quad \forall s \in \{1,2,\ldots,S\} \\
\end{aligned}
$$

$$
\begin{aligned}
& V(s) \leq V_{\text{actual}} \quad \forall s \in \{1,2,\ldots,S\} \\
& V(s) \geq V_{\text{actual}} - M \cdot (1 - \beta_{\text{invest}}(s)) \quad \forall s \in \{1,2,\ldots,S\}
\end{aligned}
$$




### Usecase 3: Find the best scenario to increment the size (Timing of the investment)
The size can only increment once (based on a starting point). This allows to optimize the timing of an investment.
#### Math
Treat $\beta_{invest}$ like an ON/OFF variable, and introduce a SwitchOn, that can only be active once.

*Thoughts:*
- Treating $\beta_{invest}$ like an ON/OFF variable suggest using the already presentconstraints linked to On/OffModel
- The timing could be constraint to be first in scenario x, or last in scenario y
- Restrict the number of consecutive scenarios
THis might needs the OnOffModel to be more generic (HOURS). Further, the span between scenarios needs to be weighted (like dt_in_hours), or the scenarios need to be measureable (integers)


### Others

#### Usecase 4: Only increase/decrease the size
Start from a certain size. For each scenario, the size can increase, but never decrease. (Or the other way around).
This would mean that a size expansion is possible,

#### Usecase 5: Restrict the increment in size per scenario
Restrict how much the size can increase/decrease for in scenario, based on the prior scenario.





Many more are possible
10 changes: 5 additions & 5 deletions examples/03_Calculation_types/example_calculation_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,34 +194,34 @@ def get_solutions(calcs: List, variable: str) -> xr.Dataset:
# --- Plotting for comparison ---
fx.plotting.with_plotly(
get_solutions(calculations, 'Speicher|charge_state').to_dataframe(),
mode='line',
style='line',
title='Charge State Comparison',
ylabel='Charge state',
).write_html('results/Charge State.html')

fx.plotting.with_plotly(
get_solutions(calculations, 'BHKW2(Q_th)|flow_rate').to_dataframe(),
mode='line',
style='line',
title='BHKW2(Q_th) Flow Rate Comparison',
ylabel='Flow rate',
).write_html('results/BHKW2 Thermal Power.html')

fx.plotting.with_plotly(
get_solutions(calculations, 'costs(operation)|total_per_timestep').to_dataframe(),
mode='line',
style='line',
title='Operation Cost Comparison',
ylabel='Costs [€]',
).write_html('results/Operation Costs.html')

fx.plotting.with_plotly(
pd.DataFrame(get_solutions(calculations, 'costs(operation)|total_per_timestep').to_dataframe().sum()).T,
mode='bar',
style='stacked_bar',
title='Total Cost Comparison',
ylabel='Costs [€]',
).update_layout(barmode='group').write_html('results/Total Costs.html')

fx.plotting.with_plotly(
pd.DataFrame([calc.durations for calc in calculations], index=[calc.name for calc in calculations]), 'bar'
pd.DataFrame([calc.durations for calc in calculations], index=[calc.name for calc in calculations]), 'stacked_bar'
).update_layout(title='Duration Comparison', xaxis_title='Calculation type', yaxis_title='Time (s)').write_html(
'results/Speed Comparison.html'
)
125 changes: 125 additions & 0 deletions examples/04_Scenarios/scenario_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""
This script shows how to use the flixopt framework to model a simple energy system.
"""

import numpy as np
import pandas as pd
from rich.pretty import pprint # Used for pretty printing

import flixopt as fx

if __name__ == '__main__':
# Create datetime array starting from '2020-01-01' for the given time period
timesteps = pd.date_range('2020-01-01', periods=9, freq='h')
scenarios = pd.Index(['Base Case', 'High Demand'])

# --- Create Time Series Data ---
# Heat demand profile (e.g., kW) over time and corresponding power prices
heat_demand_per_h = pd.DataFrame({'Base Case':[30, 0, 90, 110, 110, 20, 20, 20, 20],
'High Demand':[30, 0, 100, 118, 125, 20, 20, 20, 20]}, index=timesteps)
power_prices = np.array([0.08, 0.09])

flow_system = fx.FlowSystem(timesteps=timesteps, scenarios=scenarios, scenario_weights=np.array([0.5, 0.6]))

# --- Define Energy Buses ---
# These represent nodes, where the used medias are balanced (electricity, heat, and gas)
flow_system.add_elements(fx.Bus(label='Strom'), fx.Bus(label='Fernwärme'), fx.Bus(label='Gas'))

# --- Define Effects (Objective and CO2 Emissions) ---
# Cost effect: used as the optimization objective --> minimizing costs
costs = fx.Effect(
label='costs',
unit='€',
description='Kosten',
is_standard=True, # standard effect: no explicit value needed for costs
is_objective=True, # Minimizing costs as the optimization objective
)

# CO2 emissions effect with an associated cost impact
CO2 = fx.Effect(
label='CO2',
unit='kg',
description='CO2_e-Emissionen',
specific_share_to_other_effects_operation={costs.label: 0.2},
maximum_operation_per_hour=1000, # Max CO2 emissions per hour
)

# --- Define Flow System Components ---
# Boiler: Converts fuel (gas) into thermal energy (heat)
boiler = fx.linear_converters.Boiler(
label='Boiler',
eta=0.5,
Q_th=fx.Flow(label='Q_th', bus='Fernwärme', size=50, relative_minimum=0.1, relative_maximum=1, on_off_parameters=fx.OnOffParameters()),
Q_fu=fx.Flow(label='Q_fu', bus='Gas'),
)

# Combined Heat and Power (CHP): Generates both electricity and heat from fuel
chp = fx.linear_converters.CHP(
label='CHP',
eta_th=0.5,
eta_el=0.4,
P_el=fx.Flow('P_el', bus='Strom', size=60, relative_minimum=5 / 60, on_off_parameters=fx.OnOffParameters()),
Q_th=fx.Flow('Q_th', bus='Fernwärme'),
Q_fu=fx.Flow('Q_fu', bus='Gas'),
)

# Storage: Energy storage system with charging and discharging capabilities
storage = fx.Storage(
label='Storage',
charging=fx.Flow('Q_th_load', bus='Fernwärme', size=1000),
discharging=fx.Flow('Q_th_unload', bus='Fernwärme', size=1000),
capacity_in_flow_hours=fx.InvestParameters(fix_effects=20, fixed_size=30, optional=False),
initial_charge_state=0, # Initial storage state: empty
relative_maximum_charge_state=np.array([80, 70, 80, 80, 80, 80, 80, 80, 80, 80]) * 0.01,
eta_charge=0.9,
eta_discharge=1, # Efficiency factors for charging/discharging
relative_loss_per_hour=0.08, # 8% loss per hour. Absolute loss depends on current charge state
prevent_simultaneous_charge_and_discharge=True, # Prevent charging and discharging at the same time
)

# Heat Demand Sink: Represents a fixed heat demand profile
heat_sink = fx.Sink(
label='Heat Demand',
sink=fx.Flow(label='Q_th_Last', bus='Fernwärme', size=1, fixed_relative_profile=heat_demand_per_h),
)

# Gas Source: Gas tariff source with associated costs and CO2 emissions
gas_source = fx.Source(
label='Gastarif',
source=fx.Flow(label='Q_Gas', bus='Gas', size=1000, effects_per_flow_hour={costs.label: 0.04, CO2.label: 0.3}),
)

# Power Sink: Represents the export of electricity to the grid
power_sink = fx.Sink(
label='Einspeisung', sink=fx.Flow(label='P_el', bus='Strom', effects_per_flow_hour=-1 * power_prices)
)

# --- Build the Flow System ---
# Add all defined components and effects to the flow system
flow_system.add_elements(costs, CO2, boiler, storage, chp, heat_sink, gas_source, power_sink)

# Visualize the flow system for validation purposes
flow_system.plot_network(show=True)

# --- Define and Run Calculation ---
# Create a calculation object to model the Flow System
calculation = fx.FullCalculation(name='Sim1', flow_system=flow_system)
calculation.do_modeling() # Translate the model to a solvable form, creating equations and Variables

# --- Solve the Calculation and Save Results ---
calculation.solve(fx.solvers.HighsSolver(mip_gap=0, time_limit_seconds=30))

# --- Analyze Results ---
calculation.results['Fernwärme'].plot_node_balance_pie()
calculation.results['Fernwärme'].plot_node_balance(style='stacked_bar')
calculation.results['Storage'].plot_node_balance()
calculation.results.plot_heatmap('CHP(Q_th)|flow_rate')

# Convert the results for the storage component to a dataframe and display
df = calculation.results['Storage'].node_balance_with_charge_state()
print(df)
calculation.results['Storage'].plot_charge_state(engine='matplotlib')

# Save results to file for later usage
calculation.results.to_file()
fig, ax = calculation.results['Storage'].plot_charge_state(engine='matplotlib')
Loading