Skip to content

v6.0.0rc15

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 28 Jan 12:42
· 82 commits to main since this release

Summary: 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, a SOC_boundary variable 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_version attribute 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, or animation_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.statistics.plot.balance('Heat')
comp.statistics.flow_rates.plotly.line()

# Access combined data with 'case' dimension
comp.solution  # xr.Dataset
comp.statistics.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 StatisticsAccessor properties (flow_rates, flow_hours, sizes, charge_states, temporal_effects, periodic_effects, total_effects)
  • Mirrors all StatisticsPlotAccessor methods (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 colors

FlowContainer 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'].

💥 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_weights are now always normalized to sum to 1 when set (including after .sel() subsetting)
  • Component.inputs/outputs and Bus.inputs/outputs are now FlowContainer (dict-like). Use .values() to iterate flows.

♻️ Changed

  • FlowSystem.weights returns dict[str, xr.DataArray] (unit weights instead of 1.0 float fallback)
  • FlowSystemDimensions type now includes 'cluster'
  • statistics.plot.balance(), carrier_balance(), and storage() now use xarray_plotly.fast_bar() internally (styled stacked areas for better performance)

🗑️ Deprecated

The following items are deprecated and will be removed in v7.0.0:

Classes (use FlowSystem methods instead):

  • Optimization class → Use flow_system.optimize(solver)
  • SegmentedOptimization class → Use flow_system.optimize.rolling_horizon()
  • Results class → Use flow_system.solution and flow_system.statistics
  • SegmentedResults class → Use segment FlowSystems directly

FlowSystem methods (use transform or topology accessor instead):

  • flow_system.sel() → Use flow_system.transform.sel()
  • flow_system.isel() → Use flow_system.transform.isel()
  • flow_system.resample() → Use flow_system.transform.resample()
  • flow_system.plot_network() → Use flow_system.topology.plot()
  • flow_system.start_network_app() → Use flow_system.topology.start_app()
  • flow_system.stop_network_app() → Use flow_system.topology.stop_app()
  • flow_system.network_infos() → Use flow_system.topology.infos()

Parameters:

  • normalize_weights parameter in create_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):

  • ClusteredOptimization class - Use flow_system.transform.cluster() then optimize()
  • ClusteringParameters class - Parameters are now passed directly to transform.cluster()
  • flixopt/clustering.py module - Restructured to flixopt/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_weight and sum_temporal() now use consistent implementation
  • FlowSystem.from_old_results() now sets previous_flow_rate=0 for flows of components with status_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.py helper 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 storage cluster_mode options
  • TestInterclusterStorageLinking: Tests for SOC_boundary variable and expansion logic
  • TestMultiPeriodClustering: Tests for clustering with periods and scenarios dimensions
  • TestPeakSelection: Tests for time_series_for_high_peaks and time_series_for_low_peaks parameters

New Test Suites for Other Features:

  • test_clustering_io.py - Tests for clustering serialization roundtrip
  • test_sel_isel_single_selection.py - Tests for transform selection methods

What's Changed

  • New _ReducedFlowSystemBuilder class (lines 82-381): by @FBumann in #593

Full Changelog: v6.0.0rc14...v6.0.0rc15