-
Notifications
You must be signed in to change notification settings - Fork 9
Feature/tsam v3+rework #571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
52 commits
Select commit
Hold shift + click to select a range
65486ae
⏺ I've completed the core migration to tsam 3.0.0. Here's a summary o…
FBumann 156bc47
⏺ The tsam 3.0 migration is now complete with the correct API. All 79…
FBumann 46f3418
⏺ The simplification refactoring is complete. Here's what was done:
FBumann cfb9926
I continued the work on simplifying flixopt's clustering architecture…
FBumann cf5279a
All the clustering notebooks and documentation have been updated fo…
FBumann addde0b
Fixes made:
FBumann 65e872b
⏺ All 126 clustering tests pass. I've added 8 new tests in a new Test…
FBumann 1547a36
Summary of Changes
FBumann 5117967
Summary of changes:
FBumann ddd4d2d
rename to ClusteringResults
FBumann 3365734
New xarray-like interface:
FBumann a55b9a1
Updated the following notebooks:
FBumann 5056873
ClusteringResults class:
FBumann 6fc34cd
Renamed:
FBumann 72d6f0d
Expose SegmentConfig
FBumann 42e37e1
The segmentation feature has been ported to the tsam 3.0 API. Key c…
FBumann c5409c8
Added Properties
FBumann ad6e5e7
Summary of Changes
FBumann 860b15e
⏺ I've completed the implementation. Here's a summary of everything t…
FBumann b73a6a1
Add method to extract data used for clustering.
FBumann e6ee2dd
Summary of Refactoring
FBumann e880fad
Changes Made
FBumann fbb2b0f
Update Notebook
FBumann 151e4b3
1. Clustering class now wraps AggregationResult objects directly
FBumann 57f59ed
I've completed the refactoring to make the Clustering class derive …
FBumann b39e994
The issue was that _build_aggregation_data() was using n_timesteps_…
FBumann 8b1daf6
❯ Remove some data wrappers.
FBumann bf5d7ff
Improve docstrings and types
FBumann bb5f7aa
Add notebook and preserve input data
FBumann 556e90f
Implemented include_original_data parameter:
FBumann 810c143
Changes made:
FBumann 1696e47
Changes made:
FBumann 5cf85ac
drop_constant_arrays to use std < atol instead of max == min
FBumann 8332eaa
Temp fix (should be fixed in tsam)
FBumann 9ba340c
Revert "Temp fix (should be fixed in tsam)"
FBumann 94477b1
Merge origin/main into feature/tsam-v3+rework
FBumann 13002a0
Updated tsam dependencies to use the PR branch of tsam containing the…
FBumann fddea30
All fast notebooks now pass. Here's a summary of the fixes:
FBumann 982e75a
⏺ All fast notebooks now pass. Here's a summary of the fixes:
FBumann 9d5d969
Fix notebook
FBumann 946d374
Fix CI...
FBumann b483ad4
Revert "Fix CI..."
FBumann c847ef6
Fix CI...
FBumann 872bbbd
Merge branch 'main' into feature/tsam-v3+rework
FBumann a5f0147
Merge branch 'dev' into feature/tsam-v3+rework
FBumann 450739c
Fix: Correct expansion of segmented clustered systems (#573)
FBumann ebf2aab
Added @functools.cached_property to timestep_mapping in clustering/b…
FBumann 79d0e5e
perf: 40x faster FlowSystem I/O + storage efficiency improvements (#578)
FBumann 7a4280d
perf: Optimize clustering and I/O (4.4x faster segmented clustering) …
FBumann 1287792
2. Lines 1245-1251 (new guard): Added explicit check after drop_con…
FBumann efd91f5
Fix/broadcasting (#580)
FBumann 01a775d
Add some defensive validation
FBumann File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| """Benchmark script for FlowSystem IO performance. | ||
|
|
||
| Tests to_dataset() and from_dataset() performance with large FlowSystems. | ||
| Run this to compare performance before/after optimizations. | ||
|
|
||
| Usage: | ||
| python benchmarks/benchmark_io_performance.py | ||
| """ | ||
|
|
||
| import time | ||
| from typing import NamedTuple | ||
|
|
||
| import numpy as np | ||
| import pandas as pd | ||
|
|
||
| import flixopt as fx | ||
|
|
||
|
|
||
| class BenchmarkResult(NamedTuple): | ||
| """Results from a benchmark run.""" | ||
|
|
||
| name: str | ||
| mean_ms: float | ||
| std_ms: float | ||
| iterations: int | ||
|
|
||
|
|
||
| def create_large_flow_system( | ||
| n_timesteps: int = 2190, | ||
| n_periods: int = 12, | ||
| n_components: int = 125, | ||
| ) -> fx.FlowSystem: | ||
| """Create a large FlowSystem for benchmarking. | ||
|
|
||
| Args: | ||
| n_timesteps: Number of timesteps (default 2190 = ~1 year at 4h resolution). | ||
| n_periods: Number of periods (default 12). | ||
| n_components: Number of sink/source pairs (default 125). | ||
|
|
||
| Returns: | ||
| Configured FlowSystem ready for optimization. | ||
| """ | ||
| timesteps = pd.date_range('2024-01-01', periods=n_timesteps, freq='4h') | ||
| periods = pd.Index([2028 + i * 2 for i in range(n_periods)], name='period') | ||
|
|
||
| fs = fx.FlowSystem(timesteps=timesteps, periods=periods) | ||
| fs.add_elements(fx.Effect('Cost', '€', is_objective=True)) | ||
|
|
||
| n_buses = 10 | ||
| buses = [fx.Bus(f'Bus_{i}') for i in range(n_buses)] | ||
| fs.add_elements(*buses) | ||
|
|
||
| # Create demand profile with daily pattern | ||
| base_demand = 100 + 50 * np.sin(2 * np.pi * np.arange(n_timesteps) / 24) | ||
|
|
||
| for i in range(n_components // 2): | ||
| bus = buses[i % n_buses] | ||
| # Add noise to create unique profiles | ||
| profile = base_demand + np.random.normal(0, 10, n_timesteps) | ||
| profile = np.clip(profile / profile.max(), 0.1, 1.0) | ||
|
|
||
| fs.add_elements( | ||
| fx.Sink( | ||
| f'D_{i}', | ||
| inputs=[fx.Flow(f'Q_{i}', bus=bus.label, size=100, fixed_relative_profile=profile)], | ||
| ) | ||
| ) | ||
| fs.add_elements( | ||
| fx.Source( | ||
| f'S_{i}', | ||
| outputs=[fx.Flow(f'P_{i}', bus=bus.label, size=500, effects_per_flow_hour={'Cost': 20 + i})], | ||
| ) | ||
| ) | ||
|
|
||
| return fs | ||
|
|
||
|
|
||
| def benchmark_function(func, iterations: int = 5, warmup: int = 1) -> BenchmarkResult: | ||
| """Benchmark a function with multiple iterations. | ||
|
|
||
| Args: | ||
| func: Function to benchmark (callable with no arguments). | ||
| iterations: Number of timed iterations. | ||
| warmup: Number of warmup iterations (not timed). | ||
|
|
||
| Returns: | ||
| BenchmarkResult with timing statistics. | ||
| """ | ||
| # Warmup | ||
| for _ in range(warmup): | ||
| func() | ||
|
|
||
| # Timed runs | ||
| times = [] | ||
| for _ in range(iterations): | ||
| start = time.perf_counter() | ||
| func() | ||
| elapsed = time.perf_counter() - start | ||
| times.append(elapsed) | ||
|
|
||
| return BenchmarkResult( | ||
| name=func.__name__ if hasattr(func, '__name__') else str(func), | ||
| mean_ms=np.mean(times) * 1000, | ||
| std_ms=np.std(times) * 1000, | ||
| iterations=iterations, | ||
| ) | ||
|
|
||
|
|
||
| def run_io_benchmarks( | ||
| n_timesteps: int = 2190, | ||
| n_periods: int = 12, | ||
| n_components: int = 125, | ||
| n_clusters: int = 8, | ||
| iterations: int = 5, | ||
| ) -> dict[str, BenchmarkResult]: | ||
| """Run IO performance benchmarks. | ||
|
|
||
| Args: | ||
| n_timesteps: Number of timesteps for the FlowSystem. | ||
| n_periods: Number of periods. | ||
| n_components: Number of components (sink/source pairs). | ||
| n_clusters: Number of clusters for aggregation. | ||
| iterations: Number of benchmark iterations. | ||
|
|
||
| Returns: | ||
| Dictionary mapping benchmark names to results. | ||
| """ | ||
| print('=' * 70) | ||
| print('FlowSystem IO Performance Benchmark') | ||
| print('=' * 70) | ||
| print('\nConfiguration:') | ||
| print(f' Timesteps: {n_timesteps}') | ||
| print(f' Periods: {n_periods}') | ||
| print(f' Components: {n_components}') | ||
| print(f' Clusters: {n_clusters}') | ||
| print(f' Iterations: {iterations}') | ||
|
|
||
| # Create and prepare FlowSystem | ||
| print('\n1. Creating FlowSystem...') | ||
| fs = create_large_flow_system(n_timesteps, n_periods, n_components) | ||
| print(f' Components: {len(fs.components)}') | ||
|
|
||
| print('\n2. Clustering and solving...') | ||
| fs_clustered = fs.transform.cluster(n_clusters=n_clusters, cluster_duration='1D') | ||
|
|
||
| # Try Gurobi first, fall back to HiGHS if not available | ||
| try: | ||
| solver = fx.solvers.GurobiSolver() | ||
| fs_clustered.optimize(solver) | ||
| except Exception as e: | ||
| if 'gurobi' in str(e).lower() or 'license' in str(e).lower(): | ||
| print(f' Gurobi not available ({e}), falling back to HiGHS...') | ||
| solver = fx.solvers.HighsSolver() | ||
| fs_clustered.optimize(solver) | ||
| else: | ||
| raise | ||
|
|
||
| print('\n3. Expanding...') | ||
| fs_expanded = fs_clustered.transform.expand() | ||
| print(f' Expanded timesteps: {len(fs_expanded.timesteps)}') | ||
|
|
||
| # Create dataset with solution | ||
| print('\n4. Creating dataset...') | ||
| ds = fs_expanded.to_dataset(include_solution=True) | ||
| print(f' Variables: {len(ds.data_vars)}') | ||
| print(f' Size: {ds.nbytes / 1e6:.1f} MB') | ||
|
|
||
| results = {} | ||
|
|
||
| # Benchmark to_dataset | ||
| print('\n5. Benchmarking to_dataset()...') | ||
| result = benchmark_function(lambda: fs_expanded.to_dataset(include_solution=True), iterations=iterations) | ||
| results['to_dataset'] = result | ||
| print(f' Mean: {result.mean_ms:.1f}ms (std: {result.std_ms:.1f}ms)') | ||
|
|
||
| # Benchmark from_dataset | ||
| print('\n6. Benchmarking from_dataset()...') | ||
| result = benchmark_function(lambda: fx.FlowSystem.from_dataset(ds), iterations=iterations) | ||
| results['from_dataset'] = result | ||
| print(f' Mean: {result.mean_ms:.1f}ms (std: {result.std_ms:.1f}ms)') | ||
|
|
||
| # Verify restoration | ||
| print('\n7. Verification...') | ||
| fs_restored = fx.FlowSystem.from_dataset(ds) | ||
| print(f' Components restored: {len(fs_restored.components)}') | ||
| print(f' Timesteps restored: {len(fs_restored.timesteps)}') | ||
| print(f' Has solution: {fs_restored.solution is not None}') | ||
| if fs_restored.solution is not None: | ||
| print(f' Solution variables: {len(fs_restored.solution.data_vars)}') | ||
|
|
||
| # Summary | ||
| print('\n' + '=' * 70) | ||
| print('Summary') | ||
| print('=' * 70) | ||
| for name, res in results.items(): | ||
| print(f' {name}: {res.mean_ms:.1f}ms (+/- {res.std_ms:.1f}ms)') | ||
|
|
||
| return results | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| run_io_benchmarks() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: flixOpt/flixopt
Length of output: 3556
🏁 Script executed:
Repository: flixOpt/flixopt
Length of output: 1971
🏁 Script executed:
Repository: flixOpt/flixopt
Length of output: 2553
Fix n_components semantics and daily-cycle period math
Two issues confirmed:
n_componentsas "sink/source pairs," but the loop creates onlyn_components // 2pairs—with the default value of 125, only 62 pairs are created. This introduces confusion and silently handles odd inputs incorrectly.sin(2*pi*arange(n_timesteps)/24)produces a 4-day cycle, not a daily pattern (24 timesteps × 4h = 96h).Either clarify the parameter definition or correct the loop count; adjust the sine divisor to match the actual timestep resolution.
🔧 Suggested fix
🤖 Prompt for AI Agents