ortools.math_opt.python.solution
The solution to an optimization problem defined by Model in model.py.
1# Copyright 2010-2024 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 168def parse_primal_ray(proto: solution_pb2.PrimalRayProto, mod: model.Model) -> PrimalRay: 169 """Returns an equivalent PrimalRay from the input proto.""" 170 result = PrimalRay() 171 result.variable_values = sparse_containers.parse_variable_map( 172 proto.variable_values, mod 173 ) 174 return result 175 176 177@dataclasses.dataclass 178class DualSolution: 179 """A solution to the dual of the optimization problem given by a Model. 180 181 E.g. consider the primal dual pair linear program pair: 182 (Primal) (Dual) 183 min c * x max b * y 184 s.t. A * x >= b s.t. y * A + r = c 185 x >= 0 y, r >= 0. 186 The dual solution is the pair (y, r). It is feasible if it satisfies the 187 constraints from (Dual) above. 188 189 Below, y is dual_values, r is reduced_costs, and b * y is objective_value. 190 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). 193 194 Attributes: 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 200 solver. 201 """ 202 203 dual_values: Dict[model.LinearConstraint, float] = dataclasses.field( 204 default_factory=dict 205 ) 206 reduced_costs: Dict[model.Variable, float] = dataclasses.field(default_factory=dict) 207 objective_value: Optional[float] = None 208 feasibility_status: SolutionStatus = SolutionStatus.UNDETERMINED 209 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( 214 self.dual_values 215 ), 216 reduced_costs=sparse_containers.to_sparse_double_vector_proto( 217 self.reduced_costs 218 ), 219 objective_value=self.objective_value, 220 feasibility_status=self.feasibility_status.value, 221 ) 222 223 224def parse_dual_solution( 225 proto: solution_pb2.DualSolutionProto, mod: model.Model 226) -> DualSolution: 227 """Returns an equivalent DualSolution from the input proto.""" 228 result = DualSolution() 229 result.objective_value = ( 230 proto.objective_value if proto.HasField("objective_value") else None 231 ) 232 result.dual_values = sparse_containers.parse_linear_constraint_map( 233 proto.dual_values, mod 234 ) 235 result.reduced_costs = sparse_containers.parse_variable_map( 236 proto.reduced_costs, mod 237 ) 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") 241 result.feasibility_status = SolutionStatus(status_proto) 242 return result 243 244 245@dataclasses.dataclass 246class DualRay: 247 """A direction of unbounded objective improvement in an optimization Model. 248 249 A direction of unbounded improvement to the dual of an optimization, 250 problem; equivalently, a certificate of primal infeasibility. 251 252 E.g. consider the primal dual pair linear program pair: 253 (Primal) (Dual) 254 min c * x max b * y 255 s.t. A * x >= b s.t. y * A + r = c 256 x >= 0 y, r >= 0. 257 258 The dual ray is the pair (y, r) satisfying: 259 b * y > 0 260 y * A + r = 0 261 y, r >= 0. 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. 265 266 In the class DualRay below, y is dual_values and r is reduced_costs. 267 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). 270 271 Attributes: 272 dual_values: The value assigned for each LinearConstraint in the model. 273 reduced_costs: The value assigned for each Variable in the model. 274 """ 275 276 dual_values: Dict[model.LinearConstraint, float] = dataclasses.field( 277 default_factory=dict 278 ) 279 reduced_costs: Dict[model.Variable, float] = dataclasses.field(default_factory=dict) 280 281 282def parse_dual_ray(proto: solution_pb2.DualRayProto, mod: model.Model) -> DualRay: 283 """Returns an equivalent DualRay from the input proto.""" 284 result = DualRay() 285 result.dual_values = sparse_containers.parse_linear_constraint_map( 286 proto.dual_values, mod 287 ) 288 result.reduced_costs = sparse_containers.parse_variable_map( 289 proto.reduced_costs, mod 290 ) 291 return result 292 293 294@dataclasses.dataclass 295class Basis: 296 """A combinatorial characterization for a solution to a linear program. 297 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. 301 302 E.g. consider a standard form LP: 303 min c * x 304 s.t. A * x = b 305 x >= 0 306 that has more variables than constraints and with full row rank A. 307 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. 314 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 318 x >= 0. 319 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. 322 323 Attributes: 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. 340 """ 341 342 variable_status: Dict[model.Variable, BasisStatus] = dataclasses.field( 343 default_factory=dict 344 ) 345 constraint_status: Dict[model.LinearConstraint, BasisStatus] = dataclasses.field( 346 default_factory=dict 347 ) 348 basic_dual_feasibility: Optional[SolutionStatus] = None 349 350 def to_proto(self) -> solution_pb2.BasisProto: 351 """Returns an equivalent proto for the basis.""" 352 return solution_pb2.BasisProto( 353 variable_status=_to_sparse_basis_status_vector_proto(self.variable_status), 354 constraint_status=_to_sparse_basis_status_vector_proto( 355 self.constraint_status 356 ), 357 basic_dual_feasibility=optional_solution_status_to_proto( 358 self.basic_dual_feasibility 359 ), 360 ) 361 362 363def parse_basis(proto: solution_pb2.BasisProto, mod: model.Model) -> Basis: 364 """Returns an equivalent Basis to the input proto.""" 365 result = Basis() 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( 376 status_proto 377 ) 378 result.basic_dual_feasibility = parse_optional_solution_status( 379 proto.basic_dual_feasibility 380 ) 381 return result 382 383 384T = TypeVar("T", model.Variable, model.LinearConstraint) 385 386 387def _to_sparse_basis_status_vector_proto( 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() 392 if terms: 393 id_and_status = sorted( 394 (key.id, status.value) for (key, status) in terms.items() 395 ) 396 ids, values = zip(*id_and_status) 397 result.ids[:] = ids 398 result.values[:] = values 399 return result 400 401 402@dataclasses.dataclass 403class Solution: 404 """A solution to the optimization problem in a Model.""" 405 406 primal_solution: Optional[PrimalSolution] = None 407 dual_solution: Optional[DualSolution] = None 408 basis: Optional[Basis] = None 409 410 def to_proto(self) -> solution_pb2.SolutionProto: 411 """Returns an equivalent proto for a solution.""" 412 return solution_pb2.SolutionProto( 413 primal_solution=( 414 self.primal_solution.to_proto() 415 if self.primal_solution is not None 416 else None 417 ), 418 dual_solution=( 419 self.dual_solution.to_proto() 420 if self.dual_solution is not None 421 else None 422 ), 423 basis=self.basis.to_proto() if self.basis is not None else None, 424 ) 425 426 427def parse_solution(proto: solution_pb2.SolutionProto, mod: model.Model) -> Solution: 428 """Returns a Solution equivalent to the input proto.""" 429 result = Solution() 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 435 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.
Inherited Members
- enum.Enum
- name
- value
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.
Inherited Members
- enum.Enum
- name
- value
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 )
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.
169def parse_primal_ray(proto: solution_pb2.PrimalRayProto, mod: model.Model) -> PrimalRay: 170 """Returns an equivalent PrimalRay from the input proto.""" 171 result = PrimalRay() 172 result.variable_values = sparse_containers.parse_variable_map( 173 proto.variable_values, mod 174 ) 175 return result
Returns an equivalent PrimalRay from the input proto.
178@dataclasses.dataclass 179class DualSolution: 180 """A solution to the dual of the optimization problem given by a Model. 181 182 E.g. consider the primal dual pair linear program pair: 183 (Primal) (Dual) 184 min c * x max b * y 185 s.t. A * x >= b s.t. y * A + r = c 186 x >= 0 y, r >= 0. 187 The dual solution is the pair (y, r). It is feasible if it satisfies the 188 constraints from (Dual) above. 189 190 Below, y is dual_values, r is reduced_costs, and b * y is objective_value. 191 192 For the general case, see go/mathopt-solutions and go/mathopt-dual (and note 193 that the dual objective depends on r in the general case). 194 195 Attributes: 196 dual_values: The value assigned for each LinearConstraint in the model. 197 reduced_costs: The value assigned for each Variable in the model. 198 objective_value: The value of the dual objective value at this solution. 199 This value may not be always populated. 200 feasibility_status: The feasibility of the solution as claimed by the 201 solver. 202 """ 203 204 dual_values: Dict[model.LinearConstraint, float] = dataclasses.field( 205 default_factory=dict 206 ) 207 reduced_costs: Dict[model.Variable, float] = dataclasses.field(default_factory=dict) 208 objective_value: Optional[float] = None 209 feasibility_status: SolutionStatus = SolutionStatus.UNDETERMINED 210 211 def to_proto(self) -> solution_pb2.DualSolutionProto: 212 """Returns an equivalent proto for a dual solution.""" 213 return solution_pb2.DualSolutionProto( 214 dual_values=sparse_containers.to_sparse_double_vector_proto( 215 self.dual_values 216 ), 217 reduced_costs=sparse_containers.to_sparse_double_vector_proto( 218 self.reduced_costs 219 ), 220 objective_value=self.objective_value, 221 feasibility_status=self.feasibility_status.value, 222 )
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.
211 def to_proto(self) -> solution_pb2.DualSolutionProto: 212 """Returns an equivalent proto for a dual solution.""" 213 return solution_pb2.DualSolutionProto( 214 dual_values=sparse_containers.to_sparse_double_vector_proto( 215 self.dual_values 216 ), 217 reduced_costs=sparse_containers.to_sparse_double_vector_proto( 218 self.reduced_costs 219 ), 220 objective_value=self.objective_value, 221 feasibility_status=self.feasibility_status.value, 222 )
Returns an equivalent proto for a dual solution.
225def parse_dual_solution( 226 proto: solution_pb2.DualSolutionProto, mod: model.Model 227) -> DualSolution: 228 """Returns an equivalent DualSolution from the input proto.""" 229 result = DualSolution() 230 result.objective_value = ( 231 proto.objective_value if proto.HasField("objective_value") else None 232 ) 233 result.dual_values = sparse_containers.parse_linear_constraint_map( 234 proto.dual_values, mod 235 ) 236 result.reduced_costs = sparse_containers.parse_variable_map( 237 proto.reduced_costs, mod 238 ) 239 status_proto = proto.feasibility_status 240 if status_proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED: 241 raise ValueError("Dual solution feasibility status should not be UNSPECIFIED") 242 result.feasibility_status = SolutionStatus(status_proto) 243 return result
Returns an equivalent DualSolution from the input proto.
246@dataclasses.dataclass 247class DualRay: 248 """A direction of unbounded objective improvement in an optimization Model. 249 250 A direction of unbounded improvement to the dual of an optimization, 251 problem; equivalently, a certificate of primal infeasibility. 252 253 E.g. consider the primal dual pair linear program pair: 254 (Primal) (Dual) 255 min c * x max b * y 256 s.t. A * x >= b s.t. y * A + r = c 257 x >= 0 y, r >= 0. 258 259 The dual ray is the pair (y, r) satisfying: 260 b * y > 0 261 y * A + r = 0 262 y, r >= 0. 263 Observe that adding a positive multiple of (y, r) to dual feasible solution 264 maintains dual feasibility and improves the objective (proving the dual is 265 unbounded). The dual ray also proves the primal problem is infeasible. 266 267 In the class DualRay below, y is dual_values and r is reduced_costs. 268 269 For the general case, see go/mathopt-solutions and go/mathopt-dual (and note 270 that the dual objective depends on r in the general case). 271 272 Attributes: 273 dual_values: The value assigned for each LinearConstraint in the model. 274 reduced_costs: The value assigned for each Variable in the model. 275 """ 276 277 dual_values: Dict[model.LinearConstraint, float] = dataclasses.field( 278 default_factory=dict 279 ) 280 reduced_costs: Dict[model.Variable, float] = dataclasses.field(default_factory=dict)
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.
283def parse_dual_ray(proto: solution_pb2.DualRayProto, mod: model.Model) -> DualRay: 284 """Returns an equivalent DualRay from the input proto.""" 285 result = DualRay() 286 result.dual_values = sparse_containers.parse_linear_constraint_map( 287 proto.dual_values, mod 288 ) 289 result.reduced_costs = sparse_containers.parse_variable_map( 290 proto.reduced_costs, mod 291 ) 292 return result
Returns an equivalent DualRay from the input proto.
295@dataclasses.dataclass 296class Basis: 297 """A combinatorial characterization for a solution to a linear program. 298 299 The simplex method for solving linear programs always returns a "basic 300 feasible solution" which can be described combinatorially as a Basis. A basis 301 assigns a BasisStatus for every variable and linear constraint. 302 303 E.g. consider a standard form LP: 304 min c * x 305 s.t. A * x = b 306 x >= 0 307 that has more variables than constraints and with full row rank A. 308 309 Let n be the number of variables and m the number of linear constraints. A 310 valid basis for this problem can be constructed as follows: 311 * All constraints will have basis status FIXED. 312 * Pick m variables such that the columns of A are linearly independent and 313 assign the status BASIC. 314 * Assign the status AT_LOWER for the remaining n - m variables. 315 316 The basic solution for this basis is the unique solution of A * x = b that has 317 all variables with status AT_LOWER fixed to their lower bounds (all zero). The 318 resulting solution is called a basic feasible solution if it also satisfies 319 x >= 0. 320 321 See go/mathopt-basis for treatment of the general case and an explanation of 322 how a dual solution is determined for a basis. 323 324 Attributes: 325 variable_status: The basis status for each variable in the model. 326 constraint_status: The basis status for each linear constraint in the model. 327 basic_dual_feasibility: This is an advanced feature used by MathOpt to 328 characterize feasibility of suboptimal LP solutions (optimal solutions 329 will always have status SolutionStatus.FEASIBLE). For single-sided LPs it 330 should be equal to the feasibility status of the associated dual solution. 331 For two-sided LPs it may be different in some edge cases (e.g. incomplete 332 solves with primal simplex). For more details see 333 go/mathopt-basis-advanced#dualfeasibility. If you are providing a starting 334 basis via ModelSolveParameters.initial_basis, this value is ignored and 335 can be None. It is only relevant for the basis returned by Solution.basis, 336 and it is never None when returned from solve(). This is an advanced 337 status. For single-sided LPs it should be equal to the feasibility status 338 of the associated dual solution. For two-sided LPs it may be different in 339 some edge cases (e.g. incomplete solves with primal simplex). For more 340 details see go/mathopt-basis-advanced#dualfeasibility. 341 """ 342 343 variable_status: Dict[model.Variable, BasisStatus] = dataclasses.field( 344 default_factory=dict 345 ) 346 constraint_status: Dict[model.LinearConstraint, BasisStatus] = dataclasses.field( 347 default_factory=dict 348 ) 349 basic_dual_feasibility: Optional[SolutionStatus] = None 350 351 def to_proto(self) -> solution_pb2.BasisProto: 352 """Returns an equivalent proto for the basis.""" 353 return solution_pb2.BasisProto( 354 variable_status=_to_sparse_basis_status_vector_proto(self.variable_status), 355 constraint_status=_to_sparse_basis_status_vector_proto( 356 self.constraint_status 357 ), 358 basic_dual_feasibility=optional_solution_status_to_proto( 359 self.basic_dual_feasibility 360 ), 361 )
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.
351 def to_proto(self) -> solution_pb2.BasisProto: 352 """Returns an equivalent proto for the basis.""" 353 return solution_pb2.BasisProto( 354 variable_status=_to_sparse_basis_status_vector_proto(self.variable_status), 355 constraint_status=_to_sparse_basis_status_vector_proto( 356 self.constraint_status 357 ), 358 basic_dual_feasibility=optional_solution_status_to_proto( 359 self.basic_dual_feasibility 360 ), 361 )
Returns an equivalent proto for the basis.
364def parse_basis(proto: solution_pb2.BasisProto, mod: model.Model) -> Basis: 365 """Returns an equivalent Basis to the input proto.""" 366 result = Basis() 367 for index, vid in enumerate(proto.variable_status.ids): 368 status_proto = proto.variable_status.values[index] 369 if status_proto == solution_pb2.BASIS_STATUS_UNSPECIFIED: 370 raise ValueError("Variable basis status should not be UNSPECIFIED") 371 result.variable_status[mod.get_variable(vid)] = BasisStatus(status_proto) 372 for index, cid in enumerate(proto.constraint_status.ids): 373 status_proto = proto.constraint_status.values[index] 374 if status_proto == solution_pb2.BASIS_STATUS_UNSPECIFIED: 375 raise ValueError("Constraint basis status should not be UNSPECIFIED") 376 result.constraint_status[mod.get_linear_constraint(cid)] = BasisStatus( 377 status_proto 378 ) 379 result.basic_dual_feasibility = parse_optional_solution_status( 380 proto.basic_dual_feasibility 381 ) 382 return result
Returns an equivalent Basis to the input proto.
403@dataclasses.dataclass 404class Solution: 405 """A solution to the optimization problem in a Model.""" 406 407 primal_solution: Optional[PrimalSolution] = None 408 dual_solution: Optional[DualSolution] = None 409 basis: Optional[Basis] = None 410 411 def to_proto(self) -> solution_pb2.SolutionProto: 412 """Returns an equivalent proto for a solution.""" 413 return solution_pb2.SolutionProto( 414 primal_solution=( 415 self.primal_solution.to_proto() 416 if self.primal_solution is not None 417 else None 418 ), 419 dual_solution=( 420 self.dual_solution.to_proto() 421 if self.dual_solution is not None 422 else None 423 ), 424 basis=self.basis.to_proto() if self.basis is not None else None, 425 )
A solution to the optimization problem in a Model.
411 def to_proto(self) -> solution_pb2.SolutionProto: 412 """Returns an equivalent proto for a solution.""" 413 return solution_pb2.SolutionProto( 414 primal_solution=( 415 self.primal_solution.to_proto() 416 if self.primal_solution is not None 417 else None 418 ), 419 dual_solution=( 420 self.dual_solution.to_proto() 421 if self.dual_solution is not None 422 else None 423 ), 424 basis=self.basis.to_proto() if self.basis is not None else None, 425 )
Returns an equivalent proto for a solution.
428def parse_solution(proto: solution_pb2.SolutionProto, mod: model.Model) -> Solution: 429 """Returns a Solution equivalent to the input proto.""" 430 result = Solution() 431 if proto.HasField("primal_solution"): 432 result.primal_solution = parse_primal_solution(proto.primal_solution, mod) 433 if proto.HasField("dual_solution"): 434 result.dual_solution = parse_dual_solution(proto.dual_solution, mod) 435 result.basis = parse_basis(proto.basis, mod) if proto.HasField("basis") else None 436 return result
Returns a Solution equivalent to the input proto.