v6.0.0 (yanked)
Pre-releaseSummary: Major release featuring tsam v3 migration, complete rewrite of the clustering/aggregation system, 2-3x faster I/O for large systems, new plotly plotting accessor, FlowSystem comparison tools, and removal of deprecated v5.0 classes.
!!! warning "Breaking Changes"
This release removes ClusteredOptimization and ClusteringParameters which were deprecated in v5.0.0. Use flow_system.transform.cluster() instead. See Migration below.
The clustering API now uses tsam v3's configuration objects (`ClusterConfig`, `ExtremeConfig`) instead of individual parameters. See [tsam v3 Migration](#tsam-v3-migration) below.
Key Features
- tsam v3 Migration (#584) - Updated to tsam 3.0+ with new configuration-based API
- Clustering/Aggregation Rework (#549, #552, #584) - Complete rewrite with tsam integration, inter-cluster storage linking, segmentation support, and 4 storage modes
- I/O Performance (#584) - 2-3x faster NetCDF I/O for large systems via variable stacking
- plotly Plotting Accessor (#548) - Universal xarray plotting with automatic faceting
- Comparison Module (#550) - Compare multiple FlowSystems side-by-side
- Improved Notebooks (#542, #551) - Better tutorial data and faster CI execution
✨ Added
Time-Series Clustering (#549, #552, #584)
Reduce large time series to representative typical periods for faster investment optimization, then expand results back to full resolution.
from tsam import ClusterConfig, ExtremeConfig
# Stage 1: Cluster and optimize (fast sizing)
fs_clustered = flow_system.transform.cluster(
n_clusters=12, # 12 typical days from a year
cluster_duration='1D', # Each cluster represents one day
cluster=ClusterConfig(method='hierarchical'),
extremes=ExtremeConfig(method='new_cluster', max_value=['HeatDemand(Q)|fixed_relative_profile']),
)
fs_clustered.optimize(solver)
# Stage 2: Expand back to full resolution
fs_expanded = fs_clustered.transform.expand()Storage Modes for Clustering: Control how storage behaves across clustered periods via Storage(cluster_mode=...):
| Mode | Description | Use Case |
|---|---|---|
'intercluster_cyclic' |
Links storage across clusters + yearly cyclic (default) | Seasonal storage with yearly optimization |
'intercluster' |
Links storage across clusters, free start/end | Multi-year optimization without cyclic constraint |
'cyclic' |
Each cluster independent, but cyclic (start = end) | Daily storage only, ignores seasonal patterns |
'independent' |
Each cluster fully independent, free start/end | Fastest solve, no long-term storage value |
Clustering Parameters:
| Parameter | Description |
|---|---|
n_clusters |
Number of representative periods to create |
cluster_duration |
Duration of each cluster (e.g., '1D', '24h', or hours as float) |
weights |
Dict mapping variable names to importance weights for clustering |
cluster |
ClusterConfig object for clustering algorithm settings (method, representation, etc.) |
extremes |
ExtremeConfig object for peak/valley preservation settings |
predef_cluster_assignments |
Predefined cluster assignment for reproducibility |
**tsam_kwargs |
Additional arguments passed to tsam |
See tsam documentation for ClusterConfig and ExtremeConfig options.
Key Features:
- Inter-cluster storage linking: For
'intercluster'and'intercluster_cyclic'modes, aSOC_boundaryvariable tracks absolute state-of-charge at period boundaries, enabling accurate seasonal storage modeling - Self-discharge decay: Storage losses are correctly applied during solution expansion using the formula:
actual_SOC(t) = SOC_boundary × (1 - loss)^t + ΔE(t) - Multi-dimensional support: Works with periods, scenarios, and clusters dimensions simultaneously
- Solution expansion:
transform.expand()maps clustered results back to original timesteps with proper storage state reconstruction - Clustering IO: Save and load clustered FlowSystems with full state preservation via
to_netcdf()/from_netcdf()
Example: Seasonal Storage with Clustering:
# Configure storage for seasonal behavior
storage = fx.Storage(
'SeasonalPit',
capacity_in_flow_hours=5000,
cluster_mode='intercluster_cyclic', # Enable seasonal storage in clustering
relative_loss_per_hour=0.0001, # Small self-discharge
...
)
# Cluster, optimize, and expand
fs_clustered = flow_system.transform.cluster(n_clusters=12, cluster_duration='1D')
fs_clustered.optimize(solver)
fs_expanded = fs_clustered.transform.expand()
# Full-resolution charge state now available
charge_state = fs_expanded.solution['SeasonalPit|charge_state']!!! tip "Choosing the Right Storage Mode"
Use 'intercluster_cyclic' (default) for seasonal storage like pit storage or underground thermal storage.
Use 'cyclic' for short-term storage like batteries or hot water tanks where only daily patterns matter.
Use 'independent' for quick estimates when storage behavior isn't critical.
Time-Series Segmentation (#584)
New transform.segment() method for piecewise-constant time-series approximation. Useful for reducing problem size while preserving temporal structure:
# Segment time series into 24 segments per day
fs_segmented = flow_system.transform.segment(
segment_duration='1D',
n_segments=24,
)
fs_segmented.optimize(solver)
fs_expanded = fs_segmented.transform.expand()I/O Performance Improvements (#584)
- Variable stacking: 2-3x faster NetCDF I/O for large systems by grouping variables with same dimensions
- Fast DataArray construction: Bypasses slow xarray internals (~15x faster per variable)
- Version tracking: Datasets now include
flixopt_versionattribute for compatibility checking
# Version is automatically stored
ds = flow_system.to_dataset()
print(ds.attrs['flixopt_version']) # e.g., '6.0.0'Plotly Accessor (#548)
New global xarray accessors for universal plotting with automatic faceting and smart dimension handling. Works on any xarray Dataset, not just flixopt results.
import flixopt as fx # Registers accessors automatically
# Plot any xarray Dataset with automatic faceting
dataset.plotly.bar(x='component')
dataset.plotly.area(x='time')
dataset.plotly.imshow(x='time', y='component')
dataset.plotly.line(x='time', facet_col='scenario')
# DataArray support
data_array.plotly.line()
# Statistics transformations
dataset.fxstats.to_duration_curve()Available Plot Methods:
| Method | Description |
|---|---|
.plotly.bar() |
Bar charts (use barmode='group' or 'relative' for stacked) |
.plotly.line() |
Line charts with faceting |
.plotly.area() |
Stacked area charts |
.plotly.imshow() |
Heatmap visualizations |
.plotly.scatter() |
Scatter plots |
.plotly.pie() |
Pie charts with faceting |
.fxstats.to_duration_curve() |
Transform to duration curve format |
Key Features:
- Auto-faceting: Automatically assigns extra dimensions (period, scenario, cluster) to
facet_col,facet_row, oranimation_frame - Smart x-axis: Intelligently selects x dimension based on priority (time > duration > period > scenario)
- Universal: Works on any xarray Dataset/DataArray, not limited to flixopt
- Configurable: Customize via
CONFIG.Plotting(colorscales, facet columns, line shapes)
FlowSystem Comparison (#550)
New Comparison class for comparing multiple FlowSystems side-by-side:
# Compare systems (uses FlowSystem.name by default)
comp = fx.Comparison([fs_base, fs_modified])
# Or with custom names
comp = fx.Comparison([fs1, fs2, fs3], names=['baseline', 'low_cost', 'high_eff'])
# Side-by-side plots (auto-facets by 'case' dimension)
comp.stats.plot.balance('Heat')
comp.stats.flow_rates.plotly.line()
# Access combined data with 'case' dimension
comp.solution # xr.Dataset
comp.stats.flow_rates # xr.Dataset
# Compute differences relative to a reference case
comp.diff() # vs first case
comp.diff('baseline') # vs named case- Concatenates solutions and statistics from multiple FlowSystems with a
'case'dimension - Mirrors all
StatisticsAccessorproperties (flow_rates,flow_hours,sizes,charge_states,temporal_effects,periodic_effects,total_effects) - Mirrors all
StatisticsPlotAccessormethods (balance,carrier_balance,flows,sizes,duration_curve,effects,charge_states,heatmap,storage) - Existing plotting infrastructure automatically handles faceting by
'case'
Component Color Parameter (#585)
All component classes now accept a color parameter for visualization customization:
# Set color at instantiation
boiler = fx.Boiler('Boiler', ..., color='#D35400')
storage = fx.Storage('Battery', ..., color='green')
# Bulk assignment via topology accessor
flow_system.topology.set_component_colors({'Boiler': 'red', 'CHP': 'blue'})
flow_system.topology.set_component_colors({'Oranges': ['Solar1', 'Solar2']}) # Colorscale
flow_system.topology.set_component_colors('turbo', overwrite=False) # Only unset colorsFlowContainer for Component Flows (#587)
Component.inputs, Component.outputs, and Component.flows now use FlowContainer (dict-like) with dual access by index or label: inputs[0] or inputs['Q_th'].
before_solve Callback
New callback parameter for optimize() and rolling_horizon() allows adding custom constraints before solving:
def add_constraints(fs):
model = fs.model
boiler = model.variables['Boiler(Q_th)|flow_rate']
model.add_constraints(boiler >= 10, name='min_boiler')
flow_system.optimize(solver, before_solve=add_constraints)
# Works with rolling_horizon too
flow_system.optimize.rolling_horizon(
solver,
horizon=168,
before_solve=add_constraints
)cluster_mode for StatusParameters
New parameter to control status behavior at cluster boundaries:
fx.StatusParameters(
...,
cluster_mode='relaxed', # Default: no constraint at boundaries, prevents phantom startups
# cluster_mode='cyclic', # Each cluster's final status equals its initial status
)Comparison Class Enhancements
Comparison.inputs: Compare inputs across FlowSystems for easy side-by-side input parameter comparisondata_onlyparameter: Get data without generating plots in Comparison methodsthresholdparameter: Filter small values when comparing
Plotting Enhancements
thresholdparameter: Added to all plotting methods to filter values below a threshold (default:1e-5)round_decimalsparameter: Control decimal precision inbalance(),carrier_balance(), andstorage()plotsflow_colorsproperty: Map flows to their component's colors for consistent visualization
FlowSystem.from_old_dataset()
New method for loading datasets saved with older flixopt versions:
fs = fx.FlowSystem.from_old_dataset(old_dataset)💥 Breaking Changes
tsam v3 Migration
The clustering API now uses tsam v3's configuration objects instead of individual parameters:
# Old API (v5.x with tsam 2.x)
fs.transform.cluster(
n_clusters=8,
cluster_method='hierarchical',
time_series_for_high_peaks=['demand'],
)
# New API (v6.x with tsam 3.x)
from tsam import ClusterConfig, ExtremeConfig
fs.transform.cluster(
n_clusters=8,
cluster=ClusterConfig(method='hierarchical'),
extremes=ExtremeConfig(method='new_cluster', max_value=['demand']),
)Parameter mapping:
| Old Parameter | New Parameter |
|---|---|
cluster_method |
cluster=ClusterConfig(method=...) |
representation_method |
cluster=ClusterConfig(representation=...) |
time_series_for_high_peaks |
extremes=ExtremeConfig(max_value=[...]) |
time_series_for_low_peaks |
extremes=ExtremeConfig(min_value=[...]) |
extreme_period_method |
extremes=ExtremeConfig(method=...) |
predef_cluster_order |
predef_cluster_assignments |
Other Breaking Changes
FlowSystem.scenario_weightsare now always normalized to sum to 1 when set (including after.sel()subsetting)Component.inputs/outputsandBus.inputs/outputsare nowFlowContainer(dict-like). Use.values()to iterate flows.
♻️ Changed
FlowSystem.weightsreturnsdict[str, xr.DataArray](unit weights instead of1.0float fallback)FlowSystemDimensionstype now includes'cluster'stats.plot.balance(),carrier_balance(), andstorage()now usexarray_plotly.fast_bar()internally (styled stacked areas for better performance)stats.plot.carrier_balance()now combines inputs and outputs to show net flow per component, and aggregates per component by default
🗑️ Deprecated
The following items are deprecated and will be removed in v7.0.0:
Accessor renamed:
flow_system.statistics→ Useflow_system.stats(shorter, more convenient)
Classes (use FlowSystem methods instead):
Optimizationclass → Useflow_system.optimize(solver)SegmentedOptimizationclass → Useflow_system.optimize.rolling_horizon()Resultsclass → Useflow_system.solutionandflow_system.statsSegmentedResultsclass → Use segment FlowSystems directly
FlowSystem methods (use transform or topology accessor instead):
flow_system.sel()→ Useflow_system.transform.sel()flow_system.isel()→ Useflow_system.transform.isel()flow_system.resample()→ Useflow_system.transform.resample()flow_system.plot_network()→ Useflow_system.topology.plot()flow_system.start_network_app()→ Useflow_system.topology.start_app()flow_system.stop_network_app()→ Useflow_system.topology.stop_app()flow_system.network_infos()→ Useflow_system.topology.infos()
Parameters:
normalize_weightsparameter increate_model(),build_model(),optimize()
Topology method name simplifications (old names still work with deprecation warnings, removal in v7.0.0):
| Old (v5.x) | New (v6.0.0) |
|---|---|
topology.plot_network() |
topology.plot() |
topology.start_network_app() |
topology.start_app() |
topology.stop_network_app() |
topology.stop_app() |
topology.network_infos() |
topology.infos() |
Note: topology.plot() now renders a Sankey diagram. The old PyVis visualization is available via topology.plot_legacy().
🔥 Removed
Clustering classes removed (deprecated in v5.0.0):
ClusteredOptimizationclass - Useflow_system.transform.cluster()thenoptimize()ClusteringParametersclass - Parameters are now passed directly totransform.cluster()flixopt/clustering.pymodule - Restructured toflixopt/clustering/package with new classes
Migration from ClusteredOptimization
=== "v5.x (Old - No longer works)"
```python
from flixopt import ClusteredOptimization, ClusteringParameters
params = ClusteringParameters(hours_per_period=24, nr_of_periods=8)
calc = ClusteredOptimization('model', flow_system, params)
calc.do_modeling_and_solve(solver)
results = calc.results
```
=== "v6.0.0 (New)"
```python
# Cluster using transform accessor
fs_clustered = flow_system.transform.cluster(
n_clusters=8, # was: nr_of_periods
cluster_duration='1D', # was: hours_per_period=24
)
fs_clustered.optimize(solver)
# Results on the clustered FlowSystem
costs = fs_clustered.solution['costs'].item()
# Expand back to full resolution if needed
fs_expanded = fs_clustered.transform.expand()
```
🐛 Fixed
temporal_weightandsum_temporal()now use consistent implementationFlowSystem.from_old_results()now setsprevious_flow_rate=0for flows of components withstatus_parameters, fixing startup cost calculation mismatch when re-optimizing migrated v4 results
📝 Docs
New Documentation Pages:
- Time-Series Clustering Guide - Comprehensive guide to clustering workflows
- Cluster architecture design documentation (
docs/design/cluster_architecture.md)
New Jupyter Notebooks (#542):
- 08c-clustering.ipynb - Introduction to time-series clustering
- 08c2-clustering-storage-modes.ipynb - Comparison of all 4 storage cluster modes
- 08d-clustering-multiperiod.ipynb - Clustering with periods and scenarios
- 08e-clustering-internals.ipynb - Understanding clustering internals
Improved Tutorials:
- Added
tutorial_data.pyhelper module for cleaner notebook examples - Updated all existing notebooks to use new clustering and plotting APIs
👷 Development
CI Improvements (#551):
- Speedup notebook execution in documentation builds
New Test Suites for Clustering:
TestStorageClusterModes: Tests for all 4 storagecluster_modeoptionsTestInterclusterStorageLinking: Tests forSOC_boundaryvariable and expansion logicTestMultiPeriodClustering: Tests for clustering with periods and scenarios dimensionsTestPeakSelection: Tests fortime_series_for_high_peaksandtime_series_for_low_peaksparameters
New Test Suites for Other Features:
test_clustering_io.py- Tests for clustering serialization roundtriptest_sel_isel_single_selection.py- Tests for transform selection methods
What's Changed
- feat: clustering of time series data by @FBumann in #555
- Feature/minor improvements by @FBumann in #556
- Feature/faster ci notebooks by @FBumann in #561
- Remove fxplot accessor in favor of external ploty accessor by @FBumann in #560
- perf: Keep data in minimal form (no pre-broadcasting) by @FBumann in #575
- Feature/tsam v3+rework (#571) by @FBumann in #584
- Add color parameter to components and bulk color assignment via topology accessor by @FBumann in #585
- Use area chart instead of bar chart for better performance by @FBumann in #577
- Feature/speed up plotting by @FBumann in #586
- Add FlowContainer for inputs/outputs by @FBumann in #587
- Fix clustering expansion bugs and add ExtremeConfig validation by @FBumann in #590
- Fix/expand flow system rework by @FBumann in #591
- Refactor clustering: clean keys, unified dims, and encapsulated expansion by @FBumann in #592
- New _ReducedFlowSystemBuilder class (lines 82-381): by @FBumann in #593
Full Changelog: v5.0.4...v6.0.0