Skip to main content

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

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.10.0.tar.gz (2.4 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.10.0-cp39-abi3-win_amd64.whl (7.8 MB view details)

Uploaded CPython 3.9+Windows x86-64

pounce_solver-0.10.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (17.8 MB view details)

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

pounce_solver-0.10.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (15.2 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

pounce_solver-0.10.0-cp39-abi3-macosx_11_0_arm64.whl (6.6 MB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

pounce_solver-0.10.0-cp39-abi3-macosx_10_12_x86_64.whl (7.5 MB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

File details

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

File metadata

  • Download URL: pounce_solver-0.10.0.tar.gz
  • Upload date:
  • Size: 2.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for pounce_solver-0.10.0.tar.gz
Algorithm Hash digest
SHA256 151e59f1f2a9458766fa7acb634d6d51f68737dd97611cb64a4bd6108eb414f3
MD5 1399fbbdaa3328a89111f27a89401ea7
BLAKE2b-256 3776fe7b1469e6e4b02d9f67454cc8277f6ae8eabc400cc622d88f7b6faa5b5f

See more details on using hashes here.

Provenance

The following attestation bundles were made for pounce_solver-0.10.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.10.0-cp39-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for pounce_solver-0.10.0-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 37bd2e0b54a0cc8a34c9c6a2b55f230ed867312ef52cf08b92baf318004403e3
MD5 4900f6abd2714b9bab885784be81d4f7
BLAKE2b-256 fd299fc2900efaaf675efd2d9add10a5fb0e0b0f66021843fd03f69e38b016ba

See more details on using hashes here.

Provenance

The following attestation bundles were made for pounce_solver-0.10.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.10.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pounce_solver-0.10.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 5292a744b55ab200f0254b4fde8dc25811e0c35dcc0dd7b5d1d07fd8592042a0
MD5 c793c564869f0fc299d2cb825274e330
BLAKE2b-256 19875f88312faf532a70705242978bf6cff3cd25e7b8f03a3c3910f325152871

See more details on using hashes here.

Provenance

The following attestation bundles were made for pounce_solver-0.10.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.10.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pounce_solver-0.10.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 d851e2b460db8354b57654966c58f7625952ecedc450ed28dc9b9531fb485294
MD5 78157157f8cd06309e1ad8c3e582b40b
BLAKE2b-256 4575f3f684086887d97ce9e009b808d37a6d0f33d96f044d8ab06b0ddc4f8593

See more details on using hashes here.

Provenance

The following attestation bundles were made for pounce_solver-0.10.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.10.0-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pounce_solver-0.10.0-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 cc12fd2cae513475e7ac4e60a75b177b43c5f4dbc76cff2977b00294e9749146
MD5 75cf317f073d22dce2e0d73ec4229659
BLAKE2b-256 6331b9d3c34118f66c1e6a2d1f68f1dcdc58660b4570e5d6eec40955292f425e

See more details on using hashes here.

Provenance

The following attestation bundles were made for pounce_solver-0.10.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.10.0-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for pounce_solver-0.10.0-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 e151f616fe807c3947a032fbb7f7534d3ca1c2411097054b56487bf293a2129c
MD5 3405ed882cf7d51b2c2dabdb9dfbe279
BLAKE2b-256 eaca6fc0b4da055aa1d2253937928f42ea75748f290caf101710df6840ad4e44

See more details on using hashes here.

Provenance

The following attestation bundles were made for pounce_solver-0.10.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.

Release history Release notifications | RSS feed

This release

0.10.0 This release

6 files

0.9.0

6 files

0.8.0

6 files

0.7.0

6 files

0.6.0

6 files

0.5.0

6 files

0.4.0

6 files

0.3.0

6 files

0.2.0

6 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page