-
Notifications
You must be signed in to change notification settings - Fork 226
feat(c-api): add cuOptSetLogCallback #1636
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
3756aaf
a1ce114
adc2f81
dc14e3b
ade6670
351decb
6562dda
46249e7
85cb36d
30632ca
8831f1a
31e6c10
205512b
29c7e2a
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 |
|---|---|---|
|
|
@@ -823,6 +823,45 @@ cuopt_int_t cuOptGetFloatParameter(cuOptSolverSettings settings, | |
| const char* parameter_name, | ||
| cuopt_float_t* parameter_value); | ||
|
|
||
| /** | ||
| * @brief Type of callback invoked once per log line emitted by the solver. | ||
| * | ||
| * @param level Log level (one of CUOPT_LOG_LEVEL_*). | ||
| * @param message Null-terminated log line without trailing newline. | ||
| * @param user_data Opaque pointer passed to cuOptSetLogCallback. | ||
| * | ||
| * @note The callback is invoked from the solver thread. Do not call back into | ||
| * cuOpt from inside the callback. | ||
| */ | ||
| typedef void (*cuOptLogCallback)(int level, const char* message, void* user_data); | ||
|
|
||
| /** | ||
| * @brief Register a callback to receive solver log messages. | ||
| * | ||
| * The callback is invoked once per log line. It is called in addition to any | ||
| * file or console sink already enabled via ``log_to_console`` / ``log_file`` | ||
| * parameters. Pass NULL to remove a previously registered callback. | ||
| * | ||
| * @param[in] settings The solver settings object. | ||
| * @param[in] callback Callback function, or NULL to clear. | ||
| * @param[in] user_data Opaque pointer forwarded to the callback unchanged. | ||
| * | ||
| * @return A status code indicating success or failure. | ||
| */ | ||
| cuopt_int_t cuOptSetLogCallback(cuOptSolverSettings settings, | ||
|
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. How this handled with GRPC?
Collaborator
Author
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. Good catch — it wasn't, and it is now (30632ca). With
Both remote paths now forward each streamed line to the user callback, and request streaming when a callback is present. The registration is captured at call time rather than read inside the lambda, because Verified against a live and the remote lines are the server's own solver log ( One behavioural note now documented in the header: the callback runs on the calling thread for a local solve, and on the log-streaming thread for a remote one.
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. Can you please add a test for this?
Collaborator
Author
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. Follow-up: this is now covered by a test as well (31e6c10).
Worth noting why the assertion is what it is: the client emits its own |
||
| cuOptLogCallback callback, | ||
| void* user_data); | ||
|
|
||
| /** | ||
| * @brief Set the solver log verbosity level. | ||
| * | ||
| * @param[in] settings The solver settings object. | ||
| * @param[in] level One of CUOPT_LOG_LEVEL_TRACE … CUOPT_LOG_LEVEL_OFF. | ||
| * | ||
| * @return A status code indicating success or failure. | ||
| */ | ||
| cuopt_int_t cuOptSetLogLevel(cuOptSolverSettings settings, int level); | ||
|
|
||
| /** | ||
| * @brief Type of callback for receiving incumbent MIP solutions with user context. | ||
| * | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -89,6 +89,11 @@ struct solver_settings_handle_t { | |
| ~solver_settings_handle_t() { delete settings; } | ||
| solver_settings_t<cuopt_int_t, cuopt_float_t>* settings; | ||
| std::vector<std::unique_ptr<cuopt::internals::base_solution_callback_t>> callbacks; | ||
| // Log callback registered via cuOptSetLogCallback | ||
| cuOptLogCallback log_callback{nullptr}; | ||
| void* log_callback_user_data{nullptr}; | ||
| // Log level override registered via cuOptSetLogLevel (-1 = use default) | ||
| int log_level{-1}; | ||
| }; | ||
|
|
||
| solver_settings_handle_t* get_settings_handle(cuOptSolverSettings settings) | ||
|
|
@@ -1069,6 +1074,27 @@ cuopt_int_t cuOptSetMIPSetSolutionCallback(cuOptSolverSettings settings, | |
| return CUOPT_SUCCESS; | ||
| } | ||
|
|
||
| cuopt_int_t cuOptSetLogCallback(cuOptSolverSettings settings, | ||
| cuOptLogCallback callback, | ||
| void* user_data) | ||
| { | ||
| if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; } | ||
| solver_settings_handle_t* handle = get_settings_handle(settings); | ||
| handle->log_callback = callback; | ||
| handle->log_callback_user_data = user_data; | ||
| return CUOPT_SUCCESS; | ||
| } | ||
|
|
||
| cuopt_int_t cuOptSetLogLevel(cuOptSolverSettings settings, int level) | ||
| { | ||
| if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; } | ||
| if (level < CUOPT_LOG_LEVEL_TRACE || level > CUOPT_LOG_LEVEL_OFF) { | ||
| return CUOPT_INVALID_ARGUMENT; | ||
| } | ||
| get_settings_handle(settings)->log_level = level; | ||
| return CUOPT_SUCCESS; | ||
| } | ||
|
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. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Add coverage for the new C logging API. Add GoogleTest coverage for callback delivery/clearing, user-data forwarding, invalid log levels, and level application during 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
|
|
||
| cuopt_int_t cuOptSetInitialPrimalSolution(cuOptSolverSettings settings, | ||
| const cuopt_float_t* primal_solution, | ||
| cuopt_int_t num_variables) | ||
|
|
@@ -1145,6 +1171,29 @@ cuopt_int_t cuOptSolve(cuOptOptimizationProblem problem, | |
| if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; } | ||
| if (solution_ptr == nullptr) { return CUOPT_INVALID_ARGUMENT; } | ||
|
|
||
| // Install user log callback / level so init_logger_t inside the solver picks them up. | ||
| // The RAII guard clears them on scope exit (whether by return or exception). | ||
| solver_settings_handle_t* handle = get_settings_handle(settings); | ||
| struct log_scope_guard_t { | ||
| bool has_callback; | ||
| bool has_level; | ||
| ~log_scope_guard_t() | ||
| { | ||
| if (has_callback) { cuopt::clear_pending_log_callback(); } | ||
| if (has_level) { cuopt::clear_pending_log_level(); } | ||
| } | ||
| } log_scope{false, false}; | ||
|
|
||
| if (handle->log_callback) { | ||
| // cuOptLogCallback and log_callback_with_data_t share the same signature. | ||
| cuopt::set_pending_log_callback(handle->log_callback, handle->log_callback_user_data); | ||
| log_scope.has_callback = true; | ||
| } | ||
| if (handle->log_level >= 0) { | ||
| cuopt::set_pending_log_level(handle->log_level); | ||
| log_scope.has_level = true; | ||
| } | ||
|
|
||
| problem_and_stream_view_t* problem_and_stream_view = | ||
| static_cast<problem_and_stream_view_t*>(problem); | ||
|
|
||
|
|
||
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.
Are we sure we want to expose these log levels to the user?
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.
We can always set this Error or warn by default and it is upto user what they want to check.
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.
Addressed — no log level is exposed through the API at all now, per your Slack comments as well.
constants.hlevel enum andcuOptSetLogLevelare gone; this PR no longer touchesconstants.h.There is now no enum, no numeric mapping, and nothing for callers to branch on. The previous version tried to handle this with a doc comment ("not a stable programmatic API"), which does not stop code being written against a value it can see.
info, so debug and trace never reach user code. That previously held only by accident of the build —CUOPT_LOG_ACTIVE_LEVELdefaults to INFO so those statements are compiled out, but a lower-level build, orCUOPT_LOG_LEVEL=DEBUGagainst one, would have routed internal diagnostics into a user callback.One judgement call worth your view. You said the callback should fire only on a standard log line and that no code should see our debug and info messages. Those cannot both hold literally here: cuOpt's standard solver output is emitted at
info—CUOPT_LOG_INFOcovers "Solve status", "PDLP finished" and the progress table, roughly 153 call sites (level census: TRACE 109, DEBUG 295, INFO 153, WARN 13, ERROR 46). Filteringinfoout would leave the callback with essentially nothing to deliver.So I read "debug and info" as internal diagnostics and cut at
info: callers get what the console would show, and nothing below it. If you meant something stricter — a dedicated user-log channel separate frominfodiagnostics — that means auditing those ~153 sites and splitting them, which I would rather do as a follow-up than fold in here. Happy to go that way if you prefer.