Repository navigation
Expose solver statistics as scalar solution attributes #1715
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1110,6 +1110,54 @@ cuopt_int_t cuOptGetDualObjectiveValue(cuOptSolution solution, | |
| */ | ||
| cuopt_int_t cuOptGetReducedCosts(cuOptSolution solution, cuopt_float_t* reduced_cost_ptr); | ||
|
|
||
| /* -------------------------------------------------------------------------- */ | ||
| /* Solution attributes */ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're introducing a new concept of a solution attribute without a clearly documented distinction on what makes them different from parameters. |
||
| /* -------------------------------------------------------------------------- */ | ||
|
|
||
| /* | ||
| * Solver statistics are read as scalar attributes, using the CUOPT_SOLUTION_ATTR_* selectors in | ||
| * constants.h. New statistics can then be added as constants rather than as new exported | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment should describe what the code does, not what alternative designs could have been. |
||
| * functions, which keeps the ABI stable for existing callers. | ||
| * | ||
| * LP and MIP statistics come from different solvers, so an LP selector requires an LP solution | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we ever present "LP" and "MIP" as different solvers in the C API? It sounds like we're leaking an implementation detail. How about simply saying that not all attributes are present for all solutions, depending on the problem class? |
||
| * and a MIP selector requires a MIP solution. Query CUOPT_ATTR_IS_MIP on the originating problem | ||
| * to decide which set applies. | ||
| */ | ||
|
|
||
| /** @brief Get a scalar integer solution attribute (a CUOPT_SOLUTION_ATTR_* with an integer | ||
| * value: iteration counts, node counts, or the method that solved the problem). | ||
| * | ||
| * @param[in] solution - The solution object. | ||
| * | ||
| * @param[in] attribute - The attribute selector. | ||
| * | ||
| * @param[out] value_out - A pointer to a cuopt_int_t that on output will contain the value. | ||
| * | ||
| * @return A status code indicating success or failure. Returns CUOPT_INVALID_ARGUMENT if the | ||
| * selector is unknown, does not have an integer value, or does not apply to this solution's | ||
| * solver. | ||
| */ | ||
| cuopt_int_t cuOptGetSolutionIntAttribute(cuOptSolution solution, | ||
| cuopt_int_t attribute, | ||
| cuopt_int_t* value_out); | ||
|
|
||
| /** @brief Get a scalar floating-point solution attribute (a CUOPT_SOLUTION_ATTR_* with a | ||
| * floating-point value: residuals, gap, presolve time, or violation magnitudes). | ||
| * | ||
| * @param[in] solution - The solution object. | ||
| * | ||
| * @param[in] attribute - The attribute selector. | ||
| * | ||
| * @param[out] value_out - A pointer to a cuopt_float_t that on output will contain the value. | ||
| * | ||
| * @return A status code indicating success or failure. Returns CUOPT_INVALID_ARGUMENT if the | ||
| * selector is unknown, does not have a floating-point value, or does not apply to this | ||
| * solution's solver. | ||
| */ | ||
| cuopt_int_t cuOptGetSolutionFloatAttribute(cuOptSolution solution, | ||
| cuopt_int_t attribute, | ||
| cuopt_float_t* value_out); | ||
|
|
||
| /* -------------------------------------------------------------------------- */ | ||
| /* Generic problem attributes */ | ||
| /* -------------------------------------------------------------------------- */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |
| #include <cuopt/version_config.hpp> | ||
|
|
||
| #include <algorithm> | ||
| #include <cstdio> | ||
| #include <cstdlib> | ||
| #include <memory> | ||
| #include <span> | ||
|
|
@@ -34,6 +35,8 @@ using cuopt::mathematical_optimization::csc_matrix_t; | |
| using cuopt::mathematical_optimization::csr_matrix_t; | ||
| using cuopt::mathematical_optimization::get_memory_backend_type; | ||
| using cuopt::mathematical_optimization::is_valid_public_var_type_code; | ||
| using cuopt::mathematical_optimization::lp_solution_interface_t; | ||
| using cuopt::mathematical_optimization::mip_solution_interface_t; | ||
| using cuopt::mathematical_optimization::optimization_problem_interface_t; | ||
| using cuopt::mathematical_optimization::problem_and_stream_view_t; | ||
| using cuopt::mathematical_optimization::problem_category_t; | ||
|
|
@@ -1362,6 +1365,114 @@ cuopt_int_t cuOptGetReducedCosts(cuOptSolution solution, cuopt_float_t* reduced_ | |
| } | ||
| } | ||
|
|
||
| namespace { | ||
|
|
||
| // Solution attribute plumbing. Each selector names one scalar on the LP or MIP solution | ||
| // interface; adding a statistic later means adding a constant and one case, not a new symbol. | ||
|
|
||
| bool is_lp_solution_attribute(cuopt_int_t attribute) | ||
| { | ||
| return attribute >= CUOPT_SOLUTION_ATTR_LP_PRIMAL_RESIDUAL && | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This approach doesn't leave any room for us to define new LP solution attributes in the future. Maybe we don't really need which translates to: |
||
| attribute <= CUOPT_SOLUTION_ATTR_LP_SOLVED_BY; | ||
| } | ||
|
|
||
| lp_solution_interface_t<cuopt_int_t, cuopt_float_t>* as_lp_solution(cuOptSolution solution) | ||
| { | ||
| auto* view = static_cast<solution_and_stream_view_t*>(solution); | ||
| return view->is_mip ? nullptr : view->lp_solution_interface_ptr; | ||
| } | ||
|
|
||
| mip_solution_interface_t<cuopt_int_t, cuopt_float_t>* as_mip_solution(cuOptSolution solution) | ||
| { | ||
| auto* view = static_cast<solution_and_stream_view_t*>(solution); | ||
| return view->is_mip ? view->mip_solution_interface_ptr : nullptr; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| cuopt_int_t cuOptGetSolutionIntAttribute(cuOptSolution solution, | ||
| cuopt_int_t attribute, | ||
| cuopt_int_t* value_out) | ||
| { | ||
| if (solution == nullptr) { return CUOPT_INVALID_ARGUMENT; } | ||
| if (value_out == nullptr) { return CUOPT_INVALID_ARGUMENT; } | ||
|
|
||
| try { | ||
| if (is_lp_solution_attribute(attribute)) { | ||
| auto* lp = as_lp_solution(solution); | ||
| if (lp == nullptr) { return CUOPT_INVALID_ARGUMENT; } | ||
| switch (attribute) { | ||
| case CUOPT_SOLUTION_ATTR_LP_NUM_ITERATIONS: | ||
| *value_out = static_cast<cuopt_int_t>(lp->get_num_iterations()); | ||
| return CUOPT_SUCCESS; | ||
| case CUOPT_SOLUTION_ATTR_LP_SOLVED_BY: | ||
| *value_out = static_cast<cuopt_int_t>(lp->solved_by()); | ||
| return CUOPT_SUCCESS; | ||
| default: return CUOPT_INVALID_ARGUMENT; // a float-valued LP selector | ||
| } | ||
| } | ||
|
|
||
| auto* mip = as_mip_solution(solution); | ||
| if (mip == nullptr) { return CUOPT_INVALID_ARGUMENT; } | ||
| switch (attribute) { | ||
| case CUOPT_SOLUTION_ATTR_MIP_NUM_NODES: | ||
| *value_out = static_cast<cuopt_int_t>(mip->get_num_nodes()); | ||
| return CUOPT_SUCCESS; | ||
| case CUOPT_SOLUTION_ATTR_MIP_NUM_SIMPLEX_ITERATIONS: | ||
| *value_out = static_cast<cuopt_int_t>(mip->get_num_simplex_iterations()); | ||
| return CUOPT_SUCCESS; | ||
| default: return CUOPT_INVALID_ARGUMENT; | ||
| } | ||
| } catch (const std::exception& e) { | ||
| return CUOPT_RUNTIME_ERROR; | ||
| } | ||
| } | ||
|
|
||
| cuopt_int_t cuOptGetSolutionFloatAttribute(cuOptSolution solution, | ||
| cuopt_int_t attribute, | ||
| cuopt_float_t* value_out) | ||
| { | ||
| if (solution == nullptr) { return CUOPT_INVALID_ARGUMENT; } | ||
| if (value_out == nullptr) { return CUOPT_INVALID_ARGUMENT; } | ||
|
|
||
| try { | ||
| if (is_lp_solution_attribute(attribute)) { | ||
| auto* lp = as_lp_solution(solution); | ||
| if (lp == nullptr) { return CUOPT_INVALID_ARGUMENT; } | ||
| switch (attribute) { | ||
| case CUOPT_SOLUTION_ATTR_LP_PRIMAL_RESIDUAL: | ||
| *value_out = lp->get_l2_primal_residual(); | ||
| return CUOPT_SUCCESS; | ||
| case CUOPT_SOLUTION_ATTR_LP_DUAL_RESIDUAL: | ||
| *value_out = lp->get_l2_dual_residual(); | ||
| return CUOPT_SUCCESS; | ||
| case CUOPT_SOLUTION_ATTR_LP_GAP: *value_out = lp->get_gap(); return CUOPT_SUCCESS; | ||
| default: return CUOPT_INVALID_ARGUMENT; // an int-valued LP selector | ||
| } | ||
| } | ||
|
|
||
| auto* mip = as_mip_solution(solution); | ||
| if (mip == nullptr) { return CUOPT_INVALID_ARGUMENT; } | ||
| switch (attribute) { | ||
| case CUOPT_SOLUTION_ATTR_MIP_PRESOLVE_TIME: | ||
| *value_out = mip->get_presolve_time(); | ||
| return CUOPT_SUCCESS; | ||
| case CUOPT_SOLUTION_ATTR_MIP_MAX_CONSTRAINT_VIOLATION: | ||
| *value_out = mip->get_max_constraint_violation(); | ||
| return CUOPT_SUCCESS; | ||
| case CUOPT_SOLUTION_ATTR_MIP_MAX_INT_VIOLATION: | ||
| *value_out = mip->get_max_int_violation(); | ||
| return CUOPT_SUCCESS; | ||
| case CUOPT_SOLUTION_ATTR_MIP_MAX_VARIABLE_BOUND_VIOLATION: | ||
| *value_out = mip->get_max_variable_bound_violation(); | ||
| return CUOPT_SUCCESS; | ||
| default: return CUOPT_INVALID_ARGUMENT; | ||
| } | ||
| } catch (const std::exception& e) { | ||
| return CUOPT_RUNTIME_ERROR; | ||
| } | ||
| } | ||
|
|
||
| /* -------------------------------------------------------------------------- */ | ||
| /* Generic problem attribute getters */ | ||
| /* -------------------------------------------------------------------------- */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.