14"""The solution to an optimization problem defined by Model in model.py."""
17from typing
import Dict, Optional, TypeVar
26 """Status of a variable/constraint in a LP basis.
29 FREE: The variable/constraint is free (it has no finite bounds).
30 AT_LOWER_BOUND: The variable/constraint is at its lower bound (which must be
32 AT_UPPER_BOUND: The variable/constraint is at its upper bound (which must be
34 FIXED_VALUE: The variable/constraint has identical finite lower and upper
36 BASIC: The variable/constraint is basic.
39 FREE = solution_pb2.BASIS_STATUS_FREE
40 AT_LOWER_BOUND = solution_pb2.BASIS_STATUS_AT_LOWER_BOUND
41 AT_UPPER_BOUND = solution_pb2.BASIS_STATUS_AT_UPPER_BOUND
42 FIXED_VALUE = solution_pb2.BASIS_STATUS_FIXED_VALUE
43 BASIC = solution_pb2.BASIS_STATUS_BASIC
48 """Feasibility of a primal or dual solution as claimed by the solver.
51 UNDETERMINED: Solver does not claim a feasibility status.
52 FEASIBLE: Solver claims the solution is feasible.
53 INFEASIBLE: Solver claims the solution is infeasible.
56 UNDETERMINED = solution_pb2.SOLUTION_STATUS_UNDETERMINED
57 FEASIBLE = solution_pb2.SOLUTION_STATUS_FEASIBLE
58 INFEASIBLE = solution_pb2.SOLUTION_STATUS_INFEASIBLE
62 proto: solution_pb2.SolutionStatusProto,
63) -> Optional[SolutionStatus]:
64 """Converts a proto SolutionStatus to an optional Python SolutionStatus."""
67 if proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED
73 status: Optional[SolutionStatus],
74) -> solution_pb2.SolutionStatusProto:
75 """Converts an optional Python SolutionStatus to a proto SolutionStatus."""
76 return solution_pb2.SOLUTION_STATUS_UNSPECIFIED
if status
is None else status.value
81 """A solution to the optimization problem in a Model.
83 E.g. consider a simple linear program:
87 A primal solution is assignment values to x. It is feasible if it satisfies
88 A * x >= b and x >= 0 from above. In the class PrimalSolution variable_values
89 is x and objective_value is c * x.
91 For the general case of a MathOpt optimization model, see go/mathopt-solutions
95 variable_values: The value assigned for each Variable in the model.
96 objective_value: The value of the objective value at this solution. This
97 value may not be always populated.
98 feasibility_status: The feasibility of the solution as claimed by the
105 objective_value: float = 0.0
106 feasibility_status: SolutionStatus = SolutionStatus.UNDETERMINED
108 def to_proto(self) -> solution_pb2.PrimalSolutionProto:
109 """Returns an equivalent proto for a primal solution."""
110 return solution_pb2.PrimalSolutionProto(
111 variable_values=sparse_containers.to_sparse_double_vector_proto(
120 proto: solution_pb2.PrimalSolutionProto, mod:
model.Model
122 """Returns an equivalent PrimalSolution from the input proto."""
124 result.objective_value = proto.objective_value
125 result.variable_values = sparse_containers.parse_variable_map(
126 proto.variable_values, mod
128 status_proto = proto.feasibility_status
129 if status_proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED:
130 raise ValueError(
"Primal solution feasibility status should not be UNSPECIFIED")
135@dataclasses.dataclass
137 """A direction of unbounded objective improvement in an optimization Model.
139 Equivalently, a certificate of infeasibility for the dual of the optimization
142 E.g. consider a simple linear program:
146 A primal ray is an x that satisfies:
150 Observe that given a feasible solution, any positive multiple of the primal
151 ray plus that solution is still feasible, and gives a better objective
152 value. A primal ray also proves the dual optimization problem infeasible.
154 In the class PrimalRay, variable_values is this x.
156 For the general case of a MathOpt optimization model, see
157 go/mathopt-solutions for details.
160 variable_values: The value assigned for each Variable in the model.
169 """Returns an equivalent PrimalRay from the input proto."""
171 result.variable_values = sparse_containers.parse_variable_map(
172 proto.variable_values, mod
177@dataclasses.dataclass
179 """A solution to the dual of the optimization problem given by a Model.
181 E.g. consider the primal dual pair linear program pair:
184 s.t. A * x >= b s.t. y * A + r = c
186 The dual solution is the pair (y, r). It is feasible if it satisfies the
187 constraints from (Dual) above.
189 Below, y is dual_values, r is reduced_costs, and b * y is objective_value.
191 For the general case, see go/mathopt-solutions and go/mathopt-dual (and note
192 that the dual objective depends on r in the general case).
195 dual_values: The value assigned for each LinearConstraint in the model.
196 reduced_costs: The value assigned for each Variable in the model.
197 objective_value: The value of the dual objective value at this solution.
198 This value may not be always populated.
199 feasibility_status: The feasibility of the solution as claimed by the
206 reduced_costs: Dict[
model.Variable, float] = dataclasses.field(default_factory=dict)
207 objective_value: Optional[float] =
None
208 feasibility_status: SolutionStatus = SolutionStatus.UNDETERMINED
210 def to_proto(self) -> solution_pb2.DualSolutionProto:
211 """Returns an equivalent proto for a dual solution."""
212 return solution_pb2.DualSolutionProto(
213 dual_values=sparse_containers.to_sparse_double_vector_proto(
216 reduced_costs=sparse_containers.to_sparse_double_vector_proto(
225 proto: solution_pb2.DualSolutionProto, mod:
model.Model
227 """Returns an equivalent DualSolution from the input proto."""
229 result.objective_value = (
230 proto.objective_value
if proto.HasField(
"objective_value")
else None
232 result.dual_values = sparse_containers.parse_linear_constraint_map(
233 proto.dual_values, mod
235 result.reduced_costs = sparse_containers.parse_variable_map(
236 proto.reduced_costs, mod
238 status_proto = proto.feasibility_status
239 if status_proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED:
240 raise ValueError(
"Dual solution feasibility status should not be UNSPECIFIED")
245@dataclasses.dataclass
247 """A direction of unbounded objective improvement in an optimization Model.
249 A direction of unbounded improvement to the dual of an optimization,
250 problem; equivalently, a certificate of primal infeasibility.
252 E.g. consider the primal dual pair linear program pair:
255 s.t. A * x >= b s.t. y * A + r = c
258 The dual ray is the pair (y, r) satisfying:
262 Observe that adding a positive multiple of (y, r) to dual feasible solution
263 maintains dual feasibility and improves the objective (proving the dual is
264 unbounded). The dual ray also proves the primal problem is infeasible.
266 In the class DualRay below, y is dual_values and r is reduced_costs.
268 For the general case, see go/mathopt-solutions and go/mathopt-dual (and note
269 that the dual objective depends on r in the general case).
272 dual_values: The value assigned for each LinearConstraint in the model.
273 reduced_costs: The value assigned for each Variable in the model.
279 reduced_costs: Dict[
model.Variable, float] = dataclasses.field(default_factory=dict)
283 """Returns an equivalent DualRay from the input proto."""
285 result.dual_values = sparse_containers.parse_linear_constraint_map(
286 proto.dual_values, mod
288 result.reduced_costs = sparse_containers.parse_variable_map(
289 proto.reduced_costs, mod
294@dataclasses.dataclass
296 """A combinatorial characterization for a solution to a linear program.
298 The simplex method for solving linear programs always returns a "basic
299 feasible solution" which can be described combinatorially as a Basis. A basis
300 assigns a BasisStatus for every variable and linear constraint.
302 E.g. consider a standard form LP:
306 that has more variables than constraints and with full row rank A.
308 Let n be the number of variables and m the number of linear constraints. A
309 valid basis for this problem can be constructed as follows:
310 * All constraints will have basis status FIXED.
311 * Pick m variables such that the columns of A are linearly independent and
312 assign the status BASIC.
313 * Assign the status AT_LOWER for the remaining n - m variables.
315 The basic solution for this basis is the unique solution of A * x = b that has
316 all variables with status AT_LOWER fixed to their lower bounds (all zero). The
317 resulting solution is called a basic feasible solution if it also satisfies
320 See go/mathopt-basis for treatment of the general case and an explanation of
321 how a dual solution is determined for a basis.
324 variable_status: The basis status for each variable in the model.
325 constraint_status: The basis status for each linear constraint in the model.
326 basic_dual_feasibility: This is an advanced feature used by MathOpt to
327 characterize feasibility of suboptimal LP solutions (optimal solutions
328 will always have status SolutionStatus.FEASIBLE). For single-sided LPs it
329 should be equal to the feasibility status of the associated dual solution.
330 For two-sided LPs it may be different in some edge cases (e.g. incomplete
331 solves with primal simplex). For more details see
332 go/mathopt-basis-advanced#dualfeasibility. If you are providing a starting
333 basis via ModelSolveParameters.initial_basis, this value is ignored and
334 can be None. It is only relevant for the basis returned by Solution.basis,
335 and it is never None when returned from solve(). This is an advanced
336 status. For single-sided LPs it should be equal to the feasibility status
337 of the associated dual solution. For two-sided LPs it may be different in
338 some edge cases (e.g. incomplete solves with primal simplex). For more
339 details see go/mathopt-basis-advanced#dualfeasibility.
348 basic_dual_feasibility: Optional[SolutionStatus] =
None
351 """Returns an equivalent proto for the basis."""
352 return solution_pb2.BasisProto(
357 basic_dual_feasibility=optional_solution_status_to_proto(
364 """Returns an equivalent Basis to the input proto."""
366 for index, vid
in enumerate(proto.variable_status.ids):
367 status_proto = proto.variable_status.values[index]
368 if status_proto == solution_pb2.BASIS_STATUS_UNSPECIFIED:
369 raise ValueError(
"Variable basis status should not be UNSPECIFIED")
370 result.variable_status[mod.get_variable(vid)] =
BasisStatus(status_proto)
371 for index, cid
in enumerate(proto.constraint_status.ids):
372 status_proto = proto.constraint_status.values[index]
373 if status_proto == solution_pb2.BASIS_STATUS_UNSPECIFIED:
374 raise ValueError(
"Constraint basis status should not be UNSPECIFIED")
375 result.constraint_status[mod.get_linear_constraint(cid)] =
BasisStatus(
378 result.basic_dual_feasibility = parse_optional_solution_status(
379 proto.basic_dual_feasibility
388 terms: Dict[T, BasisStatus]
389) -> solution_pb2.SparseBasisStatusVector:
390 """Converts a basis vector from a python Dict to a protocol buffer."""
391 result = solution_pb2.SparseBasisStatusVector()
393 id_and_status = sorted(
394 (key.id, status.value)
for (key, status)
in terms.items()
396 ids, values = zip(*id_and_status)
398 result.values[:] = values
402@dataclasses.dataclass
404 """A solution to the optimization problem in a Model."""
406 primal_solution: Optional[PrimalSolution] =
None
407 dual_solution: Optional[DualSolution] =
None
408 basis: Optional[Basis] =
None
411 """Returns an equivalent proto for a solution."""
412 return solution_pb2.SolutionProto(
428 """Returns a Solution equivalent to the input proto."""
430 if proto.HasField(
"primal_solution"):
431 result.primal_solution = parse_primal_solution(proto.primal_solution, mod)
432 if proto.HasField(
"dual_solution"):
433 result.dual_solution = parse_dual_solution(proto.dual_solution, mod)
434 result.basis = parse_basis(proto.basis, mod)
if proto.HasField(
"basis")
else None