14"""The output from solving a mathematical optimization problem from model.py."""
18from typing
import Dict, Iterable, List, Optional, overload
26_NO_DUAL_SOLUTION_ERROR = (
27 "Best solution does not have an associated dual feasible solution."
29_NO_BASIS_ERROR =
"Best solution does not have an associated basis."
34 """Problem feasibility status as claimed by the solver.
36 (solver is not required to return a certificate for the claim.)
39 UNDETERMINED: Solver does not claim a status.
40 FEASIBLE: Solver claims the problem is feasible.
41 INFEASIBLE: Solver claims the problem is infeasible.
44 UNDETERMINED = result_pb2.FEASIBILITY_STATUS_UNDETERMINED
45 FEASIBLE = result_pb2.FEASIBILITY_STATUS_FEASIBLE
46 INFEASIBLE = result_pb2.FEASIBILITY_STATUS_INFEASIBLE
49@dataclasses.dataclass(frozen=True)
51 """Feasibility status of the primal problem and its dual (or dual relaxation).
53 Statuses are as claimed by the solver and a dual relaxation is the dual of a
54 continuous relaxation for the original problem (e.g. the LP relaxation of a
55 MIP). The solver is not required to return a certificate for the feasibility
56 or infeasibility claims (e.g. the solver may claim primal feasibility without
57 returning a primal feasible solutuion). This combined status gives a
58 comprehensive description of a solver's claims about feasibility and
59 unboundedness of the solved problem. For instance,
60 * a feasible status for primal and dual problems indicates the primal is
61 feasible and bounded and likely has an optimal solution (guaranteed for
62 problems without non-linear constraints).
63 * a primal feasible and a dual infeasible status indicates the primal
64 problem is unbounded (i.e. has arbitrarily good solutions).
65 Note that a dual infeasible status by itself (i.e. accompanied by an
66 undetermined primal status) does not imply the primal problem is unbounded as
67 we could have both problems be infeasible. Also, while a primal and dual
68 feasible status may imply the existence of an optimal solution, it does not
69 guarantee the solver has actually found such optimal solution.
72 primal_status: Status for the primal problem.
73 dual_status: Status for the dual problem (or for the dual of a continuous
75 primal_or_dual_infeasible: If true, the solver claims the primal or dual
76 problem is infeasible, but it does not know which (or if both are
77 infeasible). Can be true only when primal_problem_status =
78 dual_problem_status = kUndetermined. This extra information is often
79 needed when preprocessing determines there is no optimal solution to the
80 problem (but can't determine if it is due to infeasibility, unboundedness,
84 primal_status: FeasibilityStatus = FeasibilityStatus.UNDETERMINED
85 dual_status: FeasibilityStatus = FeasibilityStatus.UNDETERMINED
86 primal_or_dual_infeasible: bool =
False
88 def to_proto(self) -> result_pb2.ProblemStatusProto:
89 """Returns an equivalent proto for a problem status."""
90 return result_pb2.ProblemStatusProto(
98 """Returns an equivalent ProblemStatus from the input proto."""
99 primal_status_proto = proto.primal_status
100 if primal_status_proto == result_pb2.FEASIBILITY_STATUS_UNSPECIFIED:
101 raise ValueError(
"Primal feasibility status should not be UNSPECIFIED")
102 dual_status_proto = proto.dual_status
103 if dual_status_proto == result_pb2.FEASIBILITY_STATUS_UNSPECIFIED:
104 raise ValueError(
"Dual feasibility status should not be UNSPECIFIED")
108 primal_or_dual_infeasible=proto.primal_or_dual_infeasible,
112@dataclasses.dataclass(frozen=True)
114 """Bounds on the optimal objective value.
116 MOE:begin_intracomment_strip
117 See go/mathopt-objective-bounds for more details.
118 MOE:end_intracomment_strip
121 primal_bound: Solver claims there exists a primal solution that is
122 numerically feasible (i.e. feasible up to the solvers tolerance), and
123 whose objective value is primal_bound.
125 The optimal value is equal or better (smaller for min objectives and
126 larger for max objectives) than primal_bound, but only up to
129 MOE:begin_intracomment_strip
130 See go/mathopt-objective-bounds for more details.
131 MOE:end_intracomment_strip
132 dual_bound: Solver claims there exists a dual solution that is numerically
133 feasible (i.e. feasible up to the solvers tolerance), and whose objective
136 For MIP solvers, the associated dual problem may be some continuous
137 relaxation (e.g. LP relaxation), but it is often an implicitly defined
138 problem that is a complex consequence of the solvers execution. For both
139 continuous and MIP solvers, the optimal value is equal or worse (larger
140 for min objective and smaller for max objectives) than dual_bound, but
141 only up to solver-tolerances. Some continuous solvers provide a
142 numerically safer dual bound through solver's specific output (e.g. for
143 PDLP, pdlp_output.convergence_information.corrected_dual_objective).
145 MOE:begin_intracomment_strip
146 See go/mathopt-objective-bounds for more details.
147 MOE:end_intracomment_strip
150 primal_bound: float = 0.0
151 dual_bound: float = 0.0
153 def to_proto(self) -> result_pb2.ObjectiveBoundsProto:
154 """Returns an equivalent proto for objective bounds."""
155 return result_pb2.ObjectiveBoundsProto(
161 proto: result_pb2.ObjectiveBoundsProto,
163 """Returns an equivalent ObjectiveBounds from the input proto."""
164 return ObjectiveBounds(primal_bound=proto.primal_bound, dual_bound=proto.dual_bound)
167@dataclasses.dataclass
169 """Problem statuses and solve statistics returned by the solver.
172 solve_time: Elapsed wall clock time as measured by math_opt, roughly the
173 time inside solve(). Note: this does not include work done building the
175 simplex_iterations: Simplex iterations.
176 barrier_iterations: Barrier iterations.
177 first_order_iterations: First order iterations.
178 node_count: Node count.
181 solve_time: datetime.timedelta = datetime.timedelta()
182 simplex_iterations: int = 0
183 barrier_iterations: int = 0
184 first_order_iterations: int = 0
188 """Returns an equivalent proto for a solve stats."""
189 result = result_pb2.SolveStatsProto(
195 result.solve_time.FromTimedelta(self.
solve_time)
200 """Returns an equivalent SolveStats from the input proto."""
202 result.solve_time = proto.solve_time.ToTimedelta()
203 result.simplex_iterations = proto.simplex_iterations
204 result.barrier_iterations = proto.barrier_iterations
205 result.first_order_iterations = proto.first_order_iterations
206 result.node_count = proto.node_count
212 """The reason a solve of a model terminated.
214 These reasons are typically as reported by the underlying solver, e.g. we do
215 not attempt to verify the precision of the solution returned.
218 * OPTIMAL: A provably optimal solution (up to numerical tolerances) has
220 * INFEASIBLE: The primal problem has no feasible solutions.
221 * UNBOUNDED: The primal problem is feasible and arbitrarily good solutions
222 can be found along a primal ray.
223 * INFEASIBLE_OR_UNBOUNDED: The primal problem is either infeasible or
224 unbounded. More details on the problem status may be available in
225 solve_stats.problem_status. Note that Gurobi's unbounded status may be
226 mapped here as explained in
227 go/mathopt-solver-specific#gurobi-inf-or-unb.
228 * IMPRECISE: The problem was solved to one of the criteria above (Optimal,
229 Infeasible, Unbounded, or InfeasibleOrUnbounded), but one or more
230 tolerances was not met. Some primal/dual solutions/rays may be present,
231 but either they will be slightly infeasible, or (if the problem was
232 nearly optimal) their may be a gap between the best solution objective
233 and best objective bound.
235 Users can still query primal/dual solutions/rays and solution stats,
236 but they are responsible for dealing with the numerical imprecision.
237 * FEASIBLE: The optimizer reached some kind of limit and a primal feasible
238 solution is returned. See SolveResultProto.limit_detail for detailed
239 description of the kind of limit that was reached.
240 * NO_SOLUTION_FOUND: The optimizer reached some kind of limit and it did
241 not find a primal feasible solution. See SolveResultProto.limit_detail
242 for detailed description of the kind of limit that was reached.
243 * NUMERICAL_ERROR: The algorithm stopped because it encountered
244 unrecoverable numerical error. No solution information is present.
245 * OTHER_ERROR: The algorithm stopped because of an error not covered by one
246 of the statuses defined above. No solution information is present.
249 OPTIMAL = result_pb2.TERMINATION_REASON_OPTIMAL
250 INFEASIBLE = result_pb2.TERMINATION_REASON_INFEASIBLE
251 UNBOUNDED = result_pb2.TERMINATION_REASON_UNBOUNDED
252 INFEASIBLE_OR_UNBOUNDED = result_pb2.TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED
253 IMPRECISE = result_pb2.TERMINATION_REASON_IMPRECISE
254 FEASIBLE = result_pb2.TERMINATION_REASON_FEASIBLE
255 NO_SOLUTION_FOUND = result_pb2.TERMINATION_REASON_NO_SOLUTION_FOUND
256 NUMERICAL_ERROR = result_pb2.TERMINATION_REASON_NUMERICAL_ERROR
257 OTHER_ERROR = result_pb2.TERMINATION_REASON_OTHER_ERROR
262 """The optimizer reached a limit, partial solution information may be present.
265 * UNDETERMINED: The underlying solver does not expose which limit was
267 * ITERATION: An iterative algorithm stopped after conducting the
268 maximum number of iterations (e.g. simplex or barrier iterations).
269 * TIME: The algorithm stopped after a user-specified amount of
271 * NODE: A branch-and-bound algorithm stopped because it explored a
272 maximum number of nodes in the branch-and-bound tree.
273 * SOLUTION: The algorithm stopped because it found the required
274 number of solutions. This is often used in MIPs to get the solver to
275 return the first feasible solution it encounters.
276 * MEMORY: The algorithm stopped because it ran out of memory.
277 * OBJECTIVE: The algorithm stopped because it found a solution better
278 than a minimum limit set by the user.
279 * NORM: The algorithm stopped because the norm of an iterate became
281 * INTERRUPTED: The algorithm stopped because of an interrupt signal or a
282 user interrupt request.
283 * SLOW_PROGRESS: The algorithm stopped because it was unable to continue
284 making progress towards the solution.
285 * OTHER: The algorithm stopped due to a limit not covered by one of the
286 above. Note that UNDETERMINED is used when the reason cannot be
287 determined, and OTHER is used when the reason is known but does not fit
288 into any of the above alternatives.
291 UNDETERMINED = result_pb2.LIMIT_UNDETERMINED
292 ITERATION = result_pb2.LIMIT_ITERATION
293 TIME = result_pb2.LIMIT_TIME
294 NODE = result_pb2.LIMIT_NODE
295 SOLUTION = result_pb2.LIMIT_SOLUTION
296 MEMORY = result_pb2.LIMIT_MEMORY
297 OBJECTIVE = result_pb2.LIMIT_OBJECTIVE
298 NORM = result_pb2.LIMIT_NORM
299 INTERRUPTED = result_pb2.LIMIT_INTERRUPTED
300 SLOW_PROGRESS = result_pb2.LIMIT_SLOW_PROGRESS
301 OTHER = result_pb2.LIMIT_OTHER
304@dataclasses.dataclass
306 """An explanation of why the solver stopped.
309 reason: Why the solver stopped, e.g. it found a provably optimal solution.
310 Additional information in `limit` when value is FEASIBLE or
311 NO_SOLUTION_FOUND, see `limit` for details.
312 limit: If the solver stopped early, what caused it to stop. Have value
313 UNSPECIFIED when reason is not NO_SOLUTION_FOUND or FEASIBLE. May still be
314 UNSPECIFIED when reason is NO_SOLUTION_FOUND or FEASIBLE, some solvers
316 detail: Additional, information beyond reason about why the solver stopped,
317 typically solver specific.
318 problem_status: Feasibility statuses for primal and dual problems.
319 objective_bounds: Bounds on the optimal objective value.
322 reason: TerminationReason = TerminationReason.OPTIMAL
323 limit: Optional[Limit] =
None
330 termination_proto: result_pb2.TerminationProto,
332 """Returns a Termination that is equivalent to termination_proto."""
333 reason_proto = termination_proto.reason
334 limit_proto = termination_proto.limit
335 if reason_proto == result_pb2.TERMINATION_REASON_UNSPECIFIED:
336 raise ValueError(
"Termination reason should not be UNSPECIFIED")
338 reason_proto == result_pb2.TERMINATION_REASON_NO_SOLUTION_FOUND
339 )
or (reason_proto == result_pb2.TERMINATION_REASON_FEASIBLE)
340 limit_set = limit_proto != result_pb2.LIMIT_UNSPECIFIED
341 if reason_is_limit != limit_set:
343 f
"Termination limit (={limit_proto})) should take value other than "
344 f
"UNSPECIFIED if and only if termination reason (={reason_proto}) is "
345 "FEASIBLE or NO_SOLUTION_FOUND"
349 termination.limit =
Limit(limit_proto)
if limit_set
else None
350 termination.detail = termination_proto.detail
351 termination.problem_status = parse_problem_status(termination_proto.problem_status)
352 termination.objective_bounds = parse_objective_bounds(
353 termination_proto.objective_bounds
358@dataclasses.dataclass
360 """The result of solving an optimization problem defined by a Model.
362 We attempt to return as much solution information (primal_solutions,
363 primal_rays, dual_solutions, dual_rays) as each underlying solver will provide
364 given its return status. Differences in the underlying solvers result in a
365 weak contract on what fields will be populated for a given termination
366 reason. This is discussed in detail in termination_reasons.md, and the most
367 important points are summarized below:
368 * When the termination reason is optimal, there will be at least one primal
369 solution provided that will be feasible up to the underlying solver's
371 * Dual solutions are only given for convex optimization problems (e.g.
372 linear programs, not integer programs).
373 * A basis is only given for linear programs when solved by the simplex
374 method (e.g., not with PDLP).
375 * Solvers have widely varying support for returning primal and dual rays.
376 E.g. a termination_reason of unbounded does not ensure that a feasible
377 solution or a primal ray is returned, check termination_reasons.md for
378 solver specific guarantees if this is needed. Further, many solvers will
379 provide the ray but not the feasible solution when returning an unbounded
381 * When the termination reason is that a limit was reached or that the result
382 is imprecise, a solution may or may not be present. Further, for some
383 solvers (generally, convex optimization solvers, not MIP solvers), the
384 primal or dual solution may not be feasible.
386 Solver specific output is also returned for some solvers (and only information
387 for the solver used will be populated).
390 termination: The reason the solver stopped.
391 solve_stats: Statistics on the solve process, e.g. running time, iterations.
392 solutions: Lexicographically by primal feasibility status, dual feasibility
393 status, (basic dual feasibility for simplex solvers), primal objective
394 value and dual objective value.
395 primal_rays: Directions of unbounded primal improvement, or equivalently,
396 dual infeasibility certificates. Typically provided for terminal reasons
397 UNBOUNDED and DUAL_INFEASIBLE.
398 dual_rays: Directions of unbounded dual improvement, or equivalently, primal
399 infeasibility certificates. Typically provided for termination reason
401 gscip_specific_output: statistics returned by the gSCIP solver, if used.
402 osqp_specific_output: statistics returned by the OSQP solver, if used.
403 pdlp_specific_output: statistics returned by the PDLP solver, if used.
406 termination: Termination = dataclasses.field(default_factory=Termination)
407 solve_stats: SolveStats = dataclasses.field(default_factory=SolveStats)
412 gscip_specific_output: Optional[gscip_pb2.GScipOutput] =
None
413 osqp_specific_output: Optional[osqp_pb2.OsqpOutput] =
None
414 pdlp_specific_output: Optional[result_pb2.SolveResultProto.PdlpOutput] =
None
417 """Shortcut for SolveResult.solve_stats.solve_time."""
421 """Returns a primal bound on the optimal objective value as described in ObjectiveBounds.
423 Will return a valid (possibly infinite) bound even if no primal feasible
424 solutions are available.
426 return self.
termination.objective_bounds.primal_bound
429 """Returns a dual bound on the optimal objective value as described in ObjectiveBounds.
431 Will return a valid (possibly infinite) bound even if no dual feasible
432 solutions are available.
434 return self.
termination.objective_bounds.dual_bound
437 """Indicates if at least one primal feasible solution is available.
439 When termination.reason is TerminationReason.OPTIMAL or
440 TerminationReason.FEASIBLE, this is guaranteed to be true and need not be
444 True if there is at least one primal feasible solution is available,
450 self.
solutions[0].primal_solution
is not None
451 and self.
solutions[0].primal_solution.feasibility_status
452 == solution.SolutionStatus.FEASIBLE
456 """Returns the objective value of the best primal feasible solution.
458 An error will be raised if there are no primal feasible solutions.
459 primal_bound() above is guaranteed to be at least as good (larger or equal
460 for max problems and smaller or equal for min problems) as objective_value()
461 and will never raise an error, so it may be preferable in some cases. Note
462 that primal_bound() could be better than objective_value() even for optimal
463 terminations, but on such optimal termination, both should satisfy the
464 optimality tolerances.
467 The objective value of the best primal feasible solution.
470 ValueError: There are no primal feasible solutions.
473 raise ValueError(
"No primal feasible solution available.")
474 assert self.
solutions[0].primal_solution
is not None
475 return self.
solutions[0].primal_solution.objective_value
478 """Returns a bound on the best possible objective value.
480 best_objective_bound() is always equal to dual_bound(), so they can be
481 used interchangeably.
483 return self.
termination.objective_bounds.dual_bound
495 """The variable values from the best primal feasible solution.
497 An error will be raised if there are no primal feasible solutions.
500 variables: an optional Variable or iterator of Variables indicating what
501 variable values to return. If not provided, variable_values returns a
502 dictionary with all the variable values for all variables.
505 The variable values from the best primal feasible solution.
508 ValueError: There are no primal feasible solutions.
509 TypeError: Argument is not None, a Variable or an iterable of Variables.
510 KeyError: Variable values requested for an invalid variable (e.g. is not a
511 Variable or is a variable for another model).
514 raise ValueError(
"No primal feasible solution available.")
515 assert self.
solutions[0].primal_solution
is not None
516 if variables
is None:
517 return self.
solutions[0].primal_solution.variable_values
519 return self.
solutions[0].primal_solution.variable_values[variables]
520 if isinstance(variables, Iterable):
522 self.
solutions[0].primal_solution.variable_values[v]
for v
in variables
525 "unsupported type in argument for "
526 f
"variable_values: {type(variables).__name__!r}"
530 """Returns true only if the problem has been shown to be feasible and bounded."""
532 self.
termination.problem_status.primal_status == FeasibilityStatus.FEASIBLE
534 == FeasibilityStatus.FEASIBLE
538 """Indicates if at least one primal ray is available.
540 This is NOT guaranteed to be true when termination.reason is
541 TerminationReason.kUnbounded or TerminationReason.kInfeasibleOrUnbounded.
544 True if at least one primal ray is available.
550 self, variables: None = ...
559 ) -> List[float]: ...
562 """The variable values from the first primal ray.
564 An error will be raised if there are no primal rays.
567 variables: an optional Variable or iterator of Variables indicating what
568 variable values to return. If not provided, variable_values() returns a
569 dictionary with the variable values for all variables.
572 The variable values from the first primal ray.
575 ValueError: There are no primal rays.
576 TypeError: Argument is not None, a Variable or an iterable of Variables.
577 KeyError: Variable values requested for an invalid variable (e.g. is not a
578 Variable or is a variable for another model).
581 raise ValueError(
"No primal ray available.")
582 if variables
is None:
585 return self.
primal_rays[0].variable_values[variables]
586 if isinstance(variables, Iterable):
587 return [self.
primal_rays[0].variable_values[v]
for v
in variables]
589 "unsupported type in argument for "
590 f
"ray_variable_values: {type(variables).__name__!r}"
594 """Indicates if the best solution has an associated dual feasible solution.
596 This is NOT guaranteed to be true when termination.reason is
597 TerminationReason.Optimal. It also may be true even when the best solution
598 does not have an associated primal feasible solution.
601 True if the best solution has an associated dual feasible solution.
606 self.
solutions[0].dual_solution
is not None
607 and self.
solutions[0].dual_solution.feasibility_status
608 == solution.SolutionStatus.FEASIBLE
613 self, linear_constraints: None = ...
617 def dual_values(self, linear_constraints: model.LinearConstraint) -> float: ...
622 ) -> List[float]: ...
625 """The dual values associated to the best solution.
627 If there is at least one primal feasible solution, this corresponds to the
628 dual values associated to the best primal feasible solution. An error will
629 be raised if the best solution does not have an associated dual feasible
633 linear_constraints: an optional LinearConstraint or iterator of
634 LinearConstraint indicating what dual values to return. If not provided,
635 dual_values() returns a dictionary with the dual values for all linear
639 The dual values associated to the best solution.
642 ValueError: The best solution does not have an associated dual feasible
644 TypeError: Argument is not None, a LinearConstraint or an iterable of
646 KeyError: LinearConstraint values requested for an invalid
647 linear constraint (e.g. is not a LinearConstraint or is a linear
648 constraint for another model).
651 raise ValueError(_NO_DUAL_SOLUTION_ERROR)
652 assert self.
solutions[0].dual_solution
is not None
653 if linear_constraints
is None:
654 return self.
solutions[0].dual_solution.dual_values
656 return self.
solutions[0].dual_solution.dual_values[linear_constraints]
657 if isinstance(linear_constraints, Iterable):
659 self.
solutions[0].dual_solution.dual_values[c]
660 for c
in linear_constraints
663 "unsupported type in argument for "
664 f
"dual_values: {type(linear_constraints).__name__!r}"
677 """The reduced costs associated to the best solution.
679 If there is at least one primal feasible solution, this corresponds to the
680 reduced costs associated to the best primal feasible solution. An error will
681 be raised if the best solution does not have an associated dual feasible
685 variables: an optional Variable or iterator of Variables indicating what
686 reduced costs to return. If not provided, reduced_costs() returns a
687 dictionary with the reduced costs for all variables.
690 The reduced costs associated to the best solution.
693 ValueError: The best solution does not have an associated dual feasible
695 TypeError: Argument is not None, a Variable or an iterable of Variables.
696 KeyError: Variable values requested for an invalid variable (e.g. is not a
697 Variable or is a variable for another model).
700 raise ValueError(_NO_DUAL_SOLUTION_ERROR)
701 assert self.
solutions[0].dual_solution
is not None
702 if variables
is None:
703 return self.
solutions[0].dual_solution.reduced_costs
705 return self.
solutions[0].dual_solution.reduced_costs[variables]
706 if isinstance(variables, Iterable):
707 return [self.
solutions[0].dual_solution.reduced_costs[v]
for v
in variables]
709 "unsupported type in argument for "
710 f
"reduced_costs: {type(variables).__name__!r}"
714 """Indicates if at least one dual ray is available.
716 This is NOT guaranteed to be true when termination.reason is
717 TerminationReason.Infeasible.
720 True if at least one dual ray is available.
726 self, linear_constraints: None = ...
735 ) -> List[float]: ...
738 """The dual values from the first dual ray.
740 An error will be raised if there are no dual rays.
743 linear_constraints: an optional LinearConstraint or iterator of
744 LinearConstraint indicating what dual values to return. If not provided,
745 ray_dual_values() returns a dictionary with the dual values for all
749 The dual values from the first dual ray.
752 ValueError: There are no dual rays.
753 TypeError: Argument is not None, a LinearConstraint or an iterable of
755 KeyError: LinearConstraint values requested for an invalid
756 linear constraint (e.g. is not a LinearConstraint or is a linear
757 constraint for another model).
760 raise ValueError(
"No dual ray available.")
761 if linear_constraints
is None:
764 return self.
dual_rays[0].dual_values[linear_constraints]
765 if isinstance(linear_constraints, Iterable):
766 return [self.
dual_rays[0].dual_values[v]
for v
in linear_constraints]
768 "unsupported type in argument for "
769 f
"ray_dual_values: {type(linear_constraints).__name__!r}"
774 self, variables: None = ...
784 """The reduced costs from the first dual ray.
786 An error will be raised if there are no dual rays.
789 variables: an optional Variable or iterator of Variables indicating what
790 reduced costs to return. If not provided, ray_reduced_costs() returns a
791 dictionary with the reduced costs for all variables.
794 The reduced costs from the first dual ray.
797 ValueError: There are no dual rays.
798 TypeError: Argument is not None, a Variable or an iterable of Variables.
799 KeyError: Variable values requested for an invalid variable (e.g. is not a
800 Variable or is a variable for another model).
803 raise ValueError(
"No dual ray available.")
804 if variables
is None:
807 return self.
dual_rays[0].reduced_costs[variables]
808 if isinstance(variables, Iterable):
809 return [self.
dual_rays[0].reduced_costs[v]
for v
in variables]
811 "unsupported type in argument for "
812 f
"ray_reduced_costs: {type(variables).__name__!r}"
816 """Indicates if the best solution has an associated basis.
818 This is NOT guaranteed to be true when termination.reason is
819 TerminationReason.Optimal. It also may be true even when the best solution
820 does not have an associated primal feasible solution.
823 True if the best solution has an associated basis.
827 return self.
solutions[0].basis
is not None
831 self, linear_constraints: None = ...
836 self, linear_constraints: model.LinearConstraint
845 """The constraint basis status associated to the best solution.
847 If there is at least one primal feasible solution, this corresponds to the
848 basis associated to the best primal feasible solution. An error will
849 be raised if the best solution does not have an associated basis.
853 linear_constraints: an optional LinearConstraint or iterator of
854 LinearConstraint indicating what constraint statuses to return. If not
855 provided, returns a dictionary with the constraint statuses for all
859 The constraint basis status associated to the best solution.
862 ValueError: The best solution does not have an associated basis.
863 TypeError: Argument is not None, a LinearConstraint or an iterable of
865 KeyError: LinearConstraint values requested for an invalid
866 linear constraint (e.g. is not a LinearConstraint or is a linear
867 constraint for another model).
870 raise ValueError(_NO_BASIS_ERROR)
871 assert self.
solutions[0].basis
is not None
872 if linear_constraints
is None:
873 return self.
solutions[0].basis.constraint_status
875 return self.
solutions[0].basis.constraint_status[linear_constraints]
876 if isinstance(linear_constraints, Iterable):
878 self.
solutions[0].basis.constraint_status[c]
for c
in linear_constraints
881 "unsupported type in argument for "
882 f
"constraint_status: {type(linear_constraints).__name__!r}"
887 self, variables: None = ...
899 """The variable basis status associated to the best solution.
901 If there is at least one primal feasible solution, this corresponds to the
902 basis associated to the best primal feasible solution. An error will
903 be raised if the best solution does not have an associated basis.
906 variables: an optional Variable or iterator of Variables indicating what
907 reduced costs to return. If not provided, variable_status() returns a
908 dictionary with the reduced costs for all variables.
911 The variable basis status associated to the best solution.
914 ValueError: The best solution does not have an associated basis.
915 TypeError: Argument is not None, a Variable or an iterable of Variables.
916 KeyError: Variable values requested for an invalid variable (e.g. is not a
917 Variable or is a variable for another model).
920 raise ValueError(_NO_BASIS_ERROR)
921 assert self.
solutions[0].basis
is not None
922 if variables
is None:
923 return self.
solutions[0].basis.variable_status
925 return self.
solutions[0].basis.variable_status[variables]
926 if isinstance(variables, Iterable):
927 return [self.
solutions[0].basis.variable_status[v]
for v
in variables]
929 "unsupported type in argument for "
930 f
"variable_status: {type(variables).__name__!r}"
935 result_proto: result_pb2.SolveResultProto,
936) -> result_pb2.ProblemStatusProto:
937 if result_proto.termination.HasField(
"problem_status"):
938 return result_proto.termination.problem_status
939 return result_proto.solve_stats.problem_status
943 result_proto: result_pb2.SolveResultProto,
944) -> result_pb2.ObjectiveBoundsProto:
945 if result_proto.termination.HasField(
"objective_bounds"):
946 return result_proto.termination.objective_bounds
947 return result_pb2.ObjectiveBoundsProto(
948 primal_bound=result_proto.solve_stats.best_primal_bound,
949 dual_bound=result_proto.solve_stats.best_dual_bound,
954 result_proto: result_pb2.SolveResultProto,
955) -> result_pb2.TerminationProto:
956 return result_pb2.TerminationProto(
957 reason=result_proto.termination.reason,
958 limit=result_proto.termination.limit,
959 detail=result_proto.termination.detail,
966 proto: result_pb2.SolveResultProto, mod:
model.Model
968 """Returns a SolveResult equivalent to the input proto."""
975 result.solve_stats = parse_solve_stats(proto.solve_stats)
976 for solution_proto
in proto.solutions:
977 result.solutions.append(solution.parse_solution(solution_proto, mod))
978 for primal_ray_proto
in proto.primal_rays:
979 result.primal_rays.append(solution.parse_primal_ray(primal_ray_proto, mod))
980 for dual_ray_proto
in proto.dual_rays:
981 result.dual_rays.append(solution.parse_dual_ray(dual_ray_proto, mod))
982 if proto.HasField(
"gscip_output"):
983 result.gscip_specific_output = proto.gscip_output
984 elif proto.HasField(
"osqp_output"):
985 result.osqp_specific_output = proto.osqp_output
986 elif proto.HasField(
"pdlp_output"):
987 result.pdlp_specific_output = proto.pdlp_output