Skip to content

feat(model): Model.assign_coords — reassign coordinates on an existing model - #955

Open
olivier-lacroix wants to merge 1 commit into
PyPSA:masterfrom
olivier-lacroix:feat/model-assign-coords
Open

feat(model): Model.assign_coords — reassign coordinates on an existing model#955
olivier-lacroix wants to merge 1 commit into
PyPSA:masterfrom
olivier-lacroix:feat/model-assign-coords

Conversation

@olivier-lacroix

@olivier-lacroix olivier-lacroix commented Sep 13, 2026

Copy link
Copy Markdown

Intent

Closes #767.

Note

The following content was generated by AI (opencode).

Changes proposed in this Pull Request

New public API Model.assign_coords(**coords_kwargs): reassign coordinate
values across an existing model — variables, constraints (dense and
CSR-backed), expressions, and parameters — without changing its shape:
m.assign_coords(snapshot=new_snapshots). No relabeling, no reindexing;
the order of each dataset's variables and coordinates is preserved (plain
Dataset.assign_coords moves updated coords to the end, which breaks
downstream dim inference — the pitfall flagged in the issue).

  • Subset-aware: containers may hold subsets of a dimension (e.g. a
    piecewise commitment gate on a subset of generators). New values must
    match the full-index carrier's length; every container is then mapped
    through the master's old → new correspondence, preserving subset
    relations.
  • Implementation: per-container mutation goes through private
    _assign_coords methods backed by two new helpers in linopy/common.py
    (assign_coords_multiindex_safe, validate_coords_reassignment) that
    single-source the per-item invariants. Container-level assign_coords
    passthroughs are unchanged, so internal coordinate relabels — e.g. the
    piecewise formulation's adjacent-piece pairing (§10) — keep working as
    before.
  • v1 solve guard: Model.solve() raises when containers carry labels
    on a shared user dimension that are neither equal nor subsets of one
    another (the mislabeling trap nothing else catches). No-op under legacy
    semantics, where non-aligned containers are documented positional
    behavior (§8). Internal dims (underscore-prefixed, e.g.
    _breakpoint_piece) are exempt — the piecewise machinery labels them
    per-container on purpose; the invariant is now documented in
    linopy/constants.py.
  • Tests: new test/test_assign_coords.py — propagation across all
    container types, order preservation, values-only invariants, validation
    errors, subset mapping, guard behavior; 100% branch coverage of the
    added code, suite green under both legacy and v1 semantics.
  • Docs: Model.assign_coords entry in doc/api.rst (Modifying a
    model), release-notes entry, and a "Reassigning coordinates" section in
    examples/manipulating-models.ipynb (incl. the detached-passthrough
    caveat).

Checklist

  • AI-generated content is marked (see AGENTS.md).
  • Code changes are sufficiently documented; i.e. new functions contain docstrings and further explanations may be given in doc.
  • Unit tests for new features were added (if applicable).
  • A note for the release notes doc/release_notes.rst of the upcoming release is included.
  • I consent to the release of this PR's code under the MIT license.

@codspeed-hq

codspeed-hq Bot commented Sep 13, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 2.89%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
❌ 4 regressed benchmarks
✅ 175 untouched benchmarks
⏩ 181 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
test_to_lp[qp-n=1000] 1.9 MB 2.6 MB -24.93%
test_to_lp[nodal_balance_sparse-severity=50] 2.8 MB 3.7 MB -24.16%
test_to_lp[merge_balance-severity=0] 2.7 MB 3.3 MB -18.23%
test_to_lp[nodal_balance-severity=50] 3.3 MB 3.8 MB -14.18%
test_to_lp[sparse_network-n=10] 1,338.9 KB 722.4 KB +85.34%
test_to_lp[expression_arithmetic-n=250] 46.7 MB 41.2 MB +13.29%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing olivier-lacroix:feat/model-assign-coords (3c7c47e) with master (718c0c1)

Open in CodSpeed

Footnotes

  1. 181 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@olivier-lacroix

Copy link
Copy Markdown
Author

The performance changes look spurious, but let me know if I missed anything

@olivier-lacroix
olivier-lacroix marked this pull request as ready for review September 13, 2026 04:36
…ment

Add a public API to reassign coordinate values on an existing model,
e.g. m.assign_coords(snapshot=new_snapshots), mirroring
xarray.Dataset.assign_coords semantics for an existing model: values-only
replacement (no relabeling, no reindexing, no shape change) across all
variables, constraints (dense and CSR-backed), expressions and parameters
carrying the dimension.

Containers may hold subsets of a dimension (e.g. a piecewise commitment
gate on a subset of generators): the new values must match the length of
the full-index carrier, and every container is mapped through the
master's old -> new correspondence, preserving subset relations.

The per-container mutation goes through a new order-safe helper
(assign_coords_multiindex_safe) and a shared validator
(validate_coords_reassignment), single-sourcing the per-item invariants
(existing coordinate, index-like values, matching length). Unlike plain
Dataset.assign_coords, the helper preserves the dataset's variable and
coordinate order, which would otherwise break downstream dimension
inference.

Under v1 semantics, Model.solve() raises when containers carry labels on
a shared dimension that are neither equal nor subsets of one another -
the mislabeling trap no construction-time check catches; under legacy,
non-aligned containers remain documented positional behavior (convention
§8) and the guard is a no-op. Internal dims (underscore-prefixed) are
exempt, as the piecewise machinery labels them per-container on purpose.

Typical use is advancing the window in rolling-horizon optimization with
the persistent solver interface.

Closes PyPSA#767
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.

Model.assign_coords: coordinate reassignment on an existing model

1 participant