refactor: move alignment code to linopy/alignment.py; restructure tests#742
Merged
Conversation
…ment.py Pure move, no behavior change. The new module owns the seam between user input and linopy's labelled arrays: - coords parsing: _coords_to_dict, _as_index, _as_multiindex - conversion: get_from_iterable, pandas_to_dataarray, numpy_to_dataarray, _named_pandas_to_dataarray, fill_missing_coords, as_dataarray - MultiIndex projection: _LevelProjection, _project_onto_multiindex_levels, _warn_implicit_projections - broadcasting: _broadcast_to_coords, broadcast_to_coords - validation: validate_alignment - symmetric alignment: align common.py keeps the general utilities (formatting, label indexes, polars helpers, decorators). Importers (model, expressions, variables, __init__, tests) updated; no re-exports. Follow-up requested by @FabianHofmann in #732. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One class per concept in linopy.alignment, mirroring the module's public
surface:
- TestAsDataarrayFrom{Pandas,Numpy,Scalar,DataArray} + MultiIndexCoords
- TestCoordsToDict (the coords-entry naming rules)
- TestBroadcastToCoords (strict=False mechanics)
- TestMultiIndexProjection (projection values, deprecation warnings,
coverage gaps — the legacy/v1 fork point for #717)
- TestStrictMode (strict=True contract)
- TestValidateAlignment
- TestAlign
Shared fixtures (mi_index / mi_coords / by_level1) replace the repeated
MultiIndex setup; the pandas dims-naming and numpy labeling tests are
consolidated into parametrized tables. test_common.py keeps the utility
tests. Full suite count unchanged (3202) — no coverage lost.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
@FabianHofmann I need to convince myself that these changes are thorough and complete. Ill mark it as ready later |
…tion alignment.py: 97% -> 99%. New edge-case tests: bare-string dims, 0-d arrays, fill_missing_coords type check, partially-named MI levels, gap detection with extra dims, gap-error truncation (>5 missing combinations). The two remaining uncovered lines are defensive branches for inputs outside the DimsLike contract (non-iterable dims). common.py: 88% -> 90%. The MultiIndex round-trip through coords_to_dataset_vars / coords_from_dataset (used by CSRConstraint) had zero coverage; now pinned. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nto refactor/alignment-module
FabianHofmann
approved these changes
Jun 3, 2026
Collaborator
FabianHofmann
left a comment
There was a problem hiding this comment.
only these two, feel free to merge after
Co-authored-by: Fabian Hofmann <fab.hof@gmx.de>
Co-authored-by: Fabian Hofmann <fab.hof@gmx.de>
FBumann
added a commit
that referenced
this pull request
Jun 3, 2026
Brings in the coords-as-truth stack (#732), the alignment.py module split (#742), has_terms (#743), and the MatrixAccessor caching (#716). Conflict resolutions (consistent rule: keep this branch's v1/legacy dispatch structure, use master's conversion calls inside it): - expressions.py: _add_constant, _apply_constant_op_{v1,legacy}, and to_constraint use broadcast_to_coords(..., strict=False) instead of as_dataarray; SUPPORTED_CONSTANT_TYPES -> CONSTANT_TYPES. - variables.py: to_linexpr converts via broadcast_to_coords(strict=False), then applies the v1/legacy absence handling. - __init__.py: align from linopy.alignment + LinopySemanticsWarning export. Test adaptations for post-#732 APIs and semantics: - test_legacy_violations: name the MultiIndex coords entry (required since #732). - test_linear_expression: the masked-addend tails of test_nterm and test_variable_names pin legacy absence behavior; split into @pytest.mark.legacy / @pytest.mark.v1 pairs (section 6 divergence). Full suite under both semantics: 6446 passed, 514 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FBumann
added a commit
that referenced
this pull request
Jun 3, 2026
…cenario B v1) Closes the two integration points between the alignment layer (#732/#742) and the v1 semantics infrastructure: - _warn_implicit_projections -> _enforce_implicit_projections: under legacy semantics, the implicit MultiIndex-level projection deprecation now goes through warn_legacy() / LinopySemanticsWarning (#738, replacing EvolvingAPIWarning, which stays piecewise-only); under the v1 convention it raises ValueError (sections 8 and 11) — the projection must be written explicitly (scenario B of the #732/#737 discussion). - as_expression no longer swallows the underlying conversion error: "Cannot convert to LinearExpression: <original message>" so the v1 guidance reaches the user. Tests: the MI-projection deprecation tests in test_alignment, test_variable, test_constraint, and test_linear_expression are marked @pytest.mark.legacy and assert LinopySemanticsWarning; each gains a @pytest.mark.v1 counterpart asserting the v1 raise. Full suite under both semantics: 6446 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up requested by @FabianHofmann in #732:
Commit 1 — the module move (pure, no behavior change)
linopy/alignment.pyowns the seam between user input and linopy's labelled arrays:_coords_to_dict,_as_index,_as_multiindexget_from_iterable,pandas_to_dataarray,numpy_to_dataarray,_named_pandas_to_dataarray,fill_missing_coords,as_dataarray_LevelProjection,_project_onto_multiindex_levels,_warn_implicit_projections(withTODO(#738))_broadcast_to_coords,broadcast_to_coordsvalidate_alignmentaligncommon.pykeeps the general utilities (formatting, label indexes, polars helpers, decorators). Hard move, no re-exports (nothing outside linopy imports these fromlinopy.common— checked PyPSA). The split is clean: nothing remaining incommon.pydepends on anything moved.Commit 2 — the test restructure
test/test_alignment.py, one class per concept:mi_index/mi_coords/by_level1replace the MultiIndex setup that was repeated inline in ~10 tests.test_common.pykeeps the utility tests (iterate_slices, polars, formatting, …).v1 readiness (#717)
TestMultiIndexProjectioncollects every test that forks under the v1 semantics (deprecation warnings → raises). When #717's autousesemanticsfixture lands, these get@pytest.mark.legacyand the v1 counterparts slot into the same class — no reorganization needed. Markers themselves are deliberately not added here (they belong to #717, where they have meaning).Verification — coverage measured, not just asserted
common.py(monolith)alignment.pycommon.py(utilities)The restructure preserved coverage exactly (the same missed lines, split across files), and the third commit then closed most of the remaining gaps: bare-string dims, 0-d arrays, partially-named MI levels, gap detection with extra dims, gap-error truncation, and the previously zero-coverage MultiIndex round-trip in
coords_to_dataset_vars/coords_from_dataset(CSRConstraint serialization). The 2 remaining uncovered lines inalignment.pyare defensive branches for inputs outside theDimsLiketype contract.🤖 Generated with Claude Code