Skip to content

Commit 792bb4d

Browse files
FBumannclaude
andauthored
fix: normalize clustered solution dim order to (cluster, time) (#704)
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>
1 parent b6d66d8 commit 792bb4d

2 files changed

Lines changed: 5 additions & 5 deletions

File tree

flixopt/structure.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,8 @@ def solution(self):
335335
if 'time' in solution.coords:
336336
if not solution.indexes['time'].equals(self.flow_system.timesteps_extra):
337337
solution = solution.reindex(time=self.flow_system.timesteps_extra)
338+
if 'cluster' in solution.dims and 'time' in solution.dims:
339+
solution = solution.transpose('cluster', 'time', ...)
338340
return solution
339341

340342
@property

tests/test_math/test_clustering.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -336,12 +336,10 @@ def test_storage_cyclic_charge_discharge_pattern(self, optimize):
336336
discharge_fr = fs.solution['Battery(discharge)|flow_rate'].values[:, :4]
337337
assert_allclose(discharge_fr, [[0, 50, 0, 50], [0, 50, 0, 50]], atol=1e-5)
338338

339-
# Charge state: dims=(time, cluster), 5 entries (incl. final)
340-
# Cyclic: SOC wraps, starting with pre-charge from previous cycle
341339
charge_state = fs.solution['Battery|charge_state']
342-
assert charge_state.dims == ('time', 'cluster')
343-
cs_c0 = charge_state.values[:5, 0]
344-
cs_c1 = charge_state.values[:5, 1]
340+
assert charge_state.dims == ('cluster', 'time')
341+
cs_c0 = charge_state.isel(cluster=0).values[:5]
342+
cs_c1 = charge_state.isel(cluster=1).values[:5]
345343
assert_allclose(cs_c0, [50, 50, 0, 50, 0], atol=1e-5)
346344
assert_allclose(cs_c1, [100, 100, 50, 100, 50], atol=1e-5)
347345

0 commit comments

Comments
 (0)