Skip to content

feat: make output dimension names configurable via DimNames#101

Merged
FBumann merged 1 commit into
mainfrom
feat/configurable-dim-names
Jul 22, 2026
Merged

feat: make output dimension names configurable via DimNames#101
FBumann merged 1 commit into
mainfrom
feat/configurable-dim-names

Conversation

@FBumann

@FBumann FBumann commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Closes #99.

Summary

The four structural output dims (cluster, timestep, period, segment) were hard-coded and reserved, forcing downstream wrappers (e.g. flixopt) to rename their own dims before/after every call — most painfully when a caller's slice dim is literally named period (multi-period optimization models).

This adds a frozen DimNames config, mirroring the existing ClusterConfig/ExtremeConfig/SegmentConfig style:

from tsam_xarray import aggregate, DimNames

aggregate(
    da,  # has a slice dim literally named "period"
    time_dim="time", cluster_dim="variable", n_clusters=8,
    dim_names=DimNames(period="original_period"),
)
  • dim_names=None (default) reproduces today's names exactly.
  • The resolved names are stored on ClusteringResult, so apply(), disaggregate(), and the JSON round-trip all reproduce them.
  • Surfaced on AggregationResult via result.dim_names.

Changes

  • _dim_names.py (new) — DimNames(cluster, timestep, period, segment); __post_init__ enforces uniqueness; as_tuple() helper.
  • _core.pyaggregate(dim_names=None); threaded through _aggregate_single/_result_from_tsam and the _*_to_da builders. Replaced the blanket _RESERVED_DIMS set with _validate_dim_names, which validates the resolved output names against the actual input dims and raises on any collision.
  • _clustering.pyClusteringResult stores dim_names; all _build_* properties, apply/_apply_single, disaggregate/_disaggregate_single, and to_dict/from_dict use it. from_dict defaults missing keys to today's names for old blobs.
  • _result.py — added result.dim_names; n_clusters/n_timesteps_per_period and the slice-view/disaggregate paths read the configured names.
  • __init__.py — exports DimNames.
  • docs/data-model.md — new "Custom output dimension names" section.
  • test/test_aggregate.py — 10 new tests (period-collision resolved, apply/disaggregate/JSON round-trips, legacy-blob backcompat, uniqueness, segmentation).

Behavior-change note

segment is now part of the reserved/collision set. Previously _RESERVED_DIMS only held cluster/timestep/period, so an input dim named segment slipped through (a latent collision bug with segmented runs). Now a non-segmented run with an input dim literally named segment errors by default; pass DimNames(segment=...) to free it. Practically, segment as an output axis only labels ClusteringResult.segment_centers, which is None for all current tsam configs — so the reserve mainly closes the collision gap.

Verification

  • pytest test/ — 537 passed, 4 skipped
  • mypy src/ — clean
  • ruff check / ruff format — clean

🤖 Generated with Claude Code

The four structural output dims (cluster, timestep, period, segment)
were hard-coded and reserved, so callers whose own dims collide with
them (notably multi-period models with a `period` slice dim) had to
rename before/after every call.

Add a frozen `DimNames` config, passed as `aggregate(..., dim_names=)`
and stored on `ClusteringResult` so `apply()`, `disaggregate()`, and
the JSON round-trip reproduce the chosen names. `dim_names=None`
reproduces today's names.

The reserved-name check now validates the resolved output names against
the actual input dims (raising on any collision) instead of blanket-
reserving four literals; this also closes a latent gap where `segment`
was not reserved. `from_dict` defaults missing `dim_names` to today's
names for old blobs.

Closes #99

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in: 49 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: ea3eab29-360d-4073-b1b0-7274c2760fe6

📥 Commits

Reviewing files that changed from the base of the PR and between 5616d1c and 6514d66.

📒 Files selected for processing (7)
  • docs/data-model.md
  • src/tsam_xarray/__init__.py
  • src/tsam_xarray/_clustering.py
  • src/tsam_xarray/_core.py
  • src/tsam_xarray/_dim_names.py
  • src/tsam_xarray/_result.py
  • test/test_aggregate.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/configurable-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 merged commit 558fb9d into main Jul 22, 2026
9 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.

Make output dimension names configurable in aggregate() / ClusteringResult

1 participant