One named densification chokepoint and a dispatch table for CSR-backed expressions
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 35/100
調査の方向性
Start with linopy/expressions.py, especially _densify, the .data property, and the listed CSR branches; read the dispatch-table notes in issues #971 and #1011 for context. Review the partial fallbacks in linopy/constraints.py as well, then run the proposed parametrized tests in test/test_csr.py. Done means dispatch entries preserve CSR backing under no_densify(), fallbacks warn with the operation name without densifying the receiver, and the existing twin-build tests pass.
索引モデルが issue の本文から書いたものです。
説明
[!NOTE]
The following content was generated by AI.
Describe the feature you'd like to see
Child of #1019 (step 1). Blocked on legacy semantics removal (#845, #846): the methods this touches carry the is_v1() gates, and the dispatch table should be built once, against the v1-only code. Internal refactor of linopy/expressions.py; no change to results or to the public API. Code links point to master at 69dd5c97.
Problem
A CSR-backed LinearExpression falls back to dense in two ways. Explicit overrides call self._densify(reason) with a hand-written string. Every method without an override reads self.data (linopy/expressions.py:2443), which converts the receiver in place and for good through _densify_all (:3698) with the generic reason ".data read". There are 34 explicit branch points on _csr / is_sparse / _try_csr_* / _sparse_* (:2443-3100, groupby :485-669, _try_csr_merge :3740), and the implicit set (shift, diff, coeffs, vars, to_quadexpr, simplify, loc, densify_terms, the const setter, ...) is nowhere listed.
import warnings
import numpy as np, pandas as pd, linopy
linopy.options["semantics"] = "v1"
linopy.options["warn_on_densify"] = True
i, t = pd.RangeIndex(20, name="i"), pd.RangeIndex(5, name="t")
m = linopy.Model(sparse=True)
x = m.add_variables(0, 1, coords=[i, t], name="x")
g = pd.Series(np.arange(20) % 3, index=i, name="g")
for op in ["shift(t=1)", "diff('t')", "coeffs", "to_quadexpr()", "isnull()"]:
s = x.groupby(g).sum()
assert s.is_sparse
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
eval(f"s.{op}")
msgs = [str(x.message) for x in w if "dens" in str(x.message).lower()]
print(f"{op:16} sparse after: {s.is_sparse!s:5} warning: {msgs}")
Output on master (69dd5c97):
shift(t=1) sparse after: False warning: ['Sparse (CSR) backing densified: `.data` read, e.g. by an operation without a sparse path.']
diff('t') sparse after: False warning: ['Sparse (CSR) backing densified: `.data` read, e.g. by an operation without a sparse path.']
coeffs sparse after: False warning: ['Sparse (CSR) backing densified: `.data` read, e.g. by an operation without a sparse path.']
to_quadexpr() sparse after: False warning: ['Sparse (CSR) backing densified: `.data` read, e.g. by an operation without a sparse path.']
isnull() sparse after: True warning: []
Proposed direction
- One chokepoint
_delegate(name, ...)onLinearExpression. Ifnamehas a sparse implementation, call it. Otherwise convert withreason=nameand call the dense implementation on the converted copy. The receiver keeps its backing. - The 34
if self._csrblocks become a dispatch table mapping frontend methods toCSRLinearExpressioncounterparts (sumtosummed,__neg__/__mul__toscaled,sel/isel/wheretotaken,reindextoreindexed,fillnatofilled,renametorenamed,@tocontracted, constant arithmetic towith_const, ...). The table is the authoritative list of backing-preserving operations; #971 documents it, #1011 adds rows. .databecomes a plain read through the chokepoint with reason"data". Setters that must mutate (const,dataassignment) go through the chokepoint with their own reason.- Partial fallbacks (
_selected:2582,_sparse_matmul:2904,_try_csr_merge:3740,csr_rhsinlinopy/constraints.py:1689) return a reason string instead of calling_densify_allthemselves. - Expected warning after the change:
Sparse (CSR) backing densified by shift, one name per operation.
Tests
- One parametrized test in
test/test_csr.pyover the dispatch table: each entry keeps the backing underno_densify(); each fallback warns with its own operation name and leaves the receiver sparse. - Existing twin-build tests (
assert_sparse_matches,assert_frozen_equal) pass unchanged.
- 主要言語
- Python
- スター
- 260
- フォーク
- 89
- 平均マージ
- 2日 16時間
- マージ済み PR(30日)
- 30
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
PyPSA/linopy のほかの issue
-
bug solver interface
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
メンテナーはふだん 1 日以内に返信
-
enhancement sparse
難易度 4/5 3〜5日 初心者へのやさしさ 48/100
メンテナーはふだん 1 日以内に返信
-
Meta: separate dense and sparse stores from the expression and constraint frontend再び着手できるかも @Rishabhkanhaiya が 14 日前に担当しましたが、オープン中のプルリクエストはありません。 オープンdiscussion refactor sparse
難易度 5/5 1週間以上 初心者へのやさしさ 25/100
PyPSA/linopy#1019 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
refactor sparse
難易度 4/5 3〜5日 初心者へのやさしさ 38/100
メンテナーはふだん 1 日以内に返信
-
discussion math-spec
難易度 5/5 1週間以上 初心者へのやさしさ 30/100
メンテナーはふだん 1 日以内に返信
似ている issue
-
bug good first issue
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
yunaremaia/proto-drift#22 ·
-
bug good first issue
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
yunaremaia/memwatch#96 ·
-
bug good first issue
難易度 1/5 1時間未満 初心者へのやさしさ 91/100
yunaremaia/ci-gate-watch#34 ·
-
bug good first issue
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
yunaremaia/agent-capability-attestation#64 ·
メンテナーはふだん 1 日以内に返信
-
bug good first issue
難易度 1/5 1時間未満 初心者へのやさしさ 86/100
yunaremaia/agent-call-graph#43 ·
メンテナーはふだん 1 日以内に返信