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

awaiting responseThis expects a response from maintainer or contributor depending on who requested in last comment.feature requestNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions