-
Notifications
You must be signed in to change notification settings - Fork 9
Feature/properties #558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feature/properties #558
Conversation
Complete rework of the clustering/aggregation system: - New clustering module with inter-cluster linking and storage modes - Unified cluster/expand API via transform_accessor - Support for multi-period and multi-scenario clustering - Improved cluster weights and statistics calculations - Storage model support for clustered operation (initial=equal, cyclic, etc.) - External tsam support with data aggregation - Cluster IO and serialization 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add new dataset plotting accessor for enhanced visualization: - fxplot accessor for xarray datasets - Improved plotting defaults and styling - Animation frame handling improvements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comparison functionality for analyzing multiple FlowSystem solutions: - Compare results across different scenarios - Visualization support for comparison data 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Tests for cluster_reduce and expand functionality - Tests for clustering base and integration - Tests for clustering IO - Tests for single selection (sel/isel) - Updated existing tests to work with new API 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add new clustering notebooks (08c, 08c2, 08d, 08e) - Add cluster architecture design documentation - Add tutorial data helper module for notebooks - Update existing notebooks to use new API - Improve user guide documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Document all breaking changes and new features for the major release. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Improve CI workflow for faster notebook execution in documentation builds. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
| Case | Layout |
|------------------|-------------------------------------------------------------------------|
| Simple (no dims) | Single heatmap |
| Periods only | Vertical stack (1 column) |
| Scenarios only | Horizontal layout |
| Both | Grid (periods as rows, scenarios as columns via facet_cols=n_scenarios) |
| File | Issue | Fix |
|-------------------------------------------------|-----------------------------------------------------------|-----------------------------------------------------|
| docs/notebooks/03-investment-optimization.ipynb | Division by zero when solar_size = 0 | Added guard: if solar_size > 0 else float('nan') |
| flixopt/clustering/base.py:181-235 | ClusterStructure.plot() crashes for multi-period/scenario | Added NotImplementedError with helpful message |
| flixopt/components.py:1256-1265 | Obsolete linopy LP writer bug workaround | Removed + 0.0 workaround (fixed in linopy >= 0.5.1) |
| flixopt/dataset_plot_accessor.py:742-780 | to_duration_curve crashes on variables without time dim | Added guard to skip non-time variables |
| flixopt/features.py:234-236 | Critical: Startup count ignores cluster weighting | Now multiplies by cluster_weight before summing |
| flixopt/structure.py:268-281 | scenario_weights docstring misleading | Updated docstring to accurately describe behavior |
Nitpick Fixes
| File | Fix |
|----------------------------------------------------|-------------------------------------------------------------------------------------------|
| docs/notebooks/data/generate_example_systems.py | Fixed type hint pd.DataFrame → pd.Series for _elec_prices, added timezone guard |
| flixopt/statistics_accessor.py:2103-2132 | Added detailed comment explaining secondary y-axis offset strategy |
| tests/test_cluster_reduce_expand.py | Moved import xarray as xr to top of file |
| docs/notebooks/data/generate_realistic_profiles.py | Clarified warnings.resetwarnings() comment |
| flixopt/transform_accessor.py | Removed unused cluster_coords and time_coords params from _combine_slices_to_dataarray_2d |
| flixopt/comparison.py | Added error handling to _concat_property for FlowSystems lacking optimization data |
1. Added _invalidate() helper method to the Interface base class in structure.py
2. Made Element.label immutable - attempting to set label now raises AttributeError
3. Converted parameters to properties across all Element/Component classes:
- Flow: size, relative_minimum/maximum, fixed_relative_profile, effects_per_flow_hour, status_parameters, flow_hours constraints, load_factor bounds
- Bus: carrier, imbalance_penalty_per_flow_hour
- Component: inputs, outputs, status_parameters, prevent_simultaneous_flows
- Storage: capacity_in_flow_hours, eta_charge/discharge, initial_charge_state, all charging bounds
- LinearConverter: conversion_factors, piecewise_conversion
- Transmission, Source, Sink: all relevant parameters
- Linear converter subclasses: Boiler, CHP, HeatPump, etc. (including efficiency properties)
- InvestParameters, StatusParameters: all parameters
- Effect: unit, description, is_standard, is_objective, period_weights, share_from_*, all bounds
4. Created tests/test_property_guards.py with 19 tests verifying:
- Label immutability (4 tests)
- Modification before linking works (3 tests)
- Modification after linking invalidates FlowSystem (3 tests)
- Nested object modification invalidates (2 tests)
- List/dict properties return copies (2 tests)
- Full workflow: modify → re-optimize (1 test)
- Storage properties invalidate (3 tests)
- HeatPump COP invalidates (1 test)
5. All 1561 tests pass including the new property guard tests
Key Design Decisions
- Labels are immutable (raise AttributeError)
- All other params are mutable but trigger _invalidate() after linking
- List/dict properties return copies to prevent in-place mutation
- Internal code uses backing fields (self._size) to avoid triggering invalidation during transform_data()
Resolve 24 merge conflicts, bringing improvements from main: - tsam clustering with storage inter-cluster linking - fxplot accessor and comparison module - Improved error handling, logging, and docstrings - CI speedups and notebook improvements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Merged main branch updates while preserving property-based architecture: - Kept private attributes with property getters/setters for Storage, OnOffParameters - Added cluster_mode property to OnOffParameters - Took main's improvements: _select_dims helper, join='outer' for xr.concat - Updated CITATION.cff version to 6.0.0rc2 - Added inputs property to Comparison class - Updated dim_priority with 'case' dimension 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Warning Rate limit exceeded@FBumann has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 17 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughRefactors core classes to use private backing fields with property accessors and invalidation across components, flows, effects, invest parameters, and structure; adds a cluster-architecture design doc and tests; adjusts startup counting (cluster weighting) and internal linking to operate on backing fields. Changes
Sequence Diagram(s)(omitted — changes are broad refactors, docs, and tests without a single new multi-component runtime control-flow that benefits from a focused sequence diagram) Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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. Comment |
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
flixopt/components.py (1)
1907-1923:prevent_simultaneous_flow_ratesflag does not update the modeled mutual‑exclusion constraint when changed after initFor
SourceAndSink,Source, andSink:
- The constructor wires mutual exclusivity into the base
Componentviaprevent_simultaneous_flows=...based on the initialprevent_simultaneous_flow_ratesvalue.- The
prevent_simultaneous_flow_ratesattribute/property then only updates a boolean backing field and calls_invalidate()(forSource/Sink). It does not updateComponent._prevent_simultaneous_flows.As a result, toggling
prevent_simultaneous_flow_ratesafter construction (and even after re‑optimization) does not actually add/remove the mutual‑exclusion constraint inComponentModel; the model continues to use whateverprevent_simultaneous_flowslist was set at construction time.Given the new property/invalidaton pattern, this is surprising behavior and can lead to silent mismatches between API flags and actual model constraints.
I suggest wiring the boolean property into the underlying flow list so that future (re)optimizations see the change, e.g.:
Proposed fix for `Source`, `Sink`, and `SourceAndSink`
@@ class SourceAndSink(Component): - def __init__( + def __init__( self, label: str, inputs: list[Flow] | None = None, outputs: list[Flow] | None = None, prevent_simultaneous_flow_rates: bool = True, meta_data: dict | None = None, ): - super().__init__( + super().__init__( label, inputs=inputs, outputs=outputs, prevent_simultaneous_flows=(inputs or []) + (outputs or []) if prevent_simultaneous_flow_rates else None, meta_data=meta_data, ) - self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates + self._prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates + + @property + def prevent_simultaneous_flow_rates(self) -> bool: + """If True, prevent simultaneous nonzero input and output flows.""" + return self._prevent_simultaneous_flow_rates + + @prevent_simultaneous_flow_rates.setter + def prevent_simultaneous_flow_rates(self, value: bool) -> None: + self._prevent_simultaneous_flow_rates = value + # Keep underlying mutual‑exclusion list in sync + self._prevent_simultaneous_flows = (self._inputs + self._outputs) if value else [] + self._invalidate() @@ class Source(Component): - def __init__( + def __init__( self, label: str, outputs: list[Flow] | None = None, meta_data: dict | None = None, prevent_simultaneous_flow_rates: bool = False, ): - self._prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates + self._prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates super().__init__( label, outputs=outputs, meta_data=meta_data, prevent_simultaneous_flows=outputs if prevent_simultaneous_flow_rates else None, ) @@ @prevent_simultaneous_flow_rates.setter def prevent_simultaneous_flow_rates(self, value: bool) -> None: - self._prevent_simultaneous_flow_rates = value - self._invalidate() + self._prevent_simultaneous_flow_rates = value + # Update underlying Component mutual‑exclusion wiring + self._prevent_simultaneous_flows = list(self._outputs) if value else [] + self._invalidate() @@ class Sink(Component): - self._prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates + self._prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates super().__init__( label, inputs=inputs, meta_data=meta_data, prevent_simultaneous_flows=inputs if prevent_simultaneous_flow_rates else None, ) @@ @prevent_simultaneous_flow_rates.setter def prevent_simultaneous_flow_rates(self, value: bool) -> None: - self._prevent_simultaneous_flow_rates = value - self._invalidate() + self._prevent_simultaneous_flow_rates = value + # Update underlying Component mutual‑exclusion wiring + self._prevent_simultaneous_flows = list(self._inputs) if value else [] + self._invalidate()This keeps the user‑visible boolean in sync with the actual constraints that
ComponentModelbuilds, while preserving the backing‑field and invalidation patterns.Also applies to: 2001-2024, 2104-2137
🤖 Fix all issues with AI agents
In @flixopt/elements.py:
- Around line 685-693: The getter effects_per_flow_hour currently
unconditionally calls dict(self._effects_per_flow_hour) which will raise if
_effects_per_flow_hour is a scalar or xarray.DataArray; modify the
effects_per_flow_hour property to only return dict(self._effects_per_flow_hour)
when isinstance(self._effects_per_flow_hour, dict), otherwise return
self._effects_per_flow_hour unchanged; mirror the pattern used in
InvestParameters/StatusParameters getters and ensure you still return a copy for
dict inputs while preserving scalar/array types.
In @flixopt/structure.py:
- Around line 438-447: The current _invalidate() on Interface calls
self._flow_system._invalidate_model() even when the FlowSystem is locked, which
can leave fs.model None but fs.solution/non-empty and fs.is_locked True,
violating the “locked cannot be modified” docstring; update the code so either
(a) FlowSystem._invalidate_model() clears the existing solution (e.g., reset
_solution and set is_locked False) when invoked from Interface invalidation, or
(b) change Interface._invalidate() to first check self._flow_system.is_locked
and either no-op or raise AttributeError instructing callers to call
FlowSystem.reset() before mutating; ensure you reference the FlowSystem class,
its _invalidate_model method, the Interface._invalidate method, the is_locked
property, and the solution/_solution field when making the change and add a
short docstring note describing the chosen behavior.
🧹 Nitpick comments (6)
flixopt/features.py (1)
237-242: Cluster-weighted startup counting looks correct; consider clarifying non-clustered semanticsThe new
startup_countformulation correctly:
- sums over all temporal dimensions except
period/scenario, and- applies
clusterweights only, so that a startup in a heavier cluster represents more real startups.Two minor points you may want to document inline:
- In non-clustered systems,
self._model.weightshas no'cluster'key, so the multiplier falls back to1.0. Ifcluster_weightwere ever set without aclusterdimension, this code intentionally ignores it (unliketemporal_weight), which seems fine but is worth making explicit.- For clustered systems with non-uniform
timestep_duration,startup_countdiffers from any effect based onsum_temporal(self.startup), which usestimestep_duration * cluster_weight. If you rely on those two quantities to align, a short comment noting this deliberate difference would help future readers.flixopt/linear_converters.py (1)
104-113: Backed conversion-factor properties and invalidation pattern look consistent; consider minor robustness tweaksThe refactor to have
Boiler,Power2Heat,HeatPump,CoolingTower,CHP, andHeatPumpWithSource:
- read from
_conversion_factors[idx].get(<input_label>), and- write
{input_label: value, output_label: 1}(or{electrical: -1, thermal: sed}forCoolingTower) followed by_invalidate()is consistent with the documented conversion relationships and with the new backing-field/invalidation model.
If you want to harden this further:
- Guard getters against malformed internal state, e.g. by returning a default or raising a clear error when
_conversion_factorsis empty or missing the expected key, instead of lettingIndexError/Noneleak out.- For
HeatPumpandHeatPumpWithSource, you may optionally assert that_conversion_factorshas exactly one (or two) entries when setting, to catch accidental external mutations earlier.These are not required for correctness given the current constructors, but would make the API more defensive against future changes.
Also applies to: 201-209, 297-305, 393-401, 515-536, 636-649
flixopt/flow_system.py (1)
1172-1175: Correct use of backing fields in wiring; note pre-existing bus membership caveatThe switch in:
_assign_element_colorsto writecomponent._colordirectly, and_connect_networkto usecomponent._inputs/_outputs, mutateflow._component/flow._is_input_in_component, and append intobus._outputs/_inputsis exactly what you need now that the public list properties return copies and setters trigger invalidation. This prevents both no-op mutations and accidental
_invalidate()calls during internal wiring.One behavioral nuance to be aware of (already present before this refactor):
_connect_network()only adds flows tobus._inputs/_outputsif they’re missing, but never removes flows that might have been associated with a different bus earlier. If you ever support changing aflow.busafter initial connection and relying on a subsequent_connect_network()to move it, you’ll need an explicit clear/rebuild of bus-side lists.Not a blocker, but something to keep in mind if dynamic rewiring becomes more common.
Also applies to: 1835-1851
tests/test_property_guards.py (1)
21-326: Comprehensive property/invalidation tests; consider solver-cost tradeoffThis suite does a thorough job of pinning down the new semantics:
- Label immutability across all Element types.
- Before/after-linking behavior for flows, components, effects, and nested parameter objects.
- List/dict properties returning copies.
- Storage and HeatPump-specific guards.
- A full “optimize → modify → re-optimize” path that asserts both invalidation and changed objective.
The only minor tradeoff is the use of a real
HighsSolverinTestFullWorkflow; the model is tiny so it’s likely fine, but if test runtime ever becomes a concern you could consider a lighter-weight solver or marking that test as “slow”. Functionally though, this is solid coverage.Also applies to: 331-437
docs/design/cluster_architecture.md (1)
36-45: Documentation looks solid; fix minor Markdown/wording nitsThe design doc reads well and aligns with the implemented
(cluster, time)approach. Two tiny polish items from linters:
- The “Current Implementation” code block (around line 37) uses bare `````; adding a language (
```pythonor ` ```text `) will satisfy markdownlint.- The phrase “true
(cluster, time)shaped data” would be more idiomatic as “true(cluster, time)-shaped data”.Purely cosmetic; the technical content is fine as-is.
Also applies to: 394-404
flixopt/components.py (1)
87-107: Dynamic rewiring of flows can leave_flowsand flow metadata inconsistent
Component.__init__wires_inputs/_outputsinto_flowsand_connect_flows()once, but theinputs,outputs, andflowssetters only mutate the backing lists/dict and call_invalidate().If callers later reassign
component.inputs/component.outputs/component.flows, you end up with:
Flow._component/Flow._is_input_in_componentstill reflecting the old wiring.self._flowspotentially stale if onlyinputsoroutputswere changed.- Model construction (
ComponentModel/BusModel) using_inputs/_outputs, while other code (e.g.LinearConverterModelviaself.element.flows[...]) uses_flows.This makes topology changes after initialization fragile and can cause mismatches or attribute errors when new flows lack submodels.
I recommend, if you want to support changing flows post‑construction, to have these setters also:
- Recompute
_flowsfrom_inputs + _outputs.- Re-run
_check_unique_flow_labels()and_connect_flows()(or a lighter variant) to refreshFlow._component/Flow._is_input_in_component.- Then
_invalidate().If dynamic rewiring is deliberately unsupported, consider documenting that and possibly making the setters internal‑only to avoid misleading users.
Also applies to: 110-159, 186-235
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
docs/design/cluster_architecture.mdflixopt/components.pyflixopt/effects.pyflixopt/elements.pyflixopt/features.pyflixopt/flow_system.pyflixopt/interface.pyflixopt/linear_converters.pyflixopt/structure.pytests/test_property_guards.py
🧰 Additional context used
🧬 Code graph analysis (5)
flixopt/flow_system.py (1)
flixopt/elements.py (6)
component(776-778)component(781-783)is_input_in_component(786-788)is_input_in_component(791-793)bus(636-638)bus(641-643)
flixopt/features.py (2)
flixopt/flow_system.py (2)
dims(1947-1967)weights(2126-2146)flixopt/structure.py (2)
dims(229-231)weights(239-244)
flixopt/structure.py (1)
flixopt/flow_system.py (2)
_invalidate_model(1491-1512)FlowSystem(55-2521)
flixopt/interface.py (4)
flixopt/structure.py (7)
_invalidate(438-446)prefix(391-393)_fit_effect_coords(484-502)dims(229-231)transform_data(375-388)_fit_coords(469-482)flow_system(449-467)flixopt/flow_system.py (1)
dims(1947-1967)flixopt/components.py (3)
transform_data(243-250)transform_data(671-710)transform_data(1054-1058)flixopt/effects.py (1)
transform_data(402-445)
flixopt/effects.py (2)
flixopt/structure.py (5)
_invalidate(438-446)_fit_coords(469-482)prefix(391-393)_fit_effect_coords(484-502)dims(229-231)flixopt/flow_system.py (1)
dims(1947-1967)
🪛 LanguageTool
docs/design/cluster_architecture.md
[grammar] ~512-~512: Use a hyphen to join words.
Context: ...Goal: Produce true (cluster, time) shaped data. Tasks: 1. Update `clus...
(QB_NEW_EN_HYPHEN)
🪛 markdownlint-cli2 (0.18.1)
docs/design/cluster_architecture.md
37-37: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ 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.14)
- GitHub Check: test (3.13)
- GitHub Check: test (3.12)
- GitHub Check: test (3.11)
- GitHub Check: Build documentation
🔇 Additional comments (2)
flixopt/effects.py (1)
209-239: Effect backing fields + properties are consistent and safe for invalidationThe refactor to back
Effectfields with private attrs (_unit,_description,_is_standard, all min/max bounds, share maps, andperiod_weights) and to:
- Expose them via properties that invalidate on mutation, and
- Use the backing fields directly in
transform_data()to avoid recursive invalidation,is internally consistent and aligns with the patterns in
Flow/InvestParameters/StatusParameters. The copy‑on‑read behavior forshare_from_temporal/share_from_periodicand effect dictionaries also protects against accidental in‑place mutation of internal state.I don’t see correctness or dimension issues here; this looks good.
Also applies to: 240-394, 402-445
flixopt/interface.py (1)
1146-1262: InvestParameters/StatusParameters property refactor and transform_data behavior look consistentThe move to private backing fields with property accessors for
InvestParameters(effects, sizes, piecewise, linked_periods) andStatusParameters(effects, uptime/downtime, active hours, startup_limit, cluster_mode), combined with:
- A single validation in
InvestParameters.transform_data()that enforcesfixed_sizeormaximum_sizeto be set, andtransform_data()operating directly on the backing fields via_fit_coords/_fit_effect_coords,is coherent and matches the design elsewhere (Effect, Flow, Storage).
The new upper‑bound requirement on investments is a behavioral tightening but is well‑motivated by scaling constraints and is centrally enforced; downstream uses via
minimum_or_fixed_size/maximum_or_fixed_sizelook correct.No issues from a correctness or modeling perspective.
Also applies to: 1282-1346, 1577-1750
The getter assumed a dict but the type hint allows scalars/arrays. Fixed to pass through non-dict values while returning a copy for dict inputs:
if isinstance(self._effects_per_flow_hour, dict):
return dict(self._effects_per_flow_hour)
return self._effects_per_flow_hour
2. prevent_simultaneous_flow_rates setters (flixopt/components.py)
The setters in Source, Sink, and SourceAndSink didn't update _prevent_simultaneous_flows, so the constraint wouldn't change on re-optimization. Fixed all three:
- Source (line 2021-2026): Now updates _prevent_simultaneous_flows = list(self._outputs) if value else []
- Sink (line 2137-2141): Now updates _prevent_simultaneous_flows = list(self._inputs) if value else []
- SourceAndSink (lines 1924-1934): Added property/setter that updates _prevent_simultaneous_flows = list(self._inputs) + list(self._outputs) if value else []
3. _invalidate() behavior documentation (flixopt/structure.py:438-452 & flixopt/flow_system.py:1491-1512)
Enhanced docstrings to clarify that:
- _invalidate() clears the model but preserves the solution
- This is intentional: the next optimize() call re-transforms and replaces the old solution
- This enables modify-and-reoptimize workflows without requiring explicit reset() calls
Description
Brief description of the changes in this PR.
Type of Change
Related Issues
Closes #(issue number)
Testing
Checklist
Summary by CodeRabbit
Documentation
Improvements
Tests
✏️ Tip: You can customize this high-level summary in your review settings.