diff --git a/CHANGELOG.md b/CHANGELOG.md index b5de41f19..0e5f9ff3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,8 +69,22 @@ 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 +See the [Migration Guide](https://flixopt.github.io/flixopt/user-guide/migration-guide-v3/). + +### 📝 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/). ### ✨ Added 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 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/docs/user-guide/migration-guide-v3.md b/docs/user-guide/migration-guide-v3.md new file mode 100644 index 000000000..4f85d901b --- /dev/null +++ b/docs/user-guide/migration-guide-v3.md @@ -0,0 +1,550 @@ +# 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. + +!!! 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 + +--- + +## Breaking Changes + +### 1. Effect Sharing System Redesign + +!!! 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. + +**What changed:** Effects now "pull" shares from other effects instead of "pushing" them. + +=== "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') + ``` + +=== "v3.0.0 (New)" + + ```python + # Effects "pull" shares from other effects (clearer direction) + CO2 = fx.Effect('CO2', 'kg', 'CO2 emissions') + + 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 + +=== "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 + +!!! info "Method Chaining Support" + `Calculation.do_modeling()` now returns the Calculation object to enable method chaining. + +=== "v2.x (Old)" + + ```python + calculation = fx.FullCalculation('my_calc', flow_system) + linopy_model = calculation.do_modeling() # Returned linopy.Model + + # 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() + ``` + +!!! tip "Migration" + If you used the return value of `do_modeling()`, update to access `.model` property instead. + +--- + +### 4. Storage Charge State Bounds + +!!! warning "Array Dimensions Changed" + `relative_minimum_charge_state` and `relative_maximum_charge_state` no longer have an extra timestep. + +**Impact:** If you provided arrays with `len(timesteps) + 1` elements, reduce to `len(timesteps)`. + +=== "v2.x (Old)" + + ```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 + +=== "v2.x (Old)" + + ```python + results.plot_heatmap('component|variable', mode='line') + ``` + +=== "v3.0.0 (New)" + + ```python + results.plot_heatmap('component|variable', style='line') + ``` + +--- + +## Deprecated Parameters (Still Supported) + +!!! 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 + +**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 + +**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 + ) + ``` + +### TimeSeriesData + +=== "v2.x (Deprecated)" + + ```python + fx.TimeSeriesData( + agg_group='group1', + agg_weight=2.0 + ) + ``` + +=== "v3.0.0 (Recommended)" + + ```python + fx.TimeSeriesData( + aggregation_group='group1', + aggregation_weight=2.0 + ) + ``` + +### Calculation + +=== "v2.x (Deprecated)" + + ```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) + ``` + +--- + +## 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: "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') # ✅ +``` + +### Issue: "AttributeError: module 'flixopt' has no attribute 'SystemModel'" + +**Solution:** Rename `SystemModel` → `FlowSystemModel` + + +--- + +## 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!** 🎉 diff --git a/flixopt/calculation.py b/flixopt/calculation.py index 9e35b2dee..9d2164e1e 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 @@ -286,7 +286,10 @@ 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 (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__( @@ -306,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() @@ -317,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 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..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, + *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..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, 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]} diff --git a/mkdocs.yml b/mkdocs.yml index b7c03faac..89bca4793 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 @@ -34,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/ @@ -99,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: @@ -112,7 +90,6 @@ markdown_extensions: line_spans: __span pygments_lang_class: true - pymdownx.inlinehilite - - pymdownx.snippets - pymdownx.superfences - attr_list - abbr @@ -135,6 +112,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')