pounce — Python interface
pounce is a Python wrapper around POUNCE, a pure-Rust port of the
Ipopt interior-point nonlinear
programming solver. The Python surface area is intentionally
cyipopt-compatible: code written for cyipopt typically runs against
pounce by changing only the import.
Install (development)
# from the repo root:
make dev # extension module + the bundled `pounce` CLI
# or, extension module only (leaves the `pounce` console script standing in
# with whatever cargo last built — see CONTRIBUTING.md, gh #816):
cd python
pip install maturin
maturin develop --release
# optional extras:
pip install -e .[jax] # jax integration
pip install -e .[dev] # tests + jax + scipy
Quick start (cyipopt-style)
import numpy as np
import pounce
class HS071:
def objective(self, x):
return x[0]*x[3]*(x[0]+x[1]+x[2]) + x[2]
def gradient(self, x):
return np.array([
x[0]*x[3] + x[3]*(x[0]+x[1]+x[2]),
x[0]*x[3],
x[0]*x[3] + 1.0,
x[0]*(x[0]+x[1]+x[2]),
])
def constraints(self, x):
return np.array([np.prod(x), np.dot(x, x)])
def jacobianstructure(self):
return (np.repeat([0,1], 4), np.tile([0,1,2,3], 2))
def jacobian(self, x):
return np.array([
x[1]*x[2]*x[3], x[0]*x[2]*x[3], x[0]*x[1]*x[3], x[0]*x[1]*x[2],
2*x[0], 2*x[1], 2*x[2], 2*x[3],
])
prob = pounce.Problem(
n=4, m=2,
problem_obj=HS071(),
lb=[1]*4, ub=[5]*4,
cl=[25, 40], cu=[2e19, 40],
)
prob.add_option('tol', 1e-8)
x, info = prob.solve(x0=np.array([1.0, 5.0, 5.0, 1.0]))
print(info['status_msg'], info['obj_val'], x)
Problem scaling
For problems whose natural objective and constraint magnitudes differ by orders of magnitude, attach explicit scaling factors:
prob.set_problem_scaling(
obj_scaling=1e-3,
x_scaling=np.array([1.0, 1e-6, 1.0, 1.0]), # optional
g_scaling=np.array([1.0, 1e-2]), # optional
)
prob.add_option('nlp_scaling_method', 'user-scaling')
x_scaling= applies the change of variables x_scaled = x_scaling * x,
so it changes how the problem is conditioned and not what the problem
is. Everything handed back, including x and the bound multipliers, is
in your own units. Factors must be finite and positive: a negative one
would reverse a variable and swap its bounds.
See docs/src/scaling.md for the
gradient-based vs. user-scaling tradeoff.
Sensitivity analysis (sIPOPT-compatible)
solve_with_sens runs the standard solve, then a post-optimal
sensitivity step on the converged KKT factor — no second solve:
x_star, info = prob.solve_with_sens(
x0,
pin_constraint_indices=[2, 3], # which constraints are parametric (required)
deltas=[0.05, 0.0], # perturbation magnitudes
rh_eigendecomp=True, # also return reduced-Hessian eigendecomp
sens_boundcheck=True,
)
# Sensitivity outputs are keys in the returned info dict:
# info["dx"], info["reduced_hessian"], info["reduced_hessian_eigenvalues"]
Factor-once / solve-many: Solver
For workflows that issue several follow-up operations against the
converged KKT factor (sensitivity sweeps, reduced Hessians over many
pin sets, raw back-solves), pounce.Solver keeps the factor alive
between calls:
solver = pounce.Solver(prob)
x_star, info = solver.solve(x0=x0)
# Reuse the factor for downstream queries:
dx = solver.parametric_step(pin_constraint_indices=[2], deltas=[0.05])
rh = solver.reduced_hessian(pin_constraint_indices=[0, 1])
y = solver.kkt_solve(rhs) # raw KKT back-solve
print(solver.kkt_dim(), solver.converged())
The full walk-through is in
docs/src/sessions.md.
Warm-start working sets
For active-set or repeated-solve workflows, the working set (the guess at which constraints are active) can be pinned across solves:
prob.set_working_set(working_set)
x, info = prob.solve(x0=x0)
ws_out = prob.get_working_set()
# Or classify a fresh iterate:
ws = pounce.classify_working_set(...)
scipy.optimize-style
from pounce import minimize
res = minimize(lambda x: (x-1) @ (x-1) + 1, x0=np.zeros(5))
print(res.fun, res.x)
JAX integration
pounce.jax exposes five entry points: from_jax, solve,
solve_with_warm, vmap_solve / vmap_solve_parallel, and
JaxProblem.
import jax, jax.numpy as jnp
from pounce.jax import from_jax
def f(x): return jnp.sum((x-1)**2)
def g(x): return jnp.stack([jnp.sum(x) - 5.0])
prob = from_jax(f, g, n=4, m=1, lb=jnp.zeros(4), ub=jnp.full(4, 10.0),
cl=jnp.zeros(1), cu=jnp.zeros(1))
x, info = prob.solve(x0=jnp.ones(4))
On problems with a genuinely sparse Jacobian/Hessian (banded, block,
PDE-constrained, separable), pass sparse=True to from_jax or
JaxProblem for CPR-style colored AD — one JVP/HVP per color instead of
a dense matrix sliced to the nonzeros, dropping the per-iteration cost
from O(n) to O(k) AD passes (pounce#83; up to ~560× faster
per-Jacobian and 7.6× faster solve on a banded sweep). It's opt-in
because dense problems gain nothing. See the
Python API docs and
benchmarks/bench_sparse_ad_83.py.
Differentiate through the solver (the backward respects the active set, so slack inequalities don't pollute the gradient — pounce#73):
from pounce.jax import solve as psolve
def f_p(x, p): return jnp.sum((x - p) ** 2)
def g_p(x, p): return jnp.stack([x[0] + x[1] - 1.0])
def loss(p):
x_star = psolve(p, f=f_p, g=g_p, x0=jnp.zeros(2), n=2, m=1,
lb=jnp.full(2, -10.0), ub=jnp.full(2, 10.0),
cl=jnp.zeros(1), cu=jnp.zeros(1),
options={"tol": 1e-10, "print_level": 0})
return jnp.sum(x_star ** 2)
dloss_dp = jax.grad(loss)(jnp.array([0.3, 0.7]))
Warm-start across a parameter trajectory and batch solves in parallel (pounce#74):
from pounce.jax import solve_with_warm, vmap_solve_parallel
x, warm = solve_with_warm(p0, f=f_p, g=g_p, x0=jnp.zeros(2), n=2, m=1,
lb=..., ub=..., cl=..., cu=...,
warm_start=None)
x, warm = solve_with_warm(p1, f=f_p, g=g_p, x0=x, n=2, m=1,
lb=..., ub=..., cl=..., cu=...,
warm_start=warm) # reuse λ, z
X = vmap_solve_parallel(p_batch, f=f_p, g=g_p, x0=jnp.zeros(2), n=2, m=1,
lb=..., ub=..., cl=..., cu=..., workers=8)
For iterative use, JaxProblem builds the JIT artefacts, sparsity
probe, and underlying pounce.Problem once and reuses them across
calls — ~14× per-solve speedup on small problems (pounce#75):
from pounce.jax import JaxProblem
jp = JaxProblem(f=f_p, g=g_p, n=2, m=1, p_example=jnp.zeros(2),
lb=..., ub=..., cl=..., cu=...,
options={"tol": 1e-9, "print_level": 0})
x = jp.solve(p0, x0=jnp.zeros(2)) # differentiable
x, warm = jp.solve_with_warm(p0, x0=x, warm_start=None) # trajectory
X = jp.vmap_solve_parallel(p_batch, x0=jnp.zeros(2), workers=8)
X = jp.batched_solve(p_batch, x0=jnp.zeros(2)) # one stacked IPM (pounce#76)
batched_solve runs one IPM over a stacked block-diagonal NLP
(variables [x^(1); ...; x^(B)], constraints
concat(g(x^(k), p^(k))), objective Σ_k f(x^(k), p^(k))). One
shared barrier homotopy and symbolic factorisation across the batch
— complementary to vmap_solve_parallel, which runs B independent
IPMs in worker threads. jax.grad through it works end-to-end (the
backward vmaps the per-element dense KKT back-solve, exact because
the block-diagonal coupling makes ∂x^(k)*/∂p^(j) = 0 for k ≠ j).
The jp.solve / solve_with_warm backward defaults to a k_aug-style
factor-reuse path: instead of assembling a dense (n+m) × (n+m) KKT
block in JAX and running jnp.linalg.solve on it, it reuses the IPM's
converged LDLᵀ factor via pounce.Solver.kkt_solve (pounce#76). The
compound block's barrier rows on (z_l, z_u) and (v_l, v_u)
encode active bounds and slack inequalities exactly, so no explicit
active-set masking is needed. Set JaxProblem(..., factor_reuse=False)
for the verbatim dense path (needed for higher-order differentiation).
Metadata
Release files for pounce-solver 0.12.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pounce_solver-0.12.0.tar.gz | 3.8 MB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| pounce_solver-0.12.0-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details |
| pounce_solver-0.12.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| pounce_solver-0.12.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| pounce_solver-0.12.0-cp39-abi3-macosx_11_0_arm64.whl | CPython 3.9 | abi3 | macOS 11.0+ ARM64 | Details |
| pounce_solver-0.12.0-cp39-abi3-macosx_10_12_x86_64.whl | CPython 3.9 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 69.0 MB
Release files / pounce_solver-0.12.0.tar.gz
| Download URL | pounce_solver-0.12.0.tar.gz |
|---|---|
| Size | 3.8 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b377345421bf0fccd05b581e8d0d084f942a674a58968ddc9857671260719711
|
|
BLAKE2b-256 checksum How to use checksums |
5e740491a1bbe6c6b219e20b8e9dbc08f0b3cf6fb236a30b58fc6898d830d3c3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / pounce_solver-0.12.0-cp39-abi3-win_amd64.whl
| Download URL | pounce_solver-0.12.0-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 9.4 MB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
e128654ab807ab9f3fd84ab0e87c4ec961d751b343934f209d4fd892c139b646
|
|
BLAKE2b-256 checksum How to use checksums |
7863b3319802f3acfd009a7cae456978fdab7deaa9161bd6e3ecd9d8cc1cbab7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / pounce_solver-0.12.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | pounce_solver-0.12.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 20.8 MB |
| Tags | CPython 3.9 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
fb4e3040d6bb2e90f55640f35733899cff339d8412352be6e1c6e404990613ef
|
|
BLAKE2b-256 checksum How to use checksums |
73af0f16787a53d2c0916040162d7665d7589682ed2f8848ac86b839844395c7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / pounce_solver-0.12.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | pounce_solver-0.12.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 18.0 MB |
| Tags | CPython 3.9 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
84be92124251e9a32a35ec817831eb665daa37b32a6dfc438562081bdf0b63e0
|
|
BLAKE2b-256 checksum How to use checksums |
aa2b9414cc6bfa599a4ae3b143e36613f95914bf0b02b1772502c15f8624afea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / pounce_solver-0.12.0-cp39-abi3-macosx_11_0_arm64.whl
| Download URL | pounce_solver-0.12.0-cp39-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 8.0 MB |
| Tags | CPython 3.9 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
b7dbb26597aefb351717e8d6064077ef9124f8d4a7e9a28c05bea5dec3e9dcc4
|
|
BLAKE2b-256 checksum How to use checksums |
62406c230c72c297b16ea1cc3d0b31aff117169628402c8687e5fc7289002342
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / pounce_solver-0.12.0-cp39-abi3-macosx_10_12_x86_64.whl
| Download URL | pounce_solver-0.12.0-cp39-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 8.9 MB |
| Tags | CPython 3.9 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
38017d9d36384ee71aab67a3bf52a0fec612d0ac25ec7c5f831b43eac67c4d0a
|
|
BLAKE2b-256 checksum How to use checksums |
57e60a0ccc881607bf52f17f333ea09d835e18d68a2e3473b63bfe56b9653964
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency log