From 729183a3a390931ac07c9edcd3a58b0193a09019 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Mon, 13 Oct 2025 22:42:43 +0200 Subject: [PATCH 01/13] Add a migration guide --- docs/user-guide/migration-guide-v3.md | 433 ++++++++++++++++++++++++++ 1 file changed, 433 insertions(+) create mode 100644 docs/user-guide/migration-guide-v3.md diff --git a/docs/user-guide/migration-guide-v3.md b/docs/user-guide/migration-guide-v3.md new file mode 100644 index 000000000..a8782f446 --- /dev/null +++ b/docs/user-guide/migration-guide-v3.md @@ -0,0 +1,433 @@ +# Migration Guide: Upgrading to v3.0.0 + +This guide helps you migrate your flixopt code from v2.x to v3.0.0. Version 3.0.0 introduces powerful new features like multi-period investments and scenario-based stochastic optimization, along with a redesigned effect sharing system. + +## Quick Start + +1. **Update your installation:** + ```bash + pip install --upgrade flixopt + ``` + +2. **Review breaking changes** in the sections below +3. **Update deprecated parameters** to their new names +4. **Test your code** with the new version + +--- + +## Breaking Changes + +### 1. Effect Sharing System Redesign + +**What changed:** The effect sharing syntax has been inverted and simplified. + +**Old syntax (v2.x):** +```python +# Effects "pushed" shares to other effects +CO2 = fx.Effect('CO2', 'kg', 'CO2 emissions', + specific_share_to_other_effects_operation={'costs': 0.2}) +``` + +**New syntax (v3.0.0):** +```python +# Effects "pull" shares from other effects (clearer direction) +costs = fx.Effect('costs', '€', 'Total costs', + share_from_temporal={'CO2': 0.2}, # From temporal (operation) effects + share_from_periodic={'land': 100}) # From periodic (investment) effects +``` + +**Migration steps:** + +1. Find all uses of `specific_share_to_other_effects_operation` and `specific_share_to_other_effects_invest` +2. Move the share definition to the **receiving** effect +3. Rename to `share_from_temporal` and `share_from_periodic` + +**Note:** This change was made WITHOUT deprecation warnings due to the fundamental restructuring. + +--- + +### 2. Class and Variable Renaming + +#### Investment Variable +```python +# In optimization results +results.solution['component|is_invested'] # ❌ Old +results.solution['component|invested'] # ✅ New +``` + +--- + +### 3. Calculation API Change + +**What changed:** `Calculation.do_modeling()` now returns the Calculation object for method chaining. + +**Old usage (v2.x):** +```python +calculation = fx.FullCalculation('my_calc', flow_system) +linopy_model = calculation.do_modeling() # Returned linopy.Model +``` + +**New usage (v3.0.0):** +```python +calculation = fx.FullCalculation('my_calc', flow_system) +calculation.do_modeling() # Returns Calculation object +linopy_model = calculation.model # Access model via property + +#This enables chaining operations +fx.FullCalculation('my_calc', flow_system).do_modeling().solve() +``` + +**Migration:** If you used the return value of `do_modeling()`, update to access `.model` property instead. + +--- + +### 4. Storage Charge State Bounds + +**What changed:** `relative_minimum_charge_state` and `relative_maximum_charge_state` no longer have an extra timestep. + +**Impact:** If you provided arrays for these parameters with `len(timesteps) + 1` elements, reduce to `len(timesteps)`. + +**Example:** +```python +# Old (v2.x): Array with extra timestep +storage = fx.Storage( + 'storage', + relative_minimum_charge_state=np.array([0.2, 0.2, 0.2, 0.2, 0.2]) # 5 values for 4 timesteps +) + +# New (v3.0.0): Array matches timesteps +storage = fx.Storage( + 'storage', + relative_minimum_charge_state=np.array([0.2, 0.2, 0.2, 0.2]), # 4 values for 4 timesteps + relative_minimum_final_charge_state=0.3 #Specify the final value directly. If not, the last value is simply repeated +) +``` + +--- + +### 5. Plotting Parameter Rename + +```python +# Old +results.plot_heatmap('component|variable', mode='line') # ❌ + +# New +results.plot_heatmap('component|variable', style='line') # ✅ +``` + +--- + +## Deprecated Parameters (Still Supported) + +These parameters still work but will be removed in a future version. Update them at your convenience: + +### InvestParameters + +```python +# Old → New +fx.InvestParameters( + fix_effects=1000, # → effects_of_investment + specific_effects={'costs': 10}, # → effects_of_investment_per_size + divest_effects=100, # → effects_of_retirement + piecewise_effects=my_piecewise, # → piecewise_effects_of_investment +) +``` + +### Effect + +```python +# Old → New +fx.Effect( + minimum_investment=10, # → minimum_periodic + maximum_investment=100, # → maximum_periodic + minimum_operation=5, # → minimum_temporal + maximum_operation=50, # → maximum_temporal + minimum_operation_per_hour=1, # → minimum_per_hour + maximum_operation_per_hour=10, # → maximum_per_hour +) +``` + +### Component Parameters (Source, Sink, SourceAndSink) + +```python +# Old → New +fx.Source( + 'my_source', + source=flow, # → outputs +) + +fx.Sink( + 'my_sink', + sink=flow, # → inputs +) + +fx.SourceAndSink( + 'my_source_sink', + source=flow1, # → outputs + sink=flow2, # → inputs + prevent_simultaneous_sink_and_source=True # → prevent_simultaneous_flow_rates +) +``` + +### TimeSeriesData + +```python +# Old → New +fx.TimeSeriesData( + agg_group='group1', # → aggregation_group + agg_weight=2.0 # → aggregation_weight +) +``` + +### Calculation + +```python +# Old → New +calculation = fx.FullCalculation( + 'calc', + flow_system, + active_timesteps=[0, 1, 2] # → Use flow_system.sel(time=...) instead +) + +# Better approach: +flow_system_subset = flow_system.sel(time=slice('2020-01-01', '2020-01-03')) +calculation = fx.FullCalculation('calc', flow_system_subset) +``` + +--- + +## New Features in v3.0.0 + +### 1. Multi-Period Investments + +Model transformation pathways with distinct investment decisions in each period: + +```python +import pandas as pd + +# Define multiple investment periods +periods = pd.Index(['2020', '2030']) +flow_system = fx.FlowSystem(time=timesteps, periods=periods) + +# Components can now invest differently in each period +solar = fx.Source( + 'solar', + outputs=[fx.Flow( + 'P_el', + bus='electricity', + size=fx.InvestParameters( + minimum_size=0, + maximum_size=1000, + effects_of_investment_per_size={'costs': 100} + ) + )] +) +``` + +### 2. Scenario-Based Stochastic Optimization + +Model uncertainty with weighted scenarios: + +```python +# Define scenarios with probabilities +scenarios = pd.Index(['low_demand', 'base', 'high_demand'], name='scenario') +scenario_weights = [0.2, 0.6, 0.2] # Probabilities + +flow_system = fx.FlowSystem( + time=timesteps, + scenarios=scenarios, + scenario_weights=scenario_weights +) + +# Define scenario-dependent data +demand = xr.DataArray( + data=[[70, 80, 90], # low_demand scenario + [90, 100, 110], # base scenario + [110, 120, 130]], # high_demand scenario + dims=['scenario', 'time'], + coords={'scenario': scenarios, 'time': timesteps} +) + +``` + +**Control variable independence:** +```python +# By default: investment sizes are shared across scenarios, flow rates vary +# To make sizes scenario-independent: +flow_system = fx.FlowSystem( + time=timesteps, + scenarios=scenarios, + scenario_independent_sizes=True # Each scenario gets its own capacity +) +``` + +### 3. Enhanced I/O and Data Handling + +```python +# Save and load FlowSystem +flow_system.to_netcdf('my_system.nc') +flow_system_loaded = fx.FlowSystem.from_netcdf('my_system.nc') + +# Manipulate FlowSystem +fs_subset = flow_system.sel(time=slice('2020-01', '2020-06')) +fs_resampled = flow_system.resample(time='D') # Resample to daily +fs_copy = flow_system.copy() + +# Access FlowSystem from results (lazily loaded) +results = calculation.results +original_fs = results.flow_system # No manual restoration needed +``` + +### 4. Effects Per Component + +Analyze the impact of each component, including indirect effects through effect shares: + +```python +# Get dataset showing contribution of each component to all effects +effects_ds = calculation.results.effects_per_component() + +print(effects_ds['costs']) # Total costs by component +print(effects_ds['CO2']) # CO2 emissions by component (including indirect) +``` + +### 5. Balanced Storage + +Force charging and discharging capacities to be equal: + +```python +storage = fx.Storage( + 'storage', + charging=fx.Flow('charge', bus='electricity', size=fx.InvestParameters(effects_per_size=100, minimum_size=5)), + discharging=fx.Flow('discharge', bus='electricity', size=fx.InvestParameters(), + balanced=True, # Ensures charge_size == discharge_size + capacity_in_flow_hours=100 +) +``` + +### 6. Final Charge State Control + +Set bounds on the storage state at the end of the optimization: + +```python +storage = fx.Storage( + 'storage', + charging=fx.Flow('charge', bus='electricity', size=100), + discharging=fx.Flow('discharge', bus='electricity', size=100), + capacity_in_flow_hours=10, + relative_minimum_final_charge_state=0.5, # End at least 50% charged + relative_maximum_final_charge_state=0.8 # End at most 80% charged +) +``` + +--- + +## Configuration Changes + +### Logging (v2.2.0+) + +**Breaking change:** Console and file logging are now disabled by default. + +```python +import flixopt as fx + +# Enable console logging +fx.CONFIG.Logging.console = True +fx.CONFIG.Logging.level = 'INFO' +fx.CONFIG.apply() + +# Enable file logging +fx.CONFIG.Logging.file = 'flixopt.log' +fx.CONFIG.apply() + +# Deprecated: change_logging_level() - will be removed in future +# fx.change_logging_level('INFO') # ❌ Old way +``` + +--- + +## Testing Your Migration + +### 1. Check for Deprecation Warnings + +Run your code and watch for deprecation warnings: + +```python +import warnings +warnings.filterwarnings('default', category=DeprecationWarning) + +# Run your flixopt code +# Review any DeprecationWarning messages +``` + +### 2. Validate Results + +Compare results from v2.x and v3.0.0 to ensure consistency: + +```python +# Save v2.x results before upgrading +calculation.results.to_file('results_v2.nc') + +# After upgrading, compare +results_v3 = calculation.results +results_v2 = fx.CalculationResults.from_file('results_v2.nc') + +# Check key variables match (within numerical tolerance) +import numpy as np +v2_costs = results_v2['effect_values'].sel(effect='costs') +v3_costs = results_v3['effect_values'].sel(effect='costs') +np.testing.assert_allclose(v2_costs, v3_costs, rtol=1e-5) +``` + +--- + +## Common Migration Issues + +### Issue: "AttributeError: module 'flixopt' has no attribute 'SystemModel'" + +**Solution:** Rename `SystemModel` → `FlowSystemModel` + +### Issue: "Effect share parameters not working" + +**Solution:** Effect sharing was completely redesigned. Move share definitions to the **receiving** effect using `share_from_temporal` and `share_from_periodic`. + +### Issue: "Storage charge state has wrong dimensions" + +**Solution:** Remove the extra timestep from charge state bound arrays. + +### Issue: "Import error with Bus assignment" + +**Solution:** Pass bus labels (strings) instead of Bus objects to `Flow.bus`. + +```python +# Old +my_bus = fx.Bus('electricity') +flow = fx.Flow('P_el', bus=my_bus) # ❌ + +# New +my_bus = fx.Bus('electricity') +flow = fx.Flow('P_el', bus='electricity') # ✅ +``` + +--- + +## Getting Help + +- **Documentation:** [https://flixopt.github.io/flixopt/](https://flixopt.github.io/flixopt/) +- **GitHub Issues:** [https://github.com/flixOpt/flixopt/issues](https://github.com/flixOpt/flixopt/issues) +- **Changelog:** [Full v3.0.0 release notes](https://flixopt.github.io/flixopt/changelog/) + +--- + +## Summary Checklist + +- [ ] Update flixopt: `pip install --upgrade flixopt` +- [ ] Update effect sharing syntax (no deprecation warning!) +- [ ] Update `Calculation.do_modeling()` usage +- [ ] Fix storage charge state array dimensions +- [ ] Rename `mode` → `style` in plotting calls +- [ ] Update deprecated parameter names (optional, but recommended) +- [ ] Enable logging explicitly if needed +- [ ] Test your code thoroughly +- [ ] Explore new features (periods, scenarios, enhanced I/O) + +**Welcome to flixopt v3.0.0!** 🎉 From 4033044d6bf132d6cf8bbbaf6fd26c9d7e3ccc03 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Mon, 13 Oct 2025 22:43:05 +0200 Subject: [PATCH 02/13] Add a migration guide --- CHANGELOG.md | 1 + mkdocs.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5de41f19..79753972e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ Until here --> ## [3.0.0] - 2025-10-13 **Summary**: This release introduces new model dimensions (periods and scenarios) for multi-period investments and stochastic modeling, along with a redesigned effect sharing system and enhanced I/O capabilities. +For detailed migration instructions, see the [Migration Guide](https://flixopt.github.io/flixopt/user-guide/migration-guide-v3/). ### ✨ Added diff --git a/mkdocs.yml b/mkdocs.yml index b7c03faac..8fe013f47 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,6 +13,7 @@ nav: - Getting Started: getting-started.md - User Guide: - user-guide/index.md + - Migration to v3.0.0: user-guide/migration-guide-v3.md - Recipes: user-guide/recipes/index.md - Mathematical Notation: - Overview: user-guide/mathematical-notation/index.md From dacd68375586ab52fce0075c3a1731a75f3099f6 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Mon, 13 Oct 2025 22:49:09 +0200 Subject: [PATCH 03/13] Remove not needed docs files --- docs/faq/contribute.md | 61 ------------------------------------------ docs/faq/index.md | 3 --- 2 files changed, 64 deletions(-) delete mode 100644 docs/faq/contribute.md delete mode 100644 docs/faq/index.md diff --git a/docs/faq/contribute.md b/docs/faq/contribute.md deleted file mode 100644 index ff31c9f1f..000000000 --- a/docs/faq/contribute.md +++ /dev/null @@ -1,61 +0,0 @@ -# Contributing to the Project - -We warmly welcome contributions from the community! This guide will help you get started with contributing to our project. - -## Development Setup -1. Clone the repository `git clone https://github.com/flixOpt/flixopt.git` -2. Install the development dependencies `pip install -e ".[dev]"` -3. Install pre-commit hooks `pre-commit install` (one-time setup) -4. Run `pytest` to ensure your code passes all tests - -## Code Quality -We use [Ruff](https://github.com/astral-sh/ruff) for linting and formatting. After the one-time setup above, **code quality checks run automatically on every commit**. - -To run manually: -- `ruff check --fix .` to check and fix linting issues -- `ruff format .` to format code - -## Documentation (Optional) -FlixOpt uses [mkdocs](https://www.mkdocs.org/) to generate documentation. -To work on documentation: -```bash -pip install -e ".[docs]" -mkdocs serve -``` -Then navigate to http://127.0.0.1:8000/ - -## Testing -- `pytest` to run the test suite -- You can also run the provided python script `run_all_test.py` - ---- -# Best practices - -## Coding Guidelines - -- Follow PEP 8 style guidelines -- Write clear, commented code -- Include type hints -- Create or update tests for new functionality -- Ensure 100% test coverage for new code - -## Branches -As we start to think FlixOpt in **Releases**, we decided to introduce multiple **dev**-branches instead of only one: -Following the **Semantic Versioning** guidelines, we introduced: -- `next/patch`: This is where all pull requests for the next patch release (1.0.x) go. -- `next/minor`: This is where all pull requests for the next minor release (1.x.0) go. -- `next/major`: This is where all pull requests for the next major release (x.0.0) go. - -Everything else remains in `feature/...`-branches. - -## Pull requests -Every feature or bugfix should be merged into one of the 3 [release branches](#branches), using **Squash and merge** or a regular **single commit**. -At some point, `next/minor` or `next/major` will get merged into `main` using a regular **Merge** (not squash). -*This ensures that Features are kept separate, and the `next/...`branches stay in synch with ``main`.* - -## Releases -As stated, we follow **Semantic Versioning**. -Right after one of the 3 [release branches](#branches) is merged into main, a **Tag** should be added to the merge commit and pushed to the main branch. The tag has the form `v1.2.3`. -With this tag, a release with **Release Notes** must be created. - -*This is our current best practice* diff --git a/docs/faq/index.md b/docs/faq/index.md deleted file mode 100644 index 6a245edd3..000000000 --- a/docs/faq/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Frequently Asked Questions - -## Work in progress From 87aaccffdfa1b9f6e0dd0a77be9a389971629352 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Mon, 13 Oct 2025 22:52:40 +0200 Subject: [PATCH 04/13] Add missing type hints --- flixopt/calculation.py | 4 +++- flixopt/config.py | 2 +- flixopt/core.py | 4 ++-- flixopt/modeling.py | 6 ++---- flixopt/results.py | 4 ++-- flixopt/utils.py | 6 ++---- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/flixopt/calculation.py b/flixopt/calculation.py index 9e35b2dee..fbe94ec21 100644 --- a/flixopt/calculation.py +++ b/flixopt/calculation.py @@ -286,7 +286,9 @@ class AggregatedCalculation(FullCalculation): This equalizes variables in the components according to the typical periods computed in the aggregation active_timesteps: DatetimeIndex of timesteps to use for calculation. If None, all timesteps are used folder: Folder where results should be saved. If None, current working directory is used - aggregation: contains the aggregation model + + Attributes: + aggregation: Contains the aggregation model (initialized as None) """ def __init__( diff --git a/flixopt/config.py b/flixopt/config.py index 4ac8263b2..a7549a3ec 100644 --- a/flixopt/config.py +++ b/flixopt/config.py @@ -286,7 +286,7 @@ def _apply_config_dict(cls, config_dict: dict): setattr(cls, key, value) @classmethod - def to_dict(cls): + def to_dict(cls) -> dict: """Convert the configuration class into a dictionary for JSON serialization. Returns: diff --git a/flixopt/core.py b/flixopt/core.py index c163de554..48fc81333 100644 --- a/flixopt/core.py +++ b/flixopt/core.py @@ -46,12 +46,12 @@ class TimeSeriesData(xr.DataArray): def __init__( self, - *args, + *args: any, aggregation_group: str | None = None, aggregation_weight: float | None = None, agg_group: str | None = None, agg_weight: float | None = None, - **kwargs, + **kwargs: any, ): """ Args: diff --git a/flixopt/modeling.py b/flixopt/modeling.py index 88e652bc9..c7f0bf314 100644 --- a/flixopt/modeling.py +++ b/flixopt/modeling.py @@ -396,7 +396,7 @@ def basic_bounds( variable: linopy.Variable, bounds: tuple[TemporalData, TemporalData], name: str = None, - ): + ) -> list[linopy.constraints.Constraint]: """Create simple bounds. variable ∈ [lower_bound, upper_bound] @@ -409,9 +409,7 @@ def basic_bounds( bounds: Tuple of (lower_bound, upper_bound) absolute bounds Returns: - Tuple containing: - - variables (Dict): Empty dict - - constraints (Dict[str, linopy.Constraint]): 'ub', 'lb' + List containing lower_bound and upper_bound constraints """ if not isinstance(model, Submodel): raise ValueError('BoundingPatterns.basic_bounds() can only be used with a Submodel') diff --git a/flixopt/results.py b/flixopt/results.py index e571bc558..2e951af70 100644 --- a/flixopt/results.py +++ b/flixopt/results.py @@ -1243,7 +1243,7 @@ def node_balance_with_charge_state( class EffectResults(_ElementResults): """Results for an Effect""" - def get_shares_from(self, element: str): + def get_shares_from(self, element: str) -> xr.Dataset: """Get effect shares from specific element. Args: @@ -1399,7 +1399,7 @@ def from_calculation(cls, calculation: SegmentedCalculation): ) @classmethod - def from_file(cls, folder: str | pathlib.Path, name: str): + def from_file(cls, folder: str | pathlib.Path, name: str) -> SegmentedCalculationResults: """Load SegmentedCalculationResults from saved files. Args: diff --git a/flixopt/utils.py b/flixopt/utils.py index f1e12b9dc..cb8aabebf 100644 --- a/flixopt/utils.py +++ b/flixopt/utils.py @@ -13,7 +13,7 @@ logger = logging.getLogger('flixopt') -def round_nested_floats(obj, decimals=2): +def round_nested_floats(obj: dict | list | float | int | any, decimals: int = 2) -> dict | list | float | int | any: """Recursively round floating point numbers in nested data structures. This function traverses nested data structures (dictionaries, lists) and rounds @@ -27,9 +27,7 @@ def round_nested_floats(obj, decimals=2): decimals (int, optional): Number of decimal places to round to. Defaults to 2. Returns: - The processed object with the same structure as the input, but with all - floating point numbers rounded to the specified precision. NumPy arrays - and xarray DataArrays are converted to lists. + The processed object with the same structure as the input, but with all floating point numbers rounded to the specified precision. NumPy arrays and xarray DataArrays are converted to lists. Examples: >>> data = {'a': 3.14159, 'b': [1.234, 2.678]} From d0d6f432cbd0d70776a9d609d4944b2cb556d3a5 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Mon, 13 Oct 2025 23:12:21 +0200 Subject: [PATCH 05/13] Fix docs --- .../user-guide/mathematical-notation/index.md | 2 +- mkdocs.yml | 25 +++---------------- scripts/extract_changelog.py | 8 ++++-- 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/docs/user-guide/mathematical-notation/index.md b/docs/user-guide/mathematical-notation/index.md index ae89f3b67..05d1fed60 100644 --- a/docs/user-guide/mathematical-notation/index.md +++ b/docs/user-guide/mathematical-notation/index.md @@ -3,7 +3,7 @@ This section provides the **mathematical formulations** underlying FlixOpt's optimization models. It is intended as **reference documentation** for users who want to understand the mathematical details behind the high-level FlixOpt API described in the [FlixOpt Concepts](../index.md) guide. -**For typical usage**, refer to the [FlixOpt Concepts](../index.md) guide, [Examples](../../examples/), and [API Reference](../../api-reference/) - you don't need to understand these mathematical formulations to use FlixOpt effectively. +**For typical usage**, refer to the [FlixOpt Concepts](../index.md) guide, [Examples](../../examples/index.md), and [API Reference](../../api-reference/index.md) - you don't need to understand these mathematical formulations to use FlixOpt effectively. --- diff --git a/mkdocs.yml b/mkdocs.yml index 8fe013f47..94301ff53 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -35,28 +35,7 @@ nav: - State Transitions: user-guide/mathematical-notation/modeling-patterns/state-transitions.md - Examples: examples/ - Contribute: contribute.md - - API Reference: - - api-reference/index.md - - Aggregation: api-reference/aggregation.md - - Calculation: api-reference/calculation.md - - Commons: api-reference/commons.md - - Components: api-reference/components.md - - Config: api-reference/config.md - - Core: api-reference/core.md - - Effects: api-reference/effects.md - - Elements: api-reference/elements.md - - Features: api-reference/features.md - - Flow System: api-reference/flow_system.md - - Interface: api-reference/interface.md - - IO: api-reference/io.md - - Linear Converters: api-reference/linear_converters.md - - Modeling: api-reference/modeling.md - - Network App: api-reference/network_app.md - - Plotting: api-reference/plotting.md - - Results: api-reference/results.md - - Solvers: api-reference/solvers.md - - Structure: api-reference/structure.md - - Utils: api-reference/utils.md + - API Reference: api-reference/ - Release Notes: changelog/ @@ -136,6 +115,8 @@ plugins: - include-markdown - mike: version_selector: true + - literate-nav: + nav_file: SUMMARY.md - gen-files: scripts: - scripts/gen_ref_pages.py diff --git a/scripts/extract_changelog.py b/scripts/extract_changelog.py index c2c34d35b..d05229896 100644 --- a/scripts/extract_changelog.py +++ b/scripts/extract_changelog.py @@ -136,9 +136,13 @@ def extract_index(): raise ValueError('Intro section not found before comment block') final_content = intro_match.group(1).strip() - # Write file + # Write simple index without TOC (literate-nav plugin handles navigation) with open(index_path, 'w', encoding='utf-8') as f: - f.write('\n'.join(['# Changelog\n', final_content])) + f.write( + '# Changelog\n\n' + + final_content + + '\n\nUse the navigation menu to browse through individual release notes.' + ) print('✅ Created index.md') From 54a1ec337a3dda637d37beb968e51c056e045277 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Mon, 13 Oct 2025 23:18:41 +0200 Subject: [PATCH 06/13] Make fancy migration guide --- docs/user-guide/migration-guide-v3.md | 387 +++++++++++++++++--------- 1 file changed, 252 insertions(+), 135 deletions(-) diff --git a/docs/user-guide/migration-guide-v3.md b/docs/user-guide/migration-guide-v3.md index a8782f446..4f85d901b 100644 --- a/docs/user-guide/migration-guide-v3.md +++ b/docs/user-guide/migration-guide-v3.md @@ -2,16 +2,14 @@ This guide helps you migrate your flixopt code from v2.x to v3.0.0. Version 3.0.0 introduces powerful new features like multi-period investments and scenario-based stochastic optimization, along with a redesigned effect sharing system. -## Quick Start - -1. **Update your installation:** - ```bash - pip install --upgrade flixopt - ``` - -2. **Review breaking changes** in the sections below -3. **Update deprecated parameters** to their new names -4. **Test your code** with the new version +!!! tip "Quick Start" + 1. **Update your installation:** + ```bash + pip install --upgrade flixopt + ``` + 2. **Review breaking changes** in the sections below + 3. **Update deprecated parameters** to their new names + 4. **Test your code** with the new version --- @@ -19,180 +17,298 @@ This guide helps you migrate your flixopt code from v2.x to v3.0.0. Version 3.0. ### 1. Effect Sharing System Redesign -**What changed:** The effect sharing syntax has been inverted and simplified. +!!! warning "Breaking Change - No Deprecation" + The effect sharing syntax has been inverted and simplified. This change was made WITHOUT deprecation warnings due to the fundamental restructuring. -**Old syntax (v2.x):** -```python -# Effects "pushed" shares to other effects -CO2 = fx.Effect('CO2', 'kg', 'CO2 emissions', - specific_share_to_other_effects_operation={'costs': 0.2}) -``` +**What changed:** Effects now "pull" shares from other effects instead of "pushing" them. -**New syntax (v3.0.0):** -```python -# Effects "pull" shares from other effects (clearer direction) -costs = fx.Effect('costs', '€', 'Total costs', - share_from_temporal={'CO2': 0.2}, # From temporal (operation) effects - share_from_periodic={'land': 100}) # From periodic (investment) effects -``` +=== "v2.x (Old)" + + ```python + # Effects "pushed" shares to other effects + CO2 = fx.Effect('CO2', 'kg', 'CO2 emissions', + specific_share_to_other_effects_operation={'costs': 0.2}) + + land = fx.Effect('land', 'm²', 'Land usage', + specific_share_to_other_effects_invest={'costs': 100}) + + costs = fx.Effect('costs', '€', 'Total costs') + ``` -**Migration steps:** +=== "v3.0.0 (New)" -1. Find all uses of `specific_share_to_other_effects_operation` and `specific_share_to_other_effects_invest` -2. Move the share definition to the **receiving** effect -3. Rename to `share_from_temporal` and `share_from_periodic` + ```python + # Effects "pull" shares from other effects (clearer direction) + CO2 = fx.Effect('CO2', 'kg', 'CO2 emissions') -**Note:** This change was made WITHOUT deprecation warnings due to the fundamental restructuring. + land = fx.Effect('land', 'm²', 'Land usage') + + costs = fx.Effect('costs', '€', 'Total costs', + share_from_temporal={'CO2': 0.2}, # From temporal (operation) effects + share_from_periodic={'land': 100}) # From periodic (investment) effects + ``` + +!!! success "Migration Steps" + 1. Find all uses of `specific_share_to_other_effects_operation` and `specific_share_to_other_effects_invest` + 2. Move the share definition to the **receiving** effect + 3. Rename parameters: + - `specific_share_to_other_effects_operation` → `share_from_temporal` + - `specific_share_to_other_effects_invest` → `share_from_periodic` --- ### 2. Class and Variable Renaming -#### Investment Variable -```python -# In optimization results -results.solution['component|is_invested'] # ❌ Old -results.solution['component|invested'] # ✅ New -``` +=== "v2.x (Old)" + + ```python + # In optimization results + results.solution['component|is_invested'] + ``` + +=== "v3.0.0 (New)" + + ```python + # In optimization results + results.solution['component|invested'] + ``` --- ### 3. Calculation API Change -**What changed:** `Calculation.do_modeling()` now returns the Calculation object for method chaining. +!!! info "Method Chaining Support" + `Calculation.do_modeling()` now returns the Calculation object to enable method chaining. -**Old usage (v2.x):** -```python -calculation = fx.FullCalculation('my_calc', flow_system) -linopy_model = calculation.do_modeling() # Returned linopy.Model -``` +=== "v2.x (Old)" -**New usage (v3.0.0):** -```python -calculation = fx.FullCalculation('my_calc', flow_system) -calculation.do_modeling() # Returns Calculation object -linopy_model = calculation.model # Access model via property + ```python + calculation = fx.FullCalculation('my_calc', flow_system) + linopy_model = calculation.do_modeling() # Returned linopy.Model -#This enables chaining operations -fx.FullCalculation('my_calc', flow_system).do_modeling().solve() -``` + # Access model directly from return value + print(linopy_model) + ``` + +=== "v3.0.0 (New)" + + ```python + calculation = fx.FullCalculation('my_calc', flow_system) + calculation.do_modeling() # Returns Calculation object + linopy_model = calculation.model # Access model via property + + # This enables chaining operations + fx.FullCalculation('my_calc', flow_system).do_modeling().solve() + ``` -**Migration:** If you used the return value of `do_modeling()`, update to access `.model` property instead. +!!! tip "Migration" + If you used the return value of `do_modeling()`, update to access `.model` property instead. --- ### 4. Storage Charge State Bounds -**What changed:** `relative_minimum_charge_state` and `relative_maximum_charge_state` no longer have an extra timestep. +!!! warning "Array Dimensions Changed" + `relative_minimum_charge_state` and `relative_maximum_charge_state` no longer have an extra timestep. -**Impact:** If you provided arrays for these parameters with `len(timesteps) + 1` elements, reduce to `len(timesteps)`. +**Impact:** If you provided arrays with `len(timesteps) + 1` elements, reduce to `len(timesteps)`. -**Example:** -```python -# Old (v2.x): Array with extra timestep -storage = fx.Storage( - 'storage', - relative_minimum_charge_state=np.array([0.2, 0.2, 0.2, 0.2, 0.2]) # 5 values for 4 timesteps -) +=== "v2.x (Old)" -# New (v3.0.0): Array matches timesteps -storage = fx.Storage( - 'storage', - relative_minimum_charge_state=np.array([0.2, 0.2, 0.2, 0.2]), # 4 values for 4 timesteps - relative_minimum_final_charge_state=0.3 #Specify the final value directly. If not, the last value is simply repeated -) -``` + ```python + # Array with extra timestep + storage = fx.Storage( + 'storage', + relative_minimum_charge_state=np.array([0.2, 0.2, 0.2, 0.2, 0.2]) # 5 values for 4 timesteps + ) + ``` + +=== "v3.0.0 (New)" + + ```python + # Array matches timesteps + storage = fx.Storage( + 'storage', + relative_minimum_charge_state=np.array([0.2, 0.2, 0.2, 0.2]), # 4 values for 4 timesteps + relative_minimum_final_charge_state=0.3 # Specify the final value directly + ) + ``` + +!!! note "Final State Control" + Use the new `relative_minimum_final_charge_state` and `relative_maximum_final_charge_state` parameters to explicitly control the final charge state. --- ### 5. Plotting Parameter Rename -```python -# Old -results.plot_heatmap('component|variable', mode='line') # ❌ +=== "v2.x (Old)" -# New -results.plot_heatmap('component|variable', style='line') # ✅ -``` + ```python + results.plot_heatmap('component|variable', mode='line') + ``` + +=== "v3.0.0 (New)" + + ```python + results.plot_heatmap('component|variable', style='line') + ``` --- ## Deprecated Parameters (Still Supported) -These parameters still work but will be removed in a future version. Update them at your convenience: +!!! info "Gradual Migration" + These parameters still work but will be removed in a future version. Update them at your convenience - deprecation warnings will guide you. ### InvestParameters -```python -# Old → New -fx.InvestParameters( - fix_effects=1000, # → effects_of_investment - specific_effects={'costs': 10}, # → effects_of_investment_per_size - divest_effects=100, # → effects_of_retirement - piecewise_effects=my_piecewise, # → piecewise_effects_of_investment -) -``` +**Parameter Changes:** + +| Old Parameter (v2.x) | New Parameter (v3.0.0) | +|---------------------|----------------------| +| `fix_effects` | `effects_of_investment` | +| `specific_effects` | `effects_of_investment_per_size` | +| `divest_effects` | `effects_of_retirement` | +| `piecewise_effects` | `piecewise_effects_of_investment` | + +=== "v2.x (Deprecated)" + + ```python + fx.InvestParameters( + fix_effects=1000, + specific_effects={'costs': 10}, + divest_effects=100, + piecewise_effects=my_piecewise, + ) + ``` + +=== "v3.0.0 (Recommended)" + + ```python + fx.InvestParameters( + effects_of_investment=1000, + effects_of_investment_per_size={'costs': 10}, + effects_of_retirement=100, + piecewise_effects_of_investment=my_piecewise, + ) + ``` ### Effect -```python -# Old → New -fx.Effect( - minimum_investment=10, # → minimum_periodic - maximum_investment=100, # → maximum_periodic - minimum_operation=5, # → minimum_temporal - maximum_operation=50, # → maximum_temporal - minimum_operation_per_hour=1, # → minimum_per_hour - maximum_operation_per_hour=10, # → maximum_per_hour -) -``` - -### Component Parameters (Source, Sink, SourceAndSink) +**Parameter Changes:** + +| Old Parameter (v2.x) | New Parameter (v3.0.0) | +|---------------------|----------------------| +| `minimum_investment` | `minimum_periodic` | +| `maximum_investment` | `maximum_periodic` | +| `minimum_operation` | `minimum_temporal` | +| `maximum_operation` | `maximum_temporal` | +| `minimum_operation_per_hour` | `minimum_per_hour` | +| `maximum_operation_per_hour` | `maximum_per_hour` | + +=== "v2.x (Deprecated)" + + ```python + fx.Effect( + 'my_effect', 'unit', 'description', + minimum_investment=10, + maximum_investment=100, + minimum_operation=5, + maximum_operation=50, + minimum_operation_per_hour=1, + maximum_operation_per_hour=10, + ) + ``` + +=== "v3.0.0 (Recommended)" + + ```python + fx.Effect( + 'my_effect', 'unit', 'description', + minimum_periodic=10, + maximum_periodic=100, + minimum_temporal=5, + maximum_temporal=50, + minimum_per_hour=1, + maximum_per_hour=10, + ) + ``` + +### Component Parameters + +=== "v2.x (Deprecated)" + + ```python + fx.Source('my_source', source=flow) + + fx.Sink('my_sink', sink=flow) + + fx.SourceAndSink( + 'my_source_sink', + source=flow1, + sink=flow2, + prevent_simultaneous_sink_and_source=True + ) + ``` + +=== "v3.0.0 (Recommended)" + + ```python + fx.Source('my_source', outputs=flow) + + fx.Sink('my_sink', inputs=flow) + + fx.SourceAndSink( + 'my_source_sink', + outputs=flow1, + inputs=flow2, + prevent_simultaneous_flow_rates=True + ) + ``` -```python -# Old → New -fx.Source( - 'my_source', - source=flow, # → outputs -) +### TimeSeriesData -fx.Sink( - 'my_sink', - sink=flow, # → inputs -) +=== "v2.x (Deprecated)" -fx.SourceAndSink( - 'my_source_sink', - source=flow1, # → outputs - sink=flow2, # → inputs - prevent_simultaneous_sink_and_source=True # → prevent_simultaneous_flow_rates -) -``` + ```python + fx.TimeSeriesData( + agg_group='group1', + agg_weight=2.0 + ) + ``` -### TimeSeriesData +=== "v3.0.0 (Recommended)" -```python -# Old → New -fx.TimeSeriesData( - agg_group='group1', # → aggregation_group - agg_weight=2.0 # → aggregation_weight -) -``` + ```python + fx.TimeSeriesData( + aggregation_group='group1', + aggregation_weight=2.0 + ) + ``` ### Calculation -```python -# Old → New -calculation = fx.FullCalculation( - 'calc', - flow_system, - active_timesteps=[0, 1, 2] # → Use flow_system.sel(time=...) instead -) +=== "v2.x (Deprecated)" -# Better approach: -flow_system_subset = flow_system.sel(time=slice('2020-01-01', '2020-01-03')) -calculation = fx.FullCalculation('calc', flow_system_subset) -``` + ```python + calculation = fx.FullCalculation( + 'calc', + flow_system, + active_timesteps=[0, 1, 2] + ) + ``` + +=== "v3.0.0 (Recommended)" + + ```python + # Use FlowSystem selection methods + flow_system_subset = flow_system.sel(time=slice('2020-01-01', '2020-01-03')) + calculation = fx.FullCalculation('calc', flow_system_subset) + + # Or with isel for index-based selection + flow_system_subset = flow_system.isel(time=slice(0, 3)) + calculation = fx.FullCalculation('calc', flow_system_subset) + ``` --- @@ -382,10 +498,6 @@ np.testing.assert_allclose(v2_costs, v3_costs, rtol=1e-5) ## Common Migration Issues -### Issue: "AttributeError: module 'flixopt' has no attribute 'SystemModel'" - -**Solution:** Rename `SystemModel` → `FlowSystemModel` - ### Issue: "Effect share parameters not working" **Solution:** Effect sharing was completely redesigned. Move share definitions to the **receiving** effect using `share_from_temporal` and `share_from_periodic`. @@ -408,6 +520,11 @@ my_bus = fx.Bus('electricity') flow = fx.Flow('P_el', bus='electricity') # ✅ ``` +### Issue: "AttributeError: module 'flixopt' has no attribute 'SystemModel'" + +**Solution:** Rename `SystemModel` → `FlowSystemModel` + + --- ## Getting Help From a3997c10099a31c5d41d157005e6f78079719901 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Mon, 13 Oct 2025 23:54:04 +0200 Subject: [PATCH 07/13] Fix: Aggregation objects stored in separate attributes --- flixopt/calculation.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/flixopt/calculation.py b/flixopt/calculation.py index fbe94ec21..ff85643dc 100644 --- a/flixopt/calculation.py +++ b/flixopt/calculation.py @@ -23,7 +23,7 @@ from . import io as fx_io from . import utils as utils -from .aggregation import AggregationModel, AggregationParameters +from .aggregation import Aggregation, AggregationModel, AggregationParameters from .components import Storage from .config import CONFIG from .core import DataConverter, Scalar, TimeSeriesData, drop_constant_arrays @@ -288,7 +288,8 @@ class AggregatedCalculation(FullCalculation): folder: Folder where results should be saved. If None, current working directory is used Attributes: - aggregation: Contains the aggregation model (initialized as None) + aggregation: Contains the clustered time series data + aggregation_model: Contains Variables and Constraints that equalize clusters of the time series data """ def __init__( @@ -308,7 +309,8 @@ def __init__( super().__init__(name, flow_system, active_timesteps, folder=folder) self.aggregation_parameters = aggregation_parameters self.components_to_clusterize = components_to_clusterize - self.aggregation = None + self.aggregation: Aggregation | None = None + self.aggregation_model: AggregationModel | None = None def do_modeling(self) -> AggregatedCalculation: t_start = timeit.default_timer() @@ -319,10 +321,10 @@ def do_modeling(self) -> AggregatedCalculation: self.model = self.flow_system.create_model(self.normalize_weights) self.model.do_modeling() # Add Aggregation Submodel after modeling the rest - self.aggregation = AggregationModel( + self.aggregation_model = AggregationModel( self.model, self.aggregation_parameters, self.flow_system, self.aggregation, self.components_to_clusterize ) - self.aggregation.do_modeling() + self.aggregation_model.do_modeling() self.durations['modeling'] = round(timeit.default_timer() - t_start, 2) return self From 36ebc2559507f2488e3d109dbcb6885eb8caa292 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Mon, 13 Oct 2025 23:54:22 +0200 Subject: [PATCH 08/13] Type hints --- flixopt/core.py | 6 +++--- flixopt/utils.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/flixopt/core.py b/flixopt/core.py index 48fc81333..917ee2984 100644 --- a/flixopt/core.py +++ b/flixopt/core.py @@ -6,7 +6,7 @@ import logging import warnings from itertools import permutations -from typing import Literal, Union +from typing import Any, Literal, Union import numpy as np import pandas as pd @@ -46,12 +46,12 @@ class TimeSeriesData(xr.DataArray): def __init__( self, - *args: any, + *args: Any, aggregation_group: str | None = None, aggregation_weight: float | None = None, agg_group: str | None = None, agg_weight: float | None = None, - **kwargs: any, + **kwargs: Any, ): """ Args: diff --git a/flixopt/utils.py b/flixopt/utils.py index cb8aabebf..dd1f93d64 100644 --- a/flixopt/utils.py +++ b/flixopt/utils.py @@ -5,7 +5,7 @@ from __future__ import annotations import logging -from typing import Literal +from typing import Any, Literal import numpy as np import xarray as xr @@ -13,7 +13,7 @@ logger = logging.getLogger('flixopt') -def round_nested_floats(obj: dict | list | float | int | any, decimals: int = 2) -> dict | list | float | int | any: +def round_nested_floats(obj: dict | list | float | int | Any, decimals: int = 2) -> dict | list | float | int | Any: """Recursively round floating point numbers in nested data structures. This function traverses nested data structures (dictionaries, lists) and rounds From e33d7fcfbe0fdea92cff9ccf861a5ff5ac584379 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Mon, 13 Oct 2025 23:55:42 +0200 Subject: [PATCH 09/13] Remove duplicates in mkdocs.yml --- mkdocs.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 94301ff53..89bca4793 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -79,12 +79,10 @@ theme: - content.code.copy - content.code.annotate - content.tooltips - - content.code.copy - navigation.footer.version markdown_extensions: - admonition - - codehilite - markdown_include.include: base_path: docs - pymdownx.highlight: @@ -92,7 +90,6 @@ markdown_extensions: line_spans: __span pygments_lang_class: true - pymdownx.inlinehilite - - pymdownx.snippets - pymdownx.superfences - attr_list - abbr From 357de531e8881647c055b757bfcd3e1c2407af93 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Tue, 14 Oct 2025 00:14:56 +0200 Subject: [PATCH 10/13] Type hints --- flixopt/calculation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flixopt/calculation.py b/flixopt/calculation.py index ff85643dc..9d2164e1e 100644 --- a/flixopt/calculation.py +++ b/flixopt/calculation.py @@ -288,8 +288,8 @@ class AggregatedCalculation(FullCalculation): folder: Folder where results should be saved. If None, current working directory is used Attributes: - aggregation: Contains the clustered time series data - aggregation_model: Contains Variables and Constraints that equalize clusters of the time series data + aggregation (Aggregation | None): Contains the clustered time series data + aggregation_model (AggregationModel | None): Contains Variables and Constraints that equalize clusters of the time series data """ def __init__( From f9a3d2f7b19f4a7d95e06731f3071d18d9ea08ae Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Tue, 14 Oct 2025 00:20:12 +0200 Subject: [PATCH 11/13] Update CHANGELOG.md --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79753972e..81bdd2927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,16 @@ Please keep the format of the changelog consistent with the other releases, so t --- Until here --> +## [3.0.1] - 2025-10-14 +**Summary**: Adding a Migration Guide for the new **flixopt 3** and fixing docs + +### 📝 Docs +- Fixed deployed docs +- Added Migration Guide for flixopt 3 + +### 👷 Development +- Added missing type hints + ## [3.0.0] - 2025-10-13 **Summary**: This release introduces new model dimensions (periods and scenarios) for multi-period investments and stochastic modeling, along with a redesigned effect sharing system and enhanced I/O capabilities. For detailed migration instructions, see the [Migration Guide](https://flixopt.github.io/flixopt/user-guide/migration-guide-v3/). From 257ef7563acf4b0400034d36bd8364074bcb9220 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Tue, 14 Oct 2025 00:20:21 +0200 Subject: [PATCH 12/13] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81bdd2927..4b4159ff9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,8 @@ Until here --> ### 👷 Development - Added missing type hints +--- + ## [3.0.0] - 2025-10-13 **Summary**: This release introduces new model dimensions (periods and scenarios) for multi-period investments and stochastic modeling, along with a redesigned effect sharing system and enhanced I/O capabilities. For detailed migration instructions, see the [Migration Guide](https://flixopt.github.io/flixopt/user-guide/migration-guide-v3/). From e31ee3cf3059bef28a0efe6b2afdd4af1eb988d7 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Tue, 14 Oct 2025 00:21:36 +0200 Subject: [PATCH 13/13] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b4159ff9..0e5f9ff3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ Until here --> ## [3.0.1] - 2025-10-14 **Summary**: Adding a Migration Guide for the new **flixopt 3** and fixing docs +See the [Migration Guide](https://flixopt.github.io/flixopt/user-guide/migration-guide-v3/). ### 📝 Docs - Fixed deployed docs