Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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.
92 changes: 92 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,98 @@ Please remove all irrelevant sections before releasing.

Until here -->

## [Unreleased] - ????-??-??
Multi-Period and stochastic modeling is coming to flixopt in this release.

In this release, we introduce the following new features:
#### Multi-period-support
A flixopt model might be modeled with a "year" dimension.
This enables to model transformation pathways over multiple years.

#### Stochastic modeling
A flixopt model can be modeled with a scenario dimension.
Scenarios can be weighted and variables can be equated across scenarios. This enables to model uncertainties in the flow system, such as:
* Different demand profiles
* Different price forecasts
* Different weather conditions

Common use cases are:
* Find the best overall investment decision for possible scenarios (robust decision-making)
* Find the best dispatch for the most important assets under uncertain price and weather conditions

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

#### Improved Data handling: IO, resampling and more through xarray
* IO for all Interfaces and the FlowSystem with round-trip serialization support
* NetCDF export/import capabilities for all Interface objects and FlowSystem
* JSON export for documentation purposes
* Recursive handling of nested Interface objects
* FlowSystem data manipulation methods
* `sel()` and `isel()` methods for temporal data selection
* `resample()` method for temporal resampling
* `copy()` method to create a copy of a FlowSystem, including all underlying Elements and their data
* `__eq__()` method for FlowSystem comparison

* Core data handling improvements
* `get_dataarray_stats()` function for statistical summaries
* Enhanced `DataConverter` class with better TimeSeriesData support


### Added
* FlowSystem Restoring: The used FlowSystem will now get restired from the results (lazily). ALll Parameters can be safely acessed anytime after the solve.
* FLowResults added as a new class to store the results of Flows. They can now be accessed directly.
* Added precomputed DataArrays for `size`s, `flow_rate`s and `flow_hour`s.
* Added `effects_per_component()`-Dataset to Results that stores the direct (and indirect) effects of each component. This greatly improves the evaluation of the impact of individual Components, even with many and complex effects.
* Improved filter methods for Results
* Balanced storage - Storage charging and discharging sizes can now be forced to be equal in when optimizing their size.
* Added Example for 2-stage Investment decisions leveraging the resampling of a FlowSystem
* New Storage Parameter: `relative_minimum_final_charge_state` and `relative_maximum_final_charge_state` parameter for final state control

### Changed
* **BREAKING**: `relative_minimum_charge_state` and `relative_maximum_charge_state` don't have an extra timestep anymore. The final charge state can now be constrained by parameters `relative_minimum_final_charge_state` and `relative_maximum_final_charge_state` instead
* **BREAKING**: Renamed class `SystemModel` to `FlowSystemModel`
* **BREAKING**: Renamed class `Model` to `Submodel`
* **BREAKING**: Renamed `mode` parameter in plotting methods to `style`
* FlowSystems can not be shared across multiple Calculations anymore. A copy of the FlowSystem is created instead, making every Calculation independent
* Each Subcalculation in `SegmentedCalculation` now has its own distinct `FlowSystem` object
* Type system overhaul - added clear separation between temporal and non-temporal data throughout codebase for better clarity
* Enhanced FlowSystem interface with improved `__repr__()` and `__str__()` methods
* Improved Model Structure - Views and organisation is now divided into:
* Model: The main Model (linopy.Model) that is used to create and store the variables and constraints for the flow_system.
* Submodel: The base class for all submodels. Each is a subset of the Model, for simpler acess and clearer code.

### Deprecated
* The `agg_group` and `agg_weight` parameters of `TimeSeriesData` are deprecated and will be removed in a future version. Use `aggregation_group` and `aggregation_weight` instead.
* The `active_timesteps` parameter of `Calculation` is deprecated and will be removed in a future version. Use the new `sel(time=...)` method on the FlowSystem instead.
* The assignment of Bus Objects to Flow.bus is deprecated and will be removed in a future version. Use the label of the Bus instead.
* The usage of Effects objects in Dicts to assign shares to Effects is deprecated and will be removed in a future version. Use the label of the Effect instead.

### Removed

### Fixed
* Enhanced NetCDF I/O with proper attribute preservation for DataArrays
* Improved error handling and validation in serialization processes
* Better type consistency across all framework components

### Known issues
* IO for single Interfaces/Elemenets to Datasets might not work properly if the Interface/Element is not part of a fully transformed and connected FlowSystem. This arrises from Numeric Data not being stored as xr.DataArray by the user. To avoid this, always use the `to_dataset()` on Elements inside a FlowSystem thats connected and transformed.

### *Development*
* **BREAKING**: Calculation.do_modeling() now returns the Calculation object instead of its linopy.Model
* **BREAKING**: Renamed class `SystemModel` to `FlowSystemModel`
* **BREAKING**: Renamed class `Model` to `Submodel`
* FlowSystem data management simplified - removed `time_series_collection` pattern in favor of direct timestep properties
* Change modeling hierarchy to allow for more flexibility in future development. This leads to minimal changes in the access and creation of Submodels and their variables.
* Added new module `.modeling`that contains Modelling primitives and utilities
* Clearer separation between the main Model and "Submodels"
* Improved access to the Submodels and their variables, constraints and submodels
* Added __repr__() for Submodels to easily inspect its content
* Enhanced data handling methods
* `fit_to_model_coords()` method for data alignment
* `fit_effects_to_model_coords()` method for effect data processing
* `connect_and_transform()` method replacing several operations


## [2.1.9] - 2025-09-23
Small Bugfix which was supposed to be fixed in 2.1.8

Expand Down
2 changes: 1 addition & 1 deletion docs/images/flixopt-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in "whether"

Line 12 contains a typo: "wether" should be "whether".

-- $\beta_{invest} \in {0,1}$ = wether the size is invested or not
+- $\beta_{invest} \in {0,1}$ = whether the size is invested or not
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- $\beta_{invest} \in {0,1}$ = wether the size is invested or not
$\beta_{invest} \in {0,1}$ = whether the size is invested or not
🤖 Prompt for AI Agents
In docs/user-guide/Mathematical Notation/Investment.md around line 12, the word
"wether" is misspelled; change it to "whether" so the line reads "β_{invest} ∈
{0,1} = whether the size is invested or not" (also consider adding proper LaTeX
set braces \{0,1\} if needed).


_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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in "This"

Line 29 contains a typo: "THis" should be "This".

-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.
+This might lead to some issues regarding minimum_load factor, or others, as the size is not 0 in a scenario where the component cant produce.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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.
This might lead to some issues regarding minimum_load factor, or others, as the size is not 0 in a scenario where the component cant produce.
🤖 Prompt for AI Agents
In docs/user-guide/Mathematical Notation/Investment.md around line 29, the word
"THis" is misspelled; change "THis" to "This" to fix the typo so the sentence
reads correctly and avoids a capitalized H.

**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
Comment on lines +95 to +96
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in "present constraints"

Line 96 contains a typo: "presentconstraints" should be "present constraints".

-- Treating $\beta_{invest}$ like an ON/OFF variable suggest using the already presentconstraints linked to On/OffModel
+- Treating $\beta_{invest}$ like an ON/OFF variable suggests using the already present constraints linked to On/OffModel
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
*Thoughts:*
- Treating $\beta_{invest}$ like an ON/OFF variable suggest using the already presentconstraints linked to On/OffModel
*Thoughts:*
- Treating $\beta_{invest}$ like an ON/OFF variable suggests using the already present constraints linked to On/OffModel
🤖 Prompt for AI Agents
In docs/user-guide/Mathematical Notation/Investment.md around lines 95 to 96,
fix the typo "presentconstraints" by changing it to "present constraints" so the
sentence reads correctly; update that single word and ensure spacing is correct.

- 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in "This"

Line 99 contains a typo: "THis" should be "This".

-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)
+This might need 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 measurable (integers)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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)
This might need 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 measurable (integers)
🤖 Prompt for AI Agents
In docs/user-guide/Mathematical Notation/Investment.md around line 99, the word
"THis" is a typo; change it to "This" (capital T, lower-case h) so the sentence
reads correctly.



### 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
3 changes: 2 additions & 1 deletion examples/01_Simple/simple_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@
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=1 / 100 * np.array([80, 70, 80, 80, 80, 80, 80, 80, 80, 80]),
relative_maximum_charge_state=1 / 100 * np.array([80, 70, 80, 80, 80, 80, 80, 80, 80]),
relative_maximum_final_charge_state=0.8,
Comment on lines +69 to +70
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Storage parameter array size inconsistency detected

The relative_maximum_charge_state array contains 9 elements, but should contain 8 elements since the final state is now controlled by the new relative_maximum_final_charge_state parameter (Line 71). According to the AI summary, the storage state arrays no longer need an extra timestep for the final state constraint.

Apply this fix to align with the new API:

-        relative_maximum_charge_state=1 / 100 * np.array([80, 70, 80, 80, 80, 80, 80, 80, 80]),
+        relative_maximum_charge_state=1 / 100 * np.array([80, 70, 80, 80, 80, 80, 80, 80]),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
relative_maximum_charge_state=1 / 100 * np.array([80, 70, 80, 80, 80, 80, 80, 80, 80]),
relative_maximum_final_charge_state=0.8,
relative_maximum_charge_state=1 / 100 * np.array([80, 70, 80, 80, 80, 80, 80, 80]),
relative_maximum_final_charge_state=0.8,
🤖 Prompt for AI Agents
In examples/01_Simple/simple_example.py around lines 70-71 the
relative_maximum_charge_state array has 9 elements but should have 8 because the
final timestep is now controlled by relative_maximum_final_charge_state; remove
the extra element (reduce the array to 8 values) so the array length matches the
number of timesteps excluding the final state and keep the same proportional
values/order, leaving relative_maximum_final_charge_state=0.8 to govern the
final timestep.

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
Expand Down
23 changes: 12 additions & 11 deletions examples/03_Calculation_types/example_calculation_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@

# TimeSeriesData objects
TS_heat_demand = fx.TimeSeriesData(heat_demand)
TS_electricity_demand = fx.TimeSeriesData(electricity_demand, agg_weight=0.7)
TS_electricity_price_sell = fx.TimeSeriesData(-(electricity_demand - 0.5), agg_group='p_el')
TS_electricity_price_buy = fx.TimeSeriesData(electricity_price + 0.5, agg_group='p_el')
TS_electricity_demand = fx.TimeSeriesData(electricity_demand, aggregation_weight=0.7)
TS_electricity_price_sell = fx.TimeSeriesData(-(electricity_demand - 0.5), aggregation_group='p_el')
TS_electricity_price_buy = fx.TimeSeriesData(electricity_price + 0.5, aggregation_group='p_el')
Comment on lines +48 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use electricity_price for sell tariff; demand is incorrect here.

Sell price should be derived from price, not demand.

-TS_electricity_demand = fx.TimeSeriesData(electricity_demand, aggregation_weight=0.7)
-TS_electricity_price_sell = fx.TimeSeriesData(-(electricity_demand - 0.5), aggregation_group='p_el')
-TS_electricity_price_buy = fx.TimeSeriesData(electricity_price + 0.5, aggregation_group='p_el')
+TS_electricity_demand = fx.TimeSeriesData(electricity_demand, aggregation_weight=0.7)
+TS_electricity_price_sell = fx.TimeSeriesData(-(electricity_price - 0.5), aggregation_group='p_el')
+TS_electricity_price_buy = fx.TimeSeriesData(electricity_price + 0.5, aggregation_group='p_el')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
TS_electricity_demand = fx.TimeSeriesData(electricity_demand, aggregation_weight=0.7)
TS_electricity_price_sell = fx.TimeSeriesData(-(electricity_demand - 0.5), aggregation_group='p_el')
TS_electricity_price_buy = fx.TimeSeriesData(electricity_price + 0.5, aggregation_group='p_el')
TS_electricity_demand = fx.TimeSeriesData(electricity_demand, aggregation_weight=0.7)
TS_electricity_price_sell = fx.TimeSeriesData(-(electricity_price - 0.5), aggregation_group='p_el')
TS_electricity_price_buy = fx.TimeSeriesData(electricity_price + 0.5, aggregation_group='p_el')
🤖 Prompt for AI Agents
In examples/03_Calculation_types/example_calculation_types.py around lines 51 to
53, the sell price time series is incorrectly built from electricity_demand;
replace it to derive from electricity_price instead. Change the
TS_electricity_price_sell initialization to use electricity_price (e.g.
TS_electricity_price_sell = fx.TimeSeriesData(-(electricity_price - 0.5),
aggregation_group='p_el') or TS_electricity_price_sell =
fx.TimeSeriesData(electricity_price - 0.5, aggregation_group='p_el') depending
on the intended sign convention) so the sell tariff comes from price, not
demand.


flow_system = fx.FlowSystem(timesteps)
flow_system.add_elements(
Expand Down Expand Up @@ -161,12 +161,12 @@
if full:
calculation = fx.FullCalculation('Full', flow_system)
calculation.do_modeling()
calculation.solve(fx.solvers.HighsSolver(0, 60))
calculation.solve(fx.solvers.HighsSolver(0.01 / 100, 60))
calculations.append(calculation)

if segmented:
calculation = fx.SegmentedCalculation('Segmented', flow_system, segment_length, overlap_length)
calculation.do_modeling_and_solve(fx.solvers.HighsSolver(0, 60))
calculation.do_modeling_and_solve(fx.solvers.HighsSolver(0.01 / 100, 60))
calculations.append(calculation)

if aggregated:
Expand All @@ -175,7 +175,7 @@
aggregation_parameters.time_series_for_low_peaks = [TS_electricity_demand, TS_heat_demand]
calculation = fx.AggregatedCalculation('Aggregated', flow_system, aggregation_parameters)
calculation.do_modeling()
calculation.solve(fx.solvers.HighsSolver(0, 60))
calculation.solve(fx.solvers.HighsSolver(0.01 / 100, 60))
calculations.append(calculation)

# Get solutions for plotting for different calculations
Expand All @@ -191,34 +191,35 @@ 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')

Comment on lines 191 to 198
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure output folder exists before writing HTML files.

Avoids FileNotFoundError when 'results/' is missing.

     # --- Plotting for comparison ---
-    fx.plotting.with_plotly(
+    pathlib.Path('results').mkdir(parents=True, exist_ok=True)
+    fx.plotting.with_plotly(
         get_solutions(calculations, 'Speicher|charge_state').to_dataframe(),
         style='line',
         title='Charge State Comparison',
         ylabel='Charge state',
     ).write_html('results/Charge State.html')

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In examples/03_Calculation_types/example_calculation_types.py around lines 194
to 201, the code writes 'results/Charge State.html' without ensuring the
'results' folder exists; before calling .write_html(...) create the output
directory (e.g., use pathlib.Path('results').mkdir(parents=True, exist_ok=True)
or equivalent) so the folder is present and FileNotFoundError is avoided.

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'
)
Loading