Skip to content

refactor: drop clustering dim-rename layer via tsam-xarray DimNames#739

Merged
FBumann merged 1 commit into
mainfrom
worktree-clustering-dim-names
Jul 22, 2026
Merged

refactor: drop clustering dim-rename layer via tsam-xarray DimNames#739
FBumann merged 1 commit into
mainfrom
worktree-clustering-dim-names

Conversation

@FBumann

@FBumann FBumann commented Jul 22, 2026

Copy link
Copy Markdown
Member

Context

flixopt's clustering already delegates all aggregation math to tsam_xarray. The only meaningful incidental complexity left was a dim-name adapter layer: tsam_xarray hard-coded its output dim names (cluster, timestep, period, segment), and the period output axis collided with flixopt's own period slice dim in multi-period systems. To dodge that, cluster() renamed period → _period / cluster → _cluster on the way in and unrenamed on the way out, with an _unrename_map threaded through cluster(), apply_clustering(), _ReducedFlowSystemBuilder, and Clustering.

tsam_xarray#99tsam-xarray 0.6.4 shipped a DimNames config that makes those output names configurable.

Change

Pass dim_names=DimNames(period='original_cluster') to aggregate(), so the original-period axis is named original_cluster natively and never collides with a period slice dim. That lets the whole rename/unrename layer go:

  • cluster() / apply_clustering() no longer rename dims in and back out. apply_clustering() now applies the stored ClusteringResult directly instead of rebuilding one with renamed slice_dims.
  • _ReducedFlowSystemBuilder and Clustering lose _unrename_map / _unrename.
  • The only renames left are the honest tsam↔flixopt boundary translations timestep→time / timestep→segment (tsam's integer sub-index vs flixopt's datetime axis; the new upstream collision guard blocks collapsing these anyway).
  • Bumps the tsam_xarray pin 0.6.1 → 0.6.4.
  • test_base mock helper now builds ClusteringResults with the same DimNames(period='original_cluster') convention production uses.

Net −83 lines, entirely subtraction of adapter code.

⚠️ Breaking change — persisted clustering format

The serialized clustering format changed: slice dims are now stored as period (was _period), plus a new dim_names key.

  • Fresh transform.cluster() and same-version round-trips are unaffected (verified: to_dataset/from_dataset, to_netcdf, to_json).
  • Clustering artifacts saved to JSON/netCDF by an older flixopt no longer load for apply_clustering() / expand() in this version — the stored legacy _period slice dim no longer matches the live period dim.

Migration: re-run transform.cluster() to regenerate any persisted clustering artifacts. Also raises the minimum tsam_xarray to 0.6.4.

No runtime API change

Every public clustering output keeps identical dim names, shapes, and values in single- and multi-period systems: cluster_assignments (original_cluster[, period]), cluster_occurrences, original/reconstructed/residuals/accuracy, dim_names, disaggregate(), compare(), and the reduced FlowSystem's (cluster, time) vars + cluster_weight. The old code reached those names via internal renames; tsam now emits them directly.

Verification

  • 272 passed across tests/test_clustering/ (IO/serialization, integration, multiperiod extremes, segmentation, reduce/expand, expansion-regression) + tests/test_math/test_clustering.py, on HiGHS and Gurobi.
  • ruff check + ruff format clean; pre-commit hooks pass.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@FBumann, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 41 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5740c6e6-a3ad-4424-8044-1a1fc2f5a536

📥 Commits

Reviewing files that changed from the base of the PR and between 63c717f and c4a1915.

📒 Files selected for processing (4)
  • flixopt/clustering/base.py
  • flixopt/transform_accessor.py
  • pyproject.toml
  • tests/test_clustering/test_base.py
📝 Walkthrough

Walkthrough

The clustering pipeline removes reserved-dimension rename/unrename handling, configures tsam_xarray with original_cluster, and returns clustering and aggregation data using underlying dimension names.

Changes

Clustering dimension handling

Layer / File(s) Summary
Aggregation dimension contract
flixopt/transform_accessor.py, tests/test_clustering/test_base.py, pyproject.toml
Clustering aggregation prepares slice dimensions directly, uses original_cluster for the period dimension, updates tsam_xarray versions, and aligns test fixtures with the new contract.
Reduced flow construction
flixopt/transform_accessor.py
Reduced-flow builders and clustering application no longer accept or apply unrename mappings when constructing weights, periods, durations, or reduced systems.
Clustering result accessors
flixopt/clustering/base.py
Clustering properties, disaggregation, reconstructed data, residuals, and accuracy return tsam_xarray outputs without reverse-renaming dimensions.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • flixOpt/flixopt#654 — Introduced the clustering refactor whose reserved-dimension handling is removed here.
  • flixOpt/flixopt#593 — Refactored the reduced-flow builder used by this clustering pipeline.
  • flixOpt/flixopt#734 — Modified overlapping reserved slice-dimension handling in Clustering.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is informative, but it does not follow the repository template and omits required sections like Type of Change and Testing. Reformat it to the template: add Description, Type of Change, Related Issues, Testing, and Checklist sections, and fill in the required checkboxes/details.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: removing the clustering dim-rename layer via tsam-xarray DimNames.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-clustering-dim-names

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.

@FBumann
FBumann force-pushed the worktree-clustering-dim-names branch from 63c717f to 55923b3 Compare July 22, 2026 16:53

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
flixopt/transform_accessor.py (1)

1424-1437: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

dim_names isn't guarded by reserved_tsam_keys.

dim_names=tsam_xarray.DimNames(period='original_cluster') is now passed explicitly to tsam_xarray.aggregate() alongside **tsam_kwargs_full. Since **tsam_kwargs is documented as forwarded "for forward compatibility" to tsam.aggregate(), a caller passing dim_names=... via cluster(**tsam_kwargs) will hit an unguarded Python TypeError ("multiple values for keyword argument") instead of the friendly ValueError that reserved_tsam_keys produces for the other explicit parameters (Lines 1325-1337).

🛡️ Proposed fix
         reserved_tsam_keys = {
             'n_clusters',
             'period_duration',  # exposed as cluster_duration
             'temporal_resolution',  # computed automatically
             'timestep_duration',  # computed automatically
             'cluster',
             'segments',
             'extremes',
             'preserve_column_means',
             'rescale_exclude_columns',
             'round_decimals',
             'numerical_tolerance',
+            'dim_names',  # fixed internally to avoid original-period/slice-dim collisions
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flixopt/transform_accessor.py` around lines 1424 - 1437, Guard the explicit
dim_names argument in the clustering flow before calling tsam_xarray.aggregate,
alongside the other reserved parameters checked by reserved_tsam_keys. Ensure a
caller-provided dim_names in tsam_kwargs is rejected with the existing friendly
ValueError rather than causing duplicate-keyword TypeError, while preserving the
explicit original_cluster value for valid calls.
flixopt/clustering/base.py (1)

292-309: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale docstring: aggregation_result still describes the removed rename adapter.

This docstring says flixopt's reserved-dim renames are still applied to the raw result (citing an internal _period dim) and that original/reconstructed/residuals/accuracy offer a "friendlier" alternative. After this refactor those properties (Lines 311-356) return _aggregation_result's values directly with no unrename step, so they now expose identical dims to aggregation_result itself — the described distinction no longer holds and will confuse users choosing between the two.

📝 Suggested docstring update
-        The returned object holds the **raw** tsam_xarray result, on which
-        flixopt's reserved-dim renames are still applied (the period dim is
-        ``_period``). For a friendlier view with the original dim names, use
-        the ``original`` / ``reconstructed`` / ``residuals`` / ``accuracy``
-        properties or ``compare()`` instead.
+        The returned object holds the tsam_xarray result directly (no
+        dim renaming is applied). For convenience accessors that align
+        ``reconstructed`` to ``original``'s dim order, or for a tidy
+        original-vs-clustered Dataset, use ``original`` / ``reconstructed`` /
+        ``residuals`` / ``accuracy`` / ``compare()`` instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flixopt/clustering/base.py` around lines 292 - 309, Update the
aggregation_result docstring to remove references to the removed rename adapter,
the internal _period dimension, and the claim that
original/reconstructed/residuals/accuracy provide a friendlier view. Document
that these properties expose the same underlying aggregation-result dimensions
and values directly, while preserving the availability and ValueError guidance.
🧹 Nitpick comments (1)
flixopt/transform_accessor.py (1)

1357-1372: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate slice-dim expand_dims logic vs apply_clustering().

This period/scenario expand_dims loop (1362-1372) is essentially the same block repeated in apply_clustering() (Lines 1504-1509), just iterating a different dim list. Worth extracting a small helper (e.g. _ensure_slice_dims(da, dim_names, ds)) shared by both call sites to avoid future drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flixopt/transform_accessor.py` around lines 1357 - 1372, Extract the repeated
period/scenario dimension normalization into a shared helper such as
_ensure_slice_dims(da, dim_names, ds), preserving the existing
coordinate-dropping and expand_dims behavior. Replace the inline loop near
da_for_clustering and the corresponding logic in apply_clustering() with calls
to this helper, passing each caller’s appropriate dimension list.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@flixopt/clustering/base.py`:
- Around line 292-309: Update the aggregation_result docstring to remove
references to the removed rename adapter, the internal _period dimension, and
the claim that original/reconstructed/residuals/accuracy provide a friendlier
view. Document that these properties expose the same underlying
aggregation-result dimensions and values directly, while preserving the
availability and ValueError guidance.

In `@flixopt/transform_accessor.py`:
- Around line 1424-1437: Guard the explicit dim_names argument in the clustering
flow before calling tsam_xarray.aggregate, alongside the other reserved
parameters checked by reserved_tsam_keys. Ensure a caller-provided dim_names in
tsam_kwargs is rejected with the existing friendly ValueError rather than
causing duplicate-keyword TypeError, while preserving the explicit
original_cluster value for valid calls.

---

Nitpick comments:
In `@flixopt/transform_accessor.py`:
- Around line 1357-1372: Extract the repeated period/scenario dimension
normalization into a shared helper such as _ensure_slice_dims(da, dim_names,
ds), preserving the existing coordinate-dropping and expand_dims behavior.
Replace the inline loop near da_for_clustering and the corresponding logic in
apply_clustering() with calls to this helper, passing each caller’s appropriate
dimension list.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2c57d10a-a8bd-406f-9e87-2b5be79abf07

📥 Commits

Reviewing files that changed from the base of the PR and between 5765530 and 63c717f.

📒 Files selected for processing (4)
  • flixopt/clustering/base.py
  • flixopt/transform_accessor.py
  • pyproject.toml
  • tests/test_clustering/test_base.py

tsam-xarray 0.6.4 makes output dimension names configurable through
DimNames. Pass DimNames(period='original_cluster') to aggregate() so the
original-period axis no longer collides with a real 'period' slice dim.

This removes the entire rename/unrename adapter that existed only to work
around the fixed 'period'/'cluster' output names:
- cluster()/apply_clustering() no longer rename period->_period etc. in
  and back out, and apply_clustering() applies the stored ClusteringResult
  directly instead of rebuilding one with renamed slice dims.
- _ReducedFlowSystemBuilder and Clustering lose _unrename_map/_unrename.

The only renames left are the honest tsam<->flixopt boundary translations
timestep->time / timestep->segment. Bumps the tsam_xarray pin to 0.6.4.

All public clustering outputs (cluster_assignments, cluster_occurrences,
original/reconstructed/residuals/accuracy, dim_names, disaggregate, the
reduced FlowSystem's (cluster, time) vars and cluster_weight) keep identical
dim names, shapes and values in single- and multi-period systems.

BREAKING CHANGE: the serialized clustering format changed - slice dims are
now stored as 'period' (was '_period') plus a new 'dim_names' key. Clustering
artifacts saved to JSON/netCDF by an older flixopt no longer load for
apply_clustering()/expand() in this version; re-run transform.cluster() to
regenerate them. Fresh clustering and same-version round-trips are unaffected.
Also raises the minimum tsam_xarray to 0.6.4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@FBumann
FBumann force-pushed the worktree-clustering-dim-names branch from 55923b3 to c4a1915 Compare July 22, 2026 17:08
@FBumann
FBumann enabled auto-merge (squash) July 22, 2026 17:08
@FBumann
FBumann merged commit cc56774 into main Jul 22, 2026
10 of 11 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.

1 participant