Skip to main content

Python interface to POUNCE — a pure-Rust interior-point optimization solver for nonlinear, conic (LP/QP/SOCP/SDP/exp/power), and global problems (NLP core ported from Ipopt). cyipopt-style Problem class, scipy-style minimize() facade, solve_qp/solve_socp/sos_minimize, and JAX-friendly autodiff / implicit differentiation.

Project description

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:
cd python
pip install maturin
maturin develop --release            # builds the native extension into your venv
# 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 variable and constraint magnitudes differ by orders of magnitude, attach explicit scaling factors:

prob.set_problem_scaling(
    obj_scaling=1.0,
    x_scaling=np.array([1e-3, 1.0, 1.0, 1e3]),  # optional
    g_scaling=np.array([1.0, 1e-2]),            # optional
)
prob.add_option('nlp_scaling_method', 'user-scaling')

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pounce_solver-0.7.0.tar.gz (1.6 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pounce_solver-0.7.0-cp39-abi3-win_amd64.whl (6.6 MB view details)

Uploaded CPython 3.9+Windows x86-64

pounce_solver-0.7.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (15.5 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

pounce_solver-0.7.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (13.5 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

pounce_solver-0.7.0-cp39-abi3-macosx_11_0_arm64.whl (5.6 MB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

pounce_solver-0.7.0-cp39-abi3-macosx_10_12_x86_64.whl (6.5 MB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

File details

Details for the file pounce_solver-0.7.0.tar.gz.

File metadata

  • Download URL: pounce_solver-0.7.0.tar.gz
  • Upload date:
  • Size: 1.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pounce_solver-0.7.0.tar.gz
Algorithm Hash digest
SHA256 dc86e063032b894abb97526a7b10671a084a9214c1d502742389ef4bc402b009
MD5 09f7ecd24eb5b54269f10a1f38e1ed28
BLAKE2b-256 8da09c0ef391d6c13f5ba51c3926d1b0ca1fa13956abd7b82c54c7a41e7e8347

See more details on using hashes here.

Provenance

The following attestation bundles were made for pounce_solver-0.7.0.tar.gz:

Publisher: release-pounce.yml on jkitchin/pounce

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pounce_solver-0.7.0-cp39-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for pounce_solver-0.7.0-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 f0ff48b0e6e7c6bfeded06cae4d2c84c34b51135615aef5991ed20f2b482fd06
MD5 5657139ddb5fc95b6d561c9a98e9b861
BLAKE2b-256 c756a5c51d67ff5764fea68b0448cd628b59e1533679b5c04f84174b3a25ea6b

See more details on using hashes here.

Provenance

The following attestation bundles were made for pounce_solver-0.7.0-cp39-abi3-win_amd64.whl:

Publisher: release-pounce.yml on jkitchin/pounce

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pounce_solver-0.7.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pounce_solver-0.7.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 02a1b462714d46de628c395fa43346e301a1f93c8f3e5921c002b347ab55bd1e
MD5 a8661bd5a75dfadeaf4363227df8faee
BLAKE2b-256 d1573bc312a962aa4d825adaebd6e35cc7c6c65696a01ff4736f5814e1ff98cb

See more details on using hashes here.

Provenance

The following attestation bundles were made for pounce_solver-0.7.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release-pounce.yml on jkitchin/pounce

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pounce_solver-0.7.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pounce_solver-0.7.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 f8b813ed7f3860b1ad585a718c48efda109d64ec935686950c7dbfe89d074b6c
MD5 f88d5c5ba303e0a67bc9a762c56f8383
BLAKE2b-256 7f69f4f9619bc5163b56e7cf6d761889d118c5dc871595153be6a8bc763f17a6

See more details on using hashes here.

Provenance

The following attestation bundles were made for pounce_solver-0.7.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release-pounce.yml on jkitchin/pounce

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pounce_solver-0.7.0-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pounce_solver-0.7.0-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 be5a91e631a1ef81fa06a76dcdb2a3094425cbac0185014ca4290eb8e4c85e11
MD5 dc4b5a617ceb7a914e29f09a3c028986
BLAKE2b-256 ef835cb7c5f1946fb7b355ef919b3d378befc4972e4e9e50c8af126b18a19c59

See more details on using hashes here.

Provenance

The following attestation bundles were made for pounce_solver-0.7.0-cp39-abi3-macosx_11_0_arm64.whl:

Publisher: release-pounce.yml on jkitchin/pounce

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pounce_solver-0.7.0-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for pounce_solver-0.7.0-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 ea44ee00aa58c09a5e8afb9a81aa83ef0caa8a7ffd9a55ec07fb880eb83d71da
MD5 6f5ea1e2c28c8d3256b2564655b6cd96
BLAKE2b-256 4cd6ab71eac4a3730236f834e86c8fb4584b0b026444a7af3fbc18e56c82de63

See more details on using hashes here.

Provenance

The following attestation bundles were made for pounce_solver-0.7.0-cp39-abi3-macosx_10_12_x86_64.whl:

Publisher: release-pounce.yml on jkitchin/pounce

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page