Skip to content

Conversation

@FBumann
Copy link
Member

@FBumann FBumann commented Dec 9, 2025

Description

Brief description of the changes in this PR.

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 can be copied (including via Python copy), plus is_locked property and reset() to manage optimization state.
  • Improvements
    • More consistent linkage and naming of model elements and parameters, yielding steadier coordinate naming and status-parameter handling during model setup.
  • Bug Fixes
    • Stronger guards preventing structural changes when a solution exists; model invalidation behaves more predictably.
  • Tests
    • Added comprehensive tests for locking, reset, copy, and re-optimization workflows.

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

  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
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 9, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Replaces internal _set_flow_system with a public link_to_flow_system(flow_system, prefix='') API across components/elements/interfaces; removes per-call name_prefix from transform_data() and centralizes prefix handling on objects. Adds FlowSystem copy/reset/locking utilities and enforces modification guards when a FlowSystem is locked.

Changes

Cohort / File(s) Summary
FlowSystem state & lifecycle
flixopt/flow_system.py
Add copy(), __copy__(), __deepcopy__() to produce dataset-based structural clones without solution/model; add is_locked property, _invalidate_model(), and reset(); enforce RuntimeError on structural mutations when locked; use link_to_flow_system when re-linking elements.
Core interface & prefix plumbing
flixopt/structure.py, flixopt/interface.py
Add link_to_flow_system(flow_system, prefix=''), prefix property and _sub_prefix() helper on Interface; replace _set_flow_system usages; remove name_prefix from transform_data() signatures and migrate internal coordinate naming to self.prefix.
Elements & components propagation
flixopt/elements.py, flixopt/components.py, flixopt/effects.py, flixopt/parts/*
Replace _set_flow_system with link_to_flow_system across Component/Flow/Bus/Effect/Storage/LinearConverter/Transmission and nested subcomponents; propagate flow_system to nested InvestParameters, StatusParameters and piecewise items using explicit prefixes; update transform_data() calls to parameterless form using object prefixes.
Modeling logic adjustments
flixopt/components.py, flixopt/elements.py (models)
Update modeling paths to auto-create and link StatusParameters for flows missing status (using link_to_flow_system with flow-specific prefixes); ensure coordinate names are built from self.prefix/label_full when fitting coords.
Tests
tests/test_flow_system_locking.py
Add tests covering FlowSystem locking lifecycle, reset and copy semantics, mutation guards, model invalidation warnings, and round-trip behavior via NetCDF load/save.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay special attention to:
    • FlowSystem copy/reset/_invalidate_model logic and correctness of dataset round-tripping.
    • Correct and consistent use of prefix vs. former name_prefix when building coordinate names across models.
    • All places where StatusParameters and InvestParameters are auto-created to ensure they are linked with the correct flow-specific prefix.
    • RuntimeError messages and guard conditions in mutating methods (add_elements/add_carriers) to confirm clarity and consistency.

Possibly related PRs

Poem

🐰
I stitched prefixes in careful rows,
Linked every flow where system goes.
I copied clones without a trace,
And locked the fields in tidy place.
Hop, hop — reset, then model glows ✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is entirely a template with placeholders and no actual content filled in; none of the required sections (objectives, changes, testing details) are completed. Complete the description with actual change details, fill in the Type of Change selection, link related issues, and confirm testing status before merging.
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title is partially related to the changeset but contains a typo ('FLow' instead of 'Flow') and is vague about the full scope of changes, which include major API refactoring (link_to_flow_system, transform_data signature changes) beyond just locking. Clarify the title to reflect the major API changes (e.g., 'Refactor flow system linking and data transformation API' or fix the typo and expand scope).

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.

@FBumann
Copy link
Member Author

FBumann commented Dec 9, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 9, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@FBumann
Copy link
Member Author

FBumann commented Dec 9, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 9, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 (4)
flixopt/flow_system.py (1)

733-776: FlowSystem.copy() correctly clones structure without optimization state

The copy implementation strips solution/model/submodels by serializing with solution temporarily cleared and reconstructing via from_dataset, producing an unlocked, modifiable FlowSystem while leaving the original untouched and still locked if it had a solution.

You might consider using type(self).from_dataset(...) instead of FlowSystem.from_dataset(...) to make copy() friendlier to potential subclasses, but this is purely optional.

flixopt/elements.py (3)

114-124: Component.link_to_flow_system/transform_data: propagation looks consistent, prefix param effectively unused

Using self.label_full as the canonical prefix and pushing the FlowSystem into status_parameters and all flows matches the new prefix-based design, and the transform path correctly delegates to nested interfaces. The prefix argument on Component.link_to_flow_system is intentionally ignored per docstring; if you don't plan to ever support caller-provided prefixes here, consider asserting that prefix is empty or equal to self.label_full to catch misuse early, or documenting it explicitly in the public API reference.

Also applies to: 125-130


512-522: Flow.link_to_flow_system/transform_data: end‑to‑end prefix usage is coherent; minor type check nit

The new Flow linking and transform logic looks internally consistent: label_full is the stable prefix root, status_parameters and size (when interface-based) get nested prefixes, and all fitted coordinates now key off self.prefix, which matches the rest of the system. One small polish point: link_to_flow_system checks isinstance(self.size, Interface) while transform_data later checks isinstance(self.size, InvestParameters); given the constructor type hint (Numeric_PS | InvestParameters), you could tighten the first check to InvestParameters as well for clarity and to avoid surprising behavior if another Interface subtype is ever passed as size.

Also applies to: 523-555


962-976: ComponentModel: newly created flow StatusParameters now correctly linked to FlowSystem

Linking auto-created StatusParameters() for flows (both from status_parameters and prevent_simultaneous_flows) to self._model.flow_system with a flow-specific prefix fixes the previous missing-context issue and ensures their transform_data can run safely. For consistency with other places that build prefixes, you might prefer flow._sub_prefix('status_parameters') over manually formatting f'{flow.label_full}|status_parameters', but this is purely cosmetic.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67a7fa9 and 1cd7185.

📒 Files selected for processing (7)
  • flixopt/components.py (5 hunks)
  • flixopt/effects.py (2 hunks)
  • flixopt/elements.py (4 hunks)
  • flixopt/flow_system.py (6 hunks)
  • flixopt/interface.py (7 hunks)
  • flixopt/structure.py (2 hunks)
  • tests/test_flow_system_locking.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
flixopt/effects.py (4)
flixopt/components.py (5)
  • link_to_flow_system (183-187)
  • link_to_flow_system (429-433)
  • transform_data (219-225)
  • transform_data (435-473)
  • transform_data (717-720)
flixopt/elements.py (7)
  • link_to_flow_system (114-123)
  • link_to_flow_system (274-281)
  • link_to_flow_system (512-521)
  • label_full (596-597)
  • transform_data (125-130)
  • transform_data (283-286)
  • transform_data (523-554)
flixopt/interface.py (10)
  • link_to_flow_system (229-233)
  • link_to_flow_system (461-465)
  • link_to_flow_system (679-684)
  • link_to_flow_system (907-911)
  • transform_data (77-80)
  • transform_data (235-237)
  • transform_data (467-469)
  • transform_data (686-689)
  • transform_data (913-970)
  • transform_data (1218-1241)
flixopt/structure.py (8)
  • link_to_flow_system (346-383)
  • flow_system (386-404)
  • prefix (338-340)
  • label_full (1080-1081)
  • label_full (1626-1627)
  • transform_data (322-335)
  • _fit_coords (406-419)
  • _fit_effect_coords (421-439)
flixopt/structure.py (3)
flixopt/components.py (5)
  • transform_data (219-225)
  • transform_data (435-473)
  • transform_data (717-720)
  • link_to_flow_system (183-187)
  • link_to_flow_system (429-433)
flixopt/effects.py (2)
  • transform_data (247-290)
  • link_to_flow_system (240-245)
flixopt/interface.py (10)
  • transform_data (77-80)
  • transform_data (235-237)
  • transform_data (467-469)
  • transform_data (686-689)
  • transform_data (913-970)
  • transform_data (1218-1241)
  • link_to_flow_system (229-233)
  • link_to_flow_system (461-465)
  • link_to_flow_system (679-684)
  • link_to_flow_system (907-911)
🔇 Additional comments (17)
flixopt/effects.py (2)

240-290: Effect prefix handling and transform_data look consistent with new Interface API

Effect.link_to_flow_system correctly uses label_full as the naming prefix and ignores the passed prefix as documented, and transform_data now uniformly relies on self.prefix for all coordinate and suffix construction, aligning with the new prefix-based naming scheme across the codebase. No issues spotted.


674-680: Penalty effect linkage via public API is correct

Using penalty_effect.link_to_flow_system(self._model.flow_system) ensures both user-defined and auto-created Penalty effects are properly linked and prefixed before modeling, replacing the old private setter cleanly.

tests/test_flow_system_locking.py (1)

1-282: Locking, reset, copy, and load tests comprehensively cover new semantics

The test module exercises all key state transitions (lock/unlock, model invalidation, reset side effects, copy/deepcopy, and NetCDF round-trips) and directly matches the implemented error messages and behaviors in FlowSystem, giving strong confidence in the new API.

flixopt/structure.py (1)

318-384: Interface prefix/link_to_flow_system contract is well-defined and matches downstream usage

The new transform_data() contract, prefix/_sub_prefix helpers, and link_to_flow_system implementation provide a clear, centralized mechanism for FlowSystem linkage and naming that aligns with how Components, Elements, and Effects now propagate prefixes to nested interfaces. No issues detected.

flixopt/flow_system.py (3)

924-1014: Locking and model invalidation semantics in add_elements/add_carriers align with tests

add_elements/add_carriers now:

  • Refuse mutations when is_locked is True, with clear RuntimeError messages mentioning reset().
  • Emit a single warning and call _invalidate_model() when a model exists without a solution, resetting model and element submodels before adding new items.

This matches the new tests’ expectations and cleanly separates structural changes from optimization state.


1187-1230: is_locked and reset() provide a clear, minimal locking lifecycle

is_locked simply reflects whether a solution is present, and reset() clears solution, model, connected flag, and element modeling artifacts while returning self. This is straightforward, predictable, and is well-covered by the new tests (including re-optimization after reset and behavior for loaded FlowSystems).


1455-1475: Using link_to_flow_system in _add_effects/_add_components/_add_buses is consistent with the new Interface contract

Replacing the old private FlowSystem coupling with effect/component/bus.link_to_flow_system(self) ensures all elements (including those restored from datasets) receive both FlowSystem references and correct prefixes, harmonizing with the updated prefix-based transform_data logic.

flixopt/components.py (3)

183-226: LinearConverter flow-system linkage and transform_data are consistent and complete

The override of link_to_flow_system correctly delegates to the Component implementation and propagates the FlowSystem/prefix to piecewise_conversion via _sub_prefix, and transform_data now relies on super().transform_data plus local normalization of conversion_factors and piecewise_conversion without any name_prefix plumbing. Looks solid.


429-473: Storage linkage and prefix-based transform_data are well-aligned with the new API

Storage.link_to_flow_system properly forwards to the Component base and links InvestParameters-based capacity with a nested prefix, while transform_data systematically uses self.prefix for all storage parameters and respects the expected dims (time vs period/scenario) before plausibility checks. No issues found.


717-733: Transmission transform_data and automatic StatusParameters linkage for absolute losses are coherent

Transmission.transform_data now uses self.prefix for relative/absolute loss naming after delegating to the base transform, and TransmissionModel.init ensures flows with absolute_losses have StatusParameters linked with a consistent status_parameters prefix. This integrates cleanly with the broader link_to_flow_system/prefix framework.

flixopt/elements.py (1)

274-282: Bus.link_to_flow_system/transform_data align with element-level semantics; check _fit_coords defaults

The Bus override mirrors the Component pattern (always using label_full as prefix, then propagating to flows) and transform_data now consistently uses self.prefix for coordinate naming, which should preserve naming stability as long as link_to_flow_system is called first. Please just confirm that _fit_coords(..., self.imbalance_penalty_per_flow_hour) without explicit dims still produces the same coordinate layout as before for Numeric_TPS inputs, to avoid subtle shape changes.

Also applies to: 283-286

flixopt/interface.py (6)

77-81: Piece.transform_data: prefix‑based coords and has_time_dim handling look correct

The switch to self.prefix and has_time_dim‑driven dims selection keeps the previous behavior while aligning with the new global transform contract; as long as every Piece has been linked via link_to_flow_system (through its owning Piecewise), coordinate names and dimensions should remain consistent.


229-237: Piecewise.link_to_flow_system/transform_data: clean propagation to child pieces

Forwarding prefix to Interface.link_to_flow_system and then assigning child prefixes as self._sub_prefix(f'Piece{i}') provides a stable naming hierarchy, and the no-arg transform_data that simply iterates pieces matches that structure. This looks like a straightforward, correct port from the old name_prefix pattern.


461-469: PiecewiseConversion linkage and transform flow are consistent with nested Piecewise contracts

The new link_to_flow_system correctly sets the container’s prefix, then delegates to each nested Piecewise using _sub_prefix(name), and transform_data simply walks those children. This matches how Piecewise itself now behaves and should keep all origin/flow-specific piecewise data in sync under the same FlowSystem.


679-689: PiecewiseEffects: origin/effect prefixes and transform ordering are sensible

Using _sub_prefix('origin') for piecewise_origin and _sub_prefix(effect) for each share builds an intuitive tree (…|PiecewiseEffects|origin|Piece0, …|PiecewiseEffects|<effect>|Piece0, etc.), and the transform path (origin first, then all shares) keeps everything under the same FlowSystem context. This aligns well with how InvestParameters will now drive these structures.


907-912: InvestParameters.link_to_flow_system/transform_data: end‑to‑end integration with PiecewiseEffects and linked_periods

The InvestParameters changes look coherent: you propagate the FlowSystem and prefix to any piecewise_effects_of_investment, transform its tree with has_time_dim=False, then normalize all investment‑related effects and sizing fields via _fit_effect_coords / _fit_coords using self.prefix. The linked‑periods tuple handling still guards against missing periods and then converts to a DataArray before fitting coordinates, which is good. Just ensure (in callers) that link_to_flow_system is always invoked before transform_data, since self.flow_system is now relied upon inside transform_data for the linked‑periods computation.

Also applies to: 913-971


1218-1241: StatusParameters.transform_data: prefix usage and dimension choices remain consistent

Using self.prefix for both _fit_effect_coords calls and all subsequent _fit_coords calls (with explicit ['period', 'scenario'] where appropriate) matches the new system‑wide pattern and should preserve prior behavior for all status‑related parameters. The method is side‑effect‑only and doesn’t appear to introduce new edge cases beyond the existing data‑shape assumptions.

  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
@FBumann FBumann merged commit 82348b9 into feature/solution-storage-change Dec 9, 2025
8 checks passed
FBumann added a commit that referenced this pull request Dec 10, 2025
* 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
@coderabbitai coderabbitai bot mentioned this pull request Dec 10, 2025
9 tasks
@coderabbitai coderabbitai bot mentioned this pull request Jan 7, 2026
9 tasks
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