ortools.math_opt.python.solution
The solution to an optimization problem defined by Model in model.py.
1# Copyright 2010-2025 Google LLC 2# Licensed under the Apache License, Version 2.0 (the "License"); 3# you may not use this file except in compliance with the License. 4# You may obtain a copy of the License at 5# 6# http://www.apache.org/licenses/LICENSE-2.0 7# 8# Unless required by applicable law or agreed to in writing, software 9# distributed under the License is distributed on an "AS IS" BASIS, 10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11# See the License for the specific language governing permissions and 12# limitations under the License. 13 14"""The solution to an optimization problem defined by Model in model.py.""" 15import dataclasses 16import enum 17from typing import Dict, Optional, TypeVar 18 19from ortools.math_opt import solution_pb2 20from ortools.math_opt.python import model 21from ortools.math_opt.python import sparse_containers 22 23 24@enum.unique 25class BasisStatus(enum.Enum): 26 """Status of a variable/constraint in a LP basis. 27 28 Attributes: 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 31 finite). 32 AT_UPPER_BOUND: The variable/constraint is at its upper bound (which must be 33 finite). 34 FIXED_VALUE: The variable/constraint has identical finite lower and upper 35 bounds. 36 BASIC: The variable/constraint is basic. 37 """ 38 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 44 45 46@enum.unique 47class SolutionStatus(enum.Enum): 48 """Feasibility of a primal or dual solution as claimed by the solver. 49 50 Attributes: 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. 54 """ 55 56 UNDETERMINED = solution_pb2.SOLUTION_STATUS_UNDETERMINED 57 FEASIBLE = solution_pb2.SOLUTION_STATUS_FEASIBLE 58 INFEASIBLE = solution_pb2.SOLUTION_STATUS_INFEASIBLE 59 60 61def parse_optional_solution_status( 62 proto: solution_pb2.SolutionStatusProto, 63) -> Optional[SolutionStatus]: 64 """Converts a proto SolutionStatus to an optional Python SolutionStatus.""" 65 return ( 66 None 67 if proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED 68 else SolutionStatus(proto) 69 ) 70 71 72def optional_solution_status_to_proto( 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 77 78 79@dataclasses.dataclass 80class PrimalSolution: 81 """A solution to the optimization problem in a Model. 82 83 E.g. consider a simple linear program: 84 min c * x 85 s.t. A * x >= b 86 x >= 0. 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. 90 91 For the general case of a MathOpt optimization model, see go/mathopt-solutions 92 for details. 93 94 Attributes: 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 99 solver. 100 """ 101 102 variable_values: Dict[model.Variable, float] = dataclasses.field( 103 default_factory=dict 104 ) 105 objective_value: float = 0.0 106 feasibility_status: SolutionStatus = SolutionStatus.UNDETERMINED 107 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( 112 self.variable_values 113 ), 114 objective_value=self.objective_value, 115 feasibility_status=self.feasibility_status.value, 116 ) 117 118 119def parse_primal_solution( 120 proto: solution_pb2.PrimalSolutionProto, mod: model.Model 121) -> PrimalSolution: 122 """Returns an equivalent PrimalSolution from the input proto.""" 123 result = PrimalSolution() 124 result.objective_value = proto.objective_value 125 result.variable_values = sparse_containers.parse_variable_map( 126 proto.variable_values, mod 127 ) 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") 131 result.feasibility_status = SolutionStatus(status_proto) 132 return result 133 134 135@dataclasses.dataclass 136class PrimalRay: 137 """A direction of unbounded objective improvement in an optimization Model. 138 139 Equivalently, a certificate of infeasibility for the dual of the optimization 140 problem. 141 142 E.g. consider a simple linear program: 143 min c * x 144 s.t. A * x >= b 145 x >= 0. 146 A primal ray is an x that satisfies: 147 c * x < 0 148 A * x >= 0 149 x >= 0. 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. 153 154 In the class PrimalRay, variable_values is this x. 155 156 For the general case of a MathOpt optimization model, see 157 go/mathopt-solutions for details. 158 159 Attributes: 160 variable_values: The value assigned for each Variable in the model. 161 """ 162 163 variable_values: Dict[model.Variable, float] = dataclasses.field( 164 default_factory=dict 165 ) 166 167 def to_proto(self) -> solution_pb2.PrimalRayProto: 168 """Returns an equivalent proto to this PrimalRay.""" 169 return solution_pb2.PrimalRayProto( 170 variable_values=sparse_containers.to_sparse_double_vector_proto( 171 self.variable_values 172 ) 173 ) 174 175 176def parse_primal_ray(proto: solution_pb2.PrimalRayProto, mod: model.Model) -> PrimalRay: 177 """Returns an equivalent PrimalRay from the input proto.""" 178 result = PrimalRay() 179 result.variable_values = sparse_containers.parse_variable_map( 180 proto.variable_values, mod 181 ) 182 return result 183 184 185@dataclasses.dataclass 186class DualSolution: 187 """A solution to the dual of the optimization problem given by a Model. 188 189 E.g. consider the primal dual pair linear program pair: 190 (Primal) (Dual) 191 min c * x max b * y 192 s.t. A * x >= b s.t. y * A + r = c 193 x >= 0 y, r >= 0. 194 The dual solution is the pair (y, r). It is feasible if it satisfies the 195 constraints from (Dual) above. 196 197 Below, y is dual_values, r is reduced_costs, and b * y is objective_value. 198 199 For the general case, see go/mathopt-solutions and go/mathopt-dual (and note 200 that the dual objective depends on r in the general case). 201 202 Attributes: 203 dual_values: The value assigned for each LinearConstraint in the model. 204 reduced_costs: The value assigned for each Variable in the model. 205 objective_value: The value of the dual objective value at this solution. 206 This value may not be always populated. 207 feasibility_status: The feasibility of the solution as claimed by the 208 solver. 209 """ 210 211 dual_values: Dict[model.LinearConstraint, float] = dataclasses.field( 212 default_factory=dict 213 ) 214 reduced_costs: Dict[model.Variable, float] = dataclasses.field(default_factory=dict) 215 objective_value: Optional[float] = None 216 feasibility_status: SolutionStatus = SolutionStatus.UNDETERMINED 217 218 def to_proto(self) -> solution_pb2.DualSolutionProto: 219 """Returns an equivalent proto for a dual solution.""" 220 return solution_pb2.DualSolutionProto( 221 dual_values=sparse_containers.to_sparse_double_vector_proto( 222 self.dual_values 223 ), 224 reduced_costs=sparse_containers.to_sparse_double_vector_proto( 225 self.reduced_costs 226 ), 227 objective_value=self.objective_value, 228 feasibility_status=self.feasibility_status.value, 229 ) 230 231 232def parse_dual_solution( 233 proto: solution_pb2.DualSolutionProto, mod: model.Model 234) -> DualSolution: 235 """Returns an equivalent DualSolution from the input proto.""" 236 result = DualSolution() 237 result.objective_value = ( 238 proto.objective_value if proto.HasField("objective_value") else None 239 ) 240 result.dual_values = sparse_containers.parse_linear_constraint_map( 241 proto.dual_values, mod 242 ) 243 result.reduced_costs = sparse_containers.parse_variable_map( 244 proto.reduced_costs, mod 245 ) 246 status_proto = proto.feasibility_status 247 if status_proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED: 248 raise ValueError("Dual solution feasibility status should not be UNSPECIFIED") 249 result.feasibility_status = SolutionStatus(status_proto) 250 return result 251 252 253@dataclasses.dataclass 254class DualRay: 255 """A direction of unbounded objective improvement in an optimization Model. 256 257 A direction of unbounded improvement to the dual of an optimization, 258 problem; equivalently, a certificate of primal infeasibility. 259 260 E.g. consider the primal dual pair linear program pair: 261 (Primal) (Dual) 262 min c * x max b * y 263 s.t. A * x >= b s.t. y * A + r = c 264 x >= 0 y, r >= 0. 265 266 The dual ray is the pair (y, r) satisfying: 267 b * y > 0 268 y * A + r = 0 269 y, r >= 0. 270 Observe that adding a positive multiple of (y, r) to dual feasible solution 271 maintains dual feasibility and improves the objective (proving the dual is 272 unbounded). The dual ray also proves the primal problem is infeasible. 273 274 In the class DualRay below, y is dual_values and r is reduced_costs. 275 276 For the general case, see go/mathopt-solutions and go/mathopt-dual (and note 277 that the dual objective depends on r in the general case). 278 279 Attributes: 280 dual_values: The value assigned for each LinearConstraint in the model. 281 reduced_costs: The value assigned for each Variable in the model. 282 """ 283 284 dual_values: Dict[model.LinearConstraint, float] = dataclasses.field( 285 default_factory=dict 286 ) 287 reduced_costs: Dict[model.Variable, float] = dataclasses.field(default_factory=dict) 288 289 def to_proto(self) -> solution_pb2.DualRayProto: 290 """Returns an equivalent proto to this PrimalRay.""" 291 return solution_pb2.DualRayProto( 292 dual_values=sparse_containers.to_sparse_double_vector_proto( 293 self.dual_values 294 ), 295 reduced_costs=sparse_containers.to_sparse_double_vector_proto( 296 self.reduced_costs 297 ), 298 ) 299 300 301def parse_dual_ray(proto: solution_pb2.DualRayProto, mod: model.Model) -> DualRay: 302 """Returns an equivalent DualRay from the input proto.""" 303 result = DualRay() 304 result.dual_values = sparse_containers.parse_linear_constraint_map( 305 proto.dual_values, mod 306 ) 307 result.reduced_costs = sparse_containers.parse_variable_map( 308 proto.reduced_costs, mod 309 ) 310 return result 311 312 313@dataclasses.dataclass 314class Basis: 315 """A combinatorial characterization for a solution to a linear program. 316 317 The simplex method for solving linear programs always returns a "basic 318 feasible solution" which can be described combinatorially as a Basis. A basis 319 assigns a BasisStatus for every variable and linear constraint. 320 321 E.g. consider a standard form LP: 322 min c * x 323 s.t. A * x = b 324 x >= 0 325 that has more variables than constraints and with full row rank A. 326 327 Let n be the number of variables and m the number of linear constraints. A 328 valid basis for this problem can be constructed as follows: 329 * All constraints will have basis status FIXED. 330 * Pick m variables such that the columns of A are linearly independent and 331 assign the status BASIC. 332 * Assign the status AT_LOWER for the remaining n - m variables. 333 334 The basic solution for this basis is the unique solution of A * x = b that has 335 all variables with status AT_LOWER fixed to their lower bounds (all zero). The 336 resulting solution is called a basic feasible solution if it also satisfies 337 x >= 0. 338 339 See go/mathopt-basis for treatment of the general case and an explanation of 340 how a dual solution is determined for a basis. 341 342 Attributes: 343 variable_status: The basis status for each variable in the model. 344 constraint_status: The basis status for each linear constraint in the model. 345 basic_dual_feasibility: This is an advanced feature used by MathOpt to 346 characterize feasibility of suboptimal LP solutions (optimal solutions 347 will always have status SolutionStatus.FEASIBLE). For single-sided LPs it 348 should be equal to the feasibility status of the associated dual solution. 349 For two-sided LPs it may be different in some edge cases (e.g. incomplete 350 solves with primal simplex). For more details see 351 go/mathopt-basis-advanced#dualfeasibility. If you are providing a starting 352 basis via ModelSolveParameters.initial_basis, this value is ignored and 353 can be None. It is only relevant for the basis returned by Solution.basis, 354 and it is never None when returned from solve(). This is an advanced 355 status. For single-sided LPs it should be equal to the feasibility status 356 of the associated dual solution. For two-sided LPs it may be different in 357 some edge cases (e.g. incomplete solves with primal simplex). For more 358 details see go/mathopt-basis-advanced#dualfeasibility. 359 """ 360 361 variable_status: Dict[model.Variable, BasisStatus] = dataclasses.field( 362 default_factory=dict 363 ) 364 constraint_status: Dict[model.LinearConstraint, BasisStatus] = dataclasses.field( 365 default_factory=dict 366 ) 367 basic_dual_feasibility: Optional[SolutionStatus] = None 368 369 def to_proto(self) -> solution_pb2.BasisProto: 370 """Returns an equivalent proto for the basis.""" 371 return solution_pb2.BasisProto( 372 variable_status=_to_sparse_basis_status_vector_proto(self.variable_status), 373 constraint_status=_to_sparse_basis_status_vector_proto( 374 self.constraint_status 375 ), 376 basic_dual_feasibility=optional_solution_status_to_proto( 377 self.basic_dual_feasibility 378 ), 379 ) 380 381 382def parse_basis(proto: solution_pb2.BasisProto, mod: model.Model) -> Basis: 383 """Returns an equivalent Basis to the input proto.""" 384 result = Basis() 385 for index, vid in enumerate(proto.variable_status.ids): 386 status_proto = proto.variable_status.values[index] 387 if status_proto == solution_pb2.BASIS_STATUS_UNSPECIFIED: 388 raise ValueError("Variable basis status should not be UNSPECIFIED") 389 result.variable_status[mod.get_variable(vid)] = BasisStatus(status_proto) 390 for index, cid in enumerate(proto.constraint_status.ids): 391 status_proto = proto.constraint_status.values[index] 392 if status_proto == solution_pb2.BASIS_STATUS_UNSPECIFIED: 393 raise ValueError("Constraint basis status should not be UNSPECIFIED") 394 result.constraint_status[mod.get_linear_constraint(cid)] = BasisStatus( 395 status_proto 396 ) 397 result.basic_dual_feasibility = parse_optional_solution_status( 398 proto.basic_dual_feasibility 399 ) 400 return result 401 402 403T = TypeVar("T", model.Variable, model.LinearConstraint) 404 405 406def _to_sparse_basis_status_vector_proto( 407 terms: Dict[T, BasisStatus] 408) -> solution_pb2.SparseBasisStatusVector: 409 """Converts a basis vector from a python Dict to a protocol buffer.""" 410 result = solution_pb2.SparseBasisStatusVector() 411 if terms: 412 id_and_status = sorted( 413 (key.id, status.value) for (key, status) in terms.items() 414 ) 415 ids, values = zip(*id_and_status) 416 result.ids[:] = ids 417 result.values[:] = values 418 return result 419 420 421@dataclasses.dataclass 422class Solution: 423 """A solution to the optimization problem in a Model.""" 424 425 primal_solution: Optional[PrimalSolution] = None 426 dual_solution: Optional[DualSolution] = None 427 basis: Optional[Basis] = None 428 429 def to_proto(self) -> solution_pb2.SolutionProto: 430 """Returns an equivalent proto for a solution.""" 431 return solution_pb2.SolutionProto( 432 primal_solution=( 433 self.primal_solution.to_proto() 434 if self.primal_solution is not None 435 else None 436 ), 437 dual_solution=( 438 self.dual_solution.to_proto() 439 if self.dual_solution is not None 440 else None 441 ), 442 basis=self.basis.to_proto() if self.basis is not None else None, 443 ) 444 445 446def parse_solution(proto: solution_pb2.SolutionProto, mod: model.Model) -> Solution: 447 """Returns a Solution equivalent to the input proto.""" 448 result = Solution() 449 if proto.HasField("primal_solution"): 450 result.primal_solution = parse_primal_solution(proto.primal_solution, mod) 451 if proto.HasField("dual_solution"): 452 result.dual_solution = parse_dual_solution(proto.dual_solution, mod) 453 result.basis = parse_basis(proto.basis, mod) if proto.HasField("basis") else None 454 return result
25@enum.unique 26class BasisStatus(enum.Enum): 27 """Status of a variable/constraint in a LP basis. 28 29 Attributes: 30 FREE: The variable/constraint is free (it has no finite bounds). 31 AT_LOWER_BOUND: The variable/constraint is at its lower bound (which must be 32 finite). 33 AT_UPPER_BOUND: The variable/constraint is at its upper bound (which must be 34 finite). 35 FIXED_VALUE: The variable/constraint has identical finite lower and upper 36 bounds. 37 BASIC: The variable/constraint is basic. 38 """ 39 40 FREE = solution_pb2.BASIS_STATUS_FREE 41 AT_LOWER_BOUND = solution_pb2.BASIS_STATUS_AT_LOWER_BOUND 42 AT_UPPER_BOUND = solution_pb2.BASIS_STATUS_AT_UPPER_BOUND 43 FIXED_VALUE = solution_pb2.BASIS_STATUS_FIXED_VALUE 44 BASIC = solution_pb2.BASIS_STATUS_BASIC
Status of a variable/constraint in a LP basis.
Attributes:
- FREE: The variable/constraint is free (it has no finite bounds).
- AT_LOWER_BOUND: The variable/constraint is at its lower bound (which must be finite).
- AT_UPPER_BOUND: The variable/constraint is at its upper bound (which must be finite).
- FIXED_VALUE: The variable/constraint has identical finite lower and upper bounds.
- BASIC: The variable/constraint is basic.
47@enum.unique 48class SolutionStatus(enum.Enum): 49 """Feasibility of a primal or dual solution as claimed by the solver. 50 51 Attributes: 52 UNDETERMINED: Solver does not claim a feasibility status. 53 FEASIBLE: Solver claims the solution is feasible. 54 INFEASIBLE: Solver claims the solution is infeasible. 55 """ 56 57 UNDETERMINED = solution_pb2.SOLUTION_STATUS_UNDETERMINED 58 FEASIBLE = solution_pb2.SOLUTION_STATUS_FEASIBLE 59 INFEASIBLE = solution_pb2.SOLUTION_STATUS_INFEASIBLE
Feasibility of a primal or dual solution as claimed by the solver.
Attributes:
- UNDETERMINED: Solver does not claim a feasibility status.
- FEASIBLE: Solver claims the solution is feasible.
- INFEASIBLE: Solver claims the solution is infeasible.
62def parse_optional_solution_status( 63 proto: solution_pb2.SolutionStatusProto, 64) -> Optional[SolutionStatus]: 65 """Converts a proto SolutionStatus to an optional Python SolutionStatus.""" 66 return ( 67 None 68 if proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED 69 else SolutionStatus(proto) 70 )
Converts a proto SolutionStatus to an optional Python SolutionStatus.
73def optional_solution_status_to_proto( 74 status: Optional[SolutionStatus], 75) -> solution_pb2.SolutionStatusProto: 76 """Converts an optional Python SolutionStatus to a proto SolutionStatus.""" 77 return solution_pb2.SOLUTION_STATUS_UNSPECIFIED if status is None else status.value
Converts an optional Python SolutionStatus to a proto SolutionStatus.
80@dataclasses.dataclass 81class PrimalSolution: 82 """A solution to the optimization problem in a Model. 83 84 E.g. consider a simple linear program: 85 min c * x 86 s.t. A * x >= b 87 x >= 0. 88 A primal solution is assignment values to x. It is feasible if it satisfies 89 A * x >= b and x >= 0 from above. In the class PrimalSolution variable_values 90 is x and objective_value is c * x. 91 92 For the general case of a MathOpt optimization model, see go/mathopt-solutions 93 for details. 94 95 Attributes: 96 variable_values: The value assigned for each Variable in the model. 97 objective_value: The value of the objective value at this solution. This 98 value may not be always populated. 99 feasibility_status: The feasibility of the solution as claimed by the 100 solver. 101 """ 102 103 variable_values: Dict[model.Variable, float] = dataclasses.field( 104 default_factory=dict 105 ) 106 objective_value: float = 0.0 107 feasibility_status: SolutionStatus = SolutionStatus.UNDETERMINED 108 109 def to_proto(self) -> solution_pb2.PrimalSolutionProto: 110 """Returns an equivalent proto for a primal solution.""" 111 return solution_pb2.PrimalSolutionProto( 112 variable_values=sparse_containers.to_sparse_double_vector_proto( 113 self.variable_values 114 ), 115 objective_value=self.objective_value, 116 feasibility_status=self.feasibility_status.value, 117 )
A solution to the optimization problem in a Model.
E.g. consider a simple linear program: min c * x s.t. A * x >= b x >= 0. A primal solution is assignment values to x. It is feasible if it satisfies A * x >= b and x >= 0 from above. In the class PrimalSolution variable_values is x and objective_value is c * x.
For the general case of a MathOpt optimization model, see go/mathopt-solutions for details.
Attributes:
- variable_values: The value assigned for each Variable in the model.
- objective_value: The value of the objective value at this solution. This value may not be always populated.
- feasibility_status: The feasibility of the solution as claimed by the solver.
109 def to_proto(self) -> solution_pb2.PrimalSolutionProto: 110 """Returns an equivalent proto for a primal solution.""" 111 return solution_pb2.PrimalSolutionProto( 112 variable_values=sparse_containers.to_sparse_double_vector_proto( 113 self.variable_values 114 ), 115 objective_value=self.objective_value, 116 feasibility_status=self.feasibility_status.value, 117 )
Returns an equivalent proto for a primal solution.
120def parse_primal_solution( 121 proto: solution_pb2.PrimalSolutionProto, mod: model.Model 122) -> PrimalSolution: 123 """Returns an equivalent PrimalSolution from the input proto.""" 124 result = PrimalSolution() 125 result.objective_value = proto.objective_value 126 result.variable_values = sparse_containers.parse_variable_map( 127 proto.variable_values, mod 128 ) 129 status_proto = proto.feasibility_status 130 if status_proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED: 131 raise ValueError("Primal solution feasibility status should not be UNSPECIFIED") 132 result.feasibility_status = SolutionStatus(status_proto) 133 return result
Returns an equivalent PrimalSolution from the input proto.
136@dataclasses.dataclass 137class PrimalRay: 138 """A direction of unbounded objective improvement in an optimization Model. 139 140 Equivalently, a certificate of infeasibility for the dual of the optimization 141 problem. 142 143 E.g. consider a simple linear program: 144 min c * x 145 s.t. A * x >= b 146 x >= 0. 147 A primal ray is an x that satisfies: 148 c * x < 0 149 A * x >= 0 150 x >= 0. 151 Observe that given a feasible solution, any positive multiple of the primal 152 ray plus that solution is still feasible, and gives a better objective 153 value. A primal ray also proves the dual optimization problem infeasible. 154 155 In the class PrimalRay, variable_values is this x. 156 157 For the general case of a MathOpt optimization model, see 158 go/mathopt-solutions for details. 159 160 Attributes: 161 variable_values: The value assigned for each Variable in the model. 162 """ 163 164 variable_values: Dict[model.Variable, float] = dataclasses.field( 165 default_factory=dict 166 ) 167 168 def to_proto(self) -> solution_pb2.PrimalRayProto: 169 """Returns an equivalent proto to this PrimalRay.""" 170 return solution_pb2.PrimalRayProto( 171 variable_values=sparse_containers.to_sparse_double_vector_proto( 172 self.variable_values 173 ) 174 )
A direction of unbounded objective improvement in an optimization Model.
Equivalently, a certificate of infeasibility for the dual of the optimization problem.
E.g. consider a simple linear program: min c * x s.t. A * x >= b x >= 0.
A primal ray is an x that satisfies:
c * x < 0 A * x >= 0 x >= 0.
Observe that given a feasible solution, any positive multiple of the primal ray plus that solution is still feasible, and gives a better objective value. A primal ray also proves the dual optimization problem infeasible.
In the class PrimalRay, variable_values is this x.
For the general case of a MathOpt optimization model, see go/mathopt-solutions for details.
Attributes:
- variable_values: The value assigned for each Variable in the model.
168 def to_proto(self) -> solution_pb2.PrimalRayProto: 169 """Returns an equivalent proto to this PrimalRay.""" 170 return solution_pb2.PrimalRayProto( 171 variable_values=sparse_containers.to_sparse_double_vector_proto( 172 self.variable_values 173 ) 174 )
Returns an equivalent proto to this PrimalRay.
177def parse_primal_ray(proto: solution_pb2.PrimalRayProto, mod: model.Model) -> PrimalRay: 178 """Returns an equivalent PrimalRay from the input proto.""" 179 result = PrimalRay() 180 result.variable_values = sparse_containers.parse_variable_map( 181 proto.variable_values, mod 182 ) 183 return result
Returns an equivalent PrimalRay from the input proto.
186@dataclasses.dataclass 187class DualSolution: 188 """A solution to the dual of the optimization problem given by a Model. 189 190 E.g. consider the primal dual pair linear program pair: 191 (Primal) (Dual) 192 min c * x max b * y 193 s.t. A * x >= b s.t. y * A + r = c 194 x >= 0 y, r >= 0. 195 The dual solution is the pair (y, r). It is feasible if it satisfies the 196 constraints from (Dual) above. 197 198 Below, y is dual_values, r is reduced_costs, and b * y is objective_value. 199 200 For the general case, see go/mathopt-solutions and go/mathopt-dual (and note 201 that the dual objective depends on r in the general case). 202 203 Attributes: 204 dual_values: The value assigned for each LinearConstraint in the model. 205 reduced_costs: The value assigned for each Variable in the model. 206 objective_value: The value of the dual objective value at this solution. 207 This value may not be always populated. 208 feasibility_status: The feasibility of the solution as claimed by the 209 solver. 210 """ 211 212 dual_values: Dict[model.LinearConstraint, float] = dataclasses.field( 213 default_factory=dict 214 ) 215 reduced_costs: Dict[model.Variable, float] = dataclasses.field(default_factory=dict) 216 objective_value: Optional[float] = None 217 feasibility_status: SolutionStatus = SolutionStatus.UNDETERMINED 218 219 def to_proto(self) -> solution_pb2.DualSolutionProto: 220 """Returns an equivalent proto for a dual solution.""" 221 return solution_pb2.DualSolutionProto( 222 dual_values=sparse_containers.to_sparse_double_vector_proto( 223 self.dual_values 224 ), 225 reduced_costs=sparse_containers.to_sparse_double_vector_proto( 226 self.reduced_costs 227 ), 228 objective_value=self.objective_value, 229 feasibility_status=self.feasibility_status.value, 230 )
A solution to the dual of the optimization problem given by a Model.
E.g. consider the primal dual pair linear program pair: (Primal) (Dual) min c * x max b * y s.t. A * x >= b s.t. y * A + r = c x >= 0 y, r >= 0. The dual solution is the pair (y, r). It is feasible if it satisfies the constraints from (Dual) above.
Below, y is dual_values, r is reduced_costs, and b * y is objective_value.
For the general case, see go/mathopt-solutions and go/mathopt-dual (and note that the dual objective depends on r in the general case).
Attributes:
- dual_values: The value assigned for each LinearConstraint in the model.
- reduced_costs: The value assigned for each Variable in the model.
- objective_value: The value of the dual objective value at this solution. This value may not be always populated.
- feasibility_status: The feasibility of the solution as claimed by the solver.
219 def to_proto(self) -> solution_pb2.DualSolutionProto: 220 """Returns an equivalent proto for a dual solution.""" 221 return solution_pb2.DualSolutionProto( 222 dual_values=sparse_containers.to_sparse_double_vector_proto( 223 self.dual_values 224 ), 225 reduced_costs=sparse_containers.to_sparse_double_vector_proto( 226 self.reduced_costs 227 ), 228 objective_value=self.objective_value, 229 feasibility_status=self.feasibility_status.value, 230 )
Returns an equivalent proto for a dual solution.
233def parse_dual_solution( 234 proto: solution_pb2.DualSolutionProto, mod: model.Model 235) -> DualSolution: 236 """Returns an equivalent DualSolution from the input proto.""" 237 result = DualSolution() 238 result.objective_value = ( 239 proto.objective_value if proto.HasField("objective_value") else None 240 ) 241 result.dual_values = sparse_containers.parse_linear_constraint_map( 242 proto.dual_values, mod 243 ) 244 result.reduced_costs = sparse_containers.parse_variable_map( 245 proto.reduced_costs, mod 246 ) 247 status_proto = proto.feasibility_status 248 if status_proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED: 249 raise ValueError("Dual solution feasibility status should not be UNSPECIFIED") 250 result.feasibility_status = SolutionStatus(status_proto) 251 return result
Returns an equivalent DualSolution from the input proto.
254@dataclasses.dataclass 255class DualRay: 256 """A direction of unbounded objective improvement in an optimization Model. 257 258 A direction of unbounded improvement to the dual of an optimization, 259 problem; equivalently, a certificate of primal infeasibility. 260 261 E.g. consider the primal dual pair linear program pair: 262 (Primal) (Dual) 263 min c * x max b * y 264 s.t. A * x >= b s.t. y * A + r = c 265 x >= 0 y, r >= 0. 266 267 The dual ray is the pair (y, r) satisfying: 268 b * y > 0 269 y * A + r = 0 270 y, r >= 0. 271 Observe that adding a positive multiple of (y, r) to dual feasible solution 272 maintains dual feasibility and improves the objective (proving the dual is 273 unbounded). The dual ray also proves the primal problem is infeasible. 274 275 In the class DualRay below, y is dual_values and r is reduced_costs. 276 277 For the general case, see go/mathopt-solutions and go/mathopt-dual (and note 278 that the dual objective depends on r in the general case). 279 280 Attributes: 281 dual_values: The value assigned for each LinearConstraint in the model. 282 reduced_costs: The value assigned for each Variable in the model. 283 """ 284 285 dual_values: Dict[model.LinearConstraint, float] = dataclasses.field( 286 default_factory=dict 287 ) 288 reduced_costs: Dict[model.Variable, float] = dataclasses.field(default_factory=dict) 289 290 def to_proto(self) -> solution_pb2.DualRayProto: 291 """Returns an equivalent proto to this PrimalRay.""" 292 return solution_pb2.DualRayProto( 293 dual_values=sparse_containers.to_sparse_double_vector_proto( 294 self.dual_values 295 ), 296 reduced_costs=sparse_containers.to_sparse_double_vector_proto( 297 self.reduced_costs 298 ), 299 )
A direction of unbounded objective improvement in an optimization Model.
A direction of unbounded improvement to the dual of an optimization, problem; equivalently, a certificate of primal infeasibility.
E.g. consider the primal dual pair linear program pair: (Primal) (Dual) min c * x max b * y s.t. A * x >= b s.t. y * A + r = c x >= 0 y, r >= 0.
The dual ray is the pair (y, r) satisfying: b * y > 0 y * A + r = 0 y, r >= 0. Observe that adding a positive multiple of (y, r) to dual feasible solution maintains dual feasibility and improves the objective (proving the dual is unbounded). The dual ray also proves the primal problem is infeasible.
In the class DualRay below, y is dual_values and r is reduced_costs.
For the general case, see go/mathopt-solutions and go/mathopt-dual (and note that the dual objective depends on r in the general case).
Attributes:
- dual_values: The value assigned for each LinearConstraint in the model.
- reduced_costs: The value assigned for each Variable in the model.
290 def to_proto(self) -> solution_pb2.DualRayProto: 291 """Returns an equivalent proto to this PrimalRay.""" 292 return solution_pb2.DualRayProto( 293 dual_values=sparse_containers.to_sparse_double_vector_proto( 294 self.dual_values 295 ), 296 reduced_costs=sparse_containers.to_sparse_double_vector_proto( 297 self.reduced_costs 298 ), 299 )
Returns an equivalent proto to this PrimalRay.
302def parse_dual_ray(proto: solution_pb2.DualRayProto, mod: model.Model) -> DualRay: 303 """Returns an equivalent DualRay from the input proto.""" 304 result = DualRay() 305 result.dual_values = sparse_containers.parse_linear_constraint_map( 306 proto.dual_values, mod 307 ) 308 result.reduced_costs = sparse_containers.parse_variable_map( 309 proto.reduced_costs, mod 310 ) 311 return result
Returns an equivalent DualRay from the input proto.
314@dataclasses.dataclass 315class Basis: 316 """A combinatorial characterization for a solution to a linear program. 317 318 The simplex method for solving linear programs always returns a "basic 319 feasible solution" which can be described combinatorially as a Basis. A basis 320 assigns a BasisStatus for every variable and linear constraint. 321 322 E.g. consider a standard form LP: 323 min c * x 324 s.t. A * x = b 325 x >= 0 326 that has more variables than constraints and with full row rank A. 327 328 Let n be the number of variables and m the number of linear constraints. A 329 valid basis for this problem can be constructed as follows: 330 * All constraints will have basis status FIXED. 331 * Pick m variables such that the columns of A are linearly independent and 332 assign the status BASIC. 333 * Assign the status AT_LOWER for the remaining n - m variables. 334 335 The basic solution for this basis is the unique solution of A * x = b that has 336 all variables with status AT_LOWER fixed to their lower bounds (all zero). The 337 resulting solution is called a basic feasible solution if it also satisfies 338 x >= 0. 339 340 See go/mathopt-basis for treatment of the general case and an explanation of 341 how a dual solution is determined for a basis. 342 343 Attributes: 344 variable_status: The basis status for each variable in the model. 345 constraint_status: The basis status for each linear constraint in the model. 346 basic_dual_feasibility: This is an advanced feature used by MathOpt to 347 characterize feasibility of suboptimal LP solutions (optimal solutions 348 will always have status SolutionStatus.FEASIBLE). For single-sided LPs it 349 should be equal to the feasibility status of the associated dual solution. 350 For two-sided LPs it may be different in some edge cases (e.g. incomplete 351 solves with primal simplex). For more details see 352 go/mathopt-basis-advanced#dualfeasibility. If you are providing a starting 353 basis via ModelSolveParameters.initial_basis, this value is ignored and 354 can be None. It is only relevant for the basis returned by Solution.basis, 355 and it is never None when returned from solve(). This is an advanced 356 status. For single-sided LPs it should be equal to the feasibility status 357 of the associated dual solution. For two-sided LPs it may be different in 358 some edge cases (e.g. incomplete solves with primal simplex). For more 359 details see go/mathopt-basis-advanced#dualfeasibility. 360 """ 361 362 variable_status: Dict[model.Variable, BasisStatus] = dataclasses.field( 363 default_factory=dict 364 ) 365 constraint_status: Dict[model.LinearConstraint, BasisStatus] = dataclasses.field( 366 default_factory=dict 367 ) 368 basic_dual_feasibility: Optional[SolutionStatus] = None 369 370 def to_proto(self) -> solution_pb2.BasisProto: 371 """Returns an equivalent proto for the basis.""" 372 return solution_pb2.BasisProto( 373 variable_status=_to_sparse_basis_status_vector_proto(self.variable_status), 374 constraint_status=_to_sparse_basis_status_vector_proto( 375 self.constraint_status 376 ), 377 basic_dual_feasibility=optional_solution_status_to_proto( 378 self.basic_dual_feasibility 379 ), 380 )
A combinatorial characterization for a solution to a linear program.
The simplex method for solving linear programs always returns a "basic feasible solution" which can be described combinatorially as a Basis. A basis assigns a BasisStatus for every variable and linear constraint.
E.g. consider a standard form LP: min c * x s.t. A * x = b x >= 0 that has more variables than constraints and with full row rank A.
Let n be the number of variables and m the number of linear constraints. A valid basis for this problem can be constructed as follows:
- All constraints will have basis status FIXED.
- Pick m variables such that the columns of A are linearly independent and assign the status BASIC.
- Assign the status AT_LOWER for the remaining n - m variables.
The basic solution for this basis is the unique solution of A * x = b that has all variables with status AT_LOWER fixed to their lower bounds (all zero). The resulting solution is called a basic feasible solution if it also satisfies x >= 0.
See go/mathopt-basis for treatment of the general case and an explanation of how a dual solution is determined for a basis.
Attributes:
- variable_status: The basis status for each variable in the model.
- constraint_status: The basis status for each linear constraint in the model.
- basic_dual_feasibility: This is an advanced feature used by MathOpt to characterize feasibility of suboptimal LP solutions (optimal solutions will always have status SolutionStatus.FEASIBLE). For single-sided LPs it should be equal to the feasibility status of the associated dual solution. For two-sided LPs it may be different in some edge cases (e.g. incomplete solves with primal simplex). For more details see go/mathopt-basis-advanced#dualfeasibility. If you are providing a starting basis via ModelSolveParameters.initial_basis, this value is ignored and can be None. It is only relevant for the basis returned by Solution.basis, and it is never None when returned from solve(). This is an advanced status. For single-sided LPs it should be equal to the feasibility status of the associated dual solution. For two-sided LPs it may be different in some edge cases (e.g. incomplete solves with primal simplex). For more details see go/mathopt-basis-advanced#dualfeasibility.
370 def to_proto(self) -> solution_pb2.BasisProto: 371 """Returns an equivalent proto for the basis.""" 372 return solution_pb2.BasisProto( 373 variable_status=_to_sparse_basis_status_vector_proto(self.variable_status), 374 constraint_status=_to_sparse_basis_status_vector_proto( 375 self.constraint_status 376 ), 377 basic_dual_feasibility=optional_solution_status_to_proto( 378 self.basic_dual_feasibility 379 ), 380 )
Returns an equivalent proto for the basis.
383def parse_basis(proto: solution_pb2.BasisProto, mod: model.Model) -> Basis: 384 """Returns an equivalent Basis to the input proto.""" 385 result = Basis() 386 for index, vid in enumerate(proto.variable_status.ids): 387 status_proto = proto.variable_status.values[index] 388 if status_proto == solution_pb2.BASIS_STATUS_UNSPECIFIED: 389 raise ValueError("Variable basis status should not be UNSPECIFIED") 390 result.variable_status[mod.get_variable(vid)] = BasisStatus(status_proto) 391 for index, cid in enumerate(proto.constraint_status.ids): 392 status_proto = proto.constraint_status.values[index] 393 if status_proto == solution_pb2.BASIS_STATUS_UNSPECIFIED: 394 raise ValueError("Constraint basis status should not be UNSPECIFIED") 395 result.constraint_status[mod.get_linear_constraint(cid)] = BasisStatus( 396 status_proto 397 ) 398 result.basic_dual_feasibility = parse_optional_solution_status( 399 proto.basic_dual_feasibility 400 ) 401 return result
Returns an equivalent Basis to the input proto.
422@dataclasses.dataclass 423class Solution: 424 """A solution to the optimization problem in a Model.""" 425 426 primal_solution: Optional[PrimalSolution] = None 427 dual_solution: Optional[DualSolution] = None 428 basis: Optional[Basis] = None 429 430 def to_proto(self) -> solution_pb2.SolutionProto: 431 """Returns an equivalent proto for a solution.""" 432 return solution_pb2.SolutionProto( 433 primal_solution=( 434 self.primal_solution.to_proto() 435 if self.primal_solution is not None 436 else None 437 ), 438 dual_solution=( 439 self.dual_solution.to_proto() 440 if self.dual_solution is not None 441 else None 442 ), 443 basis=self.basis.to_proto() if self.basis is not None else None, 444 )
A solution to the optimization problem in a Model.
430 def to_proto(self) -> solution_pb2.SolutionProto: 431 """Returns an equivalent proto for a solution.""" 432 return solution_pb2.SolutionProto( 433 primal_solution=( 434 self.primal_solution.to_proto() 435 if self.primal_solution is not None 436 else None 437 ), 438 dual_solution=( 439 self.dual_solution.to_proto() 440 if self.dual_solution is not None 441 else None 442 ), 443 basis=self.basis.to_proto() if self.basis is not None else None, 444 )
Returns an equivalent proto for a solution.
447def parse_solution(proto: solution_pb2.SolutionProto, mod: model.Model) -> Solution: 448 """Returns a Solution equivalent to the input proto.""" 449 result = Solution() 450 if proto.HasField("primal_solution"): 451 result.primal_solution = parse_primal_solution(proto.primal_solution, mod) 452 if proto.HasField("dual_solution"): 453 result.dual_solution = parse_dual_solution(proto.dual_solution, mod) 454 result.basis = parse_basis(proto.basis, mod) if proto.HasField("basis") else None 455 return result
Returns a Solution equivalent to the input proto.