Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 37 additions & 9 deletions ortools/math_opt/callback.proto
Original file line number Diff line number Diff line change
Expand Up @@ -36,28 +36,32 @@ enum CallbackEventProto {

// The solver is currently running presolve.
//
// This event is supported by SOLVER_TYPE_GUROBI only.
// This event is supported by SOLVER_TYPE_GUROBI and SOLVER_TYPE_XPRESS only.
CALLBACK_EVENT_PRESOLVE = 1;

// The solver is currently running the simplex method.
//
// This event is supported by SOLVER_TYPE_GUROBI only.
// This event is supported by SOLVER_TYPE_GUROBI and SOLVER_TYPE_XPRESS only.
CALLBACK_EVENT_SIMPLEX = 2;

// The solver is in the MIP loop (called periodically before starting a new
// node). Useful for early termination. Note that this event does not provide
// information on LP relaxations nor about new incumbent solutions.
//
// This event is fully supported for MIP models by SOLVER_TYPE_GUROBI only. If
// This event is fully supported for MIP models by SOLVER_TYPE_GUROBI and
// SOLVER_TYPE_XPRESS only. If
// used with SOLVER_TYPE_CP_SAT, it is called when the dual bound is improved.
CALLBACK_EVENT_MIP = 3;

// Called every time a new MIP incumbent is found.
//
// This event is fully supported for MIP models by SOLVER_TYPE_GUROBI.
// This event is fully supported for MIP models by SOLVER_TYPE_GUROBI and
// SOLVER_TYPE_XPRESS.
// SOLVER_TYPE_CP_SAT has partial support: you can view the solutions and
// request termination, but you cannot add lazy constraints. Other solvers
// don't support this event.
// It is solver specific whether terminating from this event still collects
// the solution for which the event was triggered or not.
CALLBACK_EVENT_MIP_SOLUTION = 4;

// Called inside a MIP node. Note that there is no guarantee that the
Expand All @@ -68,15 +72,32 @@ enum CallbackEventProto {
// being called and/or adding cuts at this event, the behavior is solver
// specific.
//
// This event is supported for MIP models by SOLVER_TYPE_GUROBI only.
// This event is supported for MIP models by SOLVER_TYPE_GUROBI and
// SOLVER_TYPE_XPRESS only.
CALLBACK_EVENT_MIP_NODE = 5;

// Called in each iterate of an interior point/barrier method.
//
// This event is supported for SOLVER_TYPE_GUROBI only.
// This event is supported for SOLVER_TYPE_GUROBI and SOLVER_TYPE_XPRESS only.
CALLBACK_EVENT_BARRIER = 6;
}

// Where a solution for a CALLBACK_EVENT_MIP_SOLUTION came from.
enum CallbackSolutionSourceProto {
CALLBACK_SOLUTION_SOURCE_UNSPECIFIED = 0;

// The solution came from an LP relaxation that happened to be integer
// feasible.
CALLBACK_SOLUTION_SOURCE_INTEGRAL = 1;

// The solution came from a heuristic.
CALLBACK_SOLUTION_SOURCE_HEURISTIC = 2;

// The solution came from a solution vector provided by the user.
// This may include solutions the solver had to "repair".
CALLBACK_SOLUTION_SOURCE_USER = 3;
};

// The callback function input data.
// Note that depending on the event, some information might be unavailable.
message CallbackDataProto {
Expand Down Expand Up @@ -139,6 +160,10 @@ message CallbackDataProto {
optional int64 simplex_iterations = 5;
optional int32 number_of_solutions_found = 6;
optional int32 cutting_planes_in_lp = 7;
// Only for CALLBACK_EVENT_MIP_SOLUTION, specifies where the solution
// came from. See CallbackSolutionSource.
// Only implemented for SOLVER_TYPE_XPRESS.
optional int32 solution_source = 8;
}
MipStats mip_stats = 7;
}
Expand Down Expand Up @@ -179,16 +204,19 @@ message CallbackResultProto {

// Dynamically generated linear constraints to add to the MIP. See
// GeneratedLinearConstraint::is_lazy for details.
// All constraints must be globally valid (and not only valid for the subtree
// rooted at the search tree node for which the event was triggered).
repeated GeneratedLinearConstraint cuts = 4;

// Use only for CALLBACK_EVENT_MIP_NODE or CALLBACK_EVENT_MIP_SOLUTION.
//
// Note that some solvers (e.g. Gurobi) support partially-defined solutions.
// Note that some solvers (e.g. Gurobi or Xpress) support partially-defined
// solutions.
// The most common use case is to specify a value for each variable in the
// model. If a variable is not present in the primal solution, its value is
// taken to be undefined, and is up to the underlying solver to deal with it.
// For example, Gurobi will try to solve a Sub-MIP to get a fully feasible
// solution if necessary.
// For example, Gurobi or Xpress will try to solve a Sub-MIP to get a fully
// feasible solution if necessary.
repeated SparseDoubleVectorProto suggested_solutions = 5;
}

Expand Down
23 changes: 23 additions & 0 deletions ortools/math_opt/cpp/callback.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,29 @@ absl::Span<const CallbackEvent> Enum<CallbackEvent>::AllValues() {
return absl::MakeConstSpan(kCallbackEventValues);
}

std::optional<absl::string_view> Enum<CallbackSolutionSource>::ToOptString(
CallbackSolutionSource value) {
switch (value) {
case CallbackSolutionSource::kIntegral:
return "integral";
case CallbackSolutionSource::kHeuristic:
return "heuristic";
case CallbackSolutionSource::kUser:
return "user";
}
return std::nullopt;
}

absl::Span<const CallbackSolutionSource>
Enum<CallbackSolutionSource>::AllValues() {
static constexpr CallbackSolutionSource kCallbackSolutionSourceValues[] = {
CallbackSolutionSource::kIntegral,
CallbackSolutionSource::kHeuristic,
CallbackSolutionSource::kUser,
};
return absl::MakeConstSpan(kCallbackSolutionSourceValues);
}

CallbackData::CallbackData(const CallbackEvent event,
const absl::Duration runtime)
: event(event), runtime(runtime) {}
Expand Down
57 changes: 43 additions & 14 deletions ortools/math_opt/cpp/callback.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
// std::vector<VariableMap<double>> solutions;
// auto cb = [&solutions](const CallbackData& cb_data) {
// // NOTE: this assumes the callback is always called from the same thread.
// // Gurobi always does this, multi-threaded SCIP does not.
// // Gurobi always does this, multi-threaded SCIP or Xpress do not.
// solutions.push_back(*cb_data.solution);
// return CallbackResult();
// };
Expand All @@ -62,8 +62,8 @@
// CHECK fail). Some solvers do not support callbacks or certain events, in this
// case the callback is ignored. TODO(b/180617976): change this behavior.
//
// Some solvers may call callback from multiple threads (SCIP will, Gurobi will
// not). You should either solve with one thread (see
// Some solvers may call callback from multiple threads (SCIP and Xpress will,
// Gurobi will not). You should either solve with one thread (see
// solver_parameters.threads), write a threadsafe callback, or consult
// the documentation of your underlying solver.
#ifndef ORTOOLS_MATH_OPT_CPP_CALLBACK_H_
Expand Down Expand Up @@ -97,26 +97,30 @@ using Callback = std::function<CallbackResult(const CallbackData&)>;
enum class CallbackEvent {
// The solver is currently running presolve.
//
// This event is supported for SolverType::kGurobi only.
// This event is supported for SolverType::kGurobi or SolverType::kXpress
// only.
kPresolve = CALLBACK_EVENT_PRESOLVE,

// The solver is currently running the simplex method.
//
// This event is supported for SolverType::kGurobi only.
// This event is supported for SolverType::kGurobi or SolverType::kXpress
// only.
kSimplex = CALLBACK_EVENT_SIMPLEX,

// The solver is in the MIP loop (called periodically before starting a new
// node). Useful for early termination. Note that this event does not provide
// information on LP relaxations nor about new incumbent solutions.
//
// This event is fully supported for MIP models with SolverType::kGurobi only.
// This event is fully supported for MIP models with SolverType::kGurobi or
// SolverType::kXpress only.
// If used with SolverType::kCpSat, it is called when the dual bound is
// improved.
kMip = CALLBACK_EVENT_MIP,

// Called every time a new MIP incumbent is found.
//
// This event is fully supported for MIP models by SolverType::kGurobi.
// This event is fully supported for MIP models by SolverType::kGurobi or
// SolverType::kXpress only.
// SolverType::kCpSat has partial support: you can view the solutions and
// request termination, but you cannot add lazy constraints. Other solvers
// don't support this event.
Expand All @@ -130,17 +134,37 @@ enum class CallbackEvent {
// being called and/or adding cuts at this event, the behavior is solver
// specific.
//
// This event is supported for MIP models with SolverType::kGurobi only.
// This event is supported for MIP models with SolverType::kGurobi or
// SolverType::kXpress only.
// For Xpress disabling cuts will prevent this event. To disable cuts
// and still get this event called for Xpress, disable cuts by setting
// COVERCUTS, GOMCUTS, TREECOVERCUTS, TREEGOMCUTS to 0.
kMipNode = CALLBACK_EVENT_MIP_NODE,

// Called in each iterate of an interior point/barrier method.
//
// This event is supported for SolverType::kGurobi only.
// This event is supported for SolverType::kGurobi or SolverType::kXpress
// only.
kBarrier = CALLBACK_EVENT_BARRIER,
};

MATH_OPT_DEFINE_ENUM(CallbackEvent, CALLBACK_EVENT_UNSPECIFIED);

// Where a solution for a CALLBACK_EVENT_MIP_SOLUTION came from.
enum class CallbackSolutionSource {
// The solution came from an LP relaxation that happened to be integer
// feasible.
kIntegral = CALLBACK_SOLUTION_SOURCE_INTEGRAL,
// The solution came from a heuristic.
kHeuristic = CALLBACK_SOLUTION_SOURCE_HEURISTIC,
// The solution came from a solution vector provided by the user.
// This may include solutions the solver had to "repair".
kUser = CALLBACK_SOLUTION_SOURCE_USER,
};

MATH_OPT_DEFINE_ENUM(CallbackSolutionSource,
CALLBACK_SOLUTION_SOURCE_UNSPECIFIED);

// Provided with a callback at the start of a Solve() to inform the solver:
// * what information the callback needs,
// * how the callback might alter the solve process.
Expand Down Expand Up @@ -255,13 +279,17 @@ struct CallbackResult {
};

// Adds a "user cut," a linear constraint that excludes the current LP
// solution but does not cut off any integer points. Use only for
// CallbackEvent::kMipNode.
// solution but does not cut off any integer points.
// The constraint must be globally valid (and not only valid for the subtree
// rooted at the MIP search node at which the event was triggered).
// Use only for CallbackEvent::kMipNode.
void AddUserCut(BoundedLinearExpression linear_constraint) {
new_constraints.push_back({std::move(linear_constraint), false});
}

// Adds a "lazy constraint," a linear constraint that excludes integer points.
// The constraint must be globally valid (and not only valid for the subtree
// rooted at the MIP search node at which the event was triggered).
// Use only for CallbackEvent::kMipNode and CallbackEvent::kMipSolution.
void AddLazyConstraint(BoundedLinearExpression linear_constraint) {
new_constraints.push_back({std::move(linear_constraint), true});
Expand Down Expand Up @@ -299,12 +327,13 @@ struct CallbackResult {

// The user cuts and lazy constraints added. Prefer AddUserCut() and
// AddLazyConstraint() to modifying this directly.
// All constraints are assumed to be globally valid.
std::vector<GeneratedLinearConstraint> new_constraints;

// A list of solutions (or partially defined solutions) to suggest to the
// solver. Some solvers (e.g. gurobi) will try and convert a partial solution
// into a full solution. Use only for CallbackEvent::kMipNode or
// CallbackEvent::kMipSolution.
// solver. Some solvers (e.g. gurobi or Xpress) will try and convert a
// partial solution into a full solution. Use only for
// CallbackEvent::kMipNode or CallbackEvent::kMipSolution.
std::vector<VariableMap<double>> suggested_solutions;
};

Expand Down
Loading