Skip to content

feat: long-form inputs via from_long and relation group-by via Relation (#1019 step 5) - #1059

Draft
FabianHofmann wants to merge 3 commits into
masterfrom
store/5-long-form
Draft

FabianHofmann wants to merge 3 commits into
masterfrom
store/5-long-form

Conversation

@FabianHofmann

@FabianHofmann FabianHofmann commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #1049. Part of #1019 (step 5, the last step).

Note

The following content was generated by AI.

Changes proposed in this Pull Request

linopy.from_long(frame, value, coords, default=np.nan) -> DataArray. Converts a long table (polars or pandas frame: key columns named after dims plus one named value column) to a DataArray over the key dims, eagerly at the call site. Cells not listed take default; dims of coords without a key column are left to linopy's usual broadcasting. The result is an ordinary DataArray, so it works everywhere a constant does (bounds, coefficients, arithmetic, rhs, penalty) and passes the strict 1.0 alignment unchanged; nothing is added to ConstantLike or as_dataarray.

Strict schema, fail fast: missing value column, no key column, key not a dim, null keys, non-numeric or null values, duplicate keys, keys outside the coords, a key dtype family (numeric / temporal / string) or time zone differing from the coord, lossy key casts, and pandas frames with a non-default index raise. pl.LazyFrame, Series and MultiIndex forms are not accepted.

Pivot: polars keys are mapped to positions by a left join against each coord (arithmetic for a RangeIndex), combined into a flat index in polars, checked for duplicates with n_unique, then scattered into one np.full buffer; a full, sorted table is reshaped without the buffer. pandas input uses Index.get_indexer (no pyarrow dependency).

linopy.Relation(frame, member, group) for relation group-by. expr.groupby(Relation(...)).sum() sums over a mapping with repeated member keys, e.g. link port → bus. Member rows are gathered (Store.take_positions, now public) and scattered through the unchanged from_grouper, so both stores share one path; members listed in no row drop out. A bare frame passed to groupby raises and points to Relation. Quadratic expressions raise NotImplementedError.

Benchmark: long table → 10k × 2920 cube
rows (fill) keys lazy Table (first draft) from_long, polars from_long, pandas
1.93M (7 %) int 4.1 s 0.35 s 0.35 s
2.78M (10 %) str + datetime 6.9 s 0.39 s 0.57 s
29.2M (100 %) str + datetime 18.5 s 0.87 s 2.40 s
Review and tests

A first draft used a lazy linopy.Table constant pivoted once target coords were known; it leaked into ~40 ConstantLike call sites and converted polars through Python lists and a cube-sized MultiIndex. A design pass replaced it with the eager function above and made relations explicit.

Branch review (four reviewers plus a coordinator) on the first draft found one must-fix (quadratic relation group-by crashed), fixed. Not addressed: sparse quadratic group-by with a plain Series already fails on master; a cube too large for np.prod gives an unclear IndexError.

Full suite: 4978 passed, 84 skipped. mypy ., ruff check ., ruff format --check . and the doctests pass.

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.

Table pivots pandas/polars long tables onto target coords for bounds, coefficients, constants, rhs and penalty. expr.groupby(relation_frame).sum() scatters repeated member rows through from_grouper on both stores; quadratic relation group-by raises.
@codspeed

codspeed Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 181 untouched benchmarks
⏩ 181 skipped benchmarks1


Comparing store/5-long-form (6536fbf) with master (51c4977)

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. ↩

…n replace lazy Table

Long tables convert to a DataArray at the call site with strict key checks; nothing leaks into ConstantLike or alignment. groupby takes linopy.Relation instead of detecting relation frames by shape.
@FabianHofmann FabianHofmann changed the title feat: linopy.Table long-form inputs and relation group-by (#1019 step 5) feat: long-form inputs via from_long and relation group-by via Relation (#1019 step 5) Oct 24, 2026

This branch has not been deployed

No deployments
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.

Long-form inputs: tables as bounds, coefficients, constants and rhs; relation group-by

1 participant