You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Evaluate POUNCE as the replacement for
PharmaPy's cyipopt/Ipopt-backed bounded parameter-estimation path and, if the
parity gate below passes, migrate that path without changing the default
Levenberg-Marquardt (LM) behavior.
POUNCE is a pure-Rust port of Ipopt with a Python package published as pounce-solver (imported as pounce). Its Python API provides a SciPy-style minimize() facade and
cyipopt-compatible Problem interface without requiring a separate system
Ipopt installation. At the time of this audit, the latest PyPI release is 0.8.0 (July 11, 2026).
Package-wide audit
Audit basis: origin/master at 3ebf71f (186 tracked files, including all 36
production Python modules, tests, documentation, packaging metadata, examples,
and workflows).
There is exactly one direct cyipopt/Ipopt integration:
PharmaPy/ParamEstim.py:24-29 optionally imports cyipopt.minimize_ipopt and records have_cyipopt.
PharmaPy/ParamEstim.py:599-646 selects it with method="IPOPT" and passes
the scalar objective, analytic gradient, bounds, Ipopt options, and kwargs={"out_array": False}.
The surrounding public/downstream surface is:
PharmaPy/SimExec.py:368-373: EstimateParams() passes the solver method,
bounds, and options through to ParameterEstimation.optimize_fn().
PharmaPy/StatsModule.py:24-28,409-412: parallel fitting and bootstrap
re-run the selected opt_method, so migration must cover repeated solves.
doc/online_docs/conf.py:24: Sphinx mocks cyipopt.
pyproject.toml, requirements files, DEPENDENCIES.md, and CI do not
declare, install, or exercise cyipopt.
No current test invokes a real cyipopt/Ipopt parameter-estimation solve.
No other ipopt or cyipopt calls exist in the package. The SciPy minimize, root, fsolve, and newton calls elsewhere solve unrelated
distillation/root-finding problems and are not migration targets. Assimulo is
also a separate ODE/DAE integration concern covered by #10 and #134.
Compatibility findings to validate
A direct import rename is close, but not sufficient:
Call signature: POUNCE's minimize() accepts fun, x0, jac, bounds, and Ipopt-style options, but PharmaPy's current kwargs={"out_array": False} must not be forwarded as a solver option. get_objective() already defaults to out_array=False, so remove that
argument or use an explicit wrapper.
Results: confirm the returned OptimizeResult preserves everything
PharmaPy consumes (x) and that PharmaPy's separately assembled info={"jac", "fun"} remains correct for covariance/statistics.
Selector compatibility: decide whether the public method becomes "POUNCE" with a deprecation window for "IPOPT", or whether "IPOPT"
remains a compatibility alias backed by POUNCE. Do not silently change the
meaning without documentation.
Dependency floor: current pounce-solver metadata requires Python >=3.9, NumPy >=1.23, and SciPy >=1.11; PharmaPy currently advertises
Python >=3.9, NumPy >=1.22, and SciPy >=1.9. Decide whether POUNCE is
an optional extra (preferred for the evaluation) or whether core floors
should move.
Platform/installability: verify released wheels on the supported
Linux, macOS, and Windows architectures used by PharmaPy, and document the
source-build fallback and EPL-2.0 dependency license.
Add a small deterministic bounded ParameterEstimation fixture that
does not require Assimulo and has independently checkable parameters,
objective, weighted residuals, and gradient.
Add at least one representative PharmaPy parameter-estimation case with
multiple measured states and non-identity weights.
2. Build an isolated POUNCE adapter spike
Import pounce.minimize lazily/optionally with an actionable error when pounce-solver is absent.
Adapt the current objective, gradient, bounds, verbosity, and options
without forwarding kwargs as an unknown POUNCE option.
Preserve the info["jac"] / info["fun"] contract used by covariance, StatisticsClass, bootstrap, and parallel refits.
Exercise the public SimExec.EstimateParams() path, not only a direct
helper call.
3. Compare correctness, robustness, and cost
Record results for corrected cyipopt/Ipopt, POUNCE, and LM where applicable:
Goal
Evaluate POUNCE as the replacement for
PharmaPy's
cyipopt/Ipopt-backed bounded parameter-estimation path and, if theparity gate below passes, migrate that path without changing the default
Levenberg-Marquardt (
LM) behavior.POUNCE is a pure-Rust port of Ipopt with a Python package published as
pounce-solver(imported aspounce). Its Python API provides a SciPy-styleminimize()facade andcyipopt-compatible
Probleminterface without requiring a separate systemIpopt installation. At the time of this audit, the latest PyPI release is
0.8.0(July 11, 2026).Package-wide audit
Audit basis:
origin/masterat3ebf71f(186 tracked files, including all 36production Python modules, tests, documentation, packaging metadata, examples,
and workflows).
There is exactly one direct cyipopt/Ipopt integration:
PharmaPy/ParamEstim.py:24-29optionally importscyipopt.minimize_ipoptand recordshave_cyipopt.PharmaPy/ParamEstim.py:599-646selects it withmethod="IPOPT"and passesthe scalar objective, analytic gradient, bounds, Ipopt options, and
kwargs={"out_array": False}.The surrounding public/downstream surface is:
PharmaPy/SimExec.py:368-373:EstimateParams()passes the solver method,bounds, and options through to
ParameterEstimation.optimize_fn().PharmaPy/StatsModule.py:24-28,409-412: parallel fitting and bootstrapre-run the selected
opt_method, so migration must cover repeated solves.doc/online_docs/conf.py:24: Sphinx mockscyipopt.pyproject.toml, requirements files,DEPENDENCIES.md, and CI do notdeclare, install, or exercise cyipopt.
No other
ipoptorcyipoptcalls exist in the package. The SciPyminimize,root,fsolve, andnewtoncalls elsewhere solve unrelateddistillation/root-finding problems and are not migration targets. Assimulo is
also a separate ODE/DAE integration concern covered by #10 and #134.
Compatibility findings to validate
A direct import rename is close, but not sufficient:
pounce-solver; importpounce.minimize.minimize()acceptsfun,x0,jac,bounds, and Ipopt-style options, but PharmaPy's currentkwargs={"out_array": False}must not be forwarded as a solver option.get_objective()already defaults toout_array=False, so remove thatargument or use an explicit wrapper.
OptimizeResultpreserves everythingPharmaPy consumes (
x) and that PharmaPy's separately assembledinfo={"jac", "fun"}remains correct for covariance/statistics."POUNCE"with a deprecation window for"IPOPT", or whether"IPOPT"remains a compatibility alias backed by POUNCE. Do not silently change the
meaning without documentation.
pounce-solvermetadata requires Python>=3.9, NumPy>=1.23, and SciPy>=1.11; PharmaPy currently advertisesPython
>=3.9, NumPy>=1.22, and SciPy>=1.9. Decide whether POUNCE isan optional extra (preferred for the evaluation) or whether core floors
should move.
Linux, macOS, and Windows architectures used by PharmaPy, and document the
source-build fallback and EPL-2.0 dependency license.
POUNCE would receive the same currently incorrect residual ordering and
weighting in
get_gradient(). High: [ParamEstim/Calibration] IPOPT result assembly crashes and PCR predict recenters on new data #78 / Fix IPOPT result assembly and PCR prediction centering #137 must also land so post-solveresult assembly no longer crashes.
Evaluation plan
1. Establish corrected baselines
and High: [ParamEstim/Calibration] IPOPT result assembly crashes and PCR predict recenters on new data #78 / Fix IPOPT result assembly and PCR prediction centering #137 before comparing solvers.
ParameterEstimationfixture thatdoes not require Assimulo and has independently checkable parameters,
objective, weighted residuals, and gradient.
multiple measured states and non-identity weights.
2. Build an isolated POUNCE adapter spike
pounce.minimizelazily/optionally with an actionable error whenpounce-solveris absent.without forwarding
kwargsas an unknown POUNCE option.info["jac"]/info["fun"]contract used by covariance,StatisticsClass, bootstrap, and parallel refits.SimExec.EstimateParams()path, not only a directhelper call.
3. Compare correctness, robustness, and cost
Record results for corrected cyipopt/Ipopt, POUNCE, and LM where applicable:
covariance inputs, bound feasibility, termination status, and reproducibility;
repeated/bootstrap solve behavior;
and solver option errors;
matrix.
Use explicit tolerances justified by the problem scale. A successful exit code
alone is not a parity result.
4. Make and implement the decision
issue, including the exact POUNCE and cyipopt/Ipopt versions tested.
result object);
the current backend explicitly rather than leaving a partial adapter.
Acceptance criteria
base.
pounce-solverbuild completes the boundedparameter-estimation fixtures through both
optimize_fn()andSimExec.EstimateParams().repeated/bootstrap solves.
messages agree.
decision; a go decision is closed by the migration PR.
Out of scope
scipy.optimize.minimizecall.
Allow solver-free model imports without Assimulo #134).
Related