Skip to content

docs: document the sparse expression path - #1017

Open
FabianHofmann wants to merge 1 commit into
masterfrom
docs/sparse-path-971
Open

FabianHofmann wants to merge 1 commit into
masterfrom
docs/sparse-path-971

Conversation

@FabianHofmann

@FabianHofmann FabianHofmann commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #971

Note

The following content was generated by AI.

Documents the sparse expression path.

  • New notebook examples/sparse-models.ipynb (page sparse-models, under Advanced Features): what a sparse model is for, how to opt in (v1 semantics + Model(sparse=True); the deprecated per-call opt-ins), an end-to-end example (groupby(...).sum(), @, frozen CSRConstraint, solve), the operations that keep the CSR backing and the ones that densify, the warn_on_densify option (None/True/False, explicit conversions quiet under None), and the v1-only term-layout contract (Zero culling forces model rebuilding #925).
  • doc/api.rst: LinearExpression.is_sparse, Model.sparse, CSRConstraint.to_dense/mutable, and a list of the options keys.
  • Short "Sparse models" section in doc/user-guide.rst, one release note line.

Every claim in the guide was checked by running code on this branch. The notebook executes cleanly, and a local sphinx build (notebook execution off) shows no new warnings compared to the base branch.

@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Build cost — v1 vs legacy

v1 build peak & time relative to legacy, on this commit — not a comparison against master (that is CodSpeed).

peak — v1 / legacy time — v1 / legacy
peak v1/legacy time v1/legacy
Full table (time + peak, mean)
benchmarks/drivers/test_build.py::test_build[basic-n=10]
                  time (s)         peak (KiB) 
 name                 mean   │           mean 
──────────────────────────────────────────────
 (legacy)   0.09034 (1.08)   │   15.03 (1.00) 
 (v1)         0.0836 (1.0)   │    15.00 (1.0) 

benchmarks/drivers/test_build.py::test_build[basic-n=250]
                  time (s)         peak (MiB) 
 name                 mean   │           mean 
──────────────────────────────────────────────
 (legacy)   0.09654 (1.09)   │   12.04 (1.00) 
 (v1)        0.08858 (1.0)   │    12.04 (1.0) 

benchmarks/drivers/test_build.py::test_build[cumsum-severity=0]
                  time (s)        peak (KiB) 
 name                 mean   │          mean 
─────────────────────────────────────────────
 (legacy)   0.03929 (1.09)   │   15.20 (1.0) 
 (v1)        0.03602 (1.0)   │   15.20 (1.0) 

benchmarks/drivers/test_build.py::test_build[cumsum-severity=100]
                  time (s)        peak (MiB) 
 name                 mean   │          mean 
─────────────────────────────────────────────
 (legacy)   0.05563 (1.06)   │   44.93 (1.0) 
 (v1)        0.05246 (1.0)   │   44.93 (1.0) 

benchmarks/drivers/test_build.py::test_build[cumsum-severity=50]
                  time (s)        peak (MiB) 
 name                 mean   │          mean 
─────────────────────────────────────────────
 (legacy)   0.04263 (1.09)   │   11.51 (1.0) 
 (v1)        0.03929 (1.0)   │   11.51 (1.0) 

benchmarks/drivers/test_build.py::test_build[expression_arithmetic-n=10]
                 time (s)         peak (KiB) 
 name                mean   │           mean 
─────────────────────────────────────────────
 (legacy)   0.1019 (1.06)   │   24.34 (1.06) 
 (v1)       0.09572 (1.0)   │    23.04 (1.0) 

benchmarks/drivers/test_build.py::test_build[expression_arithmetic-n=250]
                 time (s)         peak (MiB) 
 name                mean   │           mean 
─────────────────────────────────────────────
 (legacy)   0.1109 (1.06)   │   16.12 (1.00) 
 (v1)        0.1047 (1.0)   │    16.12 (1.0) 

benchmarks/drivers/test_build.py::test_build[knapsack-n=10000]
                  time (s)          peak (KiB) 
 name                 mean   │            mean 
───────────────────────────────────────────────
 (legacy)   0.02466 (1.09)   │   752.18 (1.10) 
 (v1)        0.02267 (1.0)   │    685.15 (1.0) 

benchmarks/drivers/test_build.py::test_build[knapsack-n=100]
                  time (s)        peak (KiB) 
 name                 mean   │          mean 
─────────────────────────────────────────────
 (legacy)   0.02412 (1.09)   │   3.12 (1.33) 
 (v1)        0.02218 (1.0)   │    2.34 (1.0) 

benchmarks/drivers/test_build.py::test_build[kvl_cycles-severity=0]
                 time (s)          peak (MiB) 
 name                mean   │            mean 
──────────────────────────────────────────────
 (legacy)    0.065 (1.23)   │   126.16 (1.44) 
 (v1)       0.05276 (1.0)   │     87.71 (1.0) 

benchmarks/drivers/test_build.py::test_build[kvl_cycles-severity=100]
                  time (s)          peak (MiB) 
 name                 mean   │            mean 
───────────────────────────────────────────────
 (legacy)   0.06401 (1.23)   │   126.16 (1.44) 
 (v1)        0.05189 (1.0)   │     87.71 (1.0) 

benchmarks/drivers/test_build.py::test_build[kvl_cycles-severity=50]
                 time (s)          peak (MiB) 
 name                mean   │            mean 
──────────────────────────────────────────────
 (legacy)   0.0643 (1.22)   │   126.16 (1.44) 
 (v1)       0.05263 (1.0)   │     87.71 (1.0) 

benchmarks/drivers/test_build.py::test_build[masked-n=100]
                  time (s)          peak (KiB) 
 name                 mean   │            mean 
───────────────────────────────────────────────
 (legacy)   0.05608 (1.02)   │    715.12 (1.0) 
 (v1)        0.05471 (1.0)   │   787.73 (1.10) 

benchmarks/drivers/test_build.py::test_build[masked-n=10]
                  time (s)        peak (KiB) 
 name                 mean   │          mean 
─────────────────────────────────────────────
 (legacy)   0.05537 (1.12)   │   4.54 (1.27) 
 (v1)        0.04942 (1.0)   │    3.57 (1.0) 

benchmarks/drivers/test_build.py::test_build[merge_balance-severity=0]
                 time (s)          peak (KiB) 
 name                mean   │            mean 
──────────────────────────────────────────────
 (legacy)   0.3829 (1.06)   │   704.12 (1.09) 
 (v1)        0.3619 (1.0)   │    643.85 (1.0) 

benchmarks/drivers/test_build.py::test_build[merge_balance-severity=100]
                 time (s)        peak (MiB) 
 name                mean   │          mean 
────────────────────────────────────────────
 (legacy)   0.3988 (1.04)   │   18.34 (1.0) 
 (v1)        0.3819 (1.0)   │   18.34 (1.0) 

benchmarks/drivers/test_build.py::test_build[merge_balance-severity=50]
                 time (s)       peak (MiB) 
 name                mean   │         mean 
───────────────────────────────────────────
 (legacy)   0.3971 (1.05)   │   9.54 (1.0) 
 (v1)        0.3772 (1.0)   │   9.54 (1.0) 

benchmarks/drivers/test_build.py::test_build[milp-n=10]
                  time (s)        peak (KiB) 
 name                 mean   │          mean 
─────────────────────────────────────────────
 (legacy)   0.07738 (1.10)   │   3.77 (1.12) 
 (v1)        0.07062 (1.0)   │    3.37 (1.0) 

benchmarks/drivers/test_build.py::test_build[milp-n=50]
                  time (s)          peak (KiB) 
 name                 mean   │            mean 
───────────────────────────────────────────────
 (legacy)   0.07861 (1.12)   │   216.59 (1.10) 
 (v1)         0.0702 (1.0)   │    196.23 (1.0) 

benchmarks/drivers/test_build.py::test_build[nodal_balance-severity=0]
                  time (s)         peak (KiB) 
 name                 mean   │           mean 
──────────────────────────────────────────────
 (legacy)   0.03897 (1.09)   │   938.49 (1.0) 
 (v1)        0.03566 (1.0)   │   938.49 (1.0) 

benchmarks/drivers/test_build.py::test_build[nodal_balance-severity=100]
                  time (s)       peak (MiB) 
 name                 mean   │         mean 
────────────────────────────────────────────
 (legacy)   0.04033 (1.10)   │   9.66 (1.0) 
 (v1)        0.03675 (1.0)   │   9.66 (1.0) 

benchmarks/drivers/test_build.py::test_build[nodal_balance-severity=50]
                  time (s)       peak (MiB) 
 name                 mean   │         mean 
────────────────────────────────────────────
 (legacy)   0.03993 (1.10)   │   5.32 (1.0) 
 (v1)        0.03646 (1.0)   │   5.32 (1.0) 

benchmarks/drivers/test_build.py::test_build[nodal_balance_sparse-severity=0]
                  time (s)       peak (MiB) 
 name                 mean   │         mean 
────────────────────────────────────────────
 (legacy)   0.01957 (1.01)   │   1.28 (1.0) 
 (v1)         0.0193 (1.0)   │   1.28 (1.0) 

benchmarks/drivers/test_build.py::test_build[nodal_balance_sparse-severity=100]
                  time (s)       peak (MiB) 
 name                 mean   │         mean 
────────────────────────────────────────────
 (legacy)   0.01951 (1.02)   │   1.28 (1.0) 
 (v1)        0.01906 (1.0)   │   1.28 (1.0) 

benchmarks/drivers/test_build.py::test_build[nodal_balance_sparse-severity=50]
                  time (s)       peak (MiB) 
 name                 mean   │         mean 
────────────────────────────────────────────
 (legacy)   0.01952 (1.02)   │   1.28 (1.0) 
 (v1)        0.01916 (1.0)   │   1.28 (1.0) 

benchmarks/drivers/test_build.py::test_build[piecewise-n=1000]
                 time (s)          peak (KiB) 
 name                mean   │            mean 
──────────────────────────────────────────────
 (legacy)   0.1921 (1.07)   │   946.85 (1.06) 
 (v1)        0.1798 (1.0)   │    891.54 (1.0) 

benchmarks/drivers/test_build.py::test_build[piecewise-n=10]
                 time (s)         peak (KiB) 
 name                mean   │           mean 
─────────────────────────────────────────────
 (legacy)   0.1887 (1.06)   │   12.01 (1.00) 
 (v1)         0.178 (1.0)   │    11.99 (1.0) 

benchmarks/drivers/test_build.py::test_build[qp-n=1000]
                  time (s)          peak (KiB) 
 name                 mean   │            mean 
───────────────────────────────────────────────
 (legacy)   0.04889 (1.08)   │   147.70 (1.06) 
 (v1)        0.04528 (1.0)   │    139.87 (1.0) 

benchmarks/drivers/test_build.py::test_build[qp-n=10]
                  time (s)        peak (KiB) 
 name                 mean   │          mean 
─────────────────────────────────────────────
 (legacy)   0.04848 (1.08)   │   2.60 (1.09) 
 (v1)        0.04471 (1.0)   │    2.38 (1.0) 

benchmarks/drivers/test_build.py::test_build[rolling-severity=0]
                  time (s)          peak (KiB) 
 name                 mean   │            mean 
───────────────────────────────────────────────
 (legacy)   0.04073 (1.09)   │   696.75 (1.03) 
 (v1)        0.03724 (1.0)   │    673.70 (1.0) 

benchmarks/drivers/test_build.py::test_build[rolling-severity=100]
                  time (s)         peak (MiB) 
 name                 mean   │           mean 
──────────────────────────────────────────────
 (legacy)   0.08466 (1.09)   │   137.97 (1.0) 
 (v1)        0.07764 (1.0)   │   137.97 (1.0) 

benchmarks/drivers/test_build.py::test_build[rolling-severity=50]
                  time (s)        peak (MiB) 
 name                 mean   │          mean 
─────────────────────────────────────────────
 (legacy)   0.06154 (1.05)   │   69.22 (1.0) 
 (v1)        0.05842 (1.0)   │   69.22 (1.0) 

benchmarks/drivers/test_build.py::test_build[sos-n=1000]
                  time (s)          peak (KiB) 
 name                 mean   │            mean 
───────────────────────────────────────────────
 (legacy)   0.04664 (1.11)   │   402.33 (1.00) 
 (v1)        0.04187 (1.0)   │    402.30 (1.0) 

benchmarks/drivers/test_build.py::test_build[sos-n=10]
                  time (s)        peak (KiB) 
 name                 mean   │          mean 
─────────────────────────────────────────────
 (legacy)   0.04636 (1.12)   │   3.19 (1.19) 
 (v1)        0.04136 (1.0)   │    2.69 (1.0) 

benchmarks/drivers/test_build.py::test_build[sparse_network-n=10]
                  time (s)         peak (KiB) 
 name                 mean   │           mean 
──────────────────────────────────────────────
 (legacy)   0.05053 (1.08)   │   29.00 (1.54) 
 (v1)        0.04699 (1.0)   │    18.84 (1.0) 

benchmarks/drivers/test_build.py::test_build[sparse_network-n=250]
                  time (s)         peak (MiB) 
 name                 mean   │           mean 
──────────────────────────────────────────────
 (legacy)   0.06046 (1.12)   │   37.95 (1.43) 
 (v1)        0.05403 (1.0)   │    26.51 (1.0) 

benchmarks/drivers/test_build.py::test_build[storage-n=10]
                  time (s)          peak (KiB) 
 name                 mean   │            mean 
───────────────────────────────────────────────
 (legacy)    0.09637 (1.0)   │    410.93 (1.0) 
 (v1)       0.09675 (1.00)   │   427.84 (1.04) 

benchmarks/drivers/test_build.py::test_build[storage-n=250]
                 time (s)         peak (MiB) 
 name                mean   │           mean 
─────────────────────────────────────────────
 (legacy)    0.1023 (1.0)   │     9.94 (1.0) 
 (v1)       0.1037 (1.01)   │   10.22 (1.03) 

📊 Interactive plots + CSV: download the semantics-report-v1-vs-legacy artifact from this run.

Report-only · not a gate · refreshed on every push · obsolete once legacy is dropped.

@codspeed

codspeed Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 181 untouched benchmarks
⏩ 181 skipped benchmarks1


Comparing docs/sparse-path-971 (98b3db6) with master (ff5436f)

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

@FabianHofmann
FabianHofmann force-pushed the docs/sparse-path-971 branch 2 times, most recently from fa623e7 to a6ffc91 Compare October 6, 2026 21:12

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.

Document the sparse expression path (CSRLinearExpression, sparse=True, sparse_groupby)

1 participant