pounce-cinterface
C ABI for POUNCE. Port of Ipopt's Interfaces/IpStdCInterface.{h,cpp}.
For Julia and JuMP, use the source-checkout
Pounce.jl package, which provides bindings for
this ABI and a tested MathOptInterface optimizer.
Provides the CreateIpoptProblem / IpoptSolve / FreeIpoptProblem
entry points so existing PyIpopt, cyipopt, JuMP, and AMPL wrappers can link
against POUNCE without source changes. Function names and signatures
match upstream exactly: consumers swap libipopt.{dylib,so} for
libpounce_cinterface.{dylib,so}.
Crate type
[]
= ["lib", "cdylib"]
The cdylib is what wrappers link against. The make install target
in the workspace root drops libpounce_cinterface.{dylib,so} into
$PREFIX/lib (default $HOME/.local/lib).
Examples
examples/*.c are C programs against include/pounce.h. From the workspace
root, build and run all of them with
which compiles each with -std=c99 -Wall -Wextra -Werror -pedantic, links
-lpounce_cinterface -lm with an rpath to the cdylib's directory, and runs
it. cargo test -p pounce-cinterface does the same through
tests/c_examples_build_and_run.rs and also checks the line each example
prints on success; a new example needs an entry in that test's
EXPECTED_STDOUT. The test fails when no C compiler ($CC, else cc) can
be run; set POUNCE_C_EXAMPLES_SKIP=1 to skip it knowingly.
examples/sqp_warm_start.c— working-set warm starts across a sequence of perturbed QPs on the active-set SQP path.examples/complementarity.c— declared complementarity on QpecSmall: one pair declared natively, one throughAddPounceComplementsConstraint, the refusal codes, the session refusal, the solve, the three outcome getters and the JSON report'scomplementarityblock.examples/qp_socp.c— the matrix-formPounceQp*entry: a convex QP (solution, multipliers, residuals, report enginecvx-qp), an SOCP (report enginecvx-qcqp), a primal-infeasible LP and its Farkas certificate, and three refusal codes (POUNCE_ERR_INDEFINITE,_CONES,_ARGUMENT).
Surface
Every exported symbol is listed here and in the book's
C API page; include/pounce.h documents each
prototype. tests/export_surface.rs fails if an export is missing from the
header, either doc, or the tests, or if the header and the Rust signature
disagree.
Upstream Ipopt 3.14 entry points (same names, arguments and return codes):
CreateIpoptProblem(n, x_L, x_U, m, g_L, g_U, nele_jac, nele_hess, index_style, eval_f, eval_g, eval_grad_f, eval_jac_g, eval_h)→IpoptProblemhandle;FreeIpoptProblem(problem).AddIpoptStrOption/AddIpoptNumOption/AddIpoptIntOption— forward to the application'sOptionsList.OpenIpoptOutputFile,SetIpoptProblemScaling,SetIntermediateCallback.IpoptSolve(problem, x, g, obj_val, mult_g, mult_x_L, mult_x_U, user_data)→ApplicationReturnStatus.- In-callback inspectors:
GetIpoptCurrentIterate,GetIpoptCurrentViolations. - Post-solve:
GetIpoptIterCount,GetIpoptSolveTime,GetIpoptPrimalInf,GetIpoptDualInf,GetIpoptComplInf;GetIpoptVersion.
POUNCE extensions:
-
Post-solve diagnostics:
GetPounceRestorationStats,GetPounceFdHessianStats,GetPounceLinearSolverStats(fills the#[repr(C)]PounceLinearSolverStats). -
GetPounceOptionType— whichAddIpopt*Optionsetter a keyword expects (works with a NULL handle). -
IpoptSetNonlinearVariables/IpoptClearNonlinearVariables— restrict the limited-memory Hessian to the variables that enter nonlinearly. -
One error namespace for every extension below: a refusal is a negative
POUNCE_ERR_*whose value means the same thing whichever function returns it (generic codes -1..-19, then a block of 20 per function family), success isPOUNCE_OK, andPounceErrorString(code)returns the shared message (a static string;"unknown error code"otherwise). -
Declared complementarity pairs (gh#1147):
AddPounceComplementarityPair,SetPounceComplementarityPairs(replace, all or nothing),ClearPounceComplementarityPairs,GetPounceComplementarityPairCount,GetPounceComplementarityPairs. Indices in the problem's index style; each declaration is validated at once by the classifier the lowering runs and refused with aPOUNCE_ERR_*code (POUNCE_ERR_INTERNALis an internal error, not a diagnosis). With pairs declared,IpoptSolvesolves through the complementarity driver (as the CLI and Python do; thecomplementarity_*options apply);IpoptCreateSolverand a staged SQP working set refuse them, and the live inspectors report no data during such a solve. -
MOI / JuMP
[F, x] ∈ Complements(gh#1147):AddPounceComplementsConstrainttakes MOI's semantics (Fa free row, its sign read offx's bounds) and maps them onto the native pairs by editing the handle's copy of the row bounds —F >= 0forx >= l,F <= 0forx <= u,F = 0for a freex, nothing for a boxed (mixed pair) or fixed (no pair)x. The row must be free — a row that already has a side, even one agreeing with MOI's sign, isPOUNCE_ERR_ROW_NOT_FREEand is a native declaration; thePOUNCE_COMPLEMENTS_*return code says which row of the table applied, or why the call was refused (nothing changes then). The edits, and the record of which rows and variables the helper has used, surviveClearPounceComplementarityPairs; to get the original row bounds back, free the problem and create it again. -
In-place bound updates (gh#1147 Stage 2):
SetPounceBoundschanges variable and row bounds on an existing handle, sparse (index arrays in the problem's index style; a NULL index array with the full count is dense) and all or nothing, at a cost independent ofn,mand the number of pairs. It keeps options, scaling, the nonlinear-variables hint, declared pairs andComplementsdeclarations, and refuses (POUNCE_ERR_*, changing nothing) an update that would change what a declared pair pairs, move aComplementsvariable to another row of the MOI table, give aComplementsrow a finite bound, or contradict a staged working set.GetPounceBoundsreads the caller's bounds (POUNCE_BOUNDS_VIEW_USER) or the solve's, with theComplementsrow edits (POUNCE_BOUNDS_VIEW_EFFECTIVE). No solver state is reused; warm starts stay explicit throughIpoptSolve's buffers. -
Complementarity outcome (gh#1147):
GetPounceComplementarityStats(#[repr(C)], extensiblePounceComplementarityStats: strategy, what was reported and by which formulation, total iterations, the stationarity certificate, the B-stationarity check, the polish),GetPounceComplementaritySolve(one lowered solve,PounceComplementaritySolve) andGetPounceComplementarityText(summary, polish reason, B-check reason;snprintfcontract). The fields and encodings of the solve report'scomplementarityblock (NaNfor itsnull,-1for an absent record). They describe the most recent solve: all three answerFALSE/-1before any solve and after one without pairs or a refused one; clearing the pairs keeps the record until the next solve. -
IpoptEnableIterHistory+IpoptWriteSolveReport— emits the structuredpounce.solve-report/v1JSON (seepounce-solve-report) from the most recent solve. Schema-compatible with the CLI's--json-output; after a complementarity-driver solve it carries the samecomplementarityblock the CLI and Python write for the same driver outcome, and after a solve the second-opinion ladder ran a rung on, the samesecond_opinionblock (gh#850: which rung was promoted, and the base solve's status and cost). -
Working-set warm start (active-set SQP):
IpoptGetWorkingSet,IpoptSetWarmStartWorkingSet,IpoptClearWarmStartWorkingSet,IpoptSolveWarmStart. Example:examples/sqp_warm_start.c. Not combined with complementarity pairs. -
Factor-once / solve-many session:
IpoptCreateSolver,IpoptSolverSolve,IpoptSolverGetKktDim,IpoptSolverKktSolve,IpoptSolverKktSolveScaled,IpoptSolverParametricStep,IpoptSolverReducedHessian,IpoptFreeSolver. Reuses the converged KKT factor across parametric sweeps, reduced-Hessian queries, and raw back-solves. The classicIpoptSolveAPI is unchanged and unaffected. Seedocs/src/sessions.mdfor the walkthrough. -
Matrix-form convex QP / conic entry (gh#1147 Stage 3;
src/qp.rs): an opaquePounceQphandle —PounceCreateQp,PounceFreeQp; buildersPounceQpSetObjective(Plower triangle +c),PounceQpSetEqualities,PounceQpSetInequalities,PounceQpSetBounds,PounceQpAddCone(POUNCE_CONE_*: nonneg, SOC, exp, power, PSD); optionsPounceQpSetNumOption,PounceQpSetStrOption;PounceQpSolve(aPOUNCE_QP_STATUS_*, or a negativePOUNCE_ERR_*before any engine runs); gettersPounceQpGetSolution,PounceQpGetResiduals(#[repr(C)], extensiblePounceQpResiduals),PounceQpGetCertificate(PounceQpCertificate, likewise); andPounceQpWriteSolveReport. It runspounce_convex::pipeline, the code Python'ssolve_qp/solve_socprun, and matches Python's results bit for bit on Linux x86_64 (tests/qp_python_parity.rs). Standard formmin ½xᵀPx + cᵀx,Ax = b,Gx ≤ h(⪯_Kwith cones),lb ≤ x ≤ ub; triplets 0- or 1-based by the handle's index style,Plower triangle, a PSD cone's rows insvecorder (lower triangle by columns, off-diagonals×√2). With a cone,method=active-set,tau,tau_max, theqp_*switches and anyhessian_inertiabutautoare refused (POUNCE_ERR_OPTIONS), and the report'ssolution.engineiscvx-qcqpfor every cone kind. The IPM's PSD screen comes frompounce_convex::certify_psd_lower_triangleat the CLI's band, not Python's numpy check, so it can refuse a borderlinePthat Python accepts;hessian_inertia=psdoverrides it. Seepounce.hand the book's C API page for the cone table, every option, status and error code.
All entry points are extern "C" and #[no_mangle]. Pointers are raw
and the caller owns lifetimes; the IpoptProblem handle is opaque
(void* from C).
Fortran shim
src/fortran.rs ports IpStdFInterface.c: the eight gfortran-mangled
symbols ipcreate_, ipfree_, ipsolve_, ipaddstroption_,
ipaddnumoption_, ipaddintoption_, ipsetcallback_ and
ipunsetcallback_. Arguments are by reference, strings are
(buffer, length) pairs with the lengths trailing, and the option setters
return 0 (OKRetVal) or 1. POUNCE's C extensions are not mirrored here.
Versioning
GetIpoptVersion reports the crate version, and the header's
POUNCE_VERSION_* defines must equal it (tests/header_abi.rs and
scripts/check-release-consistency.sh both check). The ABI changes by
addition only: no existing prototype, struct layout or #define value
changes (the extensible structs below may gain fields at their end). The
#[repr(C)] structs (PounceLinearSolverStats,
PounceComplementarityStats, PounceComplementaritySolve,
PounceQpResiduals, PounceQpCertificate) are held to the
header field by field (size, offset, width) by a C program in
tests/c_examples_build_and_run.rs.
The four gh#1147 output structs (PounceComplementarityStats,
PounceComplementaritySolve, PounceQpResiduals, PounceQpCertificate)
are extensible: their first field is ipindex struct_size, which the
caller sets to sizeof the struct. A smaller value (a forgotten = {0}) is
refused — FALSE, nothing written — and the getter otherwise writes this
release's fields only, never struct_size and never a byte past this
release's sizeof (write_extensible in src/lib.rs). Growth rule: later
releases only append fields and write min(struct_size, their sizeof), so
old callers and new libraries, and new callers and old libraries, both
work. PounceLinearSolverStats shipped before the convention and stays
frozen, with no struct_size. Tests: tests/common/struct_size.rs, run by
complementarity_getters.rs and qp_cones_and_errors.rs.
Status
Feature-complete for the upstream Ipopt C ABI surface. End-to-end
solves run via IpoptSolve through the regular algorithm path, or through
the complementarity driver when pairs are declared; option forwarding,
intermediate callback, post-solve stats, and the JSON solve-report writer
are all wired. A callback model can take a different path through the
driver's solves than the CLI on the .nl file of the same model (a row's
constant in g or in its bound; the .nl nonlinear-variable list under
L-BFGS): see "Matching the CLI" in docs/src/c-api.md. The C ABI is the path PyIpopt /
cyipopt / JuMP / AMPL drivers take when they link
libpounce_cinterface in place of libipopt.
License
EPL-2.0.