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