Skip to content

[FEA] Expand cuopt_c.h with solver stats getters for non-Python language bindings #1202

@rgsl888prabhu

Description

@rgsl888prabhu

Is your feature request related to a problem? Please describe.

The C API (cpp/include/cuopt/linear_programming/cuopt_c.h) does not expose all of the solver statistics that the C++ structs lp_solution_t and mip_ret_t carry. Python sidesteps this gap by binding directly to the C++ structs via Cython (see python/cuopt/cuopt/linear_programming/solver/solver.pxd:181-205), so Python users see fields like nb_iterations, residuals, gap, solved_by, MIP nodes, simplex iterations, presolve time, and violation metrics.

Non-Python language bindings that go through the C ABI cannot see these fields. The first concrete consumer is cuopt-java (PR #1192), which binds via Java FFM (jextract on cuopt_c.h). The Java records LpStats, MilpStats, and SolverMethod were defined with fields matching Python's surface but currently report NaN / -1 / UNSET for anything not exposed by the C API. Future bindings (Go, Rust, C#, …) would hit the same wall.

Describe the solution you'd like

Add thin C getters to cuopt_c.h (and corresponding implementations in cuopt_c.cpp) that read the existing fields off lp_solution_t / mip_ret_t. Approximately 11 new functions, each a one-line wrapper around an existing struct field.

LP (read from lp_solution_t):

  • cuOptGetPrimalResidual(cuOptSolution, cuopt_float_t*)l2_primal_residual_
  • cuOptGetDualResidual(cuOptSolution, cuopt_float_t*)l2_dual_residual_
  • cuOptGetGap(cuOptSolution, cuopt_float_t*)gap_
  • cuOptGetIterationCount(cuOptSolution, cuopt_int_t*)nb_iterations_
  • cuOptGetSolvedBy(cuOptSolution, cuopt_int_t*)solved_by_ (method_t)

MIP (read from mip_ret_t):

  • cuOptGetPresolveTime(cuOptSolution, cuopt_float_t*)presolve_time_
  • cuOptGetNumNodes(cuOptSolution, cuopt_int_t*)nodes_
  • cuOptGetNumSimplexIterations(cuOptSolution, cuopt_int_t*)simplex_iterations_
  • cuOptGetMaxConstraintViolation(cuOptSolution, cuopt_float_t*)max_constraint_violation_
  • cuOptGetMaxIntViolation(cuOptSolution, cuopt_float_t*)max_int_violation_
  • cuOptGetMaxVariableBoundViolation(cuOptSolution, cuopt_float_t*)max_variable_bound_violation_

Naming/signatures should match the existing cuOptGet* convention (return cuopt_int_t error code, take an output pointer). The exact function names are open for review.

Describe alternatives you've considered

  1. Bind language bindings directly to C++ — what Python does. Not viable for FFM/CFFI/cgo style bindings that target the C ABI, since C++ name mangling and ABI stability aren't guaranteed.
  2. Skip these fields in Java — current state of PR java: cuopt-java initial release — LP/MILP/QP API + classifier JARs #1192. Acceptable v1, but users coming from Gurobi/CPLEX expect these in stats output.
  3. Add a single bulk cuOptGetStats returning a struct — possible but heavier ABI footprint; individual getters are smaller and easier to evolve.

Additional context

  • C++ struct definitions: cpp/src/linear_programming/... (where lp_solution_t / mip_ret_t are defined)
  • Python's view of the same fields: python/cuopt/cuopt/linear_programming/solver/solver.pxd:181-205
  • Python's Solution class consuming them: python/cuopt/cuopt/linear_programming/solution/solution.py:206-222
  • Java placeholder code awaiting these getters: java/cuopt-java/src/main/java22/com/nvidia/cuopt/internal/CuOptProviderImpl.java:425-441
  • PR that uncovered the gap: java: cuopt-java initial release — LP/MILP/QP API + classifier JARs #1192 (cuopt-java)

Once the C API ships these, the Java side becomes ~30 lines of straightforward FFM wiring.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions