Variable aggregation (reduced-space presolve) plugin for discopt
Project description
Variable aggregation (reduced-space presolve) for nonlinear optimization —
a standalone plugin for discopt that
installs into the discopt.aggregation namespace.
It implements Naik, Biegler, Bent & Parker, Variable aggregation for nonlinear optimization problems (arXiv:2502.13869): substitute variables defined by equality constraints into the rest of a model, eliminating both a variable and a constraint to yield a smaller reduced-space formulation, then recover the eliminated variables from the reduced solution.
import discopt.modeling as dm
from discopt.aggregation import aggregate, solve
m = dm.Model("example")
x = m.continuous("x", lb=-5, ub=5)
y = m.continuous("y", lb=-5, ub=5)
m.subject_to(y - (x + 1) == 0.0) # y is defined by x
m.minimize(x**2 + y**2)
res = solve(m, method="d2") # aggregate → solve → recover
print(res.status, res.objective) # optimal 0.5
print(res.x) # {'x': -0.5, 'y': 0.5} (y recovered)
Installation
pip install discopt-aggregation # from PyPI
Or from a checkout for development:
uv pip install -e ".[dev]" # or: pip install -e ".[dev]"
Requires discopt >= 0.6. The plugin is a discopt.aggregation namespace
package: it merges into the installed discopt package at import time. This
needs discopt/__init__.py to extend its search path, i.e. to contain:
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
discopt 0.6+ includes this line. (If you use an older discopt, add it once.)
Claude skill
This package ships a Claude Code skill
that documents how to use aggregation (method choice, the structure-preserving vs
Hessian-cost tradeoff, every option, recovery, diagnostics, the study harnesses).
Claude Code does not auto-discover skills from installed Python packages, so
after pip install run the bundled installer once to copy it into a
.claude/skills/ directory Claude scans:
discopt-aggregation-skill install # -> ./.claude/skills/ (this project)
discopt-aggregation-skill install --user # -> ~/.claude/skills/ (every project)
discopt-aggregation-skill path # where the packaged skill lives
Working inside this repo, the skill is already at .claude/skills/ (no install
needed). The repo is also a Claude Code plugin (.claude-plugin/plugin.json
skills/), so it can be loaded withclaude --plugin-dir <repo>or published to a marketplace. The single source of truth issrc/discopt/aggregation/skill/; the two.claude/skills/skillsentries are symlinks to it.
Methods
method= selects the strategy, most conservative to most aggressive:
| key | name | eliminates | paper |
|---|---|---|---|
ld1 |
fixed-variable (degree-1) | y = a |
Alg 3 |
ecd2 |
equal-coefficient degree-2 | y = x + a |
Alg 6 |
ld2 |
linear degree-2 | y = a*x + b |
Alg 5 |
d2 |
degree-2 (default) | y = f(x) (≤ 2 variables) |
Alg 4 |
gr |
greedy | y = f(w, x, …) (y linear) |
Alg 1 |
lm |
linear-matching | y = f(w, x, …) (y linear) |
Alg 2 |
ld1/ecd2/ld2/d2 are structure-preserving; gr/lm are
approximate-maximum. The paper recommends d2; it is the default.
Integer aggregation (opt-in, beyond the paper)
By default only scalar continuous variables are eliminated. Pass
aggregate(..., integer_aggregation=True) (or solve(...)) to also eliminate a
scalar integer/binary variable — but only when its integrality is implied,
the standard MIP-presolve condition (Achterberg et al. 2020): the defining
equality is fully affine, the pivot coefficient is ±1, every other variable in
it is integer/binary, and every other coefficient and the constant are integral
(e.g. an all-integer stock balance s[t] = s[t-1] + q[t] - d[t]). Recovery
returns exactly integral values; dual recovery reports available=False over
eliminated integers. The flag is default-off and never eliminates a variable it
cannot prove integral (fractional data, non-unit pivot, or a continuous
participant all abstain), so the default behaviour is unchanged.
Features
- Six strategies, each producing a strictly lower-triangular aggregation set (Lemma 1) with explicit, solve-free post-solve recovery.
- Numerical safeguards: a relative pivot tolerance (
pivot_tol) against ill-conditioned substitutions, and an optional fill-in cap (max_fill). - Redundant-bound pruning (
prune_bounds, on by default): box→inequality constraints proven redundant by interval propagation are omitted. - Array support (
scalarize, on by default): fully-indexed array variables are scalarized so aggregation reaches discretized (DAE) models, then arrays are reassembled on recovery. - Structural metrics (
compare_methods) reproducing the paper's Table 4, including the exact AD Hessian nonzero count.
Examples
python -m discopt.aggregation.examples runs commented, executable walkthroughs
of every method, the recursive pipeline, end-to-end solve + recovery, the
structural comparison, the safeguards, array scalarization, and a
performance/scaling demo.
Tests
uv pip install -e ".[dev]"
python -m pytest tests/ -q # the full suite
Layout
src/discopt/aggregation/ # the plugin (PEP 420 namespace subpackage)
core: matching · incidence · strategies · transform · recovery · api
walking: exprwalk (single DAG-walk authority for all analyses)
structure: scalarize · metrics · diagnostics (Dulmage–Mendelsohn) · ordering
numerics: guards · hessian_cost · duals · derivatives · implicit · tearing · schur
study: testproblems · benchmark · sweep · profiling · presolve · examples
tests/ # test_aggregation_*.py
design/ # follow-up roadmap · parity plan · references.bib
References
Primary:
Sakshi Naik, Lorenz Biegler, Russell Bent, Robert Parker. Variable aggregation for nonlinear optimization problems. arXiv:2502.13869 (2025). https://arxiv.org/abs/2502.13869
The full set of related work — reduced-space / implicit elimination
(Parker et al. 2022), block-triangular KKT (Parker, Garcia & Bent 2026), exact
tearing / minimum feedback set (Baharev et al. 2021), Dulmage–Mendelsohn
debugging (Parker et al. 2023), and the MIP-presolve line (Achterberg et al.
2020) — is collected as verified BibTeX in
design/references.bib, and cited from the design docs
under design/.
Contributing
See CONTRIBUTING.md for dev setup, the lint/type/test gate, and the project conventions (keep discopt/pounce/feral untouched, new behavior opt-in/default-off, a worked example per feature, hash-seed-robust tests).
License
Eclipse Public License 2.0 (EPL-2.0), matching discopt.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file discopt_aggregation-0.4.1.tar.gz.
File metadata
- Download URL: discopt_aggregation-0.4.1.tar.gz
- Upload date:
- Size: 198.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c10ffe4c273692180f70e0466fb25345d9d2c808020ee63630e06166997f5575
|
|
| MD5 |
71d335166300e4e35b2f5d63b79274ef
|
|
| BLAKE2b-256 |
e4fd61ba41b708e69aaf0f53f5f19c6bf92ee569744b41eac58fd9d2ffc14bd9
|
Provenance
The following attestation bundles were made for discopt_aggregation-0.4.1.tar.gz:
Publisher:
publish.yml on jkitchin/discopt-aggregation
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
discopt_aggregation-0.4.1.tar.gz -
Subject digest:
c10ffe4c273692180f70e0466fb25345d9d2c808020ee63630e06166997f5575 - Sigstore transparency entry: 2153718944
- Sigstore integration time:
-
Permalink:
jkitchin/discopt-aggregation@6a0b5882bffaa43fa6e603df62c068b8b99783ee -
Branch / Tag:
refs/tags/v0.4.1 - Owner: https://github.com/jkitchin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6a0b5882bffaa43fa6e603df62c068b8b99783ee -
Trigger Event:
release
-
Statement type:
File details
Details for the file discopt_aggregation-0.4.1-py3-none-any.whl.
File metadata
- Download URL: discopt_aggregation-0.4.1-py3-none-any.whl
- Upload date:
- Size: 147.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ae98f9fbd3c6137a7be5a3113beb4a66d5aba14f16d7341468ab6f9fb6bdf075
|
|
| MD5 |
bd7223b18758a8ac3e870312ca8a6bf8
|
|
| BLAKE2b-256 |
de7fda6de7160fd6c727675840bbd685bd185e5537f3d73c03d56daf3416bc44
|
Provenance
The following attestation bundles were made for discopt_aggregation-0.4.1-py3-none-any.whl:
Publisher:
publish.yml on jkitchin/discopt-aggregation
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
discopt_aggregation-0.4.1-py3-none-any.whl -
Subject digest:
ae98f9fbd3c6137a7be5a3113beb4a66d5aba14f16d7341468ab6f9fb6bdf075 - Sigstore transparency entry: 2153719007
- Sigstore integration time:
-
Permalink:
jkitchin/discopt-aggregation@6a0b5882bffaa43fa6e603df62c068b8b99783ee -
Branch / Tag:
refs/tags/v0.4.1 - Owner: https://github.com/jkitchin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6a0b5882bffaa43fa6e603df62c068b8b99783ee -
Trigger Event:
release
-
Statement type: