Skip to content

Conversation

@FBumann
Copy link
Member

@FBumann FBumann commented Dec 10, 2025

Description

This PR collects and sanitizes the commit history for a clean merge into main for flixopt 5.0.0

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Code refactoring

Related Issues

Closes #(issue number)

Testing

  • I have tested my changes
  • Existing tests still pass

Checklist

  • My code follows the project style
  • I have updated documentation if needed
  • I have added tests for new functionality (if applicable)

Summary by CodeRabbit

  • New Features

    • FlowSystem-centric run & results: single-call optimize(), flow_system.solution, new accessors for statistics, topology and transforms, richer plotting returns.
  • Documentation

    • Major docs overhaul: migration guide, expanded user guide, many how‑to pages and new interactive notebooks and examples.
  • Breaking Changes

    • Legacy Optimization/Results APIs removed; OnOffParameters renamed to StatusParameters; "excess" renamed to "imbalance"; carriers introduced with automatic coloring and updated I/O (NetCDF).

✏️ Tip: You can customize this high-level summary in your review settings.

# Conflicts:
#	flixopt/core.py
#	flixopt/interface.py
#	test_deprecations.py
# Conflicts:
#	flixopt/core.py
#	flixopt/effects.py
#	test_deprecations.py
#	tests/test_invest_parameters_deprecation.py
# Conflicts:
#	flixopt/core.py
#	flixopt/effects.py
#	test_deprecations.py
#	tests/test_invest_parameters_deprecation.py
# Conflicts:
#	flixopt/core.py
#	flixopt/effects.py
#	test_deprecations.py
#	tests/test_invest_parameters_deprecation.py
* Perfect! Here's the **final complete renaming table with Option A**:

## Parameters Class (`OnOffParameters` → `StatusParameters`)

| Current Name | Recommended Name | Rationale |
|--------------|------------------|-----------|
| `OnOffParameters` | **`StatusParameters`** | Aligns with PyPSA, clearer semantics |
| `effects_per_switch_on` | **`effects_per_startup`** | Standard UC terminology |
| `effects_per_running_hour` | **`effects_per_active_hour`** | Clear, concise, matches "active" state |
| `on_hours_total_min` | **`active_hours_min`** | Total (not consecutive) active hours |
| `on_hours_total_max` | **`active_hours_max`** | Total (not consecutive) active hours |
| `consecutive_on_hours_min` | **`min_uptime`** | Standard UC term (consecutive) |
| `consecutive_on_hours_max` | **`max_uptime`** | Standard UC term (consecutive) |
| `consecutive_off_hours_min` | **`min_downtime`** | Standard UC term (consecutive) |
| `consecutive_off_hours_max` | **`max_downtime`** | Standard UC term (consecutive) |
| `switch_on_total_max` | **`startup_limit`** | Clearer intent, matches "startup" |
| `force_switch_on` | **`force_startup_tracking`** | More explicit about what is forced |

## Model Class (`OnOffModel` → `StatusModel`)

### Class Name
| Current Name | Recommended Name |
|--------------|------------------|
| `OnOffModel` | **`StatusModel`** |

### Constructor Parameters
| Current Name | Recommended Name | Rationale |
|--------------|------------------|-----------|
| `on_variable` | **`status`** | Aligns with PyPSA and literature |
| `previous_states` | **`previous_status`** | Consistency with status variable |

### Variables (short_name in add_variables/expression_tracking_variable)
| Current Name | Recommended Name | Type | Notes |
|--------------|------------------|------|-------|
| `self.on` | **`self.status`** | Input variable | Main binary state variable |
| `'off'` | **Remove variable** | Binary variable | Replace with expression `1 - status` |
| `'switch\|on'` | **`'startup'`** | Binary variable | Startup event indicator |
| `'switch\|off'` | **`'shutdown'`** | Binary variable | Shutdown event indicator |
| `'switch\|count'` | **`'startup_count'`** | Integer variable | Number of startups |
| `'on_hours_total'` | **`'active_hours'`** | Continuous variable | Total active duration |
| `'consecutive_on_hours'` | **`'uptime'`** | Continuous variable | Consecutive active hours |
| `'consecutive_off_hours'` | **`'downtime'`** | Continuous variable | Consecutive inactive hours |

### Properties
| Current Name | Recommended Name | Returns | Meaning |
|--------------|------------------|---------|---------|
| `on_hours_total` | **`active_hours`** | `linopy.Variable` | Total active hours |
| `off` | **Remove property** | — | Use `1 - status` expression |
| `switch_on` | **`startup`** | `linopy.Variable \| None` | Startup events |
| `switch_off` | **`shutdown`** | `linopy.Variable \| None` | Shutdown events |
| `switch_on_nr` | **`startup_count`** | `linopy.Variable \| None` | Number of startups |
| `consecutive_on_hours` | **`uptime`** | `linopy.Variable \| None` | Consecutive active hours |
| `consecutive_off_hours` | **`downtime`** | `linopy.Variable \| None` | Consecutive inactive hours |

### Internal Methods
| Current Name | Recommended Name |
|--------------|------------------|
| `_get_previous_on_duration()` | **`_get_previous_uptime()`** |
| `_get_previous_off_duration()` | **`_get_previous_downtime()`** |

### Internal Properties/Flags (in parameters)
| Current Name | Recommended Name |
|--------------|------------------|
| `use_off` | **Remove** (use expression instead) |
| `use_switch_on` | **`use_startup_tracking`** |
| `use_consecutive_on_hours` | **`use_uptime_tracking`** |
| `use_consecutive_off_hours` | **`use_downtime_tracking`** |

## Constraint Names (short_name in add_constraints)
| Current Name | Recommended Name |
|--------------|------------------|
| `'complementary'` | **Remove** (no off variable) |
| `'on_hours_total'` | **`'active_hours'`** |
| `'switch\|on'`, `'switch\|off'` | **`'startup'`, `'shutdown'`** |
| `'switch\|count'` | **`'startup_count'`** |
| `'consecutive_on_hours'` | **`'uptime'`** |
| `'consecutive_off_hours'` | **`'downtime'`** |

## Complete Terminology Summary (Option A)

**State:**
- `status` (binary): 1 = active, 0 = inactive

**Events:**
- `startup` (binary): transition from inactive to active
- `shutdown` (binary): transition from active to inactive

**Durations:**
- `active_hours` (continuous): **total** hours in active state across time horizon
- `uptime` (continuous): **consecutive** hours currently active (UC standard)
- `downtime` (continuous): **consecutive** hours currently inactive (UC standard)

**Parameter Bounds:**
- `active_hours_min/max`: limits on **total** active hours
- `min_uptime/max_uptime`: limits on **consecutive** active hours (UC standard)
- `min_downtime/max_downtime`: limits on **consecutive** inactive hours (UC standard)
- `startup_limit`: maximum number of startup events

**Effects:**
- `effects_per_startup`: costs/impacts per startup event
- `effects_per_active_hour`: costs/impacts per active hour

This aligns perfectly with PyPSA and the unit commitment literature! 🎯

* Refactor tests and examples

* Refactor tests and examples

* Update CHANGELOG.md

* Python Docstrings Updated:

  1. interface.py - Module docstring now references "Status decisions"
  2. components.py - Updated all docstrings:
    - status_parameters parameter descriptions
    - Example code updated with new parameter names (effects_per_startup, min_uptime, startup_limit)
    - Fixed incorrect "OnOff feature" docstring to "Investment feature"
    - Updated TODO comment to reference StatusParameters
  3. linear_converters.py - All docstrings updated:
    - Import statement updated to StatusParameters
    - All parameter descriptions updated
    - All example code updated with new terminology
  4. flow_system.py - Updated references from "consecutive_on_hours" to "uptime and downtime" and on_off_parameters to status_parameters
  5. modeling.py - Updated docstring from "switch-on/off variables" to "state transition constraints for binary switching variables"

  Documentation Markdown Files Updated:

  1. Flow.md - All references updated:
    - Links to StatusParameters
    - "on/off state" → "active/inactive state"
    - Parameter names updated
  2. StatusParameters.md (renamed from OnOffParameters.md) - Comprehensive updates:
    - Title changed to "StatusParameters"
    - All terminology updated: on/off → active/inactive
    - Mathematical notation updated: s^on/s^off → s^startup/s^shutdown
    - Duration variables: d^on/d^off → d^uptime/d^downtime
    - Parameter names updated in all examples
    - All Python code examples updated with new API
  3. Other modeling pattern docs - Updated all references to StatusParameters and active/inactive terminology
  4. mkdocs.yml - Navigation updated to reference StatusParameters.md

  All docstrings and documentation now consistently use the new Status terminology aligned with PyPSA and unit commitment standards!

* Update remaining mentions of old parameters

* ⏺ Perfect! I've addressed all the actionable review comments:

  Changes Made:

  1. Fixed error message in modeling.py

  - Corrected ModelingPrimitives.state_transition_bounds() → BoundingPatterns.state_transition_bounds() in error message (flixopt/modeling.py:591)

  2. Fixed Transmission type hint (flixopt/components.py:667)

  - Changed status_parameters: StatusParameters = None → status_parameters: StatusParameters | None = None

  3. Fixed absolute_losses=0 edge case (flixopt/components.py:768)

  - Added np.any(self.element.absolute_losses != 0) check in create_transmission_equation to match the initialization logic
  - This prevents AttributeError when absolute_losses is explicitly set to 0

  4. Updated test assertion messages (tests/test_component.py)

  - Changed "On does not work properly" → "Status does not work properly"

  5. Fixed effects_per_startup type (examples/02_Complex/complex_example.py)

  - Changed scalar effects_per_startup=0.01 → dict effects_per_startup={Costs.label: 0.01} in all 3 occurrences
  - Now consistent with the StatusParameters API which expects a dict mapping effect names to values

  6. Updated test_functional.py docstring

  - Removed reference to non-existent TestStatus class
  - Updated to accurately describe the status-related test functions

  7. Consistent unbounded upper bounds (flixopt/features.py:191)

  - Changed np.inf → None for unbounded active_hours_max
  - Now consistent with FlowModel's total_flow_hours pattern

  All changes maintain backward compatibility and align with the codebase's existing patterns. The documentation in index.md was already correct (BoundingPatterns is
  the right class for state_transition_bounds).

* Changes Made:

  1. CHANGELOG.md - Fixed parameter rename documentation (lines 89-90)
  - Changed incorrect status_parameters → status_parameters
  - To correct: on_off_parameters → status_parameters

  2. CHANGELOG.md - Removed duplicate logger warning (line 803 in v2.1.0)
  - Removed duplicate entry that was already documented in v2.0.1
  - Fixed v2.0.1 entry to say on_off_parameters (the name at that time)

  3. StatusParameters.md - Aligned flow bounds formulation (line 229)
  - Updated summary to include max(ε, rel_lower) like the main text
  - Now consistent: s(t) · P · max(ε, rel_lower) ≤ p(t) ≤ s(t) · P · rel_upper

  4. features.py - Narrowed previous_status type hint (line 155)
  - Changed from Numeric_TPS | None to xr.DataArray | None
  - Added import xarray as xr (line 12)
  - This accurately reflects that _get_previous_uptime() and _get_previous_downtime() use xarray APIs

All changes are verified to compile correctly and maintain consistency with the codebase patterns!

* Fixed Issues

  1. Constraint naming in tests (tests/test_component.py:126-127, 158, 168,
  338, 348):
    - Updated test expectations from 'TestComponent|on|lb' and
  'TestComponent|on|ub' to 'TestComponent|status|lb' and
  'TestComponent|status|ub' to match the actual constraint names
  2. Added 'off' property to StatusModel (flixopt/features.py:284-287):
    - Added a new property that returns 1 - self.status for backward
  compatibility with tests expecting an off attribute
  3. Fixed deprecated parameter name (tests/test_functional.py:435):
    - Changed force_switch_on=True to force_startup_tracking=True in
  StatusParameters
  4. Fixed property name (tests/test_functional.py:466):
    - Changed switch_off to shutdown to match the actual property name in
  StatusModel

* Delete mistakingly added files

* Delete mistakingly added files

* Final touches

* Final touches

* Replace off with inactive

* Rename low level parameetrs as well:
switch_on -> activate
switch_off -> deactivate

* Rename low level parameetrs as well:
switch_on -> activate
switch_off -> deactivate
state_variable -> state

* Rename low level parameetrs as well:
switch_on -> activate
switch_off -> deactivate
state_variable -> state

* Docstring Improvements Summary

  ✅ All Parameters Now Documented

  Each primitive now has complete parameter documentation with:
  - Clear description of what each parameter does
  - Type expectations
  - Default values where applicable

  ✅ Focused on Math & Parameters

  Removed:
  - Excessive examples at low level
  - Use case lists that belong at higher levels

  Enhanced:
  - Mathematical formulations (using proper · symbol for multiplication)
  - Clear behavior descriptions
  - Precise return value documentation

  Updated Functions:

  ModelingPrimitives:

  1. expression_tracking_variable
    - All 6 parameters documented
    - Clear math formulation
  2. consecutive_duration_tracking
    - All 9 parameters documented
    - Explained Big-M value
    - Clear what constraints are returned
  3. mutual_exclusivity_constraint
    - All 4 parameters documented
    - Simplified, focused on math

  BoundingPatterns:

  4. basic_bounds
    - All 4 parameters documented
    - Concise formulation
  5. bounds_with_state
    - All 5 parameters documented
    - Explained epsilon (ε) usage
  6. scaled_bounds
    - All 5 parameters documented
    - Clear scaling relationship
  7. scaled_bounds_with_state
    - All 7 parameters documented
    - Explained Big-M formulation
  8. state_transition_bounds
    - All 7 parameters documented
    - Removed verbose examples, kept math focus
  9. continuous_transition_bounds
    - All 8 parameters documented
    - Clear Big-M constraint explanation

  Result

  ✅ All parameters documented
  ✅ Math-focused docstrings
  ✅ Consistent format across all primitives
  ✅ Tests still passing

  The modeling primitives now have professional, complete documentation!

* Update docs

* Add missing type hints

* Fix bullet points

* Fix bullet points

* Re-apply changes from main

* Bugfix: Usage of old on_off_parameters

* Update CHANGELOG.md

* Update CHANGELOG.md

* Update CHANGELOG.md

* Fix typos

* Improve flagging of wether to create inactive varaible

* Improve default upper bound of active_hours

* Bugfix self._model.hours_per_step.sum('time').item() with scenarios/periods

* Fix test

* FIx names

* pdate the test assertions to expect upper=total_hours instead of upper=inf when active_hours_max is not
  specified

* Empty

* Trigger CI

* Fix test

* Triggger CI

* Summary of Fixes

  1. Return type annotation for consecutive_duration_tracking (flixopt/modeling.py:255):
    - Changed from tuple[linopy.Variable, tuple[linopy.Constraint, linopy.Constraint, linopy.Constraint]]
    - To tuple[dict[str, linopy.Variable], dict[str, linopy.Constraint]] to match the actual return value
  2. Clarified inactive property docstring (flixopt/features.py:284-291):
    - Replaced the confusing "deprecated" note with a clear explanation that:
        - The variable is only created when downtime tracking is enabled
      - Users should prefer 1 - status expression for general use
  3. Fixed _get_previous_uptime docstring (flixopt/features.py:318-322):
    - Clarified that it returns 0 when no previous status is provided (assumes previously inactive)
  4. Fixed _get_previous_downtime docstring (flixopt/features.py:329-333):
    - Clarified that it returns one timestep duration when no previous status is provided (assumes previously inactive)
  5. No action needed for effects_per_startup without use_startup_tracking:
    - Verified that use_startup_tracking already returns True when effects_per_startup has values (line 1258 in interface.py), so this is already
  handled correctly
  6. Test fixes (tests/test_flow.py):
    - Updated three test assertions to use model.hours_per_step.sum('time') as the expected upper bound for active_hours when active_hours_max is
  not specified

* Trigger CI
* | File                    | Changes                                                                          |
  |-------------------------|----------------------------------------------------------------------------------|
  | flixopt/elements.py     | Renamed attributes excess_input → virtual_supply, excess_output → virtual_demand |
  | flixopt/optimization.py | Updated attribute access and result keys                                         |
  | tests/test_bus.py       | Updated variable name strings in assertions                                      |
  | docs/.../Bus.md         | Updated description of φ symbols                                                 |

  The variable names in the optimization model are now:
  - {BusName}|virtual_supply (was excess_input)
  - {BusName}|virtual_demand (was excess_output)

* Renamed excess_penalty_per_flow_hour → imbalance_penalty_per_flow_hour

* rename excess_penalty to imbalance_penalty

* Change default to None

* Added self._validate_kwargs(kwargs) to catch typos and unexpected arguments

* Renamed with_excess → allows_imbalance

* Fix docstring

* 1. docs/user-guide/mathematical-notation/elements/Bus.md - Fixed three typos:
    - "a imbalance_penalty_per_flow_hour" → "an imbalance_penalty_per_flow_hour"
    - "usefull" → "useful"
    - "ifeasiblity" → "infeasibility"
  2. tests/test_bus.py - Updated comments to use the new imbalance terminology instead of the old "excess" terminology
  3. flixopt/elements.py (BusModel) - Improved code clarity:
    - Changed eq_bus_balance.lhs -= -self.virtual_supply + self.virtual_demand to the more readable eq_bus_balance.lhs += self.virtual_supply -
  self.virtual_demand
    - Added a comment explaining the equation: # Σ(inflows) + virtual_supply = Σ(outflows) + virtual_demand
    - Combined the two separate add_share_to_effects calls into a single call with the combined expression (self.virtual_supply +
  self.virtual_demand) * imbalance_penalty

  All 12 bus tests pass with these changes.
* Flow.md - Fully restructured with:
    - Tab-based organization (Core / Advanced / Patterns / Examples)
    - Collapsible definition blocks
    - Links to both Flow and FlowModel classes
    - Updated docstrings with absolute URLs
  2. Bus.md - Restructured with tab organization and dual class linking
  3. Storage.md - Restructured with comprehensive examples and dual class linking
  4. LinearConverter.md - Restructured with detailed examples including specialized converters
  5. InvestParameters.md - Restructured with clear separation of core vs. advanced features

* Improve organization

* Improve organization

* Improve organization by using tables

* Improve organization by using tables and use eqref

* Add symbol to parameter mapping

* Changed to inline math

* Use propre constraints with numbering

* Move parameters into separate tab

* Reorder parameters

* : Use the columns "symbol" and "python name" in the variables tab

* Update Bus, Storage, and LinearConverter.md

* Update InvestParameters and OnOffParameters.md

* Update Piecewise.md

* Compact effects-penalty-objective.md

* Allow toc level 3

* Add toc to homepage

* Replace ustom css with mkdocs material stuff

* Revert some

* Revert some

* Remove layout css rule

* Show toc on homepage

* FIx broken link

* Add edit uri

* Hide bottom part

* Hide bottom part

* Restructure docs

* Show navigation in home

* Add Changelog fromating

* THighten CHANGELOG.md

* Simplify users.md

* Simplify models.md

* Shorten citing.md

* Shorten support.md

* Update CHANGELOG.md

* Simplify installation.md

* Simplify quick-start.md

* Updated FullCalculation → Optimization in documentation
 Fixed mkdocs.yml navigation
Fixed broken link in support.md

* Fixed solver calls in docs

* Move files and restructure

* Delete old docs script

* Improve docs structure

* Imrpove Optimization Modes

* Imrpove Optimization Modes

* Rewrite the core concepts to be user facing

* Reorganize Mathematical Notation

* 1. Minimal variable names — Changed from words to symbols:
    - penalty_rate → $c_\phi$
    - relative_min → $p_{rel}^{min}$
    - flow_hours → $h_f$
    - loss → $\dot{c}_{loss}$
    - etc.
  2. Tabs for conditional constraints — Used === "Tab Name" syntax for:
    - Bus.md: "Without Excess (Strict)" vs "With Excess (Soft)"
    - Flow.md: "Standard (No On/Off)" vs "With On/Off" vs "Fixed Profile"
    - Storage.md: "Fixed Initial" vs "Cyclic" vs "Final Bounds"
    - LinearConverter.md: "Single Input/Output" vs "Multiple Outputs" vs "COP > 1" vs "Time-Varying"
    - Effects.md: "Temporal (Operational)" vs "Periodic (Investment)" vs "Total"
  3. Corrected Flow constraints — Clarified that:
    - Without on/off parameters: flow cannot be zero if relative_minimum > 0
    - With on/off parameters: flow can be zero (when off) OR within bounds (when on)
  4. Cleaner structure — Removed redundant content, focused on essential formulas and examples

* The Flow.md now has four tabs for capacity bounds:

  1. Fixed Size — Standard bounds without on/off
  2. Fixed Size + On/Off — Can be zero when off
  3. Variable Size — Investment decision on capacity
  4. Variable Size + On/Off — Both investment and on/off, with big-M linearization for the bilinear term $s(t) \cdot P$

* InvestParameters.md:
  - Story-driven intro with real-world examples
  - Core concept: "Size as a Variable"
  - Tabs for: Binary (Fixed Size) | Continuous (Size Range) | Mandatory
  - Tabs for effects: Fixed | Specific | Retirement | Piecewise
  - Minimal variable names: $P$, $s_{inv}$, $c_{fix}$, $c_{spec}$, etc.
  - Cost annualization formula

  OnOffParameters.md:
  - Story-driven intro with real-world examples
  - Core concept: "Binary State" with flow bound modification
  - Tabs for state transitions: Switch Detection | Startup Costs | Running Costs
  - Tabs for duration constraints: Min Run Time | Min Off Time | Max Run Time | Total Hours | Max Startups
  - Minimal variable names: $s(t)$, $s^{on}(t)$, $s^{off}(t)$, $T_{on}^{min}$, etc.

  Piecewise.md:
  - Story-driven intro with ASCII diagram
  - Core concept: Linear segments with weighted combinations
  - Tabs for constraints: Single Piece Active | With Zero Point
  - Tabs for piece patterns: Continuous (Touching) | Gap (Forbidden Region) | Zero Point
  - Minimal variable names: $\beta_k$, $\lambda_0$, $\lambda_1$, etc.
  - Practical examples for heat pumps, boilers, and investment

* Make OnOffParameters better

* Piecewise.md:
  - Replaced the useless ASCII diagram with a more informative one showing:
    - Actual axis labels (input/output)
    - Numeric values on axes
    - Two pieces with their connection point labeled
    - Clear visual of how pieces connect at (50, 45)
    - Shows the start/end points notation

* Add plotly chart

* Add custom javascript

* Remove charts plugin

* Add missing docs file

* Fix quick start

* Delete model.md

* Update citation

* Update license.md

* Simplify faq, support and troubleshooting.md

* Remove old workflow

* 1. Renamed OnOffParameters.md → StatusParameters.md
  2. Updated all terminology:
    - on_off_parameters → status_parameters
    - OnOffParameters → StatusParameters
    - effects_per_switch_on → effects_per_startup
    - effects_per_running_hour → effects_per_active_hour
    - consecutive_on_hours_min → min_uptime
    - consecutive_on_hours_max → max_uptime
    - consecutive_off_hours_min → min_downtime
    - on_hours_min/max → active_hours_min/max
    - switch_on_max → startup_limit
    - switch_on/switch_off → startup/shutdown
    - "on/off" language → "active/inactive" language
  3. Updated references in Flow.md, LinearConverter.md, and effects-penalty-objective.md

* Remove Modeling patterns from docs

* Simplify docs

* Improve LinearConverter.md

* Improve Flow.md

* Improve effects-penalty-objective.md

* Improve InvestParameters.md

* Add durtaion constraints

* Update Piecewise stuff

* Update Piecewise stuff

* Update Piecewise stuff

* Combine effects and dimensions into one tab

* The dimension examples now correctly show how to assign them to FlowSystem using pd.Index:

* Update effects-and-dimensions.md

* Update effects-and-dimensions.md

* updated all reference tables across all Mathematical Notation pages to be consistent

* updated all reference tables across all Mathematical Notation pages to be consistent

* updated all reference tables across all Mathematical Notation pages to be consistent
…on directly on the flow_system with accessors for aggregated views and plots
…est_transmission_balanced and test_transmission_unbalanced. Removed the self-comparing assertions

  and consolidated them with the meaningful loss calculation tests.
  2. flixopt/optimize_accessor.py - Removed the unused logging import and logger variable.
  3. flixopt/config.py - Updated the docstring for compute_infeasibilities from "Whether to save model documentation on infeasibility" to "Whether to compute
  infeasibility analysis when the model is infeasible."
  4. flixopt/transform_accessor.py - Changed if not dt_min == dt_max: to the more idiomatic if dt_min != dt_max:.
  5. pyproject.toml - Added "ignore:SegmentedResults is deprecated:DeprecationWarning:flixopt" to the pytest filter warnings to handle the SegmentedResults deprecation
  warning in backward compatibility tests.

Fix depprecation warings in tests
* Add planning doc

* Finalize planning

* Add plotting acessor

* Add plotting acessor

* Add tests

* Improve

* Improve

* Update docs

* Updated the plotting API so that .data always returns xarray DataArray or Dataset instead of pandas DataFrame.

* All .data now returns xr.Dataset consistently.

* Fixed Inconsistencies and Unused Parameters

* New Plot Accessors

  results.plot.variable(pattern)

  Plots the same variable type across multiple elements for easy comparison.

  # All binary operation states across all components
  results.plot.variable('on')

  # All flow_rates, filtered to Boiler-related elements
  results.plot.variable('flow_rate', include='Boiler')

  # All storage charge states
  results.plot.variable('charge_state')

  # With aggregation
  results.plot.variable('flow_rate', aggregate='sum')

  Key features:
  - Searches all elements for variables matching the pattern
  - Filter with include/exclude on element names
  - Supports aggregation, faceting, and animation
  - Returns Dataset with element names as variables

  results.plot.duration_curve(variables)

  Plots load duration curves (sorted time series) showing utilization patterns.

  # Single variable
  results.plot.duration_curve('Boiler(Q_th)|flow_rate')

  # Multiple variables
  results.plot.duration_curve(['CHP|on', 'Boiler|on'])

  # Normalized x-axis (0-100%)
  results.plot.duration_curve('demand', normalize=True)

  Key features:
  - Sorts values from highest to lowest
  - Shows how often each power level is reached
  - normalize=True shows percentage of time on x-axis
  - Returns Dataset with duration_hours or duration_pct dimension

* Fix duration curve

* Fix duration curve

* Fix duration curve

* Fix duration curve

* xr.apply_ufunc to sort along the time axis while preserving all other dimensions automatically

* ⏺ The example runs successfully. Now let me summarize the fixes:

  Summary of Fixes

  I addressed the actionable code review comments from CodeRabbitAI:

  1. Documentation Issue - reshape parameter name ✓ (No fix needed)

  The CodeRabbitAI comment was incorrect. The public API parameter in PlotAccessor.heatmap() is correctly named reshape (line 335). The reshape_time parameter exists in the lower-level heatmap_with_plotly function, but the documentation correctly shows the public API parameter.

  2. Fixed simple_example.py (lines 129-130)

  Problem: The example called balance() and duration_curve() without required arguments, which would cause TypeError at runtime.

  Fix: Added the required arguments:
  - optimization.results.plot.balance('Fernwärme') - specifying the bus to plot
  - optimization.results.plot.duration_curve('Boiler(Q_th)|flow_rate') - specifying the variable to plot

  3. Fixed variable collision in plot_accessors.py (lines 985-988)

  Problem: When building the Dataset in the variable() method, using element names as keys could cause collisions if multiple variables from the same element matched the pattern (e.g., 'Boiler|flow_rate' and 'Boiler|flow_rate_max' would both map to 'Boiler', with the latter silently overwriting the former).

  Fix: Changed to use the full variable names as keys instead of just element names:
  ds = xr.Dataset({var_name: self._results.solution[var_name] for var_name in filtered_vars})

  All tests pass (40 passed, 1 skipped) and the example runs successfully.

* make variable names public in results

* Fix sankey

* Fix effects()

* Fix effects

* Remove bargaps

* made faceting consistent across all plot methods:

  | Method           | facet_col                                 | facet_row                   |
  |------------------|-------------------------------------------|-----------------------------|
  | balance()        | 'scenario'                                | 'period'                    |
  | heatmap()        | 'scenario'                                | 'period'                    |
  | storage()        | 'scenario'                                | 'period'                    |
  | flows()          | 'scenario'                                | 'period'                    |
  | effects()        | 'scenario'                                | 'period'                    |
  | variable()       | 'scenario'                                | 'period'                    |
  | duration_curve() | 'scenario'                                | 'period' (already had this) |
  | compare()        | N/A (uses its own mode='overlay'/'facet') |                             |
  | sankey()         | N/A (aggregates to single diagram)        |                             |

  Removed animate_by parameter from all methods

* Update storage method

* Remove mode parameter for simpli
  | Method           | Default mode                                      |
  |------------------|---------------------------------------------------|
  | balance()        | stacked_bar                                       |
  | storage()        | stacked_bar (flows) + line (charge state overlay) |
  | flows()          | line                                              |
  | variable()       | line                                              |
  | duration_curve() | line                                              |
  | effects()        | bar                                               |

* Make plotting_accessors.py more self contained

* Use faster to_long()

* Add 0-dim case

* sankey diagram now properly handles scenarios and periods:

  Changes made:
  1. Period aggregation with weights: Uses period_weights from flow_system to properly weight periods by their duration
  2. Scenario aggregation with weights: Uses normalized scenario_weights to compute a weighted average across scenarios
  3. Selection support: Users can filter specific scenarios/periods via select parameter before aggregation

  Weighting logic:
  - Periods (for aggregate='sum'): (da * period_weights).sum(dim='period') - this gives the total energy across all periods weighted by their duration
  - Periods (for aggregate='mean'): (da * period_weights).sum(dim='period') / period_weights.sum() - weighted mean
  - Scenarios: Always uses normalized weights (sum to 1) for weighted averaging, since scenarios represent probability-weighted alternatives

* Add colors to sankey

* Add sizes

* Add size filtering

* Include storage sizes

* Remove storage sizes

* Add charge state and status accessor

* Summary of Changes

  1. Added new methods to PlotAccessor (plot_accessors.py)

  charge_states() (line 658):
  - Returns a Dataset with each storage's charge state as a variable
  - Supports filtering with include/exclude parameters
  - Default plot: line chart

  on_states() (line 753):
  - Returns a Dataset with each component's |status variable
  - Supports filtering with include/exclude parameters
  - Default plot: heatmap (good for binary data visualization)

  2. Added data building helper functions (plot_accessors.py)

  build_flow_rates(results) (line 315):
  - Builds a DataArray containing flow rates for all flows
  - Used internally by PlotAccessor methods

  build_flow_hours(results) (line 333):
  - Builds a DataArray containing flow hours for all flows

  build_sizes(results) (line 347):
  - Builds a DataArray containing sizes for all flows

  _filter_dataarray_by_coord(da, **kwargs) (line 284):
  - Helper for filtering DataArrays by coordinate values

  3. Deprecated old Results methods (results.py)

  The following methods now emit DeprecationWarning:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  4. Updated PlotAccessor methods to use new helpers

  - flows() now uses build_flow_rates() / build_flow_hours() directly
  - sizes() now uses build_sizes() directly
  - sankey() now uses build_flow_hours() directly

  This ensures the deprecation warnings only fire when users directly call the old methods, not when using the plot accessor

* 1. New methods added to PlotAccessor

  - charge_states(): Returns Dataset with all storage charge states
  - on_states(): Returns Dataset with all component status variables (heatmap display)

  2. Data building helper functions (plot_accessors.py)

  - build_flow_rates(results): Builds DataArray of flow rates
  - build_flow_hours(results): Builds DataArray of flow hours
  - build_sizes(results): Builds DataArray of sizes
  - _filter_dataarray_by_coord(da, **kwargs): Filter helper
  - _assign_flow_coords(da, results): Add flow coordinates

  3. Caching in PlotAccessor

  Added lazy-cached properties for expensive computations:
  - _all_flow_rates - cached DataArray of all flow rates
  - _all_flow_hours - cached DataArray of all flow hours
  - _all_sizes - cached DataArray of all sizes
  - _all_charge_states - cached Dataset of all storage charge states
  - _all_status_vars - cached Dataset of all status variables

  4. Deprecated methods in Results class

  Added deprecation warnings to:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  5. Updated PlotAccessor methods to use cached properties

  - flows() uses _all_flow_rates / _all_flow_hours
  - sankey() uses _all_flow_hours
  - sizes() uses _all_sizes
  - charge_states() uses _all_charge_states
  - on_states() uses _all_status_vars

* Move deprectated functionality into results.py instead of porting to the new module

* Revert to simply deprectae old methods without forwarding to new code

* Remove planning file

* Update plotting methods for new datasets

* 1. Renamed data properties in PlotAccessor to use all_ prefix:
    - all_flow_rates - All flow rates as Dataset
    - all_flow_hours - All flow hours as Dataset
    - all_sizes - All flow sizes as Dataset
    - all_charge_states - All storage charge states as Dataset
    - all_on_states - All component on/off status as Dataset
  2. Updated internal references - All usages in flows(), sankey(), sizes(), charge_states(), and on_states() methods now use the new names.
  3. Updated deprecation messages in results.py to point to the new API:
    - results.flow_rates() → results.plot.all_flow_rates
    - results.flow_hours() → results.plot.all_flow_hours
    - results.sizes() → results.plot.all_sizes
  4. Updated docstring examples in PlotAccessor to use the new all_* names.

* Update deprecations messages

* Update deprecations messages

* Thsi seems much better.

* Updaet docstrings and variable name generation in plotting acessor

* Change __ to _ in private dataset caching

* Revert breaking io changes

* New solution storing interface

* Add new focused statistics and plot accessors

* Renamed all properties:
  - all_flow_rates → flow_rates
  - all_flow_hours → flow_hours
  - all_sizes → sizes
  - all_charge_states → charge_states

* Cache Statistics

* Invalidate caches

* Add effect related statistics

* Simplify statistics accessor to rely on flow_system directly instead of solution attrs

* Fix heatma fallback for 1D Data

* Add topology accessor

* All deprecation warnings in the codebase now consistently use the format will be removed in v{DEPRECATION_REMOVAL_VERSION}.

* Update tests

* created comprehensive documentation for all FlowSystem accessors

* Update results documentation

* Update results documentation

* Update effect statistics

* Update effect statistics

* Update effect statistics

* Add mkdocs plotly plugin

* Add section about custom constraints

* documentation updates:

  docs/user-guide/results/index.md:
  - Updated table to replace effects_per_component with temporal_effects, periodic_effects, total_effects, and effect_share_factors
  - Fixed flow_hours['Boiler(Q_th)|flow_rate'] → flow_hours['Boiler(Q_th)']
  - Fixed sizes['Boiler(Q_th)|size'] → sizes['Boiler(Q_th)']
  - Replaced effects_per_component example with new effect properties and groupby examples
  - Updated complete example to use total_effects

  docs/user-guide/results-plotting.md:
  - Fixed colors example from 'Boiler(Q_th)|flow_rate' → 'Boiler(Q_th)'
  - Fixed duration_curve examples to use clean labels

  docs/user-guide/migration-guide-v6.md:
  - Added new "Statistics Accessor" section explaining the clean labels and new effect properties

* implemented the effects() method in StatisticsPlotAccessor at flixopt/statistics_accessor.py:1132-1258.

  Summary of what was done:

  1. Implemented effects() method in StatisticsPlotAccessor class that was missing but documented
    - Takes aspect parameter: 'total', 'temporal', or 'periodic'
    - Takes effect parameter to filter to a specific effect (e.g., 'costs', 'CO2')
    - Takes by parameter: 'component' or 'time' for grouping
    - Supports all standard plotting parameters: select, colors, facet_col, facet_row, show
    - Returns PlotResult with both data and figure
  2. Verified the implementation works with all parameter combinations:
    - Default call: flow_system.statistics.plot.effects()
    - Specific effect: flow_system.statistics.plot.effects(effect='costs')
    - Temporal aspect: flow_system.statistics.plot.effects(aspect='temporal')
    - Temporal by time: flow_system.statistics.plot.effects(aspect='temporal', by='time')
    - Periodic aspect: flow_system.statistics.plot.effects(aspect='periodic')

* Remove intermediate plot accessor

* 1. pyproject.toml: Removed duplicate mkdocs-plotly-plugin>=0.1.3 entry (kept the exact pin ==0.1.3)
  2. flixopt/plotting.py: Fixed dimension name consistency by using squeezed_data.name instead of data.name in the fallback heatmap logic
  3. flixopt/statistics_accessor.py:
    - Fixed _dataset_to_long_df() to only use coordinates that are actually present as columns after reset_index()
    - Fixed the nested loop inefficiency with include_flows by pre-computing the flows list outside the loop
    - (Previously fixed) Fixed asymmetric NaN handling in validation check

* _create_effects_dataset method in statistics_accessor.py was simplified:

  1. Detect contributors from solution data variables instead of assuming they're only flows
    - Uses regex pattern to find {contributor}->{effect}(temporal|periodic) variables
    - Contributors can be flows OR components (e.g., components with effects_per_active_hour)
  2. Exclude effect-to-effect shares
    - Filters out contributors whose base name matches any effect label
    - For example, costs(temporal) is excluded because costs is an effect label
    - These intermediate shares are already included in the computation
  3. Removed the unused _compute_effect_total method
    - The new simplified implementation directly looks up shares from the solution
    - Uses effect_share_factors for conversion between effects
  4. Key insight from user: The solution already contains properly computed share values including all effect-to-effect conversions. The computation uses conversion
  factors because derived effects (like Effect1 which shares 0.5 from costs) don't have direct {flow}->Effect1(temporal) variables - only the source effect shares exist
   ({flow}->costs(temporal)).

* Update docs

* Improve to_netcdf method

* Update examples

* Fix IIS computaion flag

* Fix examples

* Fix faceting in heatmap and use period as facet col everywhere

* Inline plotting methods to deprecate plotting.py (#508)

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* statistics_accessor.py - Heatmap colors type safety (lines 121-148, 820-853)

  - Changed _heatmap_figure() parameter type from colors: ColorType = None to colors: str | list[str] | None = None
  - Changed heatmap() method parameter type similarly
  - Updated docstrings to clarify that dicts are not supported for heatmaps since px.imshow's color_continuous_scale only accepts colorscale names or lists

  2. statistics_accessor.py - Use configured qualitative colorscale (lines 284, 315)

  - Updated _create_stacked_bar() to use CONFIG.Plotting.default_qualitative_colorscale as the default colorscale
  - Updated _create_line() similarly
  - This ensures user-configured CONFIG.Plotting.default_qualitative_colorscale affects all bar/line plots consistently

  3. topology_accessor.py - Path type alignment (lines 219-222)

  - Added normalization of path=False to None before calling _plot_network()
  - This resolves the type mismatch where TopologyAccessor.plot() accepts bool | str | Path but _plot_network() only accepts str | Path | None

* fix usage if index name in aggregation plot
* Add planning doc

* Finalize planning

* Add plotting acessor

* Add plotting acessor

* Add tests

* Improve

* Improve

* Update docs

* Updated the plotting API so that .data always returns xarray DataArray or Dataset instead of pandas DataFrame.

* All .data now returns xr.Dataset consistently.

* Fixed Inconsistencies and Unused Parameters

* New Plot Accessors

  results.plot.variable(pattern)

  Plots the same variable type across multiple elements for easy comparison.

  # All binary operation states across all components
  results.plot.variable('on')

  # All flow_rates, filtered to Boiler-related elements
  results.plot.variable('flow_rate', include='Boiler')

  # All storage charge states
  results.plot.variable('charge_state')

  # With aggregation
  results.plot.variable('flow_rate', aggregate='sum')

  Key features:
  - Searches all elements for variables matching the pattern
  - Filter with include/exclude on element names
  - Supports aggregation, faceting, and animation
  - Returns Dataset with element names as variables

  results.plot.duration_curve(variables)

  Plots load duration curves (sorted time series) showing utilization patterns.

  # Single variable
  results.plot.duration_curve('Boiler(Q_th)|flow_rate')

  # Multiple variables
  results.plot.duration_curve(['CHP|on', 'Boiler|on'])

  # Normalized x-axis (0-100%)
  results.plot.duration_curve('demand', normalize=True)

  Key features:
  - Sorts values from highest to lowest
  - Shows how often each power level is reached
  - normalize=True shows percentage of time on x-axis
  - Returns Dataset with duration_hours or duration_pct dimension

* Fix duration curve

* Fix duration curve

* Fix duration curve

* Fix duration curve

* xr.apply_ufunc to sort along the time axis while preserving all other dimensions automatically

* ⏺ The example runs successfully. Now let me summarize the fixes:

  Summary of Fixes

  I addressed the actionable code review comments from CodeRabbitAI:

  1. Documentation Issue - reshape parameter name ✓ (No fix needed)

  The CodeRabbitAI comment was incorrect. The public API parameter in PlotAccessor.heatmap() is correctly named reshape (line 335). The reshape_time parameter exists in the lower-level heatmap_with_plotly function, but the documentation correctly shows the public API parameter.

  2. Fixed simple_example.py (lines 129-130)

  Problem: The example called balance() and duration_curve() without required arguments, which would cause TypeError at runtime.

  Fix: Added the required arguments:
  - optimization.results.plot.balance('Fernwärme') - specifying the bus to plot
  - optimization.results.plot.duration_curve('Boiler(Q_th)|flow_rate') - specifying the variable to plot

  3. Fixed variable collision in plot_accessors.py (lines 985-988)

  Problem: When building the Dataset in the variable() method, using element names as keys could cause collisions if multiple variables from the same element matched the pattern (e.g., 'Boiler|flow_rate' and 'Boiler|flow_rate_max' would both map to 'Boiler', with the latter silently overwriting the former).

  Fix: Changed to use the full variable names as keys instead of just element names:
  ds = xr.Dataset({var_name: self._results.solution[var_name] for var_name in filtered_vars})

  All tests pass (40 passed, 1 skipped) and the example runs successfully.

* make variable names public in results

* Fix sankey

* Fix effects()

* Fix effects

* Remove bargaps

* made faceting consistent across all plot methods:

  | Method           | facet_col                                 | facet_row                   |
  |------------------|-------------------------------------------|-----------------------------|
  | balance()        | 'scenario'                                | 'period'                    |
  | heatmap()        | 'scenario'                                | 'period'                    |
  | storage()        | 'scenario'                                | 'period'                    |
  | flows()          | 'scenario'                                | 'period'                    |
  | effects()        | 'scenario'                                | 'period'                    |
  | variable()       | 'scenario'                                | 'period'                    |
  | duration_curve() | 'scenario'                                | 'period' (already had this) |
  | compare()        | N/A (uses its own mode='overlay'/'facet') |                             |
  | sankey()         | N/A (aggregates to single diagram)        |                             |

  Removed animate_by parameter from all methods

* Update storage method

* Remove mode parameter for simpli
  | Method           | Default mode                                      |
  |------------------|---------------------------------------------------|
  | balance()        | stacked_bar                                       |
  | storage()        | stacked_bar (flows) + line (charge state overlay) |
  | flows()          | line                                              |
  | variable()       | line                                              |
  | duration_curve() | line                                              |
  | effects()        | bar                                               |

* Make plotting_accessors.py more self contained

* Use faster to_long()

* Add 0-dim case

* sankey diagram now properly handles scenarios and periods:

  Changes made:
  1. Period aggregation with weights: Uses period_weights from flow_system to properly weight periods by their duration
  2. Scenario aggregation with weights: Uses normalized scenario_weights to compute a weighted average across scenarios
  3. Selection support: Users can filter specific scenarios/periods via select parameter before aggregation

  Weighting logic:
  - Periods (for aggregate='sum'): (da * period_weights).sum(dim='period') - this gives the total energy across all periods weighted by their duration
  - Periods (for aggregate='mean'): (da * period_weights).sum(dim='period') / period_weights.sum() - weighted mean
  - Scenarios: Always uses normalized weights (sum to 1) for weighted averaging, since scenarios represent probability-weighted alternatives

* Add colors to sankey

* Add sizes

* Add size filtering

* Include storage sizes

* Remove storage sizes

* Add charge state and status accessor

* Summary of Changes

  1. Added new methods to PlotAccessor (plot_accessors.py)

  charge_states() (line 658):
  - Returns a Dataset with each storage's charge state as a variable
  - Supports filtering with include/exclude parameters
  - Default plot: line chart

  on_states() (line 753):
  - Returns a Dataset with each component's |status variable
  - Supports filtering with include/exclude parameters
  - Default plot: heatmap (good for binary data visualization)

  2. Added data building helper functions (plot_accessors.py)

  build_flow_rates(results) (line 315):
  - Builds a DataArray containing flow rates for all flows
  - Used internally by PlotAccessor methods

  build_flow_hours(results) (line 333):
  - Builds a DataArray containing flow hours for all flows

  build_sizes(results) (line 347):
  - Builds a DataArray containing sizes for all flows

  _filter_dataarray_by_coord(da, **kwargs) (line 284):
  - Helper for filtering DataArrays by coordinate values

  3. Deprecated old Results methods (results.py)

  The following methods now emit DeprecationWarning:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  4. Updated PlotAccessor methods to use new helpers

  - flows() now uses build_flow_rates() / build_flow_hours() directly
  - sizes() now uses build_sizes() directly
  - sankey() now uses build_flow_hours() directly

  This ensures the deprecation warnings only fire when users directly call the old methods, not when using the plot accessor

* 1. New methods added to PlotAccessor

  - charge_states(): Returns Dataset with all storage charge states
  - on_states(): Returns Dataset with all component status variables (heatmap display)

  2. Data building helper functions (plot_accessors.py)

  - build_flow_rates(results): Builds DataArray of flow rates
  - build_flow_hours(results): Builds DataArray of flow hours
  - build_sizes(results): Builds DataArray of sizes
  - _filter_dataarray_by_coord(da, **kwargs): Filter helper
  - _assign_flow_coords(da, results): Add flow coordinates

  3. Caching in PlotAccessor

  Added lazy-cached properties for expensive computations:
  - _all_flow_rates - cached DataArray of all flow rates
  - _all_flow_hours - cached DataArray of all flow hours
  - _all_sizes - cached DataArray of all sizes
  - _all_charge_states - cached Dataset of all storage charge states
  - _all_status_vars - cached Dataset of all status variables

  4. Deprecated methods in Results class

  Added deprecation warnings to:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  5. Updated PlotAccessor methods to use cached properties

  - flows() uses _all_flow_rates / _all_flow_hours
  - sankey() uses _all_flow_hours
  - sizes() uses _all_sizes
  - charge_states() uses _all_charge_states
  - on_states() uses _all_status_vars

* Move deprectated functionality into results.py instead of porting to the new module

* Revert to simply deprectae old methods without forwarding to new code

* Remove planning file

* Update plotting methods for new datasets

* 1. Renamed data properties in PlotAccessor to use all_ prefix:
    - all_flow_rates - All flow rates as Dataset
    - all_flow_hours - All flow hours as Dataset
    - all_sizes - All flow sizes as Dataset
    - all_charge_states - All storage charge states as Dataset
    - all_on_states - All component on/off status as Dataset
  2. Updated internal references - All usages in flows(), sankey(), sizes(), charge_states(), and on_states() methods now use the new names.
  3. Updated deprecation messages in results.py to point to the new API:
    - results.flow_rates() → results.plot.all_flow_rates
    - results.flow_hours() → results.plot.all_flow_hours
    - results.sizes() → results.plot.all_sizes
  4. Updated docstring examples in PlotAccessor to use the new all_* names.

* Update deprecations messages

* Update deprecations messages

* Thsi seems much better.

* Updaet docstrings and variable name generation in plotting acessor

* Change __ to _ in private dataset caching

* Revert breaking io changes

* New solution storing interface

* Add new focused statistics and plot accessors

* Renamed all properties:
  - all_flow_rates → flow_rates
  - all_flow_hours → flow_hours
  - all_sizes → sizes
  - all_charge_states → charge_states

* Cache Statistics

* Invalidate caches

* Add effect related statistics

* Simplify statistics accessor to rely on flow_system directly instead of solution attrs

* Fix heatma fallback for 1D Data

* Add topology accessor

* All deprecation warnings in the codebase now consistently use the format will be removed in v{DEPRECATION_REMOVAL_VERSION}.

* Update tests

* created comprehensive documentation for all FlowSystem accessors

* Update results documentation

* Update results documentation

* Update effect statistics

* Update effect statistics

* Update effect statistics

* Add mkdocs plotly plugin

* Add section about custom constraints

* documentation updates:

  docs/user-guide/results/index.md:
  - Updated table to replace effects_per_component with temporal_effects, periodic_effects, total_effects, and effect_share_factors
  - Fixed flow_hours['Boiler(Q_th)|flow_rate'] → flow_hours['Boiler(Q_th)']
  - Fixed sizes['Boiler(Q_th)|size'] → sizes['Boiler(Q_th)']
  - Replaced effects_per_component example with new effect properties and groupby examples
  - Updated complete example to use total_effects

  docs/user-guide/results-plotting.md:
  - Fixed colors example from 'Boiler(Q_th)|flow_rate' → 'Boiler(Q_th)'
  - Fixed duration_curve examples to use clean labels

  docs/user-guide/migration-guide-v6.md:
  - Added new "Statistics Accessor" section explaining the clean labels and new effect properties

* implemented the effects() method in StatisticsPlotAccessor at flixopt/statistics_accessor.py:1132-1258.

  Summary of what was done:

  1. Implemented effects() method in StatisticsPlotAccessor class that was missing but documented
    - Takes aspect parameter: 'total', 'temporal', or 'periodic'
    - Takes effect parameter to filter to a specific effect (e.g., 'costs', 'CO2')
    - Takes by parameter: 'component' or 'time' for grouping
    - Supports all standard plotting parameters: select, colors, facet_col, facet_row, show
    - Returns PlotResult with both data and figure
  2. Verified the implementation works with all parameter combinations:
    - Default call: flow_system.statistics.plot.effects()
    - Specific effect: flow_system.statistics.plot.effects(effect='costs')
    - Temporal aspect: flow_system.statistics.plot.effects(aspect='temporal')
    - Temporal by time: flow_system.statistics.plot.effects(aspect='temporal', by='time')
    - Periodic aspect: flow_system.statistics.plot.effects(aspect='periodic')

* Remove intermediate plot accessor

* 1. pyproject.toml: Removed duplicate mkdocs-plotly-plugin>=0.1.3 entry (kept the exact pin ==0.1.3)
  2. flixopt/plotting.py: Fixed dimension name consistency by using squeezed_data.name instead of data.name in the fallback heatmap logic
  3. flixopt/statistics_accessor.py:
    - Fixed _dataset_to_long_df() to only use coordinates that are actually present as columns after reset_index()
    - Fixed the nested loop inefficiency with include_flows by pre-computing the flows list outside the loop
    - (Previously fixed) Fixed asymmetric NaN handling in validation check

* _create_effects_dataset method in statistics_accessor.py was simplified:

  1. Detect contributors from solution data variables instead of assuming they're only flows
    - Uses regex pattern to find {contributor}->{effect}(temporal|periodic) variables
    - Contributors can be flows OR components (e.g., components with effects_per_active_hour)
  2. Exclude effect-to-effect shares
    - Filters out contributors whose base name matches any effect label
    - For example, costs(temporal) is excluded because costs is an effect label
    - These intermediate shares are already included in the computation
  3. Removed the unused _compute_effect_total method
    - The new simplified implementation directly looks up shares from the solution
    - Uses effect_share_factors for conversion between effects
  4. Key insight from user: The solution already contains properly computed share values including all effect-to-effect conversions. The computation uses conversion
  factors because derived effects (like Effect1 which shares 0.5 from costs) don't have direct {flow}->Effect1(temporal) variables - only the source effect shares exist
   ({flow}->costs(temporal)).

* Update docs

* Improve to_netcdf method

* Update examples

* Fix IIS computaion flag

* Fix examples

* Fix faceting in heatmap and use period as facet col everywhere

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* Add color accessor

* Ensure io

* Add carrier class

* implemented Carrier as a proper Interface subclass with container support. Here's what was done:

  1. Carrier class (flixopt/carrier.py)

  - Now inherits from Interface for serialization capabilities
  - Has transform_data() method (no-op since carriers have no time-series data)
  - Has label property for container keying
  - Maintains equality comparison with both Carrier objects and strings

  2. CarrierContainer class (flixopt/carrier.py)

  - Inherits from ContainerMixin['Carrier']
  - Provides dict-like access with nice repr and error messages
  - Uses carrier.name for keying

  3. FlowSystem updates (flixopt/flow_system.py)

  - _carriers is now a CarrierContainer instead of a plain dict
  - carriers property returns the CarrierContainer
  - add_carrier() uses the container's add() method
  - Serialization updated to include carriers in to_dataset() and restore them in from_dataset()

  4. Exports (flixopt/__init__.py)

  - Both Carrier and CarrierContainer are now exported

* Inline plotting methods to deprecate plotting.py (#508)

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* statistics_accessor.py - Heatmap colors type safety (lines 121-148, 820-853)

  - Changed _heatmap_figure() parameter type from colors: ColorType = None to colors: str | list[str] | None = None
  - Changed heatmap() method parameter type similarly
  - Updated docstrings to clarify that dicts are not supported for heatmaps since px.imshow's color_continuous_scale only accepts colorscale names or lists

  2. statistics_accessor.py - Use configured qualitative colorscale (lines 284, 315)

  - Updated _create_stacked_bar() to use CONFIG.Plotting.default_qualitative_colorscale as the default colorscale
  - Updated _create_line() similarly
  - This ensures user-configured CONFIG.Plotting.default_qualitative_colorscale affects all bar/line plots consistently

  3. topology_accessor.py - Path type alignment (lines 219-222)

  - Added normalization of path=False to None before calling _plot_network()
  - This resolves the type mismatch where TopologyAccessor.plot() accepts bool | str | Path but _plot_network() only accepts str | Path | None

* fix usage if index name in aggregation plot

* Add to docs

* Improve carrier colors and defaults

* Update default carriers and colors

* Update config

* Update config

* Move default carriers to config.py

* Change default carrier handling

* Add color handling

* Rmeove meta_data color handling

* Add carrierst to examples

* Improve plotting acessor

* Improve _resolve_variable_names

* Improve _resolve_variable_names

* Simplify coloring and remove color accessor

* Add connected_and_transformed handling

* Improve error message in container

* BUGFIX: Carrier from dataset
…or (#510)

* Add planning doc

* Finalize planning

* Add plotting acessor

* Add plotting acessor

* Add tests

* Improve

* Improve

* Update docs

* Updated the plotting API so that .data always returns xarray DataArray or Dataset instead of pandas DataFrame.

* All .data now returns xr.Dataset consistently.

* Fixed Inconsistencies and Unused Parameters

* New Plot Accessors

  results.plot.variable(pattern)

  Plots the same variable type across multiple elements for easy comparison.

  # All binary operation states across all components
  results.plot.variable('on')

  # All flow_rates, filtered to Boiler-related elements
  results.plot.variable('flow_rate', include='Boiler')

  # All storage charge states
  results.plot.variable('charge_state')

  # With aggregation
  results.plot.variable('flow_rate', aggregate='sum')

  Key features:
  - Searches all elements for variables matching the pattern
  - Filter with include/exclude on element names
  - Supports aggregation, faceting, and animation
  - Returns Dataset with element names as variables

  results.plot.duration_curve(variables)

  Plots load duration curves (sorted time series) showing utilization patterns.

  # Single variable
  results.plot.duration_curve('Boiler(Q_th)|flow_rate')

  # Multiple variables
  results.plot.duration_curve(['CHP|on', 'Boiler|on'])

  # Normalized x-axis (0-100%)
  results.plot.duration_curve('demand', normalize=True)

  Key features:
  - Sorts values from highest to lowest
  - Shows how often each power level is reached
  - normalize=True shows percentage of time on x-axis
  - Returns Dataset with duration_hours or duration_pct dimension

* Fix duration curve

* Fix duration curve

* Fix duration curve

* Fix duration curve

* xr.apply_ufunc to sort along the time axis while preserving all other dimensions automatically

* ⏺ The example runs successfully. Now let me summarize the fixes:

  Summary of Fixes

  I addressed the actionable code review comments from CodeRabbitAI:

  1. Documentation Issue - reshape parameter name ✓ (No fix needed)

  The CodeRabbitAI comment was incorrect. The public API parameter in PlotAccessor.heatmap() is correctly named reshape (line 335). The reshape_time parameter exists in the lower-level heatmap_with_plotly function, but the documentation correctly shows the public API parameter.

  2. Fixed simple_example.py (lines 129-130)

  Problem: The example called balance() and duration_curve() without required arguments, which would cause TypeError at runtime.

  Fix: Added the required arguments:
  - optimization.results.plot.balance('Fernwärme') - specifying the bus to plot
  - optimization.results.plot.duration_curve('Boiler(Q_th)|flow_rate') - specifying the variable to plot

  3. Fixed variable collision in plot_accessors.py (lines 985-988)

  Problem: When building the Dataset in the variable() method, using element names as keys could cause collisions if multiple variables from the same element matched the pattern (e.g., 'Boiler|flow_rate' and 'Boiler|flow_rate_max' would both map to 'Boiler', with the latter silently overwriting the former).

  Fix: Changed to use the full variable names as keys instead of just element names:
  ds = xr.Dataset({var_name: self._results.solution[var_name] for var_name in filtered_vars})

  All tests pass (40 passed, 1 skipped) and the example runs successfully.

* make variable names public in results

* Fix sankey

* Fix effects()

* Fix effects

* Remove bargaps

* made faceting consistent across all plot methods:

  | Method           | facet_col                                 | facet_row                   |
  |------------------|-------------------------------------------|-----------------------------|
  | balance()        | 'scenario'                                | 'period'                    |
  | heatmap()        | 'scenario'                                | 'period'                    |
  | storage()        | 'scenario'                                | 'period'                    |
  | flows()          | 'scenario'                                | 'period'                    |
  | effects()        | 'scenario'                                | 'period'                    |
  | variable()       | 'scenario'                                | 'period'                    |
  | duration_curve() | 'scenario'                                | 'period' (already had this) |
  | compare()        | N/A (uses its own mode='overlay'/'facet') |                             |
  | sankey()         | N/A (aggregates to single diagram)        |                             |

  Removed animate_by parameter from all methods

* Update storage method

* Remove mode parameter for simpli
  | Method           | Default mode                                      |
  |------------------|---------------------------------------------------|
  | balance()        | stacked_bar                                       |
  | storage()        | stacked_bar (flows) + line (charge state overlay) |
  | flows()          | line                                              |
  | variable()       | line                                              |
  | duration_curve() | line                                              |
  | effects()        | bar                                               |

* Make plotting_accessors.py more self contained

* Use faster to_long()

* Add 0-dim case

* sankey diagram now properly handles scenarios and periods:

  Changes made:
  1. Period aggregation with weights: Uses period_weights from flow_system to properly weight periods by their duration
  2. Scenario aggregation with weights: Uses normalized scenario_weights to compute a weighted average across scenarios
  3. Selection support: Users can filter specific scenarios/periods via select parameter before aggregation

  Weighting logic:
  - Periods (for aggregate='sum'): (da * period_weights).sum(dim='period') - this gives the total energy across all periods weighted by their duration
  - Periods (for aggregate='mean'): (da * period_weights).sum(dim='period') / period_weights.sum() - weighted mean
  - Scenarios: Always uses normalized weights (sum to 1) for weighted averaging, since scenarios represent probability-weighted alternatives

* Add colors to sankey

* Add sizes

* Add size filtering

* Include storage sizes

* Remove storage sizes

* Add charge state and status accessor

* Summary of Changes

  1. Added new methods to PlotAccessor (plot_accessors.py)

  charge_states() (line 658):
  - Returns a Dataset with each storage's charge state as a variable
  - Supports filtering with include/exclude parameters
  - Default plot: line chart

  on_states() (line 753):
  - Returns a Dataset with each component's |status variable
  - Supports filtering with include/exclude parameters
  - Default plot: heatmap (good for binary data visualization)

  2. Added data building helper functions (plot_accessors.py)

  build_flow_rates(results) (line 315):
  - Builds a DataArray containing flow rates for all flows
  - Used internally by PlotAccessor methods

  build_flow_hours(results) (line 333):
  - Builds a DataArray containing flow hours for all flows

  build_sizes(results) (line 347):
  - Builds a DataArray containing sizes for all flows

  _filter_dataarray_by_coord(da, **kwargs) (line 284):
  - Helper for filtering DataArrays by coordinate values

  3. Deprecated old Results methods (results.py)

  The following methods now emit DeprecationWarning:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  4. Updated PlotAccessor methods to use new helpers

  - flows() now uses build_flow_rates() / build_flow_hours() directly
  - sizes() now uses build_sizes() directly
  - sankey() now uses build_flow_hours() directly

  This ensures the deprecation warnings only fire when users directly call the old methods, not when using the plot accessor

* 1. New methods added to PlotAccessor

  - charge_states(): Returns Dataset with all storage charge states
  - on_states(): Returns Dataset with all component status variables (heatmap display)

  2. Data building helper functions (plot_accessors.py)

  - build_flow_rates(results): Builds DataArray of flow rates
  - build_flow_hours(results): Builds DataArray of flow hours
  - build_sizes(results): Builds DataArray of sizes
  - _filter_dataarray_by_coord(da, **kwargs): Filter helper
  - _assign_flow_coords(da, results): Add flow coordinates

  3. Caching in PlotAccessor

  Added lazy-cached properties for expensive computations:
  - _all_flow_rates - cached DataArray of all flow rates
  - _all_flow_hours - cached DataArray of all flow hours
  - _all_sizes - cached DataArray of all sizes
  - _all_charge_states - cached Dataset of all storage charge states
  - _all_status_vars - cached Dataset of all status variables

  4. Deprecated methods in Results class

  Added deprecation warnings to:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  5. Updated PlotAccessor methods to use cached properties

  - flows() uses _all_flow_rates / _all_flow_hours
  - sankey() uses _all_flow_hours
  - sizes() uses _all_sizes
  - charge_states() uses _all_charge_states
  - on_states() uses _all_status_vars

* Move deprectated functionality into results.py instead of porting to the new module

* Revert to simply deprectae old methods without forwarding to new code

* Remove planning file

* Update plotting methods for new datasets

* 1. Renamed data properties in PlotAccessor to use all_ prefix:
    - all_flow_rates - All flow rates as Dataset
    - all_flow_hours - All flow hours as Dataset
    - all_sizes - All flow sizes as Dataset
    - all_charge_states - All storage charge states as Dataset
    - all_on_states - All component on/off status as Dataset
  2. Updated internal references - All usages in flows(), sankey(), sizes(), charge_states(), and on_states() methods now use the new names.
  3. Updated deprecation messages in results.py to point to the new API:
    - results.flow_rates() → results.plot.all_flow_rates
    - results.flow_hours() → results.plot.all_flow_hours
    - results.sizes() → results.plot.all_sizes
  4. Updated docstring examples in PlotAccessor to use the new all_* names.

* Update deprecations messages

* Update deprecations messages

* Thsi seems much better.

* Updaet docstrings and variable name generation in plotting acessor

* Change __ to _ in private dataset caching

* Revert breaking io changes

* New solution storing interface

* Add new focused statistics and plot accessors

* Renamed all properties:
  - all_flow_rates → flow_rates
  - all_flow_hours → flow_hours
  - all_sizes → sizes
  - all_charge_states → charge_states

* Cache Statistics

* Invalidate caches

* Add effect related statistics

* Simplify statistics accessor to rely on flow_system directly instead of solution attrs

* Fix heatma fallback for 1D Data

* Add topology accessor

* All deprecation warnings in the codebase now consistently use the format will be removed in v{DEPRECATION_REMOVAL_VERSION}.

* Update tests

* created comprehensive documentation for all FlowSystem accessors

* Update results documentation

* Update results documentation

* Update effect statistics

* Update effect statistics

* Update effect statistics

* Add mkdocs plotly plugin

* Add section about custom constraints

* documentation updates:

  docs/user-guide/results/index.md:
  - Updated table to replace effects_per_component with temporal_effects, periodic_effects, total_effects, and effect_share_factors
  - Fixed flow_hours['Boiler(Q_th)|flow_rate'] → flow_hours['Boiler(Q_th)']
  - Fixed sizes['Boiler(Q_th)|size'] → sizes['Boiler(Q_th)']
  - Replaced effects_per_component example with new effect properties and groupby examples
  - Updated complete example to use total_effects

  docs/user-guide/results-plotting.md:
  - Fixed colors example from 'Boiler(Q_th)|flow_rate' → 'Boiler(Q_th)'
  - Fixed duration_curve examples to use clean labels

  docs/user-guide/migration-guide-v6.md:
  - Added new "Statistics Accessor" section explaining the clean labels and new effect properties

* implemented the effects() method in StatisticsPlotAccessor at flixopt/statistics_accessor.py:1132-1258.

  Summary of what was done:

  1. Implemented effects() method in StatisticsPlotAccessor class that was missing but documented
    - Takes aspect parameter: 'total', 'temporal', or 'periodic'
    - Takes effect parameter to filter to a specific effect (e.g., 'costs', 'CO2')
    - Takes by parameter: 'component' or 'time' for grouping
    - Supports all standard plotting parameters: select, colors, facet_col, facet_row, show
    - Returns PlotResult with both data and figure
  2. Verified the implementation works with all parameter combinations:
    - Default call: flow_system.statistics.plot.effects()
    - Specific effect: flow_system.statistics.plot.effects(effect='costs')
    - Temporal aspect: flow_system.statistics.plot.effects(aspect='temporal')
    - Temporal by time: flow_system.statistics.plot.effects(aspect='temporal', by='time')
    - Periodic aspect: flow_system.statistics.plot.effects(aspect='periodic')

* Remove intermediate plot accessor

* 1. pyproject.toml: Removed duplicate mkdocs-plotly-plugin>=0.1.3 entry (kept the exact pin ==0.1.3)
  2. flixopt/plotting.py: Fixed dimension name consistency by using squeezed_data.name instead of data.name in the fallback heatmap logic
  3. flixopt/statistics_accessor.py:
    - Fixed _dataset_to_long_df() to only use coordinates that are actually present as columns after reset_index()
    - Fixed the nested loop inefficiency with include_flows by pre-computing the flows list outside the loop
    - (Previously fixed) Fixed asymmetric NaN handling in validation check

* _create_effects_dataset method in statistics_accessor.py was simplified:

  1. Detect contributors from solution data variables instead of assuming they're only flows
    - Uses regex pattern to find {contributor}->{effect}(temporal|periodic) variables
    - Contributors can be flows OR components (e.g., components with effects_per_active_hour)
  2. Exclude effect-to-effect shares
    - Filters out contributors whose base name matches any effect label
    - For example, costs(temporal) is excluded because costs is an effect label
    - These intermediate shares are already included in the computation
  3. Removed the unused _compute_effect_total method
    - The new simplified implementation directly looks up shares from the solution
    - Uses effect_share_factors for conversion between effects
  4. Key insight from user: The solution already contains properly computed share values including all effect-to-effect conversions. The computation uses conversion
  factors because derived effects (like Effect1 which shares 0.5 from costs) don't have direct {flow}->Effect1(temporal) variables - only the source effect shares exist
   ({flow}->costs(temporal)).

* Update docs

* Improve to_netcdf method

* Update examples

* Fix IIS computaion flag

* Fix examples

* Fix faceting in heatmap and use period as facet col everywhere

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* Add color accessor

* Ensure io

* Add carrier class

* implemented Carrier as a proper Interface subclass with container support. Here's what was done:

  1. Carrier class (flixopt/carrier.py)

  - Now inherits from Interface for serialization capabilities
  - Has transform_data() method (no-op since carriers have no time-series data)
  - Has label property for container keying
  - Maintains equality comparison with both Carrier objects and strings

  2. CarrierContainer class (flixopt/carrier.py)

  - Inherits from ContainerMixin['Carrier']
  - Provides dict-like access with nice repr and error messages
  - Uses carrier.name for keying

  3. FlowSystem updates (flixopt/flow_system.py)

  - _carriers is now a CarrierContainer instead of a plain dict
  - carriers property returns the CarrierContainer
  - add_carrier() uses the container's add() method
  - Serialization updated to include carriers in to_dataset() and restore them in from_dataset()

  4. Exports (flixopt/__init__.py)

  - Both Carrier and CarrierContainer are now exported

* Inline plotting methods to deprecate plotting.py (#508)

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* statistics_accessor.py - Heatmap colors type safety (lines 121-148, 820-853)

  - Changed _heatmap_figure() parameter type from colors: ColorType = None to colors: str | list[str] | None = None
  - Changed heatmap() method parameter type similarly
  - Updated docstrings to clarify that dicts are not supported for heatmaps since px.imshow's color_continuous_scale only accepts colorscale names or lists

  2. statistics_accessor.py - Use configured qualitative colorscale (lines 284, 315)

  - Updated _create_stacked_bar() to use CONFIG.Plotting.default_qualitative_colorscale as the default colorscale
  - Updated _create_line() similarly
  - This ensures user-configured CONFIG.Plotting.default_qualitative_colorscale affects all bar/line plots consistently

  3. topology_accessor.py - Path type alignment (lines 219-222)

  - Added normalization of path=False to None before calling _plot_network()
  - This resolves the type mismatch where TopologyAccessor.plot() accepts bool | str | Path but _plot_network() only accepts str | Path | None

* fix usage if index name in aggregation plot

* Add to docs

* Improve carrier colors and defaults

* Update default carriers and colors

* Update config

* Update config

* Move default carriers to config.py

* Change default carrier handling

* Add color handling

* Rmeove meta_data color handling

* Add carrierst to examples

* Improve plotting acessor

* Improve _resolve_variable_names

* Improve _resolve_variable_names

* Simplify coloring and remove color accessor

* Add connected_and_transformed handling

* Improve error message in container

* Methods moved to TransformAccessor (transform_accessor.py):
    - sel() - select by label
    - isel() - select by integer index
    - resample() - resample time dimension
    - Helper methods: _dataset_sel, _dataset_isel, _dataset_resample, _resample_by_dimension_groups
  2. Solution is dropped: All transform methods return a new FlowSystem with no solution - the user must re-optimize the transformed system.
  3. Deprecation warnings: The old flow_system.sel(), flow_system.isel(), and flow_system.resample() methods now emit deprecation warnings and forward to the new
  TransformAccessor methods.
  4. Backward compatible: Existing code still works, just with deprecation warnings.

* Documentation updated

* Re-add _dataset_sel and other helper methods for proper deprectation. ALso fix new methods to be classmethods

* BUGFIX: Carrier from dataset

* Update docs
* fix: Link status parameters to flow_system

* ⏺ Summary

  I've implemented the FlowSystem locking behavior as discussed. Here's what was done:

  Core Concept

  A FlowSystem is locked (read-only) when solution is not None.

  New Features

  1. is_locked property (flow_system.py:1127-1131)
    - Returns True if the FlowSystem has a solution
  2. reset() method (flow_system.py:1145-1168)
    - Clears: solution, model, element submodels, variable/constraint names
    - Returns self for method chaining
    - Allows the FlowSystem to be modified and re-optimized
  3. _invalidate_model() helper (flow_system.py:1133-1143)
    - Called when adding elements/carriers to a FlowSystem with a model (but no solution)
    - Clears model and element state
  4. copy() method (flow_system.py:733-767)
    - Creates a fresh FlowSystem copy without solution/model
    - Supports copy.copy() and copy.deepcopy()

  Behavior Changes

  | Operation      | Before Optimization           | After build_model()                | After optimize()              |
  |----------------|-------------------------------|------------------------------------|-------------------------------|
  | add_elements() | ✓ Works                       | ⚠ Works, warns & invalidates model | ✗ RuntimeError                |
  | add_carriers() | ✓ Works                       | ⚠ Works, warns & invalidates model | ✗ RuntimeError                |
  | copy()         | Returns copy without solution | Returns copy without solution      | Returns copy without solution |
  | reset()        | No-op                         | Clears model                       | Clears solution & model       |

  Bug Fix

  Fixed an issue where StatusParameters created during modeling (for prevent_simultaneous_flows and component-level status) weren't linked to the FlowSystem
  (elements.py:957-964, components.py:731-732).

  Tests

  Added comprehensive tests in tests/test_flow_system_locking.py (28 tests) covering:
  - is_locked property behavior
  - add_elements() locking and invalidation
  - add_carriers() locking and invalidation
  - reset() method functionality
  - copy() method functionality
  - Loaded FlowSystem behavior

* Add invalidation tests

* Add link_to_flow_system method

* Add link_to_flow_system method

* New invalidate() method (flow_system.py:1232-1275)

  A public method for manual model invalidation when modifying element attributes after connect_and_transform():

  def invalidate(self) -> FlowSystem:
      """Invalidate the model to allow re-transformation after modifying elements."""

  - Raises RuntimeError if FlowSystem has a solution (must call reset() first)
  - Returns self for method chaining
  - Useful when you need to modify after connect_and_transform() but before optimize()

  2. Updated docstrings

  connect_and_transform() - Added comprehensive docstring explaining:
  - What steps it performs
  - Warning that attributes become xarray DataArrays after transformation
  - Note about idempotency and how to reset with invalidate()

  _invalidate_model() - Clarified its role and relationship to public methods

  3. New tests (test_flow_system_locking.py:285-409)

  Added TestInvalidate class with 8 tests:
  - test_invalidate_resets_connected_and_transformed
  - test_invalidate_clears_model
  - test_invalidate_raises_when_locked
  - test_invalidate_returns_self
  - test_invalidate_allows_retransformation
  - test_modify_element_and_invalidate - full workflow with reset
  - test_invalidate_needed_after_transform_before_optimize - pre-optimization modification
  - test_reset_already_invalidates - confirms reset already handles invalidation

  Key insight from testing

  reset() already calls _invalidate_model(), so modifications after reset() automatically take effect on the next optimize(). The new invalidate() method is primarily for the case where:
  1. You've called connect_and_transform() manually
  2. Haven't optimized yet (no solution)
  3. Need to modify element attributes

* Typo
* Update sankey figure building

* fix: sankey

* fix: effects sankey

* Add sankey diagramm tests

* Add sankey diagramm tests

* Minor nitpicks

* All three failing tests are now fixed. Here's a summary of the changes made:

  Summary of Fixes

  1. test_statistics_sizes_includes_all_flows - Fixed

  Problem: The statistics.sizes property was returning storage capacity sizes (like Speicher) which are not flow labels. The test expected only flow sizes.

  Fix in flixopt/statistics_accessor.py (lines 419-427):
  - Modified sizes property to filter only flow sizes by checking if the variable name (minus |size suffix) matches a flow label in self._fs.flows.keys()

  2. test_sankey_sizes_mode - Fixed

  Problem: The sankey diagram with mode='sizes' returned empty links because the test fixture (simple_flow_system) didn't have any flows with InvestParameters.

  Fix in tests/conftest.py (lines 268-272):
  - Modified Storage.simple() to use fx.InvestParameters(fixed_size=1e4, mandatory=True) on the charging flow, ensuring there's at least one flow with investment
  parameters for testing.

  3. test_sankey_sizes_max_size_filter - Fixed

  Problem: The sankey method didn't have a max_size parameter, so passing it caused the parameter to be forwarded to plotly_kwargs and then to update_layout(), which
  failed with "Invalid property specified for object of type plotly.graph_objs.Layout: 'max_size'".

  Fix in flixopt/statistics_accessor.py (lines 1351, 1367, 1392-1395):
  - Added max_size: float | None = None parameter to the sankey method signature
  - Added filtering logic to apply max_size filter when in mode='sizes'

* Add new sankey network topology plot
* Extract docs build into separate workflow

* Fix workflow

* docs deploy job uses the uploaded artifact
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 10, 2025

Walkthrough

Large API and documentation overhaul: renames OnOff→Status, introduces FlowSystem accessors (optimize/transform/statistics/topology), consolidates solution I/O (to_netcdf/from_netcdf), removes legacy Calculation/Results shims, adds carriers/colors/PlotResult, and adds many docs, notebooks, and CI for docs deployment.

Changes

Cohort / File(s) Summary
CI / Docs workflows
\.github/workflows/docs.yaml, \.github/workflows/release.yaml, \.github/workflows/tests.yaml
Add docs build+deploy workflow; update release workflow to invoke docs workflow; broaden test triggers and Python matrix.
Project config & tooling
\.gitignore, pyproject.toml, \.pre-commit-config.yaml, mkdocs.yml
New ignore patterns; add mkdocs plotly plugin and JS hook; update pytest markers/warnings and pre-commit hooks; expand mkdocs nav and edit_uri.
Site templates / assets
docs/stylesheets/extra.css, docs/javascripts/plotly-instant.js, docs/overrides/main.html
Large stylesheet rework, Plotly instant-reinit JS, and template override for notebook download.
Changelog / docs site content
CHANGELOG.md, README.md, docs/index.md, docs/home/*, docs/user-guide/*, docs/notebooks/*, docs/examples/*
Massive docs reorganization: many new user-guide pages, migration guide, FAQ, troubleshooting, notebooks, new examples; removal/relocation of many older doc pages.
Docs scripts
scripts/format_changelog.py, scripts/gen_ref_pages.py
Add changelog formatter; update API ref index generation path.
Examples & notebooks
examples/..., docs/notebooks/*.ipynb, docs/notebooks/data/generate_example_systems.py
Migrate examples to FlowSystem-centric APIs; add tutorial notebooks and a generator script producing netCDF examples.
Public API — FlowSystem & accessors
flixopt/flow_system.py, flixopt/optimize_accessor.py, flixopt/transform_accessor.py, flixopt/statistics_accessor.py, flixopt/topology_accessor.py
Introduce Optimize/Transform/Statistics/Topology accessors; FlowSystem lifecycle methods (build_model/solve/optimize/reset/copy), solution property, enhanced serialization (to_netcdf/from_netcdf/from_dataset), carriers support, and deprecation wrappers.
Public API — Status model migration
flixopt/interface.py, flixopt/features.py, flixopt/elements.py, flixopt/components.py, flixopt/linear_converters.py
Replace OnOffParameters/OnOffModel and on/off terminology with StatusParameters/StatusModel and renamed fields (startup/active/uptime/downtime, effects_per_startup, effects_per_active_hour); update constructors and signatures.
API surface & exports
flixopt/__init__.py, flixopt/calculation.py, flixopt/results.py
Remove legacy Calculation aliases and deprecated exports; expose Carrier, CarrierContainer, StatusParameters, PlotResult; remove deprecated Results/Calculation shims.
Plotting, colors & carriers
flixopt/carrier.py, flixopt/color_processing.py, flixopt/plotting.py, flixopt/clustering.py
Add Carrier and CarrierContainer, ColorType and color_to_rgba, PlotResult wrapper, new heatmap helper, and adjust plot return types to PlotResult.
Modeling internals & utilities
flixopt/modeling.py, flixopt/effects.py, flixopt/structure.py, flixopt/flow_system.py (internal)
Rename state/transition utilities to active/inactive; update ModelingPrimitives signatures/returns; add link_to_flow_system / prefixing; change transform_data signatures; populate element variable/constraint names.
I/O & migration helpers
flixopt/io.py, flixopt/flow_system.py (from_old_results)
Add conversion helpers for older netCDF formats and migration helpers (convert_old_netcdf, convert_old_dataset, FlowSystem.from_old_results).
Tests
tests/conftest.py, tests/deprecated/*
Update tests to StatusParameters and new build_model flow; add deprecated-suite fixtures and helpers.
Scripts removed
scripts/extract_changelog.py, bot-test.txt, many docs/examples/*
Remove old changelog extractor, bot-test file, and several example doc pages.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant FlowSystem
    participant OptimizeAccessor
    participant Solver
    participant StatisticsAccessor
    participant PlotResult

    User->>FlowSystem: flow_system.optimize(solver)
    FlowSystem->>OptimizeAccessor: __call__(solver)
    OptimizeAccessor->>FlowSystem: build_model(normalize_weights)
    FlowSystem->>FlowSystem: create/attach model
    OptimizeAccessor->>FlowSystem: solve(solver)
    FlowSystem->>Solver: run solver on model
    Solver-->>FlowSystem: solution stored
    FlowSystem-->>User: return FlowSystem (solution accessible)

    User->>FlowSystem: flow_system.statistics.plot.balance(bus)
    FlowSystem->>StatisticsAccessor: request data
    StatisticsAccessor->>FlowSystem: read solution dataset
    StatisticsAccessor->>PlotResult: assemble data + figure
    PlotResult-->>User: PlotResult (show / export)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Areas to focus on during review:

  • API-breaking renames and widespread signature changes (OnOff → Status; StatusParameters drift across many constructors).
  • FlowSystem lifecycle, caching, locking and persistence (build_model / solve / optimize, solution property, to_netcdf / from_netcdf, is_locked / reset / _invalidate_model).
  • Accessor interactions and their backwards-compatibility wrappers (Optimize/Transform/Statistics/Topology).
  • Serialization/migration helpers and dataset mappings (convert_old_netcdf, from_old_results, PARAMETER_RENAMES/VALUE_RENAMES).
  • PlotResult and plotting contract changes (return types, color/carrier propagation).
  • Tests and deprecated-suite coverage ensuring migration paths and deprecation warnings are consistent.

Possibly related PRs

Suggested reviewers

  • baumbude
  • PStange

Poem

🐇
I hopped through lines of code at night,
swapped on and off for status bright.
FlowSystem now optimizes, plots, and saves,
notebooks bloom in tidy docs' caves.
A carrot of features — hop, delight, and bite!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description acknowledges this is a commit-history sanitization for flixopt 5.0.0 but omits key details about the extensive breaking changes, new features, and refactoring involved in the FlowSystem-first transition. Expand the description to explain the FlowSystem-first architectural refactoring, major breaking changes (e.g., OnOffParameters→StatusParameters, Optimization→FlowSystem.optimize, new accessors), migration guidance, and scope of documentation updates.
Docstring Coverage ⚠️ Warning Docstring coverage is 69.81% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feature/flow system first' is related to the core objective of this PR: a comprehensive rewrite of the flixopt API to be FlowSystem-centric. The title reflects the main architectural change across the codebase.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/flow-system-first

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d17f9a and 78969bd.

📒 Files selected for processing (1)
  • .github/workflows/tests.yaml (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/tests.yaml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.14)
  • GitHub Check: test (3.11)
  • GitHub Check: Build documentation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
mkdocs.yml (1)

269-273: Pipeline failure: Signature formatting requires Black or Ruff.

The mkdocstrings configuration uses separate_signature: true which requires either Black or Ruff to be installed. Add one of these to documentation dependencies:

# In pyproject.toml or docs requirements
+black  # or ruff

Alternatively, disable signature separation:

separate_signature: false
🧹 Nitpick comments (19)
scripts/format_changelog.py (1)

56-63: Make changelog path independent of current working directory (and optionally fail fast on missing file)

Right now the script assumes it’s run from the repo root (Path('docs/changelog.md')). That’s brittle if invoked from a different CWD (e.g. via a tool that runs from another directory).

You can anchor the path to the script location instead:

 def main():
     """Process the changelog file."""
-    changelog_path = Path('docs/changelog.md')
+    script_dir = Path(__file__).resolve().parent
+    repo_root = script_dir.parent  # assumes this file lives in <repo>/scripts/
+    changelog_path = repo_root / 'docs' / 'changelog.md'
@@
-    if not changelog_path.exists():
-        print(f'❌ {changelog_path} not found')
-        return
+    if not changelog_path.exists():
+        print(f'❌ {changelog_path} not found')
+        return  # or `sys.exit(1)` if you want CI to fail when the changelog is missing

If this is intended for CI/pre‑release automation, consider using sys.exit(1) instead of return so a missing changelog fails the step explicitly.

docs/home/license.md (1)

7-29: Add language identifier to the fenced code block.

The license text code block is missing a language identifier, which causes a linting warning (MD040).

Apply this diff to add the language identifier:

-```
+```text
 MIT License
docs/home/citing.md (1)

15-15: Convert emphasized text to proper heading or regular text.

The line uses emphasis (italics) for what appears to be a placeholder message. This triggers a linting warning (MD036).

Apply this diff to use regular text instead:

-*Coming soon: A list of academic publications that have used flixOpt*
+Coming soon: A list of academic publications that have used flixOpt.
.github/workflows/tests.yaml (1)

7-7: Clarify the intent of the ** pattern.

The ** pattern in glob matching is semantically different from *—it matches zero or more characters including slashes (nested paths), while * matches any characters except slashes. For this repository's flat branch naming convention, both patterns currently produce identical behavior. However, the change is worth documenting to clarify intent, especially if the branch strategy may evolve to include slash-based naming conventions in the future.

flixopt/config.py (1)

650-678: Consider including Carriers in serialization.

The to_dict() method excludes Carriers from serialization. This is likely intentional since Carrier objects aren't simple JSON-serializable values, but it means carrier customizations won't round-trip through to_dict()/load_from_file().

If carrier persistence is needed in the future, consider adding a serialization path for custom carrier colors/units.

docs/user-guide/building-models/index.md (1)

1-12: Update "On/Off" terminology to "Status" for consistency with StatusParameters refactor.

Line 12 references "On/Off" but the PR is migrating to StatusParameters terminology. This should be updated to align with the broader API changes.

Suggested fix:

- Using advanced features (Investment, On/Off, Piecewise)
+ Using advanced features (Investment, Status, Piecewise)

Please verify this aligns with the terminology shift described in the PR objectives and other docs pages.

docs/user-guide/results/index.md (1)

116-116: Minor: Consider using fenced code blocks consistently.

The markdown linter suggests using fenced code blocks (triple backticks) instead of indented code blocks for consistency throughout the documentation.

Apply this change if standardizing code block formatting:

-    - **Flows**: Individual flows with `effects_per_flow_hour`
-    - **Components**: Components with `effects_per_active_hour` or similar direct effects
+- **Flows**: Individual flows with `effects_per_flow_hour`
+- **Components**: Components with `effects_per_active_hour` or similar direct effects
examples/05_Two-stage-optimization/two_stage_optimization.py (1)

141-147: Size equality check relies on floating-point rounding.

The comparison uses .round(5) which is reasonable, but consider using xr.testing.assert_allclose or np.allclose for more robust floating-point comparison:

-    if (dispatch_sizes.round(5).to_dataarray() == sizing_sizes.round(5).to_dataarray()).all().item():
+    if np.allclose(dispatch_sizes.to_dataarray(), sizing_sizes.to_dataarray(), rtol=1e-5):
flixopt/topology_accessor.py (1)

430-438: Redundant ImportError in stop_app() is inconsistent with user expectation.

If a user calls stop_app() without having started an app (because dependencies weren't installed), the error message about missing dependencies is confusing. The check at line 440 (if self._fs._network_app is None) already handles the "no app running" case. Consider removing the dependency check or making it a warning instead:

-        if not DASH_CYTOSCAPE_AVAILABLE:
-            raise ImportError(
-                f'Network visualization requires optional dependencies. '
-                ...
-            )
+        # No need to check dependencies - if app was started, dependencies exist
flixopt/statistics_accessor.py (3)

376-380: Consider clearing cached data when solution changes.

The cached properties (_flow_rates, _flow_hours, etc.) are never invalidated. If optimize() is called multiple times on the same FlowSystem, stale cached data could be returned.

Consider adding a method to clear the cache or checking if the solution has changed:

def _clear_cache(self) -> None:
    """Clear all cached data. Call when solution changes."""
    self._flow_rates = None
    self._flow_hours = None
    self._sizes = None
    self._charge_states = None
    self._effect_share_factors = None
    self._temporal_effects = None
    self._periodic_effects = None
    self._total_effects = None

717-728: Validation logging uses logger.critical for non-critical mismatch.

The validation mismatch in effect totals is logged at CRITICAL level, but it doesn't raise an exception or halt execution. This seems like a data integrity warning rather than a critical system failure.

Consider using logger.warning or logger.error instead:

-                    logger.critical(
+                    logger.warning(
                         f'Results for {effect}({mode}) in effects_dataset doesnt match {label}\n{computed=}\n, {found=}'
                     )

987-990: Complex set operation could be clearer.

The set subtraction with - {None} works but is less readable than filtering explicitly.

-        keep_dims = set(heatmap_dims) | {actual_facet, actual_animation} - {None}
+        keep_dims = set(heatmap_dims) | {d for d in [actual_facet, actual_animation] if d is not None}
flixopt/effects.py (1)

240-246: link_to_flow_system ignores passed prefix.

The method signature accepts a prefix parameter but explicitly ignores it, always using self.label_full. The docstring explains this behavior, but it could be confusing for callers.

Consider either:

  1. Removing the prefix parameter entirely if it's always ignored, or
  2. Logging a warning if a non-empty prefix is passed but ignored
def link_to_flow_system(self, flow_system, prefix: str = '') -> None:
    """Link this effect to a FlowSystem.
    
    Note: Effects always use their label_full as prefix, ignoring any passed prefix.
    """
    if prefix and prefix != self.label_full:
        logger.debug(f"Effect '{self.label}' ignoring passed prefix '{prefix}', using '{self.label_full}'")
    super().link_to_flow_system(flow_system, self.label_full)
flixopt/features.py (2)

158-159: Documentation link may be outdated.

The docstring references OnOffParameters in the URL but the class has been renamed to StatusModel and uses StatusParameters. Consider updating the link to match the new naming.

     Mathematical Formulation:
-        See <https://flixopt.github.io/flixopt/latest/user-guide/mathematical-notation/features/OnOffParameters/>
+        See <https://flixopt.github.io/flixopt/latest/user-guide/mathematical-notation/features/StatusParameters/>

347-348: Consider clearer syntax for status inversion.

The expression self._previous_status * -1 + 1 inverts the binary status. Using 1 - self._previous_status is more idiomatic and readable.

-            return ModelingUtilities.compute_consecutive_hours_in_state(self._previous_status * -1 + 1, hours_per_step)
+            return ModelingUtilities.compute_consecutive_hours_in_state(1 - self._previous_status, hours_per_step)
flixopt/transform_accessor.py (1)

506-507: Handle empty dimension groups gracefully.

When dim_groups is empty (no variables with time dimension), the code falls through to call resample on an empty dataset. This edge case is handled but could be made explicit with an early return.

         if not dim_groups:
-            return getattr(time_dataset.resample(time=time, **kwargs), method)()
+            # No variables to resample - return as-is
+            return time_dataset
tests/deprecated/conftest.py (1)

343-345: Potential issue: np.random.seed usage.

Using np.random.seed in static methods affects global state, which could cause non-deterministic test behavior if tests run in parallel or in unexpected order. Consider using np.random.default_rng(seed) for isolated random state.

     @staticmethod
     def random_thermal(length=10, seed=42):
-        np.random.seed(seed)
-        return np.array([np.random.random() for _ in range(length)]) * 180
+        rng = np.random.default_rng(seed)
+        return rng.random(length) * 180

     @staticmethod
     def random_electrical(length=10, seed=42):
-        np.random.seed(seed)
-        return (np.array([np.random.random() for _ in range(length)]) + 0.5) / 1.5 * 50
+        rng = np.random.default_rng(seed)
+        return (rng.random(length) + 0.5) / 1.5 * 50
flixopt/flow_system.py (1)

162-173: Preserve FlowSystem.name when round‑tripping via datasets

__init__ introduces the optional name attribute and to_netcdf/from_netcdf map it to the filename, but from_dataset() does not currently restore name from ds.attrs (even though to_dataset() stores all constructor parameters there). This means FlowSystem.name is lost on a dataset round‑trip while being preserved for NetCDF round‑trips.

Consider wiring name through when reconstructing:

 flow_system = cls(
     timesteps=ds.indexes['time'],
     periods=ds.indexes.get('period'),
     scenarios=ds.indexes.get('scenario'),
@@
     scenario_independent_sizes=reference_structure.get('scenario_independent_sizes', True),
     scenario_independent_flow_rates=reference_structure.get('scenario_independent_flow_rates', False),
+    name=reference_structure.get('name'),
 )

Also applies to: 207-224, 229-231, 597-643

flixopt/optimization.py (1)

632-696: SegmentedOptimization deprecation path is clear; future migration can target new APIs

SegmentedOptimization now emits a deprecation warning and uses _initialize_optimization_common() for consistent setup. Internally it still constructs sub‑optimizations via Optimization(...) and flow_system.sel(...), both of which are now deprecated but explicitly marked as such.

For a future refactor (not required for this PR), consider:

  • switching flow_system.sel(...) to flow_system.transform.sel(...), and
  • eventually re‑implementing segmented solving on top of FlowSystem.build_model()/solve() directly.

Also applies to: 696-703

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 423496a and b0202b9.

📒 Files selected for processing (85)
  • .github/workflows/docs.yaml (1 hunks)
  • .github/workflows/release.yaml (2 hunks)
  • .github/workflows/tests.yaml (1 hunks)
  • .gitignore (1 hunks)
  • CHANGELOG.md (2 hunks)
  • README.md (1 hunks)
  • bot-test.txt (0 hunks)
  • docs/getting-started.md (0 hunks)
  • docs/home/citing.md (1 hunks)
  • docs/home/installation.md (1 hunks)
  • docs/home/license.md (1 hunks)
  • docs/home/quick-start.md (1 hunks)
  • docs/home/users.md (1 hunks)
  • docs/index.md (4 hunks)
  • docs/javascripts/plotly-instant.js (1 hunks)
  • docs/roadmap.md (1 hunks)
  • docs/stylesheets/extra.css (3 hunks)
  • docs/user-guide/building-models/index.md (1 hunks)
  • docs/user-guide/core-concepts.md (1 hunks)
  • docs/user-guide/faq.md (1 hunks)
  • docs/user-guide/index.md (1 hunks)
  • docs/user-guide/mathematical-notation/dimensions.md (0 hunks)
  • docs/user-guide/mathematical-notation/effects-and-dimensions.md (1 hunks)
  • docs/user-guide/mathematical-notation/effects-penalty-objective.md (0 hunks)
  • docs/user-guide/mathematical-notation/elements/Bus.md (1 hunks)
  • docs/user-guide/mathematical-notation/elements/Flow.md (1 hunks)
  • docs/user-guide/mathematical-notation/elements/LinearConverter.md (1 hunks)
  • docs/user-guide/mathematical-notation/elements/Storage.md (1 hunks)
  • docs/user-guide/mathematical-notation/features/InvestParameters.md (1 hunks)
  • docs/user-guide/mathematical-notation/features/OnOffParameters.md (0 hunks)
  • docs/user-guide/mathematical-notation/features/Piecewise.md (1 hunks)
  • docs/user-guide/mathematical-notation/features/StatusParameters.md (1 hunks)
  • docs/user-guide/mathematical-notation/index.md (1 hunks)
  • docs/user-guide/mathematical-notation/modeling-patterns/bounds-and-states.md (0 hunks)
  • docs/user-guide/mathematical-notation/modeling-patterns/duration-tracking.md (0 hunks)
  • docs/user-guide/mathematical-notation/modeling-patterns/index.md (0 hunks)
  • docs/user-guide/mathematical-notation/modeling-patterns/state-transitions.md (0 hunks)
  • docs/user-guide/mathematical-notation/others.md (0 hunks)
  • docs/user-guide/migration-guide-v6.md (1 hunks)
  • docs/user-guide/optimization/index.md (1 hunks)
  • docs/user-guide/recipes/index.md (3 hunks)
  • docs/user-guide/recipes/plotting-custom-data.md (1 hunks)
  • docs/user-guide/results-plotting.md (1 hunks)
  • docs/user-guide/results/index.md (1 hunks)
  • docs/user-guide/support.md (1 hunks)
  • docs/user-guide/troubleshooting.md (1 hunks)
  • examples/00_Minmal/minimal_example.py (1 hunks)
  • examples/01_Simple/simple_example.py (2 hunks)
  • examples/02_Complex/complex_example.py (7 hunks)
  • examples/02_Complex/complex_example_results.py (1 hunks)
  • examples/03_Optimization_modes/example_optimization_modes.py (6 hunks)
  • examples/04_Scenarios/scenario_example.py (4 hunks)
  • examples/05_Two-stage-optimization/two_stage_optimization.py (4 hunks)
  • flixopt/__init__.py (3 hunks)
  • flixopt/calculation.py (0 hunks)
  • flixopt/carrier.py (1 hunks)
  • flixopt/clustering.py (4 hunks)
  • flixopt/color_processing.py (1 hunks)
  • flixopt/components.py (18 hunks)
  • flixopt/config.py (8 hunks)
  • flixopt/core.py (1 hunks)
  • flixopt/effects.py (5 hunks)
  • flixopt/elements.py (29 hunks)
  • flixopt/features.py (4 hunks)
  • flixopt/flow_system.py (20 hunks)
  • flixopt/interface.py (12 hunks)
  • flixopt/linear_converters.py (25 hunks)
  • flixopt/modeling.py (16 hunks)
  • flixopt/optimization.py (7 hunks)
  • flixopt/optimize_accessor.py (1 hunks)
  • flixopt/plotting.py (2 hunks)
  • flixopt/results.py (11 hunks)
  • flixopt/statistics_accessor.py (1 hunks)
  • flixopt/structure.py (12 hunks)
  • flixopt/topology_accessor.py (1 hunks)
  • flixopt/transform_accessor.py (1 hunks)
  • mkdocs.yml (5 hunks)
  • pyproject.toml (3 hunks)
  • scripts/extract_changelog.py (0 hunks)
  • scripts/format_changelog.py (1 hunks)
  • scripts/gen_ref_pages.py (1 hunks)
  • tests/conftest.py (10 hunks)
  • tests/deprecated/__init__.py (1 hunks)
  • tests/deprecated/conftest.py (1 hunks)
  • tests/deprecated/test_bus.py (1 hunks)
💤 Files with no reviewable changes (12)
  • docs/user-guide/mathematical-notation/modeling-patterns/bounds-and-states.md
  • bot-test.txt
  • docs/user-guide/mathematical-notation/dimensions.md
  • docs/user-guide/mathematical-notation/modeling-patterns/duration-tracking.md
  • docs/user-guide/mathematical-notation/modeling-patterns/state-transitions.md
  • docs/getting-started.md
  • docs/user-guide/mathematical-notation/others.md
  • docs/user-guide/mathematical-notation/effects-penalty-objective.md
  • docs/user-guide/mathematical-notation/features/OnOffParameters.md
  • docs/user-guide/mathematical-notation/modeling-patterns/index.md
  • flixopt/calculation.py
  • scripts/extract_changelog.py
🧰 Additional context used
🧬 Code graph analysis (13)
flixopt/linear_converters.py (1)
flixopt/interface.py (1)
  • StatusParameters (1014-1265)
flixopt/topology_accessor.py (3)
flixopt/color_processing.py (1)
  • process_colors (112-180)
flixopt/flow_system.py (2)
  • FlowSystem (47-2028)
  • flows (1636-1642)
flixopt/network_app.py (1)
  • flow_graph (117-180)
examples/02_Complex/complex_example.py (5)
flixopt/flow_system.py (2)
  • topology (1387-1413)
  • to_netcdf (686-711)
flixopt/elements.py (1)
  • Bus (185-305)
flixopt/linear_converters.py (7)
  • Boiler (24-111)
  • thermal_efficiency (105-106)
  • thermal_efficiency (109-111)
  • thermal_efficiency (201-202)
  • thermal_efficiency (205-207)
  • thermal_efficiency (512-513)
  • thermal_efficiency (516-518)
flixopt/carrier.py (1)
  • label (107-109)
flixopt/topology_accessor.py (2)
  • start_app (378-421)
  • plot (183-317)
examples/03_Optimization_modes/example_optimization_modes.py (6)
flixopt/results.py (2)
  • solution_without_overlap (2207-2220)
  • flow_system (338-357)
flixopt/structure.py (3)
  • flow_system (386-404)
  • solution (168-206)
  • solution (1084-1098)
flixopt/flow_system.py (3)
  • solution (1201-1203)
  • solution (1206-1209)
  • topology (1387-1413)
flixopt/elements.py (1)
  • Bus (185-305)
flixopt/interface.py (1)
  • StatusParameters (1014-1265)
flixopt/topology_accessor.py (1)
  • plot (183-317)
flixopt/config.py (1)
flixopt/carrier.py (1)
  • Carrier (14-125)
flixopt/carrier.py (2)
flixopt/structure.py (7)
  • ContainerMixin (1153-1303)
  • Interface (304-1038)
  • register_class_for_io (43-52)
  • transform_data (322-335)
  • _get_label (1188-1198)
  • _get_label (1313-1315)
  • _get_label (1325-1327)
flixopt/flow_system.py (1)
  • carriers (1080-1082)
flixopt/results.py (2)
flixopt/structure.py (4)
  • variables (1649-1655)
  • constraints (1640-1646)
  • solution (168-206)
  • solution (1084-1098)
flixopt/flow_system.py (2)
  • solution (1201-1203)
  • solution (1206-1209)
flixopt/__init__.py (4)
flixopt/carrier.py (2)
  • Carrier (14-125)
  • CarrierContainer (128-159)
flixopt/clustering.py (1)
  • ClusteringParameters (246-299)
flixopt/flow_system.py (1)
  • FlowSystem (47-2028)
flixopt/interface.py (1)
  • StatusParameters (1014-1265)
tests/conftest.py (2)
flixopt/interface.py (1)
  • StatusParameters (1014-1265)
flixopt/flow_system.py (1)
  • build_model (1099-1130)
flixopt/features.py (2)
flixopt/interface.py (5)
  • InvestParameters (693-1010)
  • StatusParameters (1014-1265)
  • use_downtime_tracking (1249-1251)
  • use_startup_tracking (1254-1265)
  • use_uptime_tracking (1244-1246)
flixopt/elements.py (3)
  • status (845-849)
  • previous_status (864-877)
  • previous_status (1024-1041)
flixopt/structure.py (1)
flixopt/flow_system.py (2)
  • FlowSystem (47-2028)
  • _create_reference_structure (510-548)
flixopt/plotting.py (3)
flixopt/color_processing.py (1)
  • process_colors (112-180)
flixopt/results.py (2)
  • colors (2147-2148)
  • colors (2151-2155)
flixopt/config.py (2)
  • CONFIG (181-854)
  • Plotting (547-576)
flixopt/statistics_accessor.py (3)
flixopt/color_processing.py (1)
  • process_colors (112-180)
flixopt/topology_accessor.py (1)
  • plot (183-317)
flixopt/effects.py (1)
  • calculate_effect_share_factors (612-636)
🪛 GitHub Actions: Docs
docs/home/quick-start.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/javascripts/plotly-instant.js

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/faq.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/home/citing.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/troubleshooting.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/results-plotting.md

[warning] 1-1: mkdocs_autorefs: Could not find cross-reference target 'flixopt.plot_accessors.PlotResult'


[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/roadmap.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/building-models/index.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/home/installation.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/support.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/home/users.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/optimization/index.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/mathematical-notation/index.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/home/license.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/stylesheets/extra.css

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/results/index.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/mathematical-notation/elements/LinearConverter.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/recipes/plotting-custom-data.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/mathematical-notation/effects-and-dimensions.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/core-concepts.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/migration-guide-v6.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

.github/workflows/docs.yaml

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

mkdocs.yml

[error] 1-1: mkdocs build --strict failed. Aborted with 3 warnings in strict mode. Command: mkdocs build --strict. Warnings: mkdocstrings formatting signatures requires either Black or Ruff to be installed; griffe: No type or annotation for parameter 'carrier' and it does not appear in the function signature; mkdocs_autorefs: Could not find cross-reference target 'flixopt.plot_accessors.PlotResult'.

docs/user-guide/recipes/index.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/mathematical-notation/elements/Storage.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/mathematical-notation/features/StatusParameters.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/mathematical-notation/elements/Flow.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/mathematical-notation/elements/Bus.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/mathematical-notation/features/InvestParameters.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/index.md

[warning] 1-1: Doc file 'index.md' contains unrecognized relative links; left as is.


[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

docs/user-guide/mathematical-notation/features/Piecewise.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

flixopt/flow_system.py

[warning] 1003-1003: No type or annotation for parameter 'carrier'. Parameter 'carrier' does not appear in the function signature.

docs/user-guide/index.md

[warning] 1-1: Formatting/signature tooling requires Black or Ruff to be installed (mkdocstrings/formatting step not fully available).

🪛 LanguageTool
docs/user-guide/results-plotting.md

[style] ~3-~3: Consider a different adjective to strengthen your wording.
Context: ...ss to common plots while still allowing deep customization. ## The Plot Accessor A...

(DEEP_PROFOUND)

docs/user-guide/building-models/index.md

[style] ~2-~2: Using many exclamation marks might seem excessive (in this case: 3 exclamation marks for a text that’s 664 characters long)
Context: # Building Models !!! note "Under Development" This secti...

(EN_EXCESSIVE_EXCLAMATION)

docs/user-guide/results/index.md

[style] ~188-~188: Using many exclamation marks might seem excessive (in this case: 6 exclamation marks for a text that’s 2701 characters long)
Context: ...ne flow_system.topology.stop_app() ``` !!! note "Optional Dependencies" The in...

(EN_EXCESSIVE_EXCLAMATION)

docs/user-guide/core-concepts.md

[style] ~17-~17: Consider an alternative for the overused word “exactly”.
Context: ...hile meeting all heat demands. This is exactly the kind of problem flixOpt solves. Let...

(EXACTLY_PRECISELY)


[style] ~98-~98: Using many exclamation marks might seem excessive (in this case: 18 exclamation marks for a text that’s 7150 characters long)
Context: ...imize), others can be constraints. !!! example "In our heating system" - *...

(EN_EXCESSIVE_EXCLAMATION)

docs/user-guide/migration-guide-v6.md

[style] ~398-~398: Using many exclamation marks might seem excessive (in this case: 16 exclamation marks for a text that’s 6313 characters long)
Context: ... success "Welcome to the new flixopt API! 🎉"

(EN_EXCESSIVE_EXCLAMATION)

docs/user-guide/mathematical-notation/elements/Flow.md

[style] ~18-~18: Using many exclamation marks might seem excessive (in this case: 3 exclamation marks for a text that’s 1352 characters long)
Context: ...e_minimum=0.3) # → 30 ≤ p(t) ≤ 100 ``` !!! warning "Cannot be zero" With `rela...

(EN_EXCESSIVE_EXCLAMATION)

docs/user-guide/mathematical-notation/elements/Bus.md

[style] ~59-~59: Using many exclamation marks might seem excessive (in this case: 3 exclamation marks for a text that’s 1616 characters long)
Context: ...e5 # High penalty for imbalance ) ``` !!! tip "Debugging" If you see a `virtu...

(EN_EXCESSIVE_EXCLAMATION)

docs/user-guide/mathematical-notation/features/Piecewise.md

[style] ~29-~29: Using many exclamation marks might seem excessive (in this case: 3 exclamation marks for a text that’s 2049 characters long)
Context: ...ve ($s_i = 0$), both lambdas are zero. !!! note "Implementation Note" This for...

(EN_EXCESSIVE_EXCLAMATION)

🪛 markdownlint-cli2 (0.18.1)
docs/home/citing.md

15-15: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

docs/home/license.md

7-7: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

docs/user-guide/results/index.md

116-116: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)

docs/user-guide/mathematical-notation/elements/LinearConverter.md

62-62: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


75-75: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

docs/user-guide/mathematical-notation/effects-and-dimensions.md

22-22: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


33-33: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


43-43: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


51-51: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


59-59: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


68-68: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


78-78: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


93-93: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


105-105: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


119-119: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


138-138: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


144-144: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


152-152: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


160-160: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


172-172: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


182-182: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


191-191: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


201-201: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


209-209: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


217-217: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


225-225: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


233-233: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


273-273: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


282-282: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


292-292: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


309-309: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


327-327: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


343-343: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


358-358: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


379-379: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)

docs/user-guide/migration-guide-v6.md

165-165: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


202-202: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


255-255: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


303-303: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

docs/user-guide/mathematical-notation/elements/Storage.md

44-44: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


50-50: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


58-58: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


71-71: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


79-79: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


93-93: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)

docs/user-guide/mathematical-notation/elements/Flow.md

28-28: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


49-49: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


68-68: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


82-82: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


99-99: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


109-109: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)

docs/user-guide/mathematical-notation/features/InvestParameters.md

30-30: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


42-42: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


53-53: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


70-70: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


82-82: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


94-94: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


106-106: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)

Comment on lines +25 to +43
### 2. Define your time horizon

```python
# 24h period with hourly timesteps
timesteps = pd.date_range('2024-01-01', periods=24, freq='h')
```

### 2. Set Up the Flow System

```python
# Create the flow system
flow_system = fx.FlowSystem(timesteps)

# Define an effect to minimize (costs)
costs = fx.Effect('costs', 'EUR', 'Minimize total system costs', is_objective=True)
flow_system.add_elements(costs)
```

### 4. Add Components
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix step numbering inconsistency.

Steps are incorrectly numbered with two "### 2." sections and missing "### 3.":

  • Line 25: "### 2. Define your time horizon"
  • Line 32: "### 2. Set Up the Flow System" (should be "### 3.")
  • Line 43: "### 4. Add Components" (should stay as-is after fixing above)
 ### 2. Define your time horizon
 
 ```python
 # 24h period with hourly timesteps
 timesteps = pd.date_range('2024-01-01', periods=24, freq='h')

-### 2. Set Up the Flow System
+### 3. Set Up the Flow System


<details>
<summary>🤖 Prompt for AI Agents</summary>

In docs/home/quick-start.md around lines 25 to 43, the step headings are
misnumbered—there are two "### 2." headings and no "### 3."; update the second
"### 2. Set Up the Flow System" to "### 3. Set Up the Flow System" so the
sequence reads "### 2. Define your time horizon", "### 3. Set Up the Flow
System", then "### 4. Add Components"; ensure no other headings are affected and
keep surrounding content unchanged.


</details>

<!-- fingerprinting:phantom:medusa:ocelot -->

<!-- This is an auto-generated comment by CodeRabbit -->

Comment on lines +178 to +187
Default: equal weights, normalized to sum to 1.

=== "Period Weights"

Computed automatically from period index (interval sizes):

```python
periods = pd.Index([2020, 2025, 2030])
# → weights: [5, 5, 5] (5-year intervals)
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clarify status variable naming (CHP|on vs Component(Flow)|status).

Here you use model.variables['CHP|on'] for a binary on/off variable, while the migration guide’s naming table describes status variables as Component(Flow)|status / startup / shutdown. If the v6 API still exposes CHP|on, it’d be good to mention that explicitly in the naming-convention docs; if not, this example should be updated (e.g., to something like Boiler(Q_th)|status or whatever the canonical status variable is now).

Right now the discrepancy is likely to trip up users trying to reproduce this minimum-runtime pattern.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

182-182: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)

🤖 Prompt for AI Agents
In docs/user-guide/mathematical-notation/effects-and-dimensions.md around lines
178 to 187, the example uses model.variables['CHP|on'] which conflicts with the
migration guide naming (Component(Flow)|status / startup / shutdown); update the
example to use the current canonical status variable name (e.g.,
Boiler(Q_th)|status or ComponentName(Flow)|status) or explicitly note that v6
still exposes the legacy 'CHP|on' name if that is true. Modify the text so it
either replaces 'CHP|on' with the canonical status variable used in v6 examples,
or adds a short parenthetical clarifying legacy compatibility and pointing
readers to the migration naming table.

Comment on lines +75 to +95
```python
# Boiler
boiler = fx.linear_converters.Boiler(
label='boiler', eta=0.9,
Q_th=fx.Flow(label='heat', bus=heat_bus, size=100),
Q_fu=fx.Flow(label='fuel', bus=gas_bus),
)

# Heat Pump
hp = fx.linear_converters.HeatPump(
label='hp', COP=3.5,
P_el=fx.Flow(label='el', bus=elec_bus, size=100),
Q_th=fx.Flow(label='heat', bus=heat_bus),
)

# CHP
chp = fx.linear_converters.CHP(
label='chp', eta_el=0.35, eta_th=0.50,
P_el=fx.Flow(...), Q_th=fx.Flow(...), Q_fu=fx.Flow(...),
)
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find the linear_converters module
fd -t f "linear_converters.py" | head -20

Repository: flixOpt/flixopt

Length of output: 87


🏁 Script executed:

# Also check the example file structure
fd -t f "simple_example.py" | head -20

Repository: flixOpt/flixopt

Length of output: 95


🏁 Script executed:

# List files in the docs directory to confirm the file exists
find docs -name "LinearConverter.md" -type f

Repository: flixOpt/flixopt

Length of output: 124


🏁 Script executed:

# Get the actual Boiler, CHP, HeatPump class signatures from linear_converters.py
# First check the file size
wc -l flixopt/linear_converters.py

Repository: flixOpt/flixopt

Length of output: 91


🏁 Script executed:

# Read the linear_converters.py file to find the class definitions
cat -n flixopt/linear_converters.py | head -150

Repository: flixOpt/flixopt

Length of output: 6720


🏁 Script executed:

# Search for Boiler class definition
rg -A 20 "^class Boiler" flixopt/linear_converters.py

Repository: flixOpt/flixopt

Length of output: 1080


🏁 Script executed:

# Search for CHP class definition
rg -A 20 "^class CHP" flixopt/linear_converters.py

Repository: flixOpt/flixopt

Length of output: 1325


🏁 Script executed:

# Search for HeatPump class definition
rg -A 20 "^class HeatPump" flixopt/linear_converters.py

Repository: flixOpt/flixopt

Length of output: 2667


🏁 Script executed:

# Check the simple_example.py to see actual parameter names used
cat -n examples/01_Simple/simple_example.py | grep -A 10 -B 2 "Boiler\|CHP\|HeatPump" | head -60

Repository: flixOpt/flixopt

Length of output: 1711


🏁 Script executed:

# Read the exact doc file lines 75-95
sed -n '75,95p' docs/user-guide/mathematical-notation/elements/LinearConverter.md

Repository: flixOpt/flixopt

Length of output: 574


🏁 Script executed:

# Search for any property decorators that might alias the parameter names in linear_converters.py
rg "@property" flixopt/linear_converters.py -A 3

Repository: flixOpt/flixopt

Length of output: 870


🏁 Script executed:

# Check HeatPump's __init__ signature more completely
rg -A 15 "class HeatPump\(LinearConverter\):" flixopt/linear_converters.py

Repository: flixOpt/flixopt

Length of output: 981


🏁 Script executed:

# Let me check if there's any backward compatibility or alias support by looking at the __init__ method signatures more carefully
rg -A 25 "def __init__" flixopt/linear_converters.py | grep -A 25 "class HeatPump" | head -40

Repository: flixOpt/flixopt

Length of output: 41


🏁 Script executed:

# Check HeatPump __init__ full signature
sed -n '300,350p' flixopt/linear_converters.py

Repository: flixOpt/flixopt

Length of output: 2369


🏁 Script executed:

# Search for HeatPump class and its init more precisely
rg -B 5 -A 30 "class HeatPump\(" flixopt/linear_converters.py

Repository: flixOpt/flixopt

Length of output: 1733


Update convenience class examples in lines 75–95 to match actual parameter names.

The examples here use outdated parameter names that will cause runtime errors if copied verbatim:

Current doc (incorrect):

  • Boiler(..., eta=0.9, Q_th=..., Q_fu=...)
  • HeatPump(..., COP=3.5, P_el=..., Q_th=...)
  • CHP(..., eta_el=0.35, eta_th=0.50, P_el=..., Q_th=..., Q_fu=...)

Actual signatures in flixopt/linear_converters.py:

  • Boiler(thermal_efficiency, fuel_flow, thermal_flow)
  • HeatPump(cop, electrical_flow, thermal_flow) ← Note: lowercase cop
  • CHP(thermal_efficiency, electrical_efficiency, fuel_flow, electrical_flow, thermal_flow)

The working example in examples/01_Simple/simple_example.py confirms these are the correct parameter names. No aliases exist for the old short names. Update the three examples to use the actual parameter names as shown in the working example.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

75-75: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

🤖 Prompt for AI Agents
In docs/user-guide/mathematical-notation/elements/LinearConverter.md around
lines 75 to 95, the convenience class examples use outdated parameter names
(eta, COP, Q_th, Q_fu, P_el) that don't match the actual signatures; update the
Boiler, HeatPump, and CHP examples to call the constructors with the real
parameter names: Boiler(thermal_efficiency, fuel_flow, thermal_flow),
HeatPump(cop, electrical_flow, thermal_flow) using lowercase 'cop', and
CHP(thermal_efficiency, electrical_efficiency, fuel_flow, electrical_flow,
thermal_flow), preserving the same Flow(...) arguments but passing them in the
correct order/with correct parameter names to match flixopt/linear_converters.py
and the examples/01_Simple/simple_example.py.

Comment on lines +1145 to +1194
def heatmap_with_plotly_v2(
data: xr.DataArray,
colors: ColorType | None = None,
title: str = '',
facet_col: str | None = None,
animation_frame: str | None = None,
facet_col_wrap: int | None = None,
**imshow_kwargs: Any,
) -> go.Figure:
"""
Plot a heatmap using Plotly's imshow.
Data should be prepared with dims in order: (y_axis, x_axis, [facet_col], [animation_frame]).
Use reshape_data_for_heatmap() to prepare time-series data before calling this.
Args:
data: DataArray with 2-4 dimensions. First two are heatmap axes.
colors: Colorscale name ('viridis', 'plasma', etc.).
title: Plot title.
facet_col: Dimension name for subplot columns (3rd dim).
animation_frame: Dimension name for animation (4th dim).
facet_col_wrap: Max columns before wrapping (only if < n_facets).
**imshow_kwargs: Additional args for px.imshow.
Returns:
Plotly Figure object.
"""
if data.size == 0:
return go.Figure()

colors = colors or CONFIG.Plotting.default_sequential_colorscale
facet_col_wrap = facet_col_wrap or CONFIG.Plotting.default_facet_cols

imshow_args: dict[str, Any] = {
'img': data,
'color_continuous_scale': colors,
'title': title,
**imshow_kwargs,
}

if facet_col and facet_col in data.dims:
imshow_args['facet_col'] = facet_col
if facet_col_wrap < data.sizes[facet_col]:
imshow_args['facet_col_wrap'] = facet_col_wrap

if animation_frame and animation_frame in data.dims:
imshow_args['animation_frame'] = animation_frame

return px.imshow(**imshow_args)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check all references to heatmap_with_plotly_v2 in the codebase
echo "=== All references to heatmap_with_plotly_v2 ==="
rg -n "heatmap_with_plotly_v2" --type=py

echo ""
echo "=== Check if it's in __all__ or exported ==="
rg -n "__all__" flixopt/plotting.py

echo ""
echo "=== Check __init__.py for exports ==="
find . -name "__init__.py" -path "*/flixopt/*" | head -5 | xargs -I {} sh -c "echo '--- {} ---' && cat {}"

Repository: flixOpt/flixopt

Length of output: 3867


🏁 Script executed:

#!/bin/bash
# Check for heatmap_with_plotly function
echo "=== heatmap_with_plotly function (non-v2) ==="
rg -n "^def heatmap_with_plotly\b" --type=py -A 20

echo ""
echo "=== Check for other _v2 functions in plotting.py ==="
rg -n "def \w+_v2\(" flixopt/plotting.py

echo ""
echo "=== Check for _v2 pattern in entire codebase ==="
rg -n "_v2" --type=py | head -20

Repository: flixOpt/flixopt

Length of output: 2155


🏁 Script executed:

#!/bin/bash
# Get the complete test for heatmap_with_plotly_v2
echo "=== Complete test_heatmap_with_plotly_v2 ==="
sed -n '405,420p' tests/test_solution_and_plotting.py

echo ""
echo "=== Check reshape_data_for_heatmap function ==="
rg -n "^def reshape_data_for_heatmap" --type=py -A 15

echo ""
echo "=== Check if heatmap_with_plotly_v2 is documented in docstrings or comments ==="
rg -B 5 "heatmap_with_plotly_v2" flixopt/plotting.py | head -30

Repository: flixOpt/flixopt

Length of output: 2113


Clarify the purpose and naming of heatmap_with_plotly_v2.

This function provides a low-level heatmap interface for pre-processed data, but the naming and documentation lack clarity:

  1. Non-standard naming: _v2 is not a naming pattern used elsewhere in the project. The function is not a versioned replacement but rather a complementary lower-level utility distinct from heatmap_with_plotly.

  2. Unclear relationship to main API: The docstring mentions that data should be prepared with reshape_data_for_heatmap(), but doesn't explain when to use this function versus heatmap_with_plotly, which handles reshaping internally.

  3. Consider a more descriptive name: Options like heatmap_imshow or heatmap_plotly_imshow would better communicate that this is a simplified, direct wrapper around px.imshow for already-shaped data, making the API more self-documenting.

Comment on lines 1056 to 1078

for flow in self._fs.flows.values():
# Get bus label (could be string or Bus object)
bus_label = flow.bus
comp_label = flow.component.label_full

# start/end filtering based on flow direction
if flow.is_input_in_component:
# Flow goes: bus -> component, so start=bus, end=component
if starts and bus_label not in starts:
continue
if ends and comp_label not in ends:
continue
else:
# Flow goes: component -> bus, so start=component, end=bus
if starts and comp_label not in starts:
continue
if ends and bus_label not in ends:
continue

if components and comp_label not in components:
continue
matching_labels.append(flow.label_full)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find the Flow class definition
rg -n "class Flow" --type py -A 10

Repository: flixOpt/flixopt

Length of output: 4402


🏁 Script executed:

# Check the specific lines mentioned in the comment (1279-1287)
sed -n '1275,1290p' flixopt/statistics_accessor.py

Repository: flixOpt/flixopt

Length of output: 557


🏁 Script executed:

# Also check lines around 1056-1078 to see the full context
sed -n '1050,1085p' flixopt/statistics_accessor.py

Repository: flixOpt/flixopt

Length of output: 1661


🏁 Script executed:

# Search for how flow.component is used throughout the file
rg -n "flow\.component" flixopt/statistics_accessor.py -B 2 -A 2

Repository: flixOpt/flixopt

Length of output: 579


🏁 Script executed:

# Find the Flow class and check its component attribute
sed -n '322,500p' flixopt/elements.py | grep -n -A 2 -B 2 "component"

Repository: flixOpt/flixopt

Length of output: 983


🏁 Script executed:

# Search for component property definition in Flow class
rg -n "@property" flixopt/elements.py | head -20

Repository: flixOpt/flixopt

Length of output: 311


🏁 Script executed:

# Look at Flow class __init__ to see component definition
sed -n '322,600p' flixopt/elements.py

Repository: flixOpt/flixopt

Length of output: 13507


Remove .label_full from flow.component at line 1060.

In the Flow class, component is defined as a string attribute (self.component: str = 'UnknownComponent'), not a Component object. Line 1060 incorrectly calls .label_full on it (comp_label = flow.component.label_full), which will fail since strings don't have that method. Line 1280 correctly treats it as a string (comp_label = flow.component). Change line 1060 to comp_label = flow.component to match the actual type.

🤖 Prompt for AI Agents
In flixopt/statistics_accessor.py around lines 1056 to 1078, comp_label is
incorrectly set using flow.component.label_full even though Flow.component is a
string; change it to comp_label = flow.component (remove .label_full), keep the
rest of the start/end and components checks the same so they compare strings
consistently, and ensure matching_labels.append(flow.label_full) remains
unchanged.

Comment on lines +159 to +160
if not self._fs.connected_and_transformed:
self._fs.connect_and_transform()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent access to connected_and_transformed state.

Line 159 and 227 use the public property self._fs.connected_and_transformed, while line 414 uses the private attribute self._fs._connected_and_transformed. Additionally, line 415 calls _connect_network() instead of connect_and_transform().

This inconsistency could cause subtle bugs if the public property has additional logic. Consider using the public API consistently:

-        if not self._fs._connected_and_transformed:
-            self._fs._connect_network()
+        if not self._fs.connected_and_transformed:
+            self._fs.connect_and_transform()

Also applies to: 227-228, 414-415

🤖 Prompt for AI Agents
In flixopt/topology_accessor.py around lines 159-160, 227-228 and 414-415, the
code inconsistently reads the FS state via the private attribute
_connected_and_transformed and calls the private method _connect_network();
replace all uses of self._fs._connected_and_transformed with the public property
self._fs.connected_and_transformed and replace calls to
self._fs._connect_network() with the public API
self._fs.connect_and_transform(), ensuring the public property is checked before
calling the public method to preserve any encapsulation or side effects.

* Reverted the time index handling to keep the original solution with charge_state including the extra timestep. All other variables now have NaN on the final timestep
  instead of being truncated.

  Changes Made:

  1. flixopt/structure.py:191-206 - Removed the code that:
    - Extracted final charge states into separate |final variables
    - Reindexed the solution to regular timesteps
  2. tests/test_solution_and_plotting.py:78-80 - Removed the assertion expecting the Speicher|charge_state|final variable
  3. tests/test_integration.py:168-178 - Updated the charge_state expectation to include the extra timestep value (10.0 at the end)
  4. tests/conftest.py:695-718 and tests/deprecated/conftest.py:694-717 - Updated the assert_almost_equal_numeric function to handle the extra timestep by trimming
  arrays when the extra values are NaN (for non-charge_state variables)

  Result:

  - charge_state variables now include len(timesteps) + 1 values, with the final value being the actual final charge state
  - All other time-indexed variables have len(timesteps) + 1 values with NaN at the final timestep (due to xarray coordinate merging)
  - All 1329 tests pass

* Verify that time index always has extra timestep in solution!

* fixup test

* Update new tests for nan as last timestep

* Update some comments and add warnings in tests for nan handling

* Update docstring

* bugfix: revert
Improve example notebooks

Fix resampling

Re-Connect FLow_system when loading from dataset
* Change default size to None. Raise if not set and Status is used

* Make maximum size mandatory (or fixed size). Update tests accordingly

* Adding maximum sizes for FLows which get Status variables (also if only through component status

* Update more tests

* BUGFIX: Minimum or foixed size in storages

* Added validation:

  5. Component with status_parameters now validates that all flows have explicit sizes

* In flixopt/elements.py - Flow._plausibility_checks():
  - relative_minimum > 0 requires size (lines 585-590)
  - relative_maximum < 1 requires size (lines 592-596)
  - load_factor_min requires size (lines 598-603)
  - load_factor_max requires size (lines 605-609)

* Added more validations

* Fix test

* Fix tests to specify size if needed

* Improve check verbosity

* Fix type hint

* Improve conftest.py

* Added more notebooks

* Improve notebooks

* Improve plotting of Piecewises

* Improve notebooks

* Streamline notebook

* Remove old notebook

* fix notebook

* fix notebook script

* Extract PlotResults class into separate module and reuse

* Improve notebook

* Add carrier units and effect units to accessors

* Add carrier units and effect units to notebooks

* Add carrier units and effect units to notebooks

* Decrease notebook font size

* Fix tests

* Fix docs
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

♻️ Duplicate comments (3)
docs/user-guide/index.md (1)

68-68: API Reference link issue already flagged.

This broken link to ../api-reference/index.md was identified in a previous review. See the past review comments for details and proposed fixes.

docs/user-guide/migration-guide-v5.md (1)

23-27: Inconsistent deprecation status between sections.

Line 27 states the Optimization class "is removed", but the deprecation timeline at lines 415-416 shows both Optimization and Results as "deprecated" in v5.0.0, and the warning box at lines 418-422 says they "will be removed in a future version."

This creates confusion: is Optimization removed or deprecated in v5.0.0?

Suggested fix - align the messaging:

-The `Optimization` class is removed. Use `FlowSystem.optimize()` directly.
+The `Optimization` class is **deprecated** and will be removed in v6.0.0. Use `FlowSystem.optimize()` directly.

Or alternatively, update the timeline table to reflect that it's removed:

 | v4.x | `Optimization` and `Results` classes available |
-| v5.0.0 | `Optimization` and `Results` deprecated, new API available |
+| v5.0.0 | `Optimization` removed, `Results` deprecated, new API available |
flixopt/flow_system.py (1)

1134-1134: Example in docstring uses incorrect method name.

The example calls fs.add_carrier(biogas) but the method is add_carriers. This was already flagged in a past review.

-            fs.add_carrier(biogas)
+            fs.add_carriers(biogas)
🧹 Nitpick comments (21)
docs/stylesheets/extra.css (3)

508-518: Conflicting guidance: comment says “remove this rule” but rule is still applied (hero-section).
Line 509-510 says to remove a rule due to TOC conflict, yet .hero-section is still being constrained/centered (Line 511-518). Either delete the obsolete comment or actually gate/disable the conflicting rule so future edits don’t “fix” the wrong thing.

-/* Center home page content when navigation is hidden */
-/* Remove this rule as it conflicts with TOC layout */
+/* Home page hero-section: centered and width-constrained */

766-784: color-swatch utility: consider sizing/contrast and inline alignment edge cases.
1em × 1em + border/shadow is fine, but if swatches are used next to small text (you set several areas to ~0.55–0.75rem), it can become tiny; consider min-width/min-height or inline-flex + vertical-align: text-bottom for better baseline alignment.


952-973: Use a more specific selector to target only social icons in the footer.

The current rule hides .md-footer-meta entirely. In this project's Material theme setup, this container holds only the social media links (GitHub, PyPI) from extra.social. Using a more specific selector like .md-footer-meta__inner .md-social would clarify the intent and provide better specificity if footer metadata is ever needed elsewhere.

-.md-footer-meta {
-  display: none;
-}
+/* Hide only the social media icons in footer */
+.md-footer-meta__inner .md-social {
+  display: none;
+}
flixopt/color_processing.py (1)

18-67: The ColorType “docstring” is a no-op string literal; consider moving it into the module docstring or using Sphinx-style variable docs.
As written, the triple-quoted block after ColorType = ... is just an unassigned string statement; many linters treat this as dead code, and doc tooling may not associate it with ColorType.

@@
-# Type alias for flexible color input
-ColorType = str | list[str] | dict[str, str]
-"""Flexible color specification type supporting multiple input formats for visualization.
+"""Simplified color handling for visualization.
+
+ColorType
+---------
+Flexible color specification type supporting multiple input formats for visualization.
@@
-"""
+"""
+
+# Type alias for flexible color input
+ColorType = str | list[str] | dict[str, str]
.github/workflows/docs.yaml (1)

3-23: PR docs build trigger likely too narrow (misses flixopt/ changes on PRs).**
On pull_request you only trigger for docs/** and mkdocs.yml. If API/docstrings are pulled from flixopt/** (or notebooks import code), doc builds can break without CI catching it.

docs/notebooks/05-multi-carrier-system.ipynb (1)

167-173: Brittle result access via hard-coded labels ('CHP(P_el)', 'GridBuy(Electricity)').
This will break easily if labeling/formatting changes. Prefer programmatic discovery (e.g., by element/flow objects) if the library supports it.

Also applies to: 281-310

docs/user-guide/building-models/choosing-components.md (1)

39-44: Snippets omit required surrounding context (Effects / FlowSystem) — consider adding minimal “full example” once.
Many snippets use effects_per_flow_hour without showing effect creation and objective selection. A single short “complete runnable” example near the top would reduce confusion.

Also applies to: 66-78, 120-150

docs/notebooks/10-transmission.ipynb (1)

248-271: Avoid relying on stringly-typed solution[...] keys for flows/sizes.
'Pipe_AB(from_A)|flow_rate' / '...|size' is brittle. If there’s a supported accessor via statistics or element/flow references, that will survive renames.

Also applies to: 317-337

flixopt/io.py (1)

732-828: Add tests for legacy dataset conversion, specifically for conversion_factors and piecewises structures.

Tests exist for basic conversion and nested attrs with __class__ renames and StatusParameters field renames. However, there's no explicit test coverage for the behavior that flow labels inside conversion_factors and piecewises structures should NOT be renamed (e.g., Q_fu, P_el keys should remain unchanged inside these structures). The implementation correctly handles this via skip_flow_renames, but add a test case to verify this behavior doesn't regress.

docs/notebooks/06a-time-varying-parameters.ipynb (1)

196-196: Consider splitting large code cell for readability.

The code cell at line 196 contains the entire FlowSystem construction and optimization in a single cell (carriers, buses, effects, components, and optimization call). For educational notebooks, consider splitting this into:

  1. FlowSystem and carrier setup
  2. Adding buses and effects
  3. Adding components (Source, Converter, Sink)
  4. Running optimization

This would make it easier for learners to understand each step and would allow for intermediate inspection points.

Example structure:

# Cell 1: Create FlowSystem and carriers
flow_system = fx.FlowSystem(timesteps)
flow_system.add_carriers(
    fx.Carrier('electricity', '#f1c40f', 'kW'),
    fx.Carrier('heat', '#e74c3c', 'kW'),
)

# Cell 2: Add buses and effects
flow_system.add_elements(
    fx.Bus('Electricity', carrier='electricity'),
    fx.Bus('Heat', carrier='heat'),
    fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True),
)

# Cell 3: Add components
flow_system.add_elements(
    fx.Source('Grid', ...),
    fx.LinearConverter('HeatPump', ...),  # Key: time-varying COP here
    fx.Sink('Building', ...),
)

# Cell 4: Optimize
flow_system.optimize(fx.solvers.HighsSolver())
docs/user-guide/results-plotting.md (1)

350-354: Wording/link mismatch: “Carriers” links to Carrier.
Either change the visible text to singular, or link to the container type if that’s what you intend.

-[`Carriers`][flixopt.carrier.Carrier] define energy or material types with associated colors. Built-in carriers are available in `CONFIG.Carriers`:
+[`Carrier`][flixopt.carrier.Carrier] defines an energy or material type with an associated color. Built-in carriers are available in `CONFIG.Carriers`:
docs/notebooks/03-investment-optimization.ipynb (1)

70-96: Clarify fixed_relative_profile semantics (relative vs absolute) in the narrative.
Right now the notebook feeds kW-ish absolute values as a “relative profile” (with size=1), which can confuse readers new to flixOpt. A short note like “we use size=1 so the profile is interpreted in kW for this example” would help.

Also applies to: 169-175

docs/notebooks/02-heat-system.ipynb (1)

45-78: Implicit unit assumption: sum(kW)kWh only because timestep is hourly.
Given this is a teaching notebook, consider a one-liner noting “hourly resolution” is why heat_demand.sum() is energy.

Also applies to: 253-265

docs/notebooks/01-quickstart.ipynb (1)

106-132: Optional: add carriers (or a note) to align with later color/plotting docs.
Since later guides emphasize carrier-based colors, quickstart could either set fx.Bus(..., carrier=...) or explicitly say it’s omitted for minimalism.

docs/user-guide/core-concepts.md (1)

249-265: Inconsistent API usage: solve() vs optimize().

The advanced section uses flow_system.solve() on line 264, but earlier examples (line 144) use flow_system.optimize(). The migration guide also shows optimize() as the primary method. This inconsistency could confuse users.

Based on the pattern shown earlier in this file and in the migration guide, the two-step workflow should use solve() after build_model(), which appears correct. However, clarify that optimize() = build_model() + solve() for users who may be confused.

Consider adding a brief comment to clarify the relationship:

 # Build the model (without solving)
 flow_system.build_model()
 
 # Access the linopy model
 model = flow_system.model
 
 # Add custom constraints using linopy API
 model.add_constraints(...)
 
-# Then solve
+# Then solve (note: optimize() = build_model() + solve())
 flow_system.solve(fx.solvers.HighsSolver())
flixopt/components.py (2)

519-546: Review capacity-related validation logic for clarity.

The validation logic at lines 519-546 skips capacity checks when capacity_in_flow_hours is None, then performs complex checks for investment-related scenarios. Consider the following:

  1. Lines 531-545: The validation for initial_charge_state constraining investment decisions is complex. The error messages are helpful, but the logic could benefit from inline comments explaining the constraint derivation.

  2. Potential edge case: When capacity_in_flow_hours is None but initial_charge_state is numeric and non-zero, should that be flagged as inconsistent? Currently, this is allowed.

Consider adding clarifying comments:

         # Skip capacity-related checks if capacity is None (unbounded)
         if self.capacity_in_flow_hours is not None:
             # Use new InvestParameters methods to get capacity bounds
             if isinstance(self.capacity_in_flow_hours, InvestParameters):
                 minimum_capacity = self.capacity_in_flow_hours.minimum_or_fixed_size
                 maximum_capacity = self.capacity_in_flow_hours.maximum_or_fixed_size
             else:
                 maximum_capacity = self.capacity_in_flow_hours
                 minimum_capacity = self.capacity_in_flow_hours
 
+            # Initial charge state should not constrain investment decision.
+            # The initial value must be feasible across the entire investment range:
+            # - If initial > (min_cap * rel_max), the optimizer is forced to increase capacity
+            # - If initial < (max_cap * rel_min), the optimizer is forced to decrease capacity
             min_initial_at_max_capacity = maximum_capacity * self.relative_minimum_charge_state.isel(time=0)
             max_initial_at_min_capacity = minimum_capacity * self.relative_maximum_charge_state.isel(time=0)

763-767: Review dynamic StatusParameters creation for absolute losses.

When absolute_losses is non-zero, StatusParameters are dynamically created and linked for flows that don't already have them. This automatic behavior might be unexpected to users.

Consider whether this should be documented more prominently in the Transmission docstring, as it introduces binary variables that affect computational performance.

Add a note in the Transmission docstring (around line 679) explicitly stating:

-        Absolute losses force the creation of binary on/inactive variables, which increases
+        Absolute losses automatically add StatusParameters to flows that don't have them, creating binary on/off variables. This increases
         computational complexity but enables realistic modeling of equipment with
         standby power consumption.
flixopt/elements.py (3)

264-276: Review deprecated kwarg handling and carrier lowercasing.

Lines 270-273 handle a deprecated excess_penalty_per_flow_hour parameter, and line 274 lowercases the carrier string. A few observations:

  1. Line 274: self.carrier = carrier.lower() if carrier else None - This silently lowercases user input. While this may be intentional for case-insensitive matching, it could be surprising if users expect case preservation.

  2. Lines 270-273: The _handle_deprecated_kwarg call suggests backward compatibility, but the implementation and deprecation warnings aren't visible in this file.

Consider adding a note in the Bus docstring about carrier name normalization:

    carrier: Name of the energy/material carrier type (e.g., 'electricity', 'heat', 'gas').
        Carriers are registered via ``flow_system.add_carrier()`` or available as
        predefined defaults in CONFIG.Carriers. Used for automatic color assignment in plots.
+       Carrier names are normalized to lowercase for case-insensitive matching.

611-622: Review warning logic for status_parameters combinations.

Two warnings are issued for potentially problematic configurations:

  1. Lines 611-615: Warning when both fixed_relative_profile and status_parameters are used. The message states the flow can be "switched active and inactive, effectively differing from the fixed_flow_rate." This might be confusing—does it mean the profile can be scaled down to zero, or that the profile itself can be violated?

  2. Lines 617-622: Warning when relative_minimum > 0 without status_parameters. This suggests the flow can never be inactive (flow_rate = 0), which is correct.

Consider clarifying the first warning message:

         if self.fixed_relative_profile is not None and self.status_parameters is not None:
             logger.warning(
-                f'Flow {self.label_full} has both a fixed_relative_profile and status_parameters.'
-                f'This will allow the flow to be switched active and inactive, effectively differing from the fixed_flow_rate.'
+                f'Flow {self.label_full} has both a fixed_relative_profile and status_parameters. '
+                f'The status can switch the flow off (flow_rate = 0), allowing deviations from the fixed profile. '
+                f'Consider removing status_parameters if the profile must always be followed exactly.'
             )

1006-1020: Review dynamic StatusParameters creation in ComponentModel.

Similar to the Transmission component, ComponentModel dynamically creates StatusParameters for flows when:

  1. The component has status_parameters (lines 1006-1012)
  2. Flows are in prevent_simultaneous_flows (lines 1014-1020)

This automatic creation:

  • Is convenient for users
  • Introduces binary variables that may not be obvious
  • Happens during modeling rather than initialization

Consider whether this behavior should be documented in the Component docstring to make users aware that binary variables will be added to their flows.

Would you like me to draft updated docstring text for the Component class that explains this automatic StatusParameters propagation behavior?

flixopt/flow_system.py (1)

820-820: Redundant local import of json.

json is already imported at module level (line 7). Remove the local import.

-        import json
-
         from flixopt.io import convert_old_dataset, load_dataset_from_netcdf
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0202b9 and 2c2ace1.

📒 Files selected for processing (48)
  • .github/workflows/docs.yaml (1 hunks)
  • .pre-commit-config.yaml (1 hunks)
  • docs/examples/00-Minimal Example.md (0 hunks)
  • docs/examples/01-Basic Example.md (0 hunks)
  • docs/examples/02-Complex Example.md (0 hunks)
  • docs/examples/03-Optimization Modes.md (0 hunks)
  • docs/examples/04-Scenarios.md (0 hunks)
  • docs/examples/05-Two-stage-optimization.md (0 hunks)
  • docs/examples/index.md (0 hunks)
  • docs/home/citing.md (1 hunks)
  • docs/home/installation.md (1 hunks)
  • docs/home/license.md (1 hunks)
  • docs/home/quick-start.md (1 hunks)
  • docs/index.md (3 hunks)
  • docs/notebooks/01-quickstart.ipynb (1 hunks)
  • docs/notebooks/02-heat-system.ipynb (1 hunks)
  • docs/notebooks/03-investment-optimization.ipynb (1 hunks)
  • docs/notebooks/04-operational-constraints.ipynb (1 hunks)
  • docs/notebooks/05-multi-carrier-system.ipynb (1 hunks)
  • docs/notebooks/06a-time-varying-parameters.ipynb (1 hunks)
  • docs/notebooks/07-scenarios-and-periods.ipynb (1 hunks)
  • docs/notebooks/08-large-scale-optimization.ipynb (1 hunks)
  • docs/notebooks/10-transmission.ipynb (1 hunks)
  • docs/notebooks/data/generate_example_systems.py (1 hunks)
  • docs/notebooks/index.md (1 hunks)
  • docs/overrides/main.html (1 hunks)
  • docs/stylesheets/extra.css (3 hunks)
  • docs/user-guide/building-models/choosing-components.md (1 hunks)
  • docs/user-guide/building-models/index.md (1 hunks)
  • docs/user-guide/core-concepts.md (1 hunks)
  • docs/user-guide/index.md (1 hunks)
  • docs/user-guide/migration-guide-v5.md (1 hunks)
  • docs/user-guide/optimization/index.md (1 hunks)
  • docs/user-guide/results-plotting.md (1 hunks)
  • docs/user-guide/results/index.md (1 hunks)
  • docs/user-guide/support.md (1 hunks)
  • examples/05_Two-stage-optimization/two_stage_optimization.py (5 hunks)
  • flixopt/__init__.py (3 hunks)
  • flixopt/clustering.py (4 hunks)
  • flixopt/color_processing.py (2 hunks)
  • flixopt/components.py (23 hunks)
  • flixopt/config.py (9 hunks)
  • flixopt/effects.py (6 hunks)
  • flixopt/elements.py (27 hunks)
  • flixopt/features.py (4 hunks)
  • flixopt/flow_system.py (24 hunks)
  • flixopt/interface.py (12 hunks)
  • flixopt/io.py (2 hunks)
💤 Files with no reviewable changes (7)
  • docs/examples/00-Minimal Example.md
  • docs/examples/03-Optimization Modes.md
  • docs/examples/02-Complex Example.md
  • docs/examples/index.md
  • docs/examples/05-Two-stage-optimization.md
  • docs/examples/04-Scenarios.md
  • docs/examples/01-Basic Example.md
✅ Files skipped from review due to trivial changes (3)
  • docs/notebooks/index.md
  • docs/home/installation.md
  • docs/user-guide/support.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • docs/home/citing.md
  • docs/home/license.md
  • docs/home/quick-start.md
  • docs/user-guide/optimization/index.md
🧰 Additional context used
🧬 Code graph analysis (5)
docs/notebooks/data/generate_example_systems.py (8)
flixopt/flow_system.py (7)
  • FlowSystem (48-2204)
  • add_elements (1063-1111)
  • scenario_weights (1834-1841)
  • scenario_weights (1844-1865)
  • to_netcdf (719-744)
  • solution (1345-1362)
  • solution (1365-1368)
flixopt/carrier.py (1)
  • Carrier (14-125)
flixopt/elements.py (2)
  • Bus (196-316)
  • Flow (333-647)
flixopt/effects.py (1)
  • Effect (34-306)
flixopt/linear_converters.py (2)
  • Boiler (24-111)
  • HeatPump (211-302)
flixopt/interface.py (4)
  • PiecewiseConversion (243-607)
  • Piecewise (86-239)
  • Piece (29-82)
  • InvestParameters (956-1279)
flixopt/structure.py (4)
  • scenario_weights (220-243)
  • to_netcdf (894-921)
  • solution (170-209)
  • solution (1109-1123)
flixopt/solvers.py (1)
  • HighsSolver (67-89)
flixopt/config.py (1)
flixopt/carrier.py (1)
  • Carrier (14-125)
flixopt/components.py (4)
flixopt/interface.py (12)
  • InvestParameters (956-1279)
  • StatusParameters (1283-1534)
  • link_to_flow_system (231-235)
  • link_to_flow_system (463-467)
  • link_to_flow_system (817-822)
  • link_to_flow_system (1170-1174)
  • transform_data (79-82)
  • transform_data (237-239)
  • transform_data (469-471)
  • transform_data (824-827)
  • transform_data (1176-1239)
  • transform_data (1487-1510)
flixopt/elements.py (8)
  • link_to_flow_system (113-122)
  • link_to_flow_system (284-291)
  • link_to_flow_system (523-532)
  • transform_data (124-129)
  • transform_data (293-296)
  • transform_data (534-565)
  • label_full (637-638)
  • status (890-894)
flixopt/structure.py (8)
  • link_to_flow_system (354-391)
  • flow_system (394-412)
  • prefix (346-348)
  • _sub_prefix (350-352)
  • transform_data (330-343)
  • _fit_coords (414-427)
  • label_full (1105-1106)
  • label_full (1651-1652)
flixopt/core.py (1)
  • PlausibilityError (22-25)
flixopt/effects.py (2)
flixopt/elements.py (7)
  • link_to_flow_system (113-122)
  • link_to_flow_system (284-291)
  • link_to_flow_system (523-532)
  • label_full (637-638)
  • transform_data (124-129)
  • transform_data (293-296)
  • transform_data (534-565)
flixopt/structure.py (8)
  • link_to_flow_system (354-391)
  • flow_system (394-412)
  • prefix (346-348)
  • label_full (1105-1106)
  • label_full (1651-1652)
  • transform_data (330-343)
  • _fit_coords (414-427)
  • _fit_effect_coords (429-447)
flixopt/clustering.py (2)
flixopt/plot_result.py (3)
  • PlotResult (20-143)
  • show (55-58)
  • update (60-73)
flixopt/topology_accessor.py (1)
  • plot (289-455)
🪛 LanguageTool
docs/user-guide/core-concepts.md

[style] ~17-~17: Consider an alternative for the overused word “exactly”.
Context: ...hile meeting all heat demands. This is exactly the kind of problem flixOpt solves. Let...

(EXACTLY_PRECISELY)


[style] ~98-~98: Using many exclamation marks might seem excessive (in this case: 18 exclamation marks for a text that’s 7151 characters long)
Context: ...imize), others can be constraints. !!! example "In our heating system" - *...

(EN_EXCESSIVE_EXCLAMATION)

docs/user-guide/migration-guide-v5.md

[style] ~428-~428: Using many exclamation marks might seem excessive (in this case: 19 exclamation marks for a text that’s 6900 characters long)
Context: ... success "Welcome to the new flixopt API! 🎉"

(EN_EXCESSIVE_EXCLAMATION)

docs/user-guide/results-plotting.md

[style] ~3-~3: Consider a different adjective to strengthen your wording.
Context: ...ss to common plots while still allowing deep customization. ## The Plot Accessor A...

(DEEP_PROFOUND)

docs/user-guide/results/index.md

[style] ~188-~188: Using many exclamation marks might seem excessive (in this case: 6 exclamation marks for a text that’s 2702 characters long)
Context: ...ne flow_system.topology.stop_app() ``` !!! note "Optional Dependencies" The in...

(EN_EXCESSIVE_EXCLAMATION)

🪛 markdownlint-cli2 (0.18.1)
docs/user-guide/migration-guide-v5.md

165-165: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


202-202: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


257-257: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


284-284: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


332-332: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
  • GitHub Check: Build documentation
🔇 Additional comments (51)
docs/overrides/main.html (1)

1-11: Good template structure and safe Jinja2 usage.

The template correctly extends the base template, uses conditional rendering to guard against undefined nb_url, and leverages {{ super() }} to preserve parent block content. The auto-escaping of Jinja2 variables protects against XSS risks.

docs/stylesheets/extra.css (2)

596-602: architecture-section now centered + max-width: good, but ensure it doesn’t double-constrain nested content.
With max-width: 1200px on .architecture-section plus global .md-content__inner { max-width: 1300px !important; }, confirm the section renders as intended (no awkward side padding/line lengths) across breakpoints.


786-947: [Rewritten review comment]
[Classification tag]

docs/notebooks/05-multi-carrier-system.ipynb (1)

103-107: Nice: deterministic randomness.
Seeding before adding noise keeps the notebook outputs stable.

docs/user-guide/building-models/choosing-components.md (1)

289-306: StatusParameters field names need verification against current API.

The code snippet uses effects_per_switch_on, on_hours_min, and off_hours_min, but these may be outdated. Verify against the current StatusParameters definition in the codebase and update to the correct field names if they have been renamed (e.g., to effects_per_startup, active_hours_min, min_downtime).

examples/05_Two-stage-optimization/two_stage_optimization.py (3)

43-46: LGTM! Carrier parameters improve visualization.

Adding carrier parameters to Bus constructors enables automatic color assignment in plots, improving the user experience. This is a clean enhancement that aligns with the new Carrier system introduced in the PR.


62-62: LGTM! Improved terminology for state-based parameters.

The rename from OnOffParameters to StatusParameters and the parameter updates (effects_per_startup, min_uptime, min_downtime) make the API more intuitive and aligned with standard industry terminology.

Also applies to: 69-69


138-148: LGTM! Simplified solution access pattern.

The shift to accessing solutions directly via flow_system.solution and using flow_system.statistics.sizes provides a cleaner, more intuitive API compared to the previous results.solution pattern. The size comparison logic using to_dataarray() is appropriate for xarray-based validation.

flixopt/config.py (2)

33-33: LGTM! Reasonable deprecation timeline.

Setting DEPRECATION_REMOVAL_VERSION to '6.0.0' for the v5.0.0 release provides users with a full major version cycle to migrate away from deprecated APIs, following good deprecation policy practices.


598-606: Class-level import is unconventional but not problematic.

The from .carrier import Carrier import at line 598 is placed inside the class definition because the Carrier class is needed both for type annotations (: Carrier) and for creating instances immediately after. Since carrier.py has no imports from config.py, there is no circular import risk.

The import could be moved to the module level for consistency with Python conventions, but the current placement is functional and poses no issues with type checking or IDE support. The pattern is acceptable as-is.

flixopt/interface.py (3)

1283-1286: LGTM! StatusParameters terminology is more intuitive.

The rename from OnOffParameters to StatusParameters better reflects the purpose of modeling equipment operational states. The updated parameter names (effects_per_startup, min_uptime, min_downtime, etc.) are clearer and more aligned with standard terminology in unit commitment problems.


1177-1182: LGTM! Helpful validation for InvestParameters.

The new validation ensuring either fixed_size or maximum_size is set prevents silent failures and provides a clear, actionable error message. This improves the user experience by catching configuration errors early in the model setup process.


473-607: LGTM! Well-implemented visualization for PiecewiseConversion.

The plot() method provides comprehensive visualization capabilities:

  • Proper validation of FlowSystem connection state
  • Smart handling of time-varying vs static data
  • Good use of faceting for multiple flows
  • Appropriate colorscale application
  • Returns PlotResult for further customization

The implementation follows the patterns established in the plotting module and provides valuable debugging/analysis capabilities.

docs/user-guide/building-models/index.md (1)

1-378: LGTM! Excellent educational documentation.

This comprehensive guide provides:

  • Clear step-by-step model building process
  • Well-organized component selection guidance
  • Practical patterns and examples
  • Helpful debugging tips
  • Good use of tables and structured information

The examples correctly use the updated API (carriers, StatusParameters, etc.) and align with the broader changes in this PR.

docs/user-guide/results/index.md (1)

1-283: LGTM! Comprehensive results analysis documentation.

This guide provides excellent coverage of:

  • Multiple access patterns (solution, statistics, element-specific)
  • Clear property descriptions with examples
  • Visualization methods and customization
  • Network topology visualization options
  • Save/load workflows
  • xarray integration patterns

The examples correctly demonstrate the new FlowSystem-centric API and statistics accessor patterns introduced in this PR.

docs/notebooks/06a-time-varying-parameters.ipynb (1)

1-339: LGTM! Excellent educational notebook on time-varying parameters.

This notebook effectively demonstrates:

  • Real-world use case (temperature-dependent heat pump COP)
  • Data preparation and visualization
  • Time-varying conversion factors using arrays
  • Result analysis and validation
  • Clear explanation of when to use this approach vs alternatives

The code examples correctly use the updated API and provide good learning value.

docs/notebooks/03-investment-optimization.ipynb (2)

18-33: Nice: clean notebook state (no outputs, null execution counts) + deterministic randomness.
This is ideal for docs notebooks in-repo.


169-175: No API issues found—parameter names and accessor are correct.

The notebook correctly uses effects_of_investment_per_size (current parameter name, as confirmed in InvestParameters.__init__) and statistics.sizes (confirmed as a standard accessor). The deprecation mapping in io.py shows these are the current names; older names like specific_effects map to effects_of_investment_per_size. No updates needed.

docs/notebooks/01-quickstart.ipynb (1)

99-133: Tutorial flow matches the new API surface (FlowSystem-first + statistics.plot).
Reads cleanly and is consistent with the rest of the docs set.

docs/user-guide/results-plotting.md (1)

21-25: The PlotResult cross-reference target is valid. The class PlotResult exists at flixopt/plot_result.py:20 and the mkdocs_autorefs target [flixopt.plot_result.PlotResult] will resolve correctly in the docs build.

docs/notebooks/02-heat-system.ipynb (1)

147-153: No action needed—fx.Storage() parameter names match the v5 API.

Both capacity_in_flow_hours and minimal_final_charge_state are correct and will not cause failures. The parameters are properly defined in the current Storage class signature and used consistently throughout the codebase.

docs/user-guide/core-concepts.md (2)

1-17: Well-structured introduction to core concepts.

The scenario-driven approach using a district heating example is excellent for onboarding users. The framing clearly establishes the problem domain and optimization goal.


38-43: Verify the link to Color Management section exists.

Line 43 references results-plotting.md#color-management. Ensure this anchor exists in the target file.

docs/notebooks/08-large-scale-optimization.ipynb (3)

23-34: Unused import: xarray appears unused.

The xr import (xarray) at line 29 is used later in the notebook for creating the profiles Dataset (line 106), so this is actually correct.


136-136: The build_system function is well-designed for demonstrating optimization techniques.

The function creates a realistic district heating system with investment optimization. Good use of:

  • Carriers for semantic organization
  • imbalance_penalty_per_flow_hour to prevent infeasibilities with fixed sizes
  • Investment parameters on multiple components

One minor observation: the function is defined inline in a single cell (line 136 contains the entire function). This is fine for a notebook but makes the code harder to read in the raw JSON format.


362-397: Documentation tables and workflow guidance are clear and actionable.

The "When to Use Each Technique" table and code examples provide excellent guidance for users deciding between optimization approaches.

docs/user-guide/migration-guide-v5.md (3)

161-194: Excellent variable naming convention documentation.

The pattern reference tables clearly document the new naming convention. This will significantly help users migrate their code.


249-276: Good migration path for old result files with clear limitations.

The FlowSystem.from_old_results() method and its documented limitations provide a pragmatic migration path while setting appropriate expectations.


382-407: Transform accessor migration is clearly documented.

The side-by-side comparison of old vs new patterns and the important note about solution being dropped are helpful for users.

docs/notebooks/data/generate_example_systems.py (5)

19-24: Robust fallback for notebook execution context.

The try/except block handling NameError when __file__ is unavailable (e.g., in mkdocs-jupyter) is a good defensive pattern.


26-83: Well-structured simple system example.

The create_simple_system function creates a realistic heating system with appropriate parameters. Good use of seeded random for reproducibility.


159-187: CHP with piecewise efficiency is a valuable advanced example.

The piecewise conversion definition demonstrates varying efficiency at different load points, which is a common real-world characteristic. The status_parameters usage aligns with the PR's rename from on_off_parameters.


232-314: Multiperiod system demonstrates advanced features well.

The create_multiperiod_system function effectively demonstrates:

  • Period definitions with rising gas prices
  • Scenario definitions with weighted probabilities
  • DataFrame-based demand profiles matching scenario columns
  • Investment optimization within multiperiod context

317-341: No action needed. The solution["objective"] key is valid and correctly used. The solution Dataset explicitly includes an "objective" key (set in structure.py line 173), which is distinct from effect labels. The access pattern fs.solution["objective"].item() matches the established usage throughout the codebase.

docs/notebooks/07-scenarios-and-periods.ipynb (4)

1-8: Clear introduction to multi-dimensional planning.

The notebook introduction effectively frames the learning objectives: periods, scenarios, scenario weights, and multi-dimensional data handling.


96-134: Scenario-dependent demand profiles are well-constructed.

Good use of:

  • Seeded random for reproducibility
  • Clipping to ensure realistic bounds
  • DataFrame with scenario column names matching the scenario Index

432-443: Potential shape mismatch in CHP size comparison.

Line 440 uses chp_size.max("scenario") to get a scalar for comparison, but line 442 computes (chp_size - chp_size_mild) where chp_size may have scenario dimension and chp_size_mild is from a single-scenario system. xarray should handle broadcasting, but the output message may be confusing if dimensions differ.

Consider explicitly selecting matching dimensions or clarifying the comparison:

# More explicit comparison
both_scenarios_size = chp_size.max("scenario").item()
mild_only_size = chp_size_mild.item()
print(f'CHP size (both scenarios): {both_scenarios_size:.2f} kW')
print(f'CHP size (mild only): {mild_only_size:.2f} kW')
print(f'\nPlanning for uncertainty adds {both_scenarios_size - mild_only_size:.2f} kW capacity')

466-526: Comprehensive "Key Concepts" summary section.

The summary effectively recaps:

  • Multi-dimensional FlowSystem initialization
  • Dimension-varying parameter shapes
  • Expected cost minimization formula
  • Selection methods for analysis

This provides excellent reference material for users.

flixopt/__init__.py (1)

17-18: LGTM! Public API surface updates are well-structured.

The additions of Carrier, CarrierContainer, StatusParameters, and PlotResult to the public API are properly imported and exported through __all__. These changes align with the PR's goal of introducing a Carrier-based visualization system and Status-based terminology.

Also applies to: 32-32, 34-34, 39-40, 56-56, 62-62

flixopt/clustering.py (1)

146-212: LGTM! Plot method successfully migrated to PlotResult pattern.

The updated signature and implementation properly:

  • Returns PlotResult for method chaining (.show(), .to_html(), etc.)
  • Builds an xarray Dataset with both 'original' and 'aggregated' data for export
  • Respects CONFIG.Plotting.default_show when show=None
  • Removes the save parameter in favor of PlotResult's .to_html() and .to_image() methods

This aligns with the PlotResult pattern shown in flixopt/plot_result.py and flixopt/topology_accessor.py.

flixopt/effects.py (3)

240-245: LGTM! Consistent with link_to_flow_system pattern.

The new link_to_flow_system method correctly uses self.label_full as the prefix (as documented: "Elements use their label_full as prefix by default"). This aligns with the pattern shown in flixopt/elements.py and flixopt/structure.py.


247-290: LGTM! Transform data now uses internal prefix.

The signature change from transform_data(self, name_prefix: str = '') to transform_data(self) is correct. The method now uses self.prefix (set by link_to_flow_system) for all coordinate naming, which is consistent with the broader refactoring across Interface-based classes.


484-493: Effect objects are no longer accepted in effect specifications—use label strings instead.

The helper function now raises TypeError if an Effect object is passed instead of a label string. This is an intentional breaking change reflected in the error message: "Effect objects are no longer accepted when specifying EffectValues. Use the label string instead."

flixopt/features.py (1)

152-348: LGTM! Consistent OnOff → Status terminology migration.

The renaming from OnOffModel to StatusModel and associated parameters/properties is thorough and consistent:

  • Constructor: on_off_parametersparameters: StatusParameters, on_variablestatus, previous_statesprevious_status
  • Properties: on_hours_totalactive_hours, offinactive, switch_onstartup, switch_offshutdown, switch_on_nrstartup_count, consecutive_on_hoursuptime, consecutive_off_hoursdowntime
  • Internal flags: use_offuse_downtime_tracking, use_switch_onuse_startup_tracking, use_consecutive_on_hoursuse_uptime_tracking

The logic is preserved while updating the terminology to be more intuitive (active/inactive vs on/off).

flixopt/components.py (1)

388-388: Good addition of unbounded storage support with proper validation.

The Storage now supports capacity_in_flow_hours=None for unbounded capacity, with validation ensuring:

  • relative_minimum_charge_state > 0 requires a capacity (Line 497-501)
  • relative_maximum_charge_state < 1 requires a capacity (Line 502-506)
  • relative_minimum/maximum_final_charge_state require a capacity (Lines 507-516)

This correctly prevents nonsensical configurations where relative bounds would have no meaning without a defined capacity.

Also applies to: 496-516

flixopt/elements.py (2)

141-150: Good validation for status_parameters requiring flow sizes.

The new validation correctly enforces that all flows must have explicit sizes when status_parameters are used on a Component. The error message clearly explains that sizes are "required for big-M constraints."


572-622: Comprehensive validation logic for Flow size requirements.

The validation correctly enforces size requirements for various Flow configurations:

  • Lines 573-577: status_parameters requires size (for big-M constraints)
  • Lines 579-583: fixed_relative_profile requires size (flow_rate = size × profile)
  • Lines 586-596: Relative bounds require size when non-default values are used
  • Lines 599-609: Load factor constraints require size

The error messages are clear and explain why the size is needed. This is excellent defensive programming.

flixopt/flow_system.py (4)

848-882: LGTM! Clean implementation of copy semantics.

The copy() method safely handles the solution state using try/finally, ensuring the original solution is restored even if serialization fails. Both __copy__ and __deepcopy__ correctly delegate to copy(), which is appropriate since FlowSystem contains complex interdependent state that requires full re-serialization.


1344-1376: LGTM! Solution property with proper cache invalidation.

The solution setter correctly invalidates the _statistics cache, ensuring stale statistics are not returned after a new solution is set. The is_locked property provides a clean way to check if the FlowSystem can be modified.


1378-1423: LGTM! Well-designed lifecycle management.

The separation between _invalidate_model() (internal), reset() (clears solution + invalidates), and invalidate() (requires no solution) provides clear semantics. The _invalidate_model() method properly clears all element submodels and accessor caches.


1520-1545: LGTM! Statistics accessor with proper caching.

The lazy initialization pattern with cache invalidation on solution change is well-implemented. The cache is cleared via the solution setter, ensuring consistency.

docs/index.md Outdated
Comment on lines 16 to 17
<a href="home/installation/" class="md-button md-button--primary">🚀 Get Started</a>
<a href="notebooks/" class="md-button">💡 View Examples</a>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify existence of main documentation targets
DOCS_PATHS=(
  "docs/home/installation.md"
  "docs/home/installation/index.md"
  "docs/notebooks/index.md"
  "docs/api-reference/index.md"
  "docs/user-guide/recipes/index.md"
  "docs/user-guide/mathematical-notation/index.md"
  "docs/roadmap.md"
  "docs/roadmap/index.md"
)

for path in "${DOCS_PATHS[@]}"; do
  if [ -f "$path" ]; then
    echo "$path"
  else
    echo "$path (not found)"
  fi
done

# Also check the actual docs directory structure
echo ""
echo "=== Full docs directory structure ==="
find docs -type f -name "*.md" | head -50

Repository: flixOpt/flixopt

Length of output: 1602


Fix broken documentation link targets.

The following documentation links in docs/index.md reference files that do not exist:

  • home/installation/ (lines 16, 27, 128) — expects docs/home/installation/index.md but only docs/home/installation.md exists; use home/installation.md or create the directory structure
  • api-reference/ (line 39) — directory and file do not exist; either create the documentation or remove the link
  • roadmap/ (line 57) — expects docs/roadmap/index.md but only docs/roadmap.md exists; use roadmap.md or create the directory structure

The links to notebooks/, user-guide/recipes/, and user-guide/mathematical-notation/ are valid.

🤖 Prompt for AI Agents
In docs/index.md around lines 16–17 (also affecting references at lines 27, 39,
57, and 128), fix broken link targets that point to non-existent directories:
update href="home/installation/" to href="home/installation.md" (or create
docs/home/installation/index.md), change href="roadmap/" to href="roadmap.md"
(or add docs/roadmap/index.md), and either add the missing api-reference
documentation under docs/api-reference/index.md or remove the api-reference
link; leave valid links (notebooks/, user-guide/recipes/,
user-guide/mathematical-notation/) unchanged.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
CHANGELOG.md (2)

295-308: Removed items are comprehensive but list is very long—consider adding a categorized reference table.

The removal section covers ~12 classes, ~11 properties, and multiple parameters. While complete, the list is dense and hard to scan during migration. A quick reference table (Old → New → Migration Path) would improve user experience.

Consider adding a condensed reference table after line 295 with three columns:

  • Removed Item
  • Replacement
  • Usage Example

This would make it easier for users to find specific items they're migrating without reading the full narrative.


383-395: Migration checklist is well-structured but misses a few nuances.

The checklist is a good high-level guide, but some tasks lack depth:

  1. "Replace Optimization class" (line 387): Doesn't mention SegmentedOptimizationSegmentedOptimization alias change
  2. "Update Bus excess parameters" (line 391): Doesn't clarify the strict balance default change (lines 250–255)
  3. "Update deprecated parameters" (line 392): Table reference is helpful but could benefit from a "priority" hint (e.g., "Most critical are...")

Enhance line 391 to clarify the strict balance behavior:

| Update Bus imbalance behavior | Review default `allows_imbalance=False` (strict balance); add explicit penalties if needed |
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c2ace1 and 400796c.

📒 Files selected for processing (1)
  • CHANGELOG.md (2 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
CHANGELOG.md

66-66: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


96-96: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


113-113: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


141-141: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


159-159: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


172-172: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


187-187: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


213-213: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


259-259: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: Build documentation
🔇 Additional comments (5)
CHANGELOG.md (5)

226-248: OnOffParameters renaming table is comprehensive and clear.

The terminology update from OnOff to Status is well-documented with a complete mapping table. The transition from operation-centric language (on/off) to state-centric terminology (status/startup/shutdown) is clear and aligns well with industry standards (PyPSA, unit commitment terminology).


54-61: [Upcoming] section header and migration guide link are well-positioned.

The v5.0.0 summary clearly articulates the major paradigm shift (FlowSystem-centric) and prominently links to the migration guide. This is excellent for user adoption.


66-83: All code examples in the CHANGELOG are accurate and match the v5.0 API implementation. The OptimizeAccessor class implements __call__ to support the documented syntax flow_system.optimize(solver), and all referenced accessors (statistics, topology, transform), PlotResult class, and CONFIG.Carriers structure exist with the documented APIs.


271-271: The description default change from None to empty string is safe with no identified side effects.

No code in the codebase relies on None-specific checks for effect.description. The field is purely informational metadata without conditional logic or validation that would break from this change. Both None and empty string are falsy, making them functionally equivalent in any truthy/falsy context.


66-66: No changes needed. The CHANGELOG.md file uses fenced code blocks (python, etc.) consistently throughout, which aligns with markdownlint's default MD046 rule (style: 'consistent'`). Fenced blocks are the modern Markdown standard and are already the correct style for this project.

Comment on lines +250 to +255
**Bus imbalance terminology and default changed**:
- `excess_penalty_per_flow_hour``imbalance_penalty_per_flow_hour`
- Default changed from `1e5` to `None` (strict balance)
- `with_excess``allows_imbalance`
- `excess_input``virtual_supply`
- `excess_output``virtual_demand`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

git ls-files | head -20

Repository: flixOpt/flixopt

Length of output: 527


🏁 Script executed:

# Check the CHANGELOG.md file size and read the relevant sections
wc -l CHANGELOG.md
echo "=== Lines 50-70 (migration guide area) ==="
sed -n '50,70p' CHANGELOG.md
echo ""
echo "=== Lines 240-260 (Bus imbalance terminology change) ==="
sed -n '240,260p' CHANGELOG.md

Repository: flixOpt/flixopt

Length of output: 1839


🏁 Script executed:

# Find the Bus class definition
find . -type f -name "*.py" | head -20
echo ""
echo "=== Searching for Bus class definition ==="
rg "class Bus" --type py -A 5 | head -50

Repository: flixOpt/flixopt

Length of output: 2442


🏁 Script executed:

# Search for imbalance_penalty_per_flow_hour and excess_penalty_per_flow_hour
echo "=== Searching for imbalance_penalty_per_flow_hour ==="
rg "imbalance_penalty_per_flow_hour" --type py -B 2 -A 2 | head -40
echo ""
echo "=== Searching for excess_penalty_per_flow_hour ==="
rg "excess_penalty_per_flow_hour" --type py -B 2 -A 2 | head -40
echo ""
echo "=== Searching for allows_imbalance ==="
rg "allows_imbalance" --type py -B 2 -A 2 | head -40

Repository: flixOpt/flixopt

Length of output: 5171


🏁 Script executed:

# Search for error messages related to bus imbalance or balance
echo "=== Searching for bus balance validation errors ==="
rg "balance|imbalance" --type py -i | grep -i "error\|raise\|warning" | head -30
echo ""
echo "=== Searching for Bus validation functions ==="
rg "def.*validate.*bus\|def.*check.*bus" --type py -i -A 3 | head -50

Repository: flixOpt/flixopt

Length of output: 398


🏁 Script executed:

# Find main Python modules
fd "\.py$" --type f -d 2 | head -30

Repository: flixOpt/flixopt

Length of output: 740


🏁 Script executed:

# List directory structure
ls -la
echo ""
# Look for Python source code directory
find . -maxdepth 3 -type d -name "src" -o -name "lib" -o -name "balmorel" -o -name "power*" | head -10

Repository: flixOpt/flixopt

Length of output: 1437


🏁 Script executed:

# Search for parameter definition with defaults
echo "=== Looking for Bus class with parameter defaults ==="
rg "imbalance_penalty_per_flow_hour\s*[:=]" --type py -B 3 -A 1
echo ""
echo "=== Looking for excess_penalty_per_flow_hour defaults ==="
rg "excess_penalty_per_flow_hour\s*[:=]" --type py -B 3 -A 1

Repository: flixOpt/flixopt

Length of output: 8069


🏁 Script executed:

# Search in all files, not just Python
echo "=== All references to imbalance_penalty_per_flow_hour ==="
rg "imbalance_penalty_per_flow_hour" -A 2 -B 2 | head -60
echo ""
echo "=== All references to excess_penalty_per_flow_hour ==="
rg "excess_penalty_per_flow_hour" -A 2 -B 2 | head -60

Repository: flixOpt/flixopt

Length of output: 7992


🏁 Script executed:

# Get full CHANGELOG content
cat CHANGELOG.md

Repository: flixOpt/flixopt

Length of output: 50373


🏁 Script executed:

# Count Python files
find . -name "*.py" -type f | wc -l
echo ""
# List all files in repo
ls -la
echo ""
# Check if CHANGELOG exists
file CHANGELOG.md
wc -l CHANGELOG.md

Repository: flixOpt/flixopt

Length of output: 1519


🏁 Script executed:

# Use git to list all files
git ls-files

Repository: flixOpt/flixopt

Length of output: 7770


🏁 Script executed:

# Read CHANGELOG.md with line numbers
cat -n CHANGELOG.md

Repository: flixOpt/flixopt

Length of output: 50373


🏁 Script executed:

# Extract lines 40-80 to see the migration guide area
sed -n '40,80p' CHANGELOG.md | cat -n
echo ""
echo "=== Lines 240-270 (Bus imbalance terminology) ==="
sed -n '240,270p' CHANGELOG.md | cat -n

Repository: flixOpt/flixopt

Length of output: 3041


🏁 Script executed:

# Get full CHANGELOG content and display with line numbers
head -300 CHANGELOG.md | tail -100 | cat -n

Repository: flixOpt/flixopt

Length of output: 4715


🏁 Script executed:

# Just print the file with a simpler method
python3 << 'EOF'
with open('CHANGELOG.md', 'r') as f:
    lines = f.readlines()
    
print(f"Total lines: {len(lines)}\n")

# Show lines 50-80 (migration guide area)
print("=== Lines 50-80 (migration guide area) ===")
for i in range(49, min(80, len(lines))):
    print(f"{i+1:3d}: {lines[i]}", end='')

print("\n=== Lines 240-270 (Bus imbalance section) ===")
for i in range(239, min(270, len(lines))):
    print(f"{i+1:3d}: {lines[i]}", end='')
EOF

Repository: flixOpt/flixopt

Length of output: 145


🏁 Script executed:

# Display the entire CHANGELOG file
wc -l CHANGELOG.md
head -100 CHANGELOG.md

Repository: flixOpt/flixopt

Length of output: 3573


🏁 Script executed:

# Search for migration-related content
grep -n "migration\|restore\|upgrade\|backward\|breaking" CHANGELOG.md -i
echo ""
echo "=== Search for excess_penalty ==="
grep -n "excess_penalty\|imbalance_penalty" CHANGELOG.md
echo ""
echo "=== Search for allows_imbalance ==="
grep -n "allows_imbalance\|with_excess" CHANGELOG.md

Repository: flixOpt/flixopt

Length of output: 8979


🏁 Script executed:

# Get line count and show if file exists
stat CHANGELOG.md
echo ""
# Try to display from line 1 to 100
sed -n '1,100p' CHANGELOG.md

Repository: flixOpt/flixopt

Length of output: 3616


🏁 Script executed:

# Final attempt to read the entire file
cat CHANGELOG.md | head -n 300

Repository: flixOpt/flixopt

Length of output: 10715


🏁 Script executed:

# Verify file exists and get its size
ls -lh CHANGELOG.md
echo "---"
# Try with less paging disabled
cat CHANGELOG.md 2>&1 | head -200
echo "---"
# Try wc to get size
wc -c CHANGELOG.md
wc -l CHANGELOG.md

Repository: flixOpt/flixopt

Length of output: 6866


🌐 Web query:

Bus imbalance terminology excess_penalty_per_flow_hour imbalance_penalty_per_flow_hour CHANGELOG

💡 Result:

Summary: I could not find any CHANGELOG or release note that renames excess_penalty_per_flow_hour to imbalance_penalty_per_flow_hour. The Bus API/docs still use excess_penalty_per_flow_hour (default 1e5). Sources: flixopt Bus API docs. [1][2]

Sources

  • flixOpt commons — Bus class (shows excess_penalty_per_flow_hour parameter). [1]
  • flixopt elements API — Bus documentation (same parameter and description). [2]

🌐 Web query:

"imbalance_penalty_per_flow_hour" OR "excess_penalty_per_flow_hour" Bus

💡 Result:

I couldn't find any web results for the exact parameters "imbalance_penalty_per_flow_hour" or "excess_penalty_per_flow_hour". Where did you see those names (codebase, paper, config file, library)? Tell me the source or context (project/language/tool) and I’ll search that specifically.


🏁 Script executed:

# Check git history to understand what project this is
git log --oneline -n 5 2>/dev/null || echo "No git history available"
echo ""
# Check if there's a README or any documentation
find . -maxdepth 1 -type f | grep -E "\.(md|txt|rst)$"
echo ""
# Try to read CHANGELOG with a hex dump to see if there's an encoding issue
xxd CHANGELOG.md | head -20

Repository: flixOpt/flixopt

Length of output: 174


🏁 Script executed:

# Since we can't read the file easily, let's analyze what we know from the review comment itself
# The review shows this code snippet (lines 250-255):
cat << 'EOF'
**Bus imbalance terminology and default changed**:
- `excess_penalty_per_flow_hour` → `imbalance_penalty_per_flow_hour`
- Default changed from `1e5` to `None` (strict balance)
- `with_excess` → `allows_imbalance`
- `excess_input` → `virtual_supply`
- `excess_output` → `virtual_demand`
EOF

# This is WHAT the CHANGELOG says. Now we need to verify the claims about:
# 1. Whether the migration guide (lines 58-60) actually addresses this
# 2. Whether error messages are clear (can't verify without source)
# 3. Whether the change impacts are accurately assessed

# Let's try one more direct read
python3 -c "with open('CHANGELOG.md') as f: content = f.read(); print('File size:', len(content), 'chars'); print('First 500 chars:'); print(content[:500])"

Repository: flixOpt/flixopt

Length of output: 145


🏁 Script executed:

python3 << 'SCRIPT'
import os

try:
    with open('CHANGELOG.md', 'r', encoding='utf-8') as f:
        lines = f.readlines()
    
    print(f"Total lines: {len(lines)}\n")
    
    # Show lines around 58-60 (migration guide claim)
    print("=" * 60)
    print("LINES 50-70 (Migration guide area):")
    print("=" * 60)
    start = max(0, 49)
    end = min(len(lines), 71)
    for i in range(start, end):
        print(f"{i+1:4d}: {lines[i]}", end='')
    
    print("\n" + "=" * 60)
    print("LINES 240-270 (Bus imbalance terminology):")
    print("=" * 60)
    start = max(0, 239)
    end = min(len(lines), 271)
    for i in range(start, end):
        print(f"{i+1:4d}: {lines[i]}", end='')
        
except FileNotFoundError:
    print("CHANGELOG.md not found!")
except Exception as e:
    print(f"Error: {e}")
SCRIPT

Repository: flixOpt/flixopt

Length of output: 145


Default change to strict balance requires clear upgrade guidance inline in CHANGELOG.

The breaking change is accurately documented: the default for imbalance_penalty_per_flow_hour changed from 1e5 (allowing imbalance with penalty) to None (requiring strict balance). However, lines 58–60 reference an external migration guide link—they do not contain the inline restoration instructions mentioned in the review.

The migration checklist at line 391 states "Update Bus excess parameters | Use imbalance_penalty_per_flow_hour" but does not provide the critical example: users upgrading from v4.x who relied on the old default must now explicitly set both imbalance_penalty_per_flow_hour and allows_imbalance=True. Add a dedicated subsection in the CHANGELOG's Breaking Changes for Bus parameters with concrete examples of restoring old behavior.

🤖 Prompt for AI Agents
In CHANGELOG.md around lines 250–255, the breaking-change note about bus
imbalance defaults is missing inline upgrade instructions; add a dedicated "Bus
parameters (breaking change)" subsection under Breaking Changes that shows exact
migration steps and examples to restore previous behavior (e.g., for users
upgrading from v4.x: set imbalance_penalty_per_flow_hour=1e5 and
allows_imbalance=True, and show the old→new name mappings:
excess_penalty_per_flow_hour → imbalance_penalty_per_flow_hour, with_excess →
allows_imbalance, excess_input → virtual_supply, excess_output →
virtual_demand). Also update the migration checklist near line 391 to replace
the terse "Use `imbalance_penalty_per_flow_hour`" with a one-line example
demonstrating both keys set to restore legacy behavior.

Comment on lines +267 to +270
**Storage charge_state changes**:
- `charge_state` no longer has an extra timestep
- Final charge state is now a separate variable: `charge_state|final`

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Storage charge_state structural change needs migration example.

The change to remove the extra timestep from charge_state and introduce a separate charge_state|final variable is breaking and potentially subtle. Users extracting charge state time series may be caught off guard by the dimensional change.

Add a brief migration example in the migration checklist or a new subsection showing:

  • Old: charge_state had an extra timestep (length: timesteps + 1)
  • New: charge_state is aligned with timesteps; final state is in charge_state|final

Example code would help users update their result processing code.

🤖 Prompt for AI Agents
In CHANGELOG.md around lines 267–270, add a short migration subsection that
shows the structural change and a minimal before/after example: state that Old
charge_state had length timesteps+1 while New charge_state has length timesteps
with the final state moved to charge_state|final; include a concise “Old”
description of how users previously extracted the full timeseries from
charge_state and an “New” description showing they must now read charge_state
for the timestep-aligned series and read charge_state|final for the final value,
plus a one-line note advising to update any code that assumed the extra
timestep.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (6)
flixopt/io.py (1)

682-731: Past issue addressed - value renames now use renamed key with fallback.

The fix from the past review has been implemented. Lines 714-717 now correctly look up value renames by the renamed key first (new_key), falling back to the original key if not found:

vr_key = new_key if new_key in value_renames else key
flixopt/config.py (1)

788-825: Past issue addressed - docstring now matches implementation.

The docstring at line 796 now correctly states "Enables solver console output and main results logging", which matches the implementation at lines 822-823. The comment at line 818 ("Light logging - SUCCESS level without too much noise") also aligns with the behavior.

flixopt/flow_system.py (3)

533-536: Past issue addressed - safe pop with default value.

The pop('scenario_weights', None) now correctly handles the case where the key doesn't exist.


1288-1340: LGTM - Solve method with proper error handling.

The solve method correctly checks for model existence, handles infeasibility with configurable diagnostics, and stores the solution. The typo "Sucessfully" has been fixed to "Successfully" at line 1332.


1577-1597: Past issue addressed - deprecation warning now emitted.

The plot_network method now properly emits a DeprecationWarning before delegating to topology.plot_legacy().

docs/user-guide/migration-guide-v5.md (1)

411-422: Deprecation timeline and warning are consistent.

The timeline correctly shows the progression from v4.x (available) → v5.0.0 (deprecated) and the warning box accurately reflects that these classes will be removed "in a future version" (implying v6.0.0 based on line 27). This addresses the past review concern about inconsistent wording.

🧹 Nitpick comments (1)
flixopt/flow_system.py (1)

1812-1822: Minor: storages property creates a new container on each access.

Unlike flows which is cached in _flows_cache, storages creates a new ElementContainer every call. If accessed frequently in loops, consider caching similar to flows.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 400796c and ac595bc.

📒 Files selected for processing (8)
  • .github/workflows/docs.yaml (1 hunks)
  • .pre-commit-config.yaml (1 hunks)
  • docs/index.md (3 hunks)
  • docs/user-guide/index.md (1 hunks)
  • docs/user-guide/migration-guide-v5.md (1 hunks)
  • flixopt/config.py (9 hunks)
  • flixopt/flow_system.py (24 hunks)
  • flixopt/io.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • .pre-commit-config.yaml
  • .github/workflows/docs.yaml
  • docs/user-guide/index.md
🧰 Additional context used
🧬 Code graph analysis (1)
flixopt/config.py (3)
flixopt/carrier.py (1)
  • Carrier (14-125)
tests/conftest.py (3)
  • electricity (85-86)
  • heat (89-90)
  • gas (93-94)
tests/deprecated/conftest.py (3)
  • electricity (88-89)
  • heat (92-93)
  • gas (96-97)
🪛 LanguageTool
docs/user-guide/migration-guide-v5.md

[style] ~428-~428: Using many exclamation marks might seem excessive (in this case: 19 exclamation marks for a text that’s 6937 characters long)
Context: ... success "Welcome to the new flixopt API! 🎉"

(EN_EXCESSIVE_EXCLAMATION)

🪛 markdownlint-cli2 (0.18.1)
docs/user-guide/migration-guide-v5.md

165-165: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


202-202: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


257-257: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


284-284: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


332-332: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: Build documentation
🔇 Additional comments (14)
docs/index.md (1)

16-17: All documentation links are correctly configured.

The files docs/home/installation.md and docs/roadmap.md exist and the links at lines 16–17, 27, 57, and 128 correctly reference them as relative markdown file paths.

flixopt/io.py (3)

600-667: LGTM - Comprehensive backwards-compatibility mappings.

The PARAMETER_RENAMES dictionary provides a thorough mapping from old parameter names to new ones, covering effects, flows, components, classes, and status parameters. The organization with inline comments makes maintenance easier.


669-680: LGTM - Value renames and context keys are well-defined.

The VALUE_RENAMES and flow label reference keys (_FLOW_LABEL_REFERENCE_KEYS, _FLOW_PARAMETER_KEYS) correctly separate value transformations from key renames and handle the special case where flow labels should not be renamed inside certain contexts like piecewises.


733-829: LGTM - Clean migration utilities with good documentation.

The convert_old_dataset and convert_old_netcdf functions provide a straightforward migration path with clear docstrings and usage examples. The in-place modification semantics are documented appropriately.

flixopt/config.py (4)

23-33: LGTM - Public exports and deprecation version constant added.

The DEPRECATION_REMOVAL_VERSION constant centralizes version management for deprecation warnings, making it easier to update when planning future releases.


174-175: LGTM - New solving default added.

The compute_infeasibilities default is properly added to the immutable _DEFAULTS dictionary.


634-642: LGTM - Carriers reset is consistent with class definition.

The reset logic correctly recreates the same Carrier instances defined in the class body, maintaining consistency.


578-606: No circular import risk from this import.

The from .carrier import Carrier import inside the class body executes at module load time, but carrier.py does not import from config.py, so there is no circular dependency to be concerned about.

Likely an incorrect or invalid review comment.

flixopt/flow_system.py (5)

579-623: LGTM - Solution and carriers serialization in to_dataset.

The solution variables are properly namespaced with 'solution|' prefix and the time coordinate is renamed to 'solution_time' to avoid conflicts. Carriers are JSON-serialized into attrs.


699-716: LGTM - Solution and carriers restoration in from_dataset.

The deserialization correctly reverses the serialization: solution variables are extracted by prefix, time coordinate is renamed back, and carriers are restored from JSON attrs.


846-888: LGTM - Copy semantics exclude solution state.

The copy() method correctly creates a clean copy without the solution, enabling independent modifications. The __copy__ and __deepcopy__ methods delegate appropriately.


1073-1086: Good defensive check for locked FlowSystem.

The is_locked check prevents modifications after optimization, with a clear error message guiding users to call reset() first. The invalidation of model and topology caches when adding elements is appropriate.


1376-1396: LGTM - _invalidate_model cleans up all cached state.

The method properly clears the model, topology cache, and element submodels/variable names when structure changes. This prevents stale state issues.

docs/user-guide/migration-guide-v5.md (1)

382-407: LGTM - Transform accessor deprecation clearly documented.

The migration path from flow_system.sel/isel/resample() to flow_system.transform.sel/isel/resample() is clearly explained with the important note that solutions are dropped on transformation.

* Updated CHangelog and migration guide

* The rolling_horizon method is now implemented on OptimizeAccessor:

  API:
  segments = flow_system.optimize.rolling_horizon(
      solver,
      horizon=100,          # Timesteps per segment
      overlap=0,            # Lookahead timesteps
      nr_of_previous_values=1,  # State transfer depth
  )

  flow_system.solution  # Combined solution (overlaps trimmed)
  segments              # List[FlowSystem] with individual segment solutions

  Key features implemented:
  1. Segment calculation - Divides timesteps into overlapping windows
  2. State transfer - Transfers storage charge states and flow rates between segments
  3. Solution combining - Trims overlaps and concatenates time-indexed variables
  4. Effect recalculation - Correctly sums costs from combined per-timestep values
  5. Investment validation - Raises error if InvestParameters are used
  6. Progress bar - Shows solving progress with tqdm

  Location: flixopt/optimize_accessor.py:91-399

* Summary: Rolling Horizon Optimization

  The optimize.rolling_horizon() method has been implemented in flixopt/optimize_accessor.py with the following features:

  API

  segments = flow_system.optimize.rolling_horizon(
      solver,
      horizon=100,      # Timesteps per segment (excluding overlap)
      overlap=0,        # Additional lookahead timesteps
      nr_of_previous_values=1,  # For uptime/downtime tracking
  )

  Key Features

  1. Segment creation: Divides time horizon into overlapping segments using transform.isel()
  2. State transfer: Transfers storage charge states and previous flow rates between segments
  3. Solution combining: Trims overlaps from segment solutions and concatenates
  4. Effect recomputation: Computes total effect values from combined per-timestep values (no re-solve needed)
  5. Investment check: Raises error if InvestParameters are detected (not supported)

  Implementation Details

  - Uses _calculate_segment_indices() to compute start/end indices
  - Uses _transfer_state() to pass storage charge states and flow rates to next segment
  - Uses _combine_solutions() to merge segment solutions and recompute effect totals
  - Progress bar shows segment progress via tqdm

* Add output supression

* Improve output supression

* Improve log message

* Improve solution concatenation

* Improve solution concatenation

* Improve solution concatenation

* Adding notebook for rolling

* Update migration guide

* Improve notebook

* Speed up notebook

* Improve notebook

* Improve notebook

* reorganize notebooks

* Update CHANGELOG.md

* Fix docs

* Fix docs
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
CHANGELOG.md (2)

268-273: Bus imbalance breaking change needs inline migration example.

The terminology and default changes are documented, but users upgrading from v4.x who relied on the old permissive default (1e5) need explicit guidance. Consider adding a brief example showing how to restore the old behavior.

This echoes the past review comment. Add a brief inline example:

# To restore v4.x permissive balance behavior:
bus = fx.Bus('MyBus', allows_imbalance=True, imbalance_penalty_per_flow_hour=1e5)

285-288: Storage charge_state structural change needs migration guidance.

The dimensional change (removing the extra timestep and introducing charge_state|final) is subtle but breaking for users parsing charge state arrays. A brief migration note would help.

This echoes the past review comment. Consider adding:

**Migration**: If your code expected `charge_state` to have length `timesteps + 1`, 
update it to read `charge_state` (length: timesteps) and `charge_state|final` separately.
docs/notebooks/10-transmission.ipynb (1)

396-399: Python version metadata mismatch with repo standard.

The notebook metadata specifies Python 3.10.0, but the repository and other notebooks use Python 3.11. This should be aligned for consistency.

This echoes the past review comment. Update the metadata:

  "language_info": {
   "name": "python",
-  "version": "3.10.0"
+  "version": "3.11.0"
  }
🧹 Nitpick comments (4)
docs/user-guide/migration-guide-v5.md (1)

435-446: Deprecation timeline could be clearer about removal versions.

The timeline table (Lines 437-440) states that Optimization and Results are "deprecated" in v5.0.0, but doesn't specify when they will be removed. The warning box says "will be removed in a future version" which is vague. Consider adding a concrete removal version (e.g., v6.0.0) for better migration planning.

 | Version | Status |
 |---------|--------|
 | v4.x | `Optimization` and `Results` classes available |
-| v5.0.0 | `Optimization` and `Results` deprecated, new API available |
+| v5.0.0 | `Optimization` and `Results` deprecated, new API available |
+| v6.0.0 | `Optimization` and `Results` classes removed |
docs/notebooks/08a-aggregation.ipynb (3)

42-64: Consider adding error handling and path robustness.

The relative path ../../examples/resources/Zeitreihen2020.csv may fail if users run the notebook from different directories. Additionally, there's no validation that the file exists or that the requested date range is available in the data.

Consider adding:

  • A check for file existence with a helpful error message
  • Validation that the date range exists in the loaded data
  • Optionally, use Path(__file__).parent or document the expected working directory

Example improvement:

from pathlib import Path

# Construct robust path
data_file = Path(__file__).parent.parent.parent / 'examples' / 'resources' / 'Zeitreihen2020.csv'
if not data_file.exists():
    raise FileNotFoundError(f"Data file not found: {data_file}\nPlease ensure you're running from the repository root.")

data = pd.read_csv(data_file, index_col=0, parse_dates=True).sort_index()

# Validate date range
try:
    data = data['2020-01-01':'2020-01-31 23:45:00']
except KeyError:
    raise ValueError("Required date range not available in data")

242-248: Consider adding error handling for optimization failures.

The code assumes optimization succeeds and directly accesses fs_resampled.solution["costs"]. If the solver fails (infeasible model, timeout, etc.), this will raise an unclear error.

For a tutorial notebook, consider adding basic error handling to provide helpful feedback:

# Optimize resampled system
start = timeit.default_timer()
result = fs_resampled.optimize(solver)
time_resampled = timeit.default_timer() - start

if not result.success:  # or however the API indicates failure
    print(f'Optimization failed: {result.message}')
else:
    print(f'\nResampled optimization: {time_resampled:.2f} seconds')
    print(f'Cost: {fs_resampled.solution["costs"].item():.2f} €')

1-7: Consider clarifying clustering mention in introduction.

The introduction (line 7) lists "Clustering: Identify typical periods" as one of the techniques introduced, but the notebook doesn't demonstrate clustering—it only mentions it in passing later. This might create an expectation gap for users.

Consider either:

  1. Removing clustering from the introduction list, or
  2. Adding a brief note: "Clustering (introduced conceptually, requires tsam package—see Further Reading)"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac595bc and baed6db.

📒 Files selected for processing (8)
  • CHANGELOG.md (2 hunks)
  • README.md (1 hunks)
  • docs/notebooks/07-scenarios-and-periods.ipynb (1 hunks)
  • docs/notebooks/08a-aggregation.ipynb (1 hunks)
  • docs/notebooks/10-transmission.ipynb (1 hunks)
  • docs/notebooks/index.md (1 hunks)
  • docs/user-guide/migration-guide-v5.md (1 hunks)
  • docs/user-guide/support.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/user-guide/support.md
  • README.md
  • docs/notebooks/index.md
🧰 Additional context used
🪛 LanguageTool
docs/user-guide/migration-guide-v5.md

[style] ~452-~452: Using many exclamation marks might seem excessive (in this case: 19 exclamation marks for a text that’s 7067 characters long)
Context: ... success "Welcome to the new flixopt API! 🎉"

(EN_EXCESSIVE_EXCLAMATION)

🪛 markdownlint-cli2 (0.18.1)
docs/user-guide/migration-guide-v5.md

165-165: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


202-202: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


257-257: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


284-284: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


356-356: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

CHANGELOG.md

66-66: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


96-96: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


113-113: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


141-141: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


159-159: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


172-172: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


190-190: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


205-205: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


231-231: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


277-277: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: Build documentation
🔇 Additional comments (22)
docs/user-guide/migration-guide-v5.md (3)

1-61: Well-structured migration guide with clear examples.

The introduction and Optimization API section provide clear side-by-side comparisons that will help users migrate. The two-step alternative for pre-solve model access is a helpful addition.


71-158: Results access patterns are comprehensive and well-documented.

The variable naming convention section (Lines 161-195) provides an excellent reference table. The examples cover the main use cases (effects, flow rates, investment variables, status variables, storage variables).


306-349: Rolling horizon API documented correctly after previous fix.

The flow_system.optimize.rolling_horizon() method is properly documented with appropriate parameters (horizon, overlap). This addresses the previous review comment about the missing segment() method.

CHANGELOG.md (2)

54-61: Clear summary and migration guide reference for v5.0.0.

The summary effectively communicates the major architectural shift to a FlowSystem-centric API, and the tip box properly links to the detailed migration guide.


401-414: Migration checklist is comprehensive and actionable.

The checklist provides a clear task list for users upgrading. It appropriately covers the major API changes and references the detailed sections above.

docs/notebooks/07-scenarios-and-periods.ipynb (6)

23-31: Clean setup with proper configuration.

The imports and fx.CONFIG.notebook() call follow the recommended pattern for Jupyter notebooks.


63-78: Proper use of strict=False in zip for Python compatibility.

Good practice using strict=False explicitly for the zip() calls, ensuring compatibility and clarity.


201-201: FlowSystem construction follows new API correctly.

The FlowSystem initialization with timesteps, periods, scenarios, and scenario_weights parameters is correct. The add_carriers() method usage demonstrates the new Carrier system properly.


217-288: Component definitions are well-structured and demonstrate key features.

The example effectively shows:

  • Period-varying parameters (effects_per_flow_hour=gas_prices array)
  • Investment optimization with InvestParameters
  • Scenario-varying demand via DataFrame with scenario columns

This is a valuable pedagogical example.


432-443: Sensitivity comparison logic has a potential issue with dimensionality.

Line 440 uses chp_size.max("scenario") but chp_size_mild was computed from a single-scenario FlowSystem and may not have a scenario dimension. The comparison (chp_size - chp_size_mild) could fail if dimensions don't align.

Verify that the subtraction works correctly when chp_size has a scenario dimension but chp_size_mild does not. xarray should broadcast correctly, but this depends on the actual structure. Consider making the comparison more explicit:

print(f'Planning for uncertainty adds {(chp_size.max("scenario") - chp_size_mild).round(2).values} kW capacity')

469-469: Comprehensive Key Concepts section with accurate documentation.

The markdown cell provides excellent reference material covering multi-dimensional FlowSystem usage, dimension-varying parameters, and selection methods. The mathematical notation for scenario optimization is clear.

docs/notebooks/10-transmission.ipynb (5)

34-43: Clean setup with proper configuration.

The imports and fx.CONFIG.notebook() call follow the recommended pattern. The xarray import is appropriate for the later visualization code.


138-139: Unidirectional transmission example is well-constructed.

The example clearly demonstrates:

  • Multi-bus setup for two sites
  • Carrier registration with the new API
  • Transmission with relative losses
  • LinearConverter usage for boilers

The code follows the new FlowSystem-centric API correctly.


231-232: Bidirectional transmission example demonstrates key constraints.

Good demonstration of:

  • in1/out1 and in2/out2 for bidirectional flow
  • prevent_simultaneous_flows_in_both_directions=True constraint
  • Time-varying electricity prices

322-323: Investment optimization example correctly uses InvestParameters.

The example shows:

  • InvestParameters as the size parameter for capacity optimization
  • balanced=True for symmetric capacity
  • Cost calculation based on weekly amortization

This is a practical example for infrastructure sizing.


365-366: Key Concepts section provides excellent reference documentation.

The markdown clearly explains:

  • Transmission component structure with code example
  • Loss types with formulas
  • Bidirectional vs unidirectional configurations
  • Investment optimization pattern
docs/notebooks/08a-aggregation.ipynb (6)

18-33: LGTM!

The imports and notebook configuration are appropriate for a tutorial demonstrating optimization techniques with time-series data.


101-206: LGTM!

The system builder function is well-structured and properly parameterized, making it reusable for different time resolutions and profiles—essential for the aggregation techniques demonstrated later in the notebook.


269-299: LGTM!

The two-stage optimization workflow is well-implemented: sizing with resampled data (fast) followed by full-resolution dispatch with fixed sizes. This demonstrates the proper use of the new transform.fix_sizes() API.

Note: Error handling for optimization failures would improve robustness here as well (same concern as the resampling section).


313-326: LGTM!

The full optimization provides an appropriate baseline for comparing the aggregation techniques. Using .copy() correctly preserves the original system for subsequent comparisons.


337-387: LGTM!

The results comparison table effectively quantifies the speed vs. accuracy trade-offs. The relative metrics (cost gap and speedup) are correctly calculated and provide valuable insights for users deciding which technique to apply.


399-437: LGTM!

The visualizations effectively demonstrate the differences between optimization approaches using the new statistics.plot API. Comparing heat balance plots and showing the Sankey diagram helps users understand the energy flow patterns.

Comment on lines +241 to +242

# Load FlowSystem with solution
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent variable name between migration guide and changelog.

Line 241 shows flow_system.solution['costs(total)'].item() but the actual variable name appears to be just 'costs' based on line 90 (flow_system.solution['costs'].item()) and the CHANGELOG examples. The (total) suffix seems incorrect.

 # New (v5.0)
 flow_system.optimize(solver)
-costs = flow_system.solution['costs(total)'].item()
+costs = flow_system.solution['costs'].item()

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In docs/user-guide/migration-guide-v5.md around lines 241-242, the migration
guide uses flow_system.solution['costs(total)'].item() which is inconsistent
with line 90 and the CHANGELOG that use flow_system.solution['costs'].item();
update the guide to use flow_system.solution['costs'].item() (remove the
"(total)" suffix) so the variable name matches the rest of the docs and
examples.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
CHANGELOG.md (2)

268-273: Add explicit inline migration snippet for Bus imbalance rename/default (currently too easy to miss).
This matches earlier review feedback: you document the rename and default change, but the Migration Checklist still only says “Use imbalance_penalty_per_flow_hour” without showing that users likely need to set both allows_imbalance=True and a penalty to retain v4 behavior.

Also applies to: 410-411


285-288: Storage charge_state structural change needs a before/after example.
This matches earlier review feedback: the breaking shape change is stated, but the checklist doesn’t show how to update common result-extraction code (timestep-aligned series vs final value in charge_state|final).

Also applies to: 401-414

🧹 Nitpick comments (1)
CHANGELOG.md (1)

19-20: Changelog header format is inconsistent with your own rule (## [VERSION] - YYYY-MM-DD).
Line 19 asks to keep ## [VERSION] - YYYY-MM-DD, but the new entry is ## [Upcoming] - v5.0.0 (no date, and “Upcoming” isn’t a version). Consider either:

  • ## [5.0.0] - YYYY-MM-DD (and optionally an “Unreleased/Upcoming” section separately), or
  • ## [Unreleased] per Keep a Changelog, with 5.0.0 added once dated.

Also applies to: 54-56

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between baed6db and 70f670c.

📒 Files selected for processing (1)
  • CHANGELOG.md (2 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
CHANGELOG.md

66-66: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


96-96: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


113-113: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


141-141: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


159-159: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


172-172: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


190-190: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


205-205: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


231-231: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


277-277: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.10)
  • GitHub Check: Build documentation

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70f670c and 3bcd81a.

📒 Files selected for processing (1)
  • flixopt/flow_system.py (24 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
flixopt/flow_system.py (4)
flixopt/topology_accessor.py (2)
  • TopologyAccessor (111-579)
  • infos (242-287)
flixopt/transform_accessor.py (2)
  • TransformAccessor (26-703)
  • sel (202-242)
flixopt/carrier.py (2)
  • Carrier (14-125)
  • label (107-109)
flixopt/io.py (1)
  • convert_old_dataset (733-778)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.10)
  • GitHub Check: Build documentation
🔇 Additional comments (3)
flixopt/flow_system.py (3)

518-539: LGTM! Scenario metadata helper is consistent with time/period patterns.

The _update_scenario_metadata method correctly handles scenario weight cleanup and follows the same pattern as _update_time_metadata and _update_period_metadata. The safe pop('scenario_weights', None) avoids KeyError.


580-718: LGTM! Solution and carrier serialization is well-designed.

The dataset serialization correctly:

  • Prefixes solution variables with 'solution|' to avoid conflicts with config variables
  • Renames time coordinates to 'solution_time' to preserve the full solution independently
  • Stores carriers as JSON metadata and restores them
  • Reconnects the network post-load (line 716) to populate bus inputs/outputs

1242-1341: LGTM! New lifecycle methods and accessor pattern are well-designed.

The refactored API is clean and extensible:

  • Accessor pattern organizes functionality (optimize, transform, statistics, topology) with appropriate caching
  • Locking mechanism (is_locked checks in add_elements/add_carriers) prevents modifications after optimization, improving safety
  • Lifecycle separation (build_modelsolve) enables model inspection before solving
  • Cache invalidation is correct (statistics cleared when solution changes line 1367, topology cleared in _invalidate_model line 1393)
  • Clustering integration in build_model (lines 1270-1271) supports time aggregation workflows
  • Infeasibility handling in solve (lines 1321-1334) with configurable extraction is helpful for debugging

Also applies to: 1369-1466, 1468-1577

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
flixopt/flow_system.py (1)

1017-1037: Carrier key casing is inconsistent (can break lookups and auto-registration).

_register_missing_carriers() uses bus.carrier as-is for membership and as the key, but get_carrier() / flow_carriers normalize with .lower(). This can lead to “registered but not found” (or duplicated entries) when users pass mixed-case carrier names.

 def _register_missing_carriers(self) -> None:
     """Auto-register carriers from CONFIG for buses that reference unregistered carriers."""
     for bus in self.buses.values():
-        if bus.carrier and bus.carrier not in self._carriers:
+        if not bus.carrier:
+            continue
+        carrier_key = bus.carrier.lower()
+        if carrier_key not in self._carriers:
             # Try to get from CONFIG defaults
-            default_carrier = getattr(CONFIG.Carriers, bus.carrier, None)
+            default_carrier = getattr(CONFIG.Carriers, carrier_key, None) or getattr(CONFIG.Carriers, bus.carrier, None)
             if default_carrier is not None:
-                self._carriers[bus.carrier] = default_carrier
-                logger.debug(f"Auto-registered carrier '{bus.carrier}' from CONFIG")
+                self._carriers[carrier_key] = default_carrier
+                logger.debug(f"Auto-registered carrier '{carrier_key}' from CONFIG")

(Then you can drop repeated .lower() calls elsewhere if you want, but this alone fixes the mismatch.)

Also applies to: 1183-1194, 1218-1224

🧹 Nitpick comments (1)
flixopt/flow_system.py (1)

580-625: Harden carriers (de)serialization against malformed attrs.

json.loads(reference_structure['carriers']) will raise if the attr is missing/empty/corrupted. Given NetCDF files can be edited or partially written, a defensive parse makes this much more resilient.

-        if 'carriers' in reference_structure:
-            carriers_structure = json.loads(reference_structure['carriers'])
-            for carrier_data in carriers_structure.values():
-                carrier = cls._resolve_reference_structure(carrier_data, {})
-                flow_system._carriers.add(carrier)
+        carriers_raw = reference_structure.get('carriers')
+        if carriers_raw:
+            try:
+                carriers_structure = json.loads(carriers_raw)
+            except (TypeError, json.JSONDecodeError):
+                carriers_structure = {}
+                logger.warning("Ignoring malformed dataset attr 'carriers'")
+            for carrier_data in carriers_structure.values():
+                carrier = cls._resolve_reference_structure(carrier_data, {})
+                flow_system._carriers.add(carrier)

Also applies to: 627-718

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3bcd81a and 3981c3c.

📒 Files selected for processing (1)
  • flixopt/flow_system.py (24 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.11)
  • GitHub Check: Build documentation
🔇 Additional comments (3)
flixopt/flow_system.py (3)

1062-1111: Locking + invalidation lifecycle looks consistent and user-protective.

  • is_locked gating in add_elements() / add_carriers() + reset() / invalidate() is a clean model-edit workflow.
  • _invalidate_model() clearing _topology and _flow_carriers aligns with the new caches and prevents stale derived state.

Also applies to: 1112-1162, 1370-1467


1579-1642: Deprecation wrappers are consistent and point to the right migration targets.

The wrappers emit DeprecationWarning with stacklevel=2 and delegate to the accessor-based API, which should make v5→v6 transitions much smoother.

Also applies to: 1986-2211


1695-1717: Linking elements to the FlowSystem + storages cache is a nice usability improvement.

link_to_flow_system() on add, plus a dedicated storages container/cache (with invalidation on component/bus adds) reads straightforward and coherent with the existing flows cache behavior.

Also applies to: 1816-1829

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
flixopt/flow_system.py (2)

1039-1062: Past review concern may still apply: label vs label_full mismatch.

Line 1049 collects comp.label but line 1059-1060 uses it to index self.components[label]. If ElementContainer is keyed by label_full, this works only when label == label_full (true for top-level components, but potentially fragile).

Consider using the component reference directly instead of re-looking it up:

-        elements_without_colors = [comp.label for comp in self.components.values() if comp.color is None]
+        elements_without_colors = [comp for comp in self.components.values() if comp.color is None]
 
         if not elements_without_colors:
             return
 
         # Generate colors from the default colorscale
         colorscale = CONFIG.Plotting.default_qualitative_colorscale
-        color_mapping = process_colors(colorscale, elements_without_colors)
+        labels = [comp.label for comp in elements_without_colors]
+        color_mapping = process_colors(colorscale, labels)
 
         # Assign colors to elements
-        for label, color in color_mapping.items():
-            self.components[label].color = color
-            logger.debug(f"Auto-assigned color '{color}' to component '{label}'")
+        for comp, color in zip(elements_without_colors, color_mapping.values()):
+            comp.color = color
+            logger.debug(f"Auto-assigned color '{color}' to component '{comp.label}'")

1276-1288: Consider adding defensive validation for _clustering_info structure.

As noted in a previous review, directly indexing info['parameters'], info['clustering'], and info['components_to_clusterize'] assumes the dict has the correct shape. While this is an internal method with controlled input from transform.cluster(), defensive validation would make debugging easier if the structure is malformed.

     def _add_clustering_constraints(self) -> None:
         """Add clustering constraints to the model."""
         from .clustering import ClusteringModel
 
         info = self._clustering_info
+        required = {'parameters', 'clustering', 'components_to_clusterize'}
+        if missing := required - set(info or {}):
+            raise KeyError(f'_clustering_info missing required keys: {sorted(missing)}')
+
         clustering_model = ClusteringModel(
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3981c3c and 68992eb.

📒 Files selected for processing (1)
  • flixopt/flow_system.py (24 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.10)
  • GitHub Check: Build documentation
🔇 Additional comments (15)
flixopt/flow_system.py (15)

7-46: LGTM!

The new imports are well-organized: runtime dependencies at module level, type-only imports in TYPE_CHECKING block. The _Solver type hint correctly uses deferred import pattern.


213-238: LGTM!

Good design choices: lazy initialization for caches (_statistics, _topology, _flow_carriers), empty CarrierContainer as default, and clear comments explaining each cache's purpose.


518-538: LGTM!

The pop('scenario_weights', None) pattern correctly handles the missing key case, addressing the previous review concern.


580-629: LGTM!

Clean serialization design: solution variables get solution| prefix to avoid conflicts, time coordinate renamed to solution_time to preserve full solution timeline, and carriers stored as JSON. The conditional rename on line 609 correctly handles solutions without time dimension.


705-722: LGTM!

Solution restoration correctly reverses the serialization: renames solution_time back to time, restores carriers from JSON, and re-connects the network. The unconditional connect_and_transform() call ensures bus inputs/outputs are rebuilt since they're not stored in NetCDF.


852-885: LGTM!

Excellent improvement over the previous approach. Using to_dataset(include_solution=False) avoids mutating the original instance entirely, addressing the past review concern about thread safety and side effects.


1113-1162: LGTM!

The docstring now correctly uses plural carriers in the Args section and the example uses the correct method name add_carriers(). Good defensive programming with is_locked check and model invalidation.


1322-1335: LGTM!

Both previous review concerns are addressed:

  1. Termination condition now uses explicit tuple membership check in ('infeasible', 'infeasible_or_unbounded') instead of substring matching
  2. Typo fixed: "Successfully" (line 1334)

1378-1399: LGTM!

Cache invalidation is now complete: _flow_carriers is cleared (line 1395) alongside _topology, addressing the previous review concern about stale cache after structural changes.


1580-1600: LGTM!

The plot_network() method now properly emits a DeprecationWarning with stacklevel=2, consistent with other deprecated methods in this file.


772-850: LGTM!

Good migration utility for backward compatibility. The warning clearly documents limitations (solution variable names not renamed, advanced access may require original names). The JSON conversion for solution attrs (lines 845-847) ensures consistency with the new format.


1470-1578: LGTM!

Good design: stateless accessors (optimize, transform) create fresh instances, while stateful accessors (statistics, topology) are cached and invalidated appropriately. The statistics cache is cleared via the solution setter, and topology is cleared in _invalidate_model().


1987-2211: LGTM!

Consistent deprecation pattern: all legacy methods emit DeprecationWarning with stacklevel=2 and cleanly delegate to the new TransformAccessor implementations. Docstrings properly document the deprecation with the alternative to use.


1817-1829: LGTM!

The storages property follows the same lazy-caching pattern as flows, with proper cache invalidation in _add_components and _add_buses. Sorting by label_full.lower() ensures reproducible ordering.


1696-1717: LGTM!

Consistent pattern: all element types call link_to_flow_system(self) before being added to their container. This establishes the bidirectional reference and ensures elements know their parent FlowSystem.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
flixopt/flow_system.py (2)

1043-1066: Bug: _assign_element_colors() likely keys container incorrectly (label vs label_full).
This can throw KeyError or assign colors to the wrong component if ElementContainer is keyed by label_full.

 def _assign_element_colors(self) -> None:
@@
-        elements_without_colors = [comp.label for comp in self.components.values() if comp.color is None]
+        elements_without_colors = [comp.label_full for comp in self.components.values() if comp.color is None]
@@
-        for label, color in color_mapping.items():
-            self.components[label].color = color
-            logger.debug(f"Auto-assigned color '{color}' to component '{label}'")
+        for label_full, color in color_mapping.items():
+            self.components[label_full].color = color
+            logger.debug(f"Auto-assigned color '{color}' to component '{label_full}'")

1247-1293: Guard _clustering_info shape before indexing.
Currently info['parameters'], etc. can throw KeyError deep in optimization. Validate early with a clear error.

 def _add_clustering_constraints(self) -> None:
     """Add clustering constraints to the model."""
     from .clustering import ClusteringModel
 
-    info = self._clustering_info
+    info = self._clustering_info or {}
+    required = {'parameters', 'clustering', 'components_to_clusterize'}
+    missing = required - set(info)
+    if missing:
+        raise KeyError(f'_clustering_info missing keys: {sorted(missing)}')
     clustering_model = ClusteringModel(
         model=self.model,
         clustering_parameters=info['parameters'],
         flow_system=self,
         clustering_data=info['clustering'],
         components_to_clusterize=info['components_to_clusterize'],
     )
🧹 Nitpick comments (3)
flixopt/flow_system.py (3)

1028-1042: Carrier auto-registration likely bypasses CarrierContainer invariants.
Using self._carriers[carrier_key] = default_carrier assumes CarrierContainer behaves like a plain dict and that key normalization is correct. Prefer self._carriers.add(default_carrier) (and ensure .add() normalizes name casing) to avoid mismatched keys vs carrier.name.


1168-1231: Carrier lookup/mapping API is useful; ensure label expectations are documented.
get_carrier() treats the argument as either a bus label or a flow label; consider explicitly stating it expects label_full (or supporting both) to avoid surprises.


1474-1583: Accessor exposure pattern is clean; consider caching consistency.
statistics/topology are cached, optimize/transform aren’t—seems intentional; just ensure accessors are lightweight or make caching consistent.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68992eb and 002a337.

📒 Files selected for processing (1)
  • flixopt/flow_system.py (23 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
flixopt/flow_system.py (4)
flixopt/topology_accessor.py (2)
  • TopologyAccessor (111-579)
  • infos (242-287)
flixopt/transform_accessor.py (5)
  • TransformAccessor (26-703)
  • _dataset_sel (348-395)
  • sel (202-242)
  • isel (244-284)
  • resample (286-343)
flixopt/carrier.py (3)
  • Carrier (14-125)
  • CarrierContainer (128-159)
  • label (107-109)
flixopt/io.py (2)
  • convert_old_dataset (733-778)
  • load_dataset_from_netcdf (571-597)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: Build documentation
🔇 Additional comments (16)
flixopt/flow_system.py (16)

7-45: New imports/accessors/carriers wiring looks consistent.
No issues spotted with the added imports/types in this file.


164-240: State/caching initialization is clear; consider cache consistency around name.
name being mutable and overwritten on save/load is intentional per docstrings—just ensure callers don’t rely on a manually set name persisting across to_netcdf().


519-540: Scenario metadata update: safe pop is good.
dataset.attrs.pop('scenario_weights', None) correctly avoids KeyError for missing attrs and keeps scenario subsets consistent.


581-634: to_dataset(include_solution=...) design is solid; one edge-case to verify.
Renaming solution timesolution_time is a good conflict-avoidance strategy; please verify behavior when solution has a time coordinate but not a time dimension (and/or already has solution_time).


635-728: from_dataset() eagerly connect_and_transform() can be surprising.
Reconnecting makes sense because bus inputs/outputs aren’t stored, but it also introduces side effects and can raise if the dataset is invalid. Worth confirming this is desired for all dataset loads (including “template” datasets).


729-775: to_netcdf()/from_netcdf() name derivation: verify base-class signature.
super().to_netcdf(path, compression, overwrite) assumes positional parameters match the base implementation; please confirm Interface.to_netcdf() has the same order/types.


776-855: from_old_results() migration path is pragmatic; watch attr normalization.
Converting dict attrs to JSON strings for specific keys is reasonable—just confirm downstream readers consistently expect JSON strings in solution.attrs[...] (not dicts) after this path.


856-890: copy() semantics look good (no solution copied).
Using to_dataset(include_solution=False) avoids mutating the original instance and keeps copying simple.


987-1027: connect_and_transform() lifecycle + integrity validation is a good improvement.
The explicit idempotency notes and early cross-element validation reduce late model-build failures.


1067-1116: Locking add_elements() when solution exists is a good guardrail.
The invalidation behavior when model exists / already transformed is also consistent with the new lifecycle.


1117-1167: add_carriers() locking + invalidation is consistent with add_elements().
Docstring/example/signature alignment looks corrected.


1294-1347: Infeasibility handling is improved; verify termination-condition typing across linopy versions.
The explicit tuple check avoids substring pitfalls; please confirm self.model.termination_condition is always a string (or handle enum values as well).


1348-1473: Solution/lock/reset/invalidate lifecycle looks coherent.
Reset clears solution (unlock) and invalidates, while invalidate blocks when locked—this is a clean mental model.


1584-1647: Deprecation wrappers are consistent and actionable.
Clear DeprecationWarning messages + redirect to accessors is exactly what downstream users need.


1700-1722: Linking elements to FlowSystem + cache invalidation is correct.
Good to see _storages_cache invalidated alongside _flows_cache.


1821-1834: storages cached container is a nice usability improvement.
Sorting by label_full for reproducibility is a good touch.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
flixopt/flow_system.py (1)

724-726: Consider making connect_and_transform() optional in from_dataset().

Line 725 unconditionally calls connect_and_transform(), which performs network connection, carrier registration, color assignment, and data transformation. If the caller plans to modify the FlowSystem before using it (e.g., adding more elements), this is wasted work.

Consider either:

  1. Removing this call and documenting that callers should call it explicitly when needed
  2. Adding a connect parameter to control this behavior

Option 1: Remove automatic call (simpler, matches __init__ behavior)

         # Restore carriers if present
         if 'carriers' in reference_structure:
             carriers_structure = json.loads(reference_structure['carriers'])
             for carrier_data in carriers_structure.values():
                 carrier = cls._resolve_reference_structure(carrier_data, {})
                 flow_system._carriers.add(carrier)
-
-        # Reconnect network to populate bus inputs/outputs (not stored in NetCDF).
-        flow_system.connect_and_transform()

         return flow_system

Update docstring to note:

+        Note:
+            The returned FlowSystem is not connected. Call `connect_and_transform()`
+            or `build_model()` before optimizing.

Option 2: Add parameter for control

     @classmethod
-    def from_dataset(cls, ds: xr.Dataset) -> FlowSystem:
+    def from_dataset(cls, ds: xr.Dataset, connect: bool = True) -> FlowSystem:
         """
         ...
+        Args:
+            ds: Dataset containing the FlowSystem data
+            connect: If True, call connect_and_transform() before returning
         """
         ...
-        # Reconnect network to populate bus inputs/outputs (not stored in NetCDF).
-        flow_system.connect_and_transform()
+        if connect:
+            flow_system.connect_and_transform()
         
         return flow_system
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 002a337 and 5d17f9a.

📒 Files selected for processing (1)
  • flixopt/flow_system.py (23 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: Build documentation
🔇 Additional comments (5)
flixopt/flow_system.py (5)

519-539: LGTM: Safe attribute removal.

The method correctly uses .pop('scenario_weights', None) to safely remove the attribute when scenarios are not multi-valued, avoiding KeyError exceptions.


856-889: LGTM: Clean copy implementation without mutation.

The copy methods correctly exclude the solution using include_solution=False and avoid mutating the original instance. The __copy__ and __deepcopy__ implementations properly delegate to the main copy() method.


1028-1067: LGTM: Carrier registration and color assignment.

The new helper methods correctly:

  • Try both original and lowercase carrier names when auto-registering from CONFIG
  • Use label_full consistently for element keying (avoiding KeyError risks)
  • Provide good debug logging for troubleshooting

1118-1232: LGTM: Carrier management with proper locking and caching.

The carrier-related methods correctly:

  • Check is_locked before allowing modifications
  • Invalidate caches appropriately (including _flow_carriers in _invalidate_model)
  • Build the carrier mapping lazily with proper runtime checks
  • Use consistent lowercase keying for carrier lookups

1997-2184: LGTM: Comprehensive deprecation strategy.

The deprecated methods (_dataset_sel, _dataset_isel, _dataset_resample, _resample_by_dimension_groups, sel, isel, resample) correctly:

  • Emit clear deprecation warnings with version information
  • Redirect to the new TransformAccessor implementations
  • Maintain backward compatibility during the transition period

Comment on lines +749 to 754
path = pathlib.Path(path)
# Set name from filename (without extension)
self.name = path.stem

super().to_netcdf(path, compression, overwrite)
logger.info(f'Saved FlowSystem to {path}')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

to_netcdf() mutates self.name as a side effect.

Line 751 sets self.name = path.stem, which modifies the FlowSystem instance during a save operation. This is unexpected behavior for a serialization method.

Consider alternatives:

  1. Only set name if it's currently None (preserve existing names)
  2. Store the name in the dataset without mutating self (read-only save)
  3. Document this behavior prominently if it's intentional

Option 1: Preserve existing names (recommended)

         path = pathlib.Path(path)
-        # Set name from filename (without extension)
-        self.name = path.stem
+        # Set name from filename only if not already set
+        if self.name is None:
+            self.name = path.stem

Option 2: Store without mutating (more pure)

         path = pathlib.Path(path)
-        # Set name from filename (without extension)
-        self.name = path.stem
-
-        super().to_netcdf(path, compression, overwrite)
+        # Temporarily store filename as name for serialization
+        ds = self.to_dataset()
+        ds.attrs['name'] = path.stem
+        # Use Interface._save_dataset directly instead of super().to_netcdf()
+        self._save_dataset(ds, path, compression, overwrite)
🤖 Prompt for AI Agents
In flixopt/flow_system.py around lines 749 to 754, to_netcdf() currently mutates
self.name by unconditionally assigning path.stem; change this so the method does
not unexpectedly modify the object: only set self.name = path.stem if self.name
is currently None (preserve existing names), otherwise leave self.name
untouched; alternatively, if you prefer not to modify the instance at all,
remove the assignment and ensure the filename is written into the saved dataset
metadata instead. Ensure tests cover both existing-name and no-name cases.

* drop python 3.10 support and add 3.14

* Exclude gurobi from py314

* scipy==1.16.3 in dev for python 3.14

* Fix tests without gurobi installed (py314)
@FBumann FBumann merged commit 91e4594 into main Dec 16, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants