Skip to content

fix: normalize clustered solution dim order to (cluster, time)#704

Merged
FBumann merged 1 commit into
mainfrom
fix/linopy-0.8-charge-state-dim-order
Jun 14, 2026
Merged

fix: normalize clustered solution dim order to (cluster, time)#704
FBumann merged 1 commit into
mainfrom
fix/linopy-0.8-charge-state-dim-order

Conversation

@FBumann

@FBumann FBumann commented Jun 14, 2026

Copy link
Copy Markdown
Member

What

Makes the public solution layout deterministic for clustered systems:
charge_state (and everything else) is always (cluster, time), regardless
of the linopy version. Unblocks the linopy 0.8 bump in #701.

Root cause

linopy <0.8 stores the extra-timestep charge_state variable as
(time, cluster), while every other variable is (cluster, time). The cause
is internal to linopy 0.7: charge_state's time coordinate has length n+1
(the extra SOC boundary), which conflicts with the model's time (length n),
and 0.7 reorders the conflicting dim to the front. I confirmed flixopt cannot
fix this from the input side — passing bounds/coords as (cluster, time) still
comes back (time, cluster) on 0.7.

linopy 0.8.0 (PyPSA/linopy#732,
"unify coords-as-truth handling") makes the explicit coords the source of
truth for dim order, so it's already consistent there.

linopy charge_state.dims flow_rate.dims
<0.8 (time, cluster) (cluster, time)
>=0.8 (cluster, time) (cluster, time)

Fix

Normalize in FlowSystemModel.solution: transpose('cluster', 'time', ...).

  • No-op on linopy >=0.8 and for non-clustered systems.
  • Only reorders the cluster/time axes; all other dims and scalars are preserved
    via the ellipsis.

Production code already accesses charge_state purely by label, so it was
unaffected either way; this just makes the user-facing solution stable.

The test is updated to access charge_state by label and assert the
now-deterministic (cluster, time). The linopy version bump itself stays in #701.

Verification

  • Target test passes on linopy 0.7 (all 3 parametrizations).
  • tests/test_clustering + tests/test_math/test_clustering.py +
    tests/flow_system/test_flow_system_resample.py: 288 passed.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

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

More reviews will be available in 22 minutes and 45 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

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.

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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b07da080-95be-4fcd-8cb4-3d4736b95384

📥 Commits

Reviewing files that changed from the base of the PR and between d2f7faf and 687e819.

📒 Files selected for processing (2)
  • flixopt/structure.py
  • tests/test_math/test_clustering.py
📝 Walkthrough

Walkthrough

In test_storage_cyclic_charge_discharge_pattern, the charge_state DataArray dimension check is changed from a fixed-tuple comparison to a set comparison, and per-cluster SOC slices are now extracted via isel(cluster=0/1) instead of positional .values indexing.

Changes

Cyclic clustering test assertion fix

Layer / File(s) Summary
Dimension-order-safe charge_state assertions
tests/test_math/test_clustering.py
Replaces charge_state.dims == ("time", "cluster") with set(charge_state.dims) == {"time", "cluster"} and switches per-cluster SOC extraction from positional .values[..., i] to isel(cluster=0) / isel(cluster=1) labeled indexing.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~2 minutes

Poem

A rabbit once tripped on the order of dims,
So now we use sets for our clustering whims.
isel by label, not position or place,
The test hops along at a confident pace.
🐇 No more fragile index guessing games!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 accurately summarizes the main change—normalizing clustered solution dimensions to (cluster, time) order—and is concise and specific.
Description check ✅ Passed The description comprehensively covers the root cause, fix, and verification with clear context about the linopy version differences and dimensional ordering issues.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/linopy-0.8-charge-state-dim-order

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 FBumann force-pushed the fix/linopy-0.8-charge-state-dim-order branch from d2f7faf to 371a9c6 Compare June 14, 2026 17:04
@FBumann FBumann changed the title test: make charge_state assertion order-agnostic for linopy 0.8 fix: normalize clustered solution dim order to (cluster, time) Jun 14, 2026
linopy <0.8 stores the extra-timestep charge_state variable as
(time, cluster), because its 'time' coordinate (length n+1) conflicts
with the model's 'time' (length n), and 0.7 reorders the conflicting
dim to the front. Every other variable is (cluster, time). linopy >=0.8
makes coords the source of truth and is already consistent.

flixopt cannot control this from the bounds/coords it passes (0.7
reorders internally), so normalize in the solution property: transpose
'cluster' before 'time'. This is a no-op on linopy >=0.8 and for
non-clustered systems, and only touches the cluster/time axis ordering
(other dims and scalars are preserved via the ellipsis).

Also update the clustering test to access charge_state by label and
assert the now-deterministic (cluster, time) order. Unblocks the
linopy 0.8 bump in #701.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@FBumann FBumann force-pushed the fix/linopy-0.8-charge-state-dim-order branch from 371a9c6 to 687e819 Compare June 14, 2026 17:07
@FBumann FBumann enabled auto-merge (squash) June 14, 2026 17:07
@FBumann FBumann merged commit 792bb4d into main Jun 14, 2026
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