Skip to content

Commit 59b64cb

Browse files
djunglasXPRSc4v4
andcommitted
Implement callback support for Xpress
We implement full support for the callback in Xpress. The Xpress implementation supports all events and all features of the ortools callbacks. We also added a "solution source" attribute to the callback data in case of the MIP_SOLUTION event because we think this can be useful. In general the Xpress callback is much richer than what the ortools callback offers but we did not want to add too many things that could be Xpress-specific and might not be available with other solvers. Co-authored-by: Francesco Cavaliere <francescocavaliere@fico.com>
1 parent 2e86134 commit 59b64cb

16 files changed

Lines changed: 1619 additions & 198 deletions

ortools/math_opt/callback.proto

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,28 +36,32 @@ enum CallbackEventProto {
3636

3737
// The solver is currently running presolve.
3838
//
39-
// This event is supported by SOLVER_TYPE_GUROBI only.
39+
// This event is supported by SOLVER_TYPE_GUROBI and SOLVER_TYPE_XPRESS only.
4040
CALLBACK_EVENT_PRESOLVE = 1;
4141

4242
// The solver is currently running the simplex method.
4343
//
44-
// This event is supported by SOLVER_TYPE_GUROBI only.
44+
// This event is supported by SOLVER_TYPE_GUROBI and SOLVER_TYPE_XPRESS only.
4545
CALLBACK_EVENT_SIMPLEX = 2;
4646

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

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

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

7479
// Called in each iterate of an interior point/barrier method.
7580
//
76-
// This event is supported for SOLVER_TYPE_GUROBI only.
81+
// This event is supported for SOLVER_TYPE_GUROBI and SOLVER_TYPE_XPRESS only.
7782
CALLBACK_EVENT_BARRIER = 6;
7883
}
7984

85+
// Where a solution for a CALLBACK_EVENT_MIP_SOLUTION came from.
86+
enum CallbackSolutionSourceProto {
87+
CALLBACK_SOLUTION_SOURCE_UNSPECIFIED = 0;
88+
89+
// The solution came from an LP relaxation that happened to be integer
90+
// feasible.
91+
CALLBACK_SOLUTION_SOURCE_INTEGRAL = 1;
92+
93+
// The solution came from a heuristic.
94+
CALLBACK_SOLUTION_SOURCE_HEURISTIC = 2;
95+
96+
// The solution came from a solution vector provided by the user.
97+
// This may include solutions the solver had to "repair".
98+
CALLBACK_SOLUTION_SOURCE_USER = 3;
99+
};
100+
80101
// The callback function input data.
81102
// Note that depending on the event, some information might be unavailable.
82103
message CallbackDataProto {
@@ -139,6 +160,10 @@ message CallbackDataProto {
139160
optional int64 simplex_iterations = 5;
140161
optional int32 number_of_solutions_found = 6;
141162
optional int32 cutting_planes_in_lp = 7;
163+
// Only for CALLBACK_EVENT_MIP_SOLUTION, specifies where the solution
164+
// came from. See CallbackSolutionSource.
165+
// Only implemented for SOLVER_TYPE_XPRESS.
166+
optional int32 solution_source = 8;
142167
}
143168
MipStats mip_stats = 7;
144169
}
@@ -179,16 +204,19 @@ message CallbackResultProto {
179204

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

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

ortools/math_opt/cpp/callback.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,29 @@ absl::Span<const CallbackEvent> Enum<CallbackEvent>::AllValues() {
6666
return absl::MakeConstSpan(kCallbackEventValues);
6767
}
6868

69+
std::optional<absl::string_view> Enum<CallbackSolutionSource>::ToOptString(
70+
CallbackSolutionSource value) {
71+
switch (value) {
72+
case CallbackSolutionSource::kIntegral:
73+
return "integral";
74+
case CallbackSolutionSource::kHeuristic:
75+
return "heuristic";
76+
case CallbackSolutionSource::kUser:
77+
return "user";
78+
}
79+
return std::nullopt;
80+
}
81+
82+
absl::Span<const CallbackSolutionSource>
83+
Enum<CallbackSolutionSource>::AllValues() {
84+
static constexpr CallbackSolutionSource kCallbackSolutionSourceValues[] = {
85+
CallbackSolutionSource::kIntegral,
86+
CallbackSolutionSource::kHeuristic,
87+
CallbackSolutionSource::kUser,
88+
};
89+
return absl::MakeConstSpan(kCallbackSolutionSourceValues);
90+
}
91+
6992
CallbackData::CallbackData(const CallbackEvent event,
7093
const absl::Duration runtime)
7194
: event(event), runtime(runtime) {}

ortools/math_opt/cpp/callback.h

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
// std::vector<VariableMap<double>> solutions;
4646
// auto cb = [&solutions](const CallbackData& cb_data) {
4747
// // NOTE: this assumes the callback is always called from the same thread.
48-
// // Gurobi always does this, multi-threaded SCIP does not.
48+
// // Gurobi always does this, multi-threaded SCIP or Xpress do not.
4949
// solutions.push_back(*cb_data.solution);
5050
// return CallbackResult();
5151
// };
@@ -62,8 +62,8 @@
6262
// CHECK fail). Some solvers do not support callbacks or certain events, in this
6363
// case the callback is ignored. TODO(b/180617976): change this behavior.
6464
//
65-
// Some solvers may call callback from multiple threads (SCIP will, Gurobi will
66-
// not). You should either solve with one thread (see
65+
// Some solvers may call callback from multiple threads (SCIP and Xpress will,
66+
// Gurobi will not). You should either solve with one thread (see
6767
// solver_parameters.threads), write a threadsafe callback, or consult
6868
// the documentation of your underlying solver.
6969
#ifndef ORTOOLS_MATH_OPT_CPP_CALLBACK_H_
@@ -97,26 +97,30 @@ using Callback = std::function<CallbackResult(const CallbackData&)>;
9797
enum class CallbackEvent {
9898
// The solver is currently running presolve.
9999
//
100-
// This event is supported for SolverType::kGurobi only.
100+
// This event is supported for SolverType::kGurobi or SolverType::kXpress
101+
// only.
101102
kPresolve = CALLBACK_EVENT_PRESOLVE,
102103

103104
// The solver is currently running the simplex method.
104105
//
105-
// This event is supported for SolverType::kGurobi only.
106+
// This event is supported for SolverType::kGurobi or SolverType::kXpress
107+
// only.
106108
kSimplex = CALLBACK_EVENT_SIMPLEX,
107109

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

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

136144
// Called in each iterate of an interior point/barrier method.
137145
//
138-
// This event is supported for SolverType::kGurobi only.
146+
// This event is supported for SolverType::kGurobi or SolverType::kXpress
147+
// only.
139148
kBarrier = CALLBACK_EVENT_BARRIER,
140149
};
141150

142151
MATH_OPT_DEFINE_ENUM(CallbackEvent, CALLBACK_EVENT_UNSPECIFIED);
143152

153+
// Where a solution for a CALLBACK_EVENT_MIP_SOLUTION came from.
154+
enum class CallbackSolutionSource {
155+
// The solution came from an LP relaxation that happened to be integer
156+
// feasible.
157+
kIntegral = CALLBACK_SOLUTION_SOURCE_INTEGRAL,
158+
// The solution came from a heuristic.
159+
kHeuristic = CALLBACK_SOLUTION_SOURCE_HEURISTIC,
160+
// The solution came from a solution vector provided by the user.
161+
// This may include solutions the solver had to "repair".
162+
kUser = CALLBACK_SOLUTION_SOURCE_USER,
163+
};
164+
165+
MATH_OPT_DEFINE_ENUM(CallbackSolutionSource,
166+
CALLBACK_SOLUTION_SOURCE_UNSPECIFIED);
167+
144168
// Provided with a callback at the start of a Solve() to inform the solver:
145169
// * what information the callback needs,
146170
// * how the callback might alter the solve process.
@@ -255,13 +279,17 @@ struct CallbackResult {
255279
};
256280

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

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

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

304333
// A list of solutions (or partially defined solutions) to suggest to the
305-
// solver. Some solvers (e.g. gurobi) will try and convert a partial solution
306-
// into a full solution. Use only for CallbackEvent::kMipNode or
307-
// CallbackEvent::kMipSolution.
334+
// solver. Some solvers (e.g. gurobi or Xpress) will try and convert a
335+
// partial solution into a full solution. Use only for
336+
// CallbackEvent::kMipNode or CallbackEvent::kMipSolution.
308337
std::vector<VariableMap<double>> suggested_solutions;
309338
};
310339

0 commit comments

Comments
 (0)