14"""Defines how to request a callback and the input and output of a callback."""
19from typing
import Dict, List, Mapping, Optional, Set, Union
28 """The supported events during a solve for callbacks.
30 * UNSPECIFIED: The event is unknown (typically an internal error).
31 * PRESOLVE: The solver is currently running presolve. Gurobi only.
32 * SIMPLEX: The solver is currently running the simplex method. Gurobi only.
33 * MIP: The solver is in the MIP loop (called periodically before starting a
34 new node). Useful for early termination. Note that this event does not
35 provide information on LP relaxations nor about new incumbent solutions.
37 * MIP_SOLUTION: Called every time a new MIP incumbent is found. Fully
38 supported by Gurobi, partially supported by CP-SAT (you can observe new
39 solutions, but not add lazy constraints).
40 * MIP_NODE: Called inside a MIP node. Note that there is no guarantee that the
41 callback function will be called on every node. That behavior is
42 solver-dependent. Gurobi only.
44 Disabling cuts using SolveParameters may interfere with this event being
45 called and/or adding cuts at this event, the behavior is solver specific.
46 * BARRIER: Called in each iterate of an interior point/barrier method. Gurobi
50 UNSPECIFIED = callback_pb2.CALLBACK_EVENT_UNSPECIFIED
51 PRESOLVE = callback_pb2.CALLBACK_EVENT_PRESOLVE
52 SIMPLEX = callback_pb2.CALLBACK_EVENT_SIMPLEX
53 MIP = callback_pb2.CALLBACK_EVENT_MIP
54 MIP_SOLUTION = callback_pb2.CALLBACK_EVENT_MIP_SOLUTION
55 MIP_NODE = callback_pb2.CALLBACK_EVENT_MIP_NODE
56 BARRIER = callback_pb2.CALLBACK_EVENT_BARRIER
59PresolveStats = callback_pb2.CallbackDataProto.PresolveStats
60SimplexStats = callback_pb2.CallbackDataProto.SimplexStats
61BarrierStats = callback_pb2.CallbackDataProto.BarrierStats
62MipStats = callback_pb2.CallbackDataProto.MipStats
67 """Input to the solve callback (produced by the solver).
70 event: The current state of the solver when the callback is run. The event
71 (partially) determines what data is available and what the user is allowed
73 solution: A solution to the primal optimization problem, if available. For
74 Event.MIP_SOLUTION, solution is always present, integral, and feasible.
75 For Event.MIP_NODE, the primal_solution contains the current LP-node
76 relaxation. In some cases, no solution will be available (e.g. because LP
77 was infeasible or the solve was imprecise). Empty for other events.
78 messages: Logs generated by the underlying solver, as a list of strings
79 without new lines (each string is a line). Only filled on Event.MESSAGE.
80 runtime: The time since Solve() was invoked.
81 presolve_stats: Filled for Event.PRESOLVE only.
82 simplex_stats: Filled for Event.SIMPLEX only.
83 barrier_stats: Filled for Event.BARRIER only.
84 mip_stats: Filled for the events MIP, MIP_SOLUTION and MIP_NODE only.
87 event: Event = Event.UNSPECIFIED
89 messages: List[str] = dataclasses.field(default_factory=list)
90 runtime: datetime.timedelta = datetime.timedelta()
91 presolve_stats: PresolveStats = dataclasses.field(default_factory=PresolveStats)
92 simplex_stats: SimplexStats = dataclasses.field(default_factory=SimplexStats)
93 barrier_stats: BarrierStats = dataclasses.field(default_factory=BarrierStats)
94 mip_stats: MipStats = dataclasses.field(default_factory=MipStats)
98 cb_data: callback_pb2.CallbackDataProto, mod:
model.Model
100 """Creates a CallbackData from an equivalent proto.
103 cb_data: A protocol buffer with the information the user needs for a
105 mod: The model being solved.
108 An equivalent CallbackData.
111 ValueError: if cb_data is invalid or inconsistent with mod, e.g. cb_data
112 refers to a variable id not in mod.
115 result.event =
Event(cb_data.event)
116 if cb_data.HasField(
"primal_solution_vector"):
117 primal_solution = cb_data.primal_solution_vector
119 mod.get_variable(id): val
120 for (id, val)
in zip(primal_solution.ids, primal_solution.values)
122 result.runtime = cb_data.runtime.ToTimedelta()
123 result.presolve_stats = cb_data.presolve_stats
124 result.simplex_stats = cb_data.simplex_stats
125 result.barrier_stats = cb_data.barrier_stats
126 result.mip_stats = cb_data.mip_stats
130@dataclasses.dataclass
132 """Request the events and input data and reports output types for a callback.
134 Note that it is an error to add a constraint in a callback without setting
135 add_cuts and/or add_lazy_constraints to true.
138 events: When the callback should be invoked, by default, never. If an
139 unsupported event for a solver/model combination is selected, an
140 excecption is raised, see Event above for details.
141 mip_solution_filter: restricts the variable values returned in
142 CallbackData.solution (the callback argument) at each MIP_SOLUTION event.
143 By default, values are returned for all variables.
144 mip_node_filter: restricts the variable values returned in
145 CallbackData.solution (the callback argument) at each MIP_NODE event. By
146 default, values are returned for all variables.
147 add_cuts: The callback may add "user cuts" (linear constraints that
148 strengthen the LP without cutting of integer points) at MIP_NODE events.
149 add_lazy_constraints: The callback may add "lazy constraints" (linear
150 constraints that cut off integer solutions) at MIP_NODE or MIP_SOLUTION
154 events: Set[Event] = dataclasses.field(default_factory=set)
155 mip_solution_filter: sparse_containers.VariableFilter = (
156 sparse_containers.VariableFilter()
158 mip_node_filter: sparse_containers.VariableFilter = (
159 sparse_containers.VariableFilter()
161 add_cuts: bool =
False
162 add_lazy_constraints: bool =
False
164 def to_proto(self) -> callback_pb2.CallbackRegistrationProto:
165 """Returns an equivalent proto to this CallbackRegistration."""
166 result = callback_pb2.CallbackRegistrationProto()
167 result.request_registration[:] = sorted([event.value
for event
in self.
events])
175@dataclasses.dataclass
177 """A linear constraint to add inside a callback.
179 Models a constraint of the form:
180 lb <= sum_{i in I} a_i * x_i <= ub
182 Two types of generated linear constraints are supported based on is_lazy:
183 * The "lazy constraint" can remove integer points from the feasible
184 region and can be added at event Event.MIP_NODE or
186 * The "user cut" (on is_lazy=false) strengthens the LP without removing
187 integer points. It can only be added at Event.MIP_NODE.
191 terms: The variables and linear coefficients in the constraint, a_i and x_i
193 lower_bound: lb in the model above.
194 upper_bound: ub in the model above.
195 is_lazy: Indicates if the constraint should be interpreted as a "lazy
196 constraint" (cuts off integer solutions) or a "user cut" (strengthens the
197 LP relaxation without cutting of integer solutions).
201 lower_bound: float = -math.inf
202 upper_bound: float = math.inf
203 is_lazy: bool =
False
207 ) -> callback_pb2.CallbackResultProto.GeneratedLinearConstraint:
208 """Returns an equivalent proto for the constraint."""
209 result = callback_pb2.CallbackResultProto.GeneratedLinearConstraint()
213 result.linear_expression.CopyFrom(
214 sparse_containers.to_sparse_double_vector_proto(self.
terms)
219@dataclasses.dataclass
221 """The value returned by a solve callback (produced by the user).
224 terminate: Stop the solve process and return early. Can be called from any
226 generated_constraints: Constraints to add to the model. For details, see
227 GeneratedConstraint documentation.
228 suggested_solutions: A list of solutions (or partially defined solutions) to
229 suggest to the solver. Some solvers (e.g. gurobi) will try and convert a
230 partial solution into a full solution by solving a MIP. Use only for
234 terminate: bool =
False
235 generated_constraints: List[GeneratedConstraint] = dataclasses.field(
244 bounded_expr: Optional[Union[bool, model.BoundedLinearTypes]] =
None,
246 lb: Optional[float] =
None,
247 ub: Optional[float] =
None,
248 expr: Optional[model.LinearTypes] =
None,
251 """Adds a linear constraint to the list of generated constraints.
253 The constraint can be of two exclusive types: a "lazy constraint" or a
254 "user cut. A "user cut" is a constraint that excludes the current LP
255 solution, but does not cut off any integer-feasible points that satisfy the
256 already added constraints (either in callbacks or through
257 Model.add_linear_constraint()). A "lazy constraint" is a constraint that
258 excludes such integer-feasible points and hence is needed for corrctness of
261 The simplest way to specify the constraint is by passing a one-sided or
262 two-sided linear inequality as in:
263 * add_generated_constraint(x + y + 1.0 <= 2.0, is_lazy=True),
264 * add_generated_constraint(x + y >= 2.0, is_lazy=True), or
265 * add_generated_constraint((1.0 <= x + y) <= 2.0, is_lazy=True).
267 Note the extra parenthesis for two-sided linear inequalities, which is
268 required due to some language limitations (see
269 https://peps.python.org/pep-0335/ and https://peps.python.org/pep-0535/).
270 If the parenthesis are omitted, a TypeError will be raised explaining the
271 issue (if this error was not raised the first inequality would have been
272 silently ignored because of the noted language limitations).
274 The second way to specify the constraint is by setting lb, ub, and/o expr as
276 * add_generated_constraint(expr=x + y + 1.0, ub=2.0, is_lazy=True),
277 * add_generated_constraint(expr=x + y, lb=2.0, is_lazy=True),
278 * add_generated_constraint(expr=x + y, lb=1.0, ub=2.0, is_lazy=True), or
279 * add_generated_constraint(lb=1.0, is_lazy=True).
280 Omitting lb is equivalent to setting it to -math.inf and omiting ub is
281 equivalent to setting it to math.inf.
283 These two alternatives are exclusive and a combined call like:
284 * add_generated_constraint(x + y <= 2.0, lb=1.0, is_lazy=True), or
285 * add_generated_constraint(x + y <= 2.0, ub=math.inf, is_lazy=True)
286 will raise a ValueError. A ValueError is also raised if expr's offset is
290 bounded_expr: a linear inequality describing the constraint. Cannot be
291 specified together with lb, ub, or expr.
292 lb: The constraint's lower bound if bounded_expr is omitted (if both
293 bounder_expr and lb are omitted, the lower bound is -math.inf).
294 ub: The constraint's upper bound if bounded_expr is omitted (if both
295 bounder_expr and ub are omitted, the upper bound is math.inf).
296 expr: The constraint's linear expression if bounded_expr is omitted.
297 is_lazy: Whether the constraint is lazy or not.
299 normalized_inequality = model.as_normalized_linear_inequality(
300 bounded_expr, lb=lb, ub=ub, expr=expr
304 lower_bound=normalized_inequality.lb,
305 terms=normalized_inequality.coefficients,
306 upper_bound=normalized_inequality.ub,
313 bounded_expr: Optional[Union[bool, model.BoundedLinearTypes]] =
None,
315 lb: Optional[float] =
None,
316 ub: Optional[float] =
None,
317 expr: Optional[model.LinearTypes] =
None,
319 """Shortcut for add_generated_constraint(..., is_lazy=True).."""
321 bounded_expr, lb=lb, ub=ub, expr=expr, is_lazy=
True
326 bounded_expr: Optional[Union[bool, model.BoundedLinearTypes]] =
None,
328 lb: Optional[float] =
None,
329 ub: Optional[float] =
None,
330 expr: Optional[model.LinearTypes] =
None,
332 """Shortcut for add_generated_constraint(..., is_lazy=False)."""
334 bounded_expr, lb=lb, ub=ub, expr=expr, is_lazy=
False
337 def to_proto(self) -> callback_pb2.CallbackResultProto:
338 """Returns a proto equivalent to this CallbackResult."""
339 result = callback_pb2.CallbackResultProto(terminate=self.
terminate)
341 result.cuts.add().CopyFrom(generated_constraint.to_proto())
343 result.suggested_solutions.add().CopyFrom(
344 sparse_containers.to_sparse_double_vector_proto(suggested_solution)