-
Notifications
You must be signed in to change notification settings - Fork 9
Feature/tsam v3+rework (#571) #584
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
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
7c09a5f
Feature/tsam v3+rework (#571)
FBumann 1234515
Merge main into dev
FBumann 534bfec
Update CHANGELOG.md
FBumann b780cbc
Fix imports
FBumann 0405617
Fix CHANGELOG.md
FBumann 6a6fef7
Update becnhmark
FBumann 76b292b
addressed all the coderabbitai comments. Here's a summary of the chan…
FBumann db8d503
Address remaining comments
FBumann 47b20c6
Documentation Updates for v6 Release
FBumann b2ab454
Remove timestep duration from attrs to allow recomputation
FBumann 42d0403
All 167 tests pass. Here's a summary of the fixes:
FBumann b2f7a46
I updated 6 places in io.py to use the more performant pattern:
FBumann b97fb65
More performance optimizations
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
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,195 @@ | ||
| """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 tempfile | ||
| 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. | ||
| """ | ||
| 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): | ||
| 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) | ||
|
|
||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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, | ||
| 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). | ||
| 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' Iterations: {iterations}') | ||
|
|
||
| # Create FlowSystem | ||
| print('\n1. Creating FlowSystem...') | ||
| fs = create_large_flow_system(n_timesteps, n_periods, n_components) | ||
| print(f' Components: {len(fs.components)}') | ||
|
|
||
| # Create dataset | ||
| print('\n2. Creating dataset...') | ||
| ds = fs.to_dataset() | ||
| print(f' Variables: {len(ds.data_vars)}') | ||
| print(f' Size: {ds.nbytes / 1e6:.1f} MB') | ||
|
|
||
| results = {} | ||
|
|
||
| # Benchmark to_dataset | ||
| print('\n3. Benchmarking to_dataset()...') | ||
| result = benchmark_function(lambda: fs.to_dataset(), iterations=iterations) | ||
| results['to_dataset'] = result | ||
| print(f' Mean: {result.mean_ms:.1f}ms (std: {result.std_ms:.1f}ms)') | ||
|
|
||
| # Benchmark from_dataset | ||
| print('\n4. 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)') | ||
|
|
||
| # Benchmark NetCDF round-trip | ||
| print('\n5. Benchmarking NetCDF round-trip...') | ||
| with tempfile.NamedTemporaryFile(suffix='.nc', delete=False) as f: | ||
| tmp_path = f.name | ||
|
|
||
| def netcdf_roundtrip(): | ||
| fs.to_netcdf(tmp_path, overwrite=True) | ||
| return fx.FlowSystem.from_netcdf(tmp_path) | ||
|
|
||
| result = benchmark_function(netcdf_roundtrip, iterations=iterations) | ||
| results['netcdf_roundtrip'] = result | ||
| print(f' Mean: {result.mean_ms:.1f}ms (std: {result.std_ms:.1f}ms)') | ||
|
|
||
| # Verify restoration | ||
| print('\n6. 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' Periods restored: {len(fs_restored.periods)}') | ||
|
|
||
| # 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)') | ||
|
|
||
| total_ms = results['to_dataset'].mean_ms + results['from_dataset'].mean_ms | ||
| print(f'\n Total (to + from): {total_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
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.
Uh oh!
There was an error while loading. Please reload this page.