ortools.math_opt.python.objectives
An Objective for a MathOpt optimization model.
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"""An Objective for a MathOpt optimization model.""" 15 16import abc 17from typing import Any, Iterator 18 19from ortools.math_opt.elemental.python import enums 20from ortools.math_opt.python import from_model 21from ortools.math_opt.python import variables 22from ortools.math_opt.python.elemental import elemental 23 24 25class Objective(from_model.FromModel, metaclass=abc.ABCMeta): 26 """The objective for an optimization model. 27 28 An objective is either of the form: 29 min o + sum_{i in I} c_i * x_i + sum_{i, j in I, i <= j} q_i,j * x_i * x_j 30 or 31 max o + sum_{i in I} c_i * x_i + sum_{(i, j) in Q} q_i,j * x_i * x_j 32 where x_i are the decision variables of the problem and where all pairs (i, j) 33 in Q satisfy i <= j. The values of o, c_i and q_i,j should be finite and not 34 NaN. 35 36 The objective can be configured as follows: 37 * offset: a float property, o above. Should be finite and not NaN. 38 * is_maximize: a bool property, if the objective is to maximize or minimize. 39 * set_linear_coefficient and get_linear_coefficient control the c_i * x_i 40 terms. The variables must be from the same model as this objective, and 41 the c_i must be finite and not NaN. The coefficient for any variable not 42 set is 0.0, and setting a coefficient to 0.0 removes it from I above. 43 * set_quadratic_coefficient and get_quadratic_coefficient control the 44 q_i,j * x_i * x_j terms. The variables must be from the same model as this 45 objective, and the q_i,j must be finite and not NaN. The coefficient for 46 any pair of variables not set is 0.0, and setting a coefficient to 0.0 47 removes the associated (i,j) from Q above. 48 49 Do not create an Objective directly, use Model.objective to access the 50 objective instead (or Model.add_auxiliary_objective()). Two Objective objects 51 can represent the same objective (for the same model). They will have the same 52 underlying Objective.elemental for storing the data. The Objective class is 53 simply a reference to an Elemental. 54 55 The objective is linear if only linear coefficients are set. This can be 56 useful to avoid solve-time errors with solvers that do not accept quadratic 57 objectives. To facilitate this linear objective guarantee we provide three 58 functions to add to the objective: 59 * add(), which accepts linear or quadratic expressions, 60 * add_quadratic(), which also accepts linear or quadratic expressions and 61 can be used to signal a quadratic objective is possible, and 62 * add_linear(), which only accepts linear expressions and can be used to 63 guarantee the objective remains linear. 64 65 For quadratic terms, the order that variables are provided does not matter, 66 we always canonicalize to first_var <= second_var. So if you set (x1, x2) to 7 67 then: 68 * getting (x2, x1) returns 7 69 * setting (x2, x1) to 10 overwrites the value of 7. 70 Likewise, when we return nonzero quadratic coefficients, we always use the 71 form first_var <= second_var. 72 73 Most problems have only a single objective, but hierarchical objectives are 74 supported (see Model.add_auxiliary_objective()). Note that quadratic Auxiliary 75 objectives are not supported. 76 """ 77 78 __slots__ = ("_elemental",) 79 80 def __init__(self, elem: elemental.Elemental) -> None: 81 """Do not invoke directly, prefer Model.objective.""" 82 self._elemental: elemental.Elemental = elem 83 84 @property 85 def elemental(self) -> elemental.Elemental: 86 """The underlying data structure for the model, for internal use only.""" 87 return self._elemental 88 89 @property 90 @abc.abstractmethod 91 def name(self) -> str: 92 """The immutable name of this objective, for display only.""" 93 94 @property 95 @abc.abstractmethod 96 def is_maximize(self) -> bool: 97 """If true, the direction is maximization, otherwise minimization.""" 98 99 @is_maximize.setter 100 @abc.abstractmethod 101 def is_maximize(self, is_maximize: bool) -> None: ... 102 103 @property 104 @abc.abstractmethod 105 def offset(self) -> float: 106 """A constant added to the objective.""" 107 108 @offset.setter 109 @abc.abstractmethod 110 def offset(self, value: float) -> None: ... 111 112 @property 113 @abc.abstractmethod 114 def priority(self) -> int: 115 """For hierarchical problems, determines the order to apply objectives. 116 117 The objectives are applied from lowest priority to highest. 118 119 The default priority for the primary objective is zero, and auxiliary 120 objectives must specific a priority at creation time. 121 122 Priority has no effect for problems with only one objective. 123 """ 124 125 @priority.setter 126 @abc.abstractmethod 127 def priority(self, value: int) -> None: ... 128 129 @abc.abstractmethod 130 def set_linear_coefficient(self, var: variables.Variable, coef: float) -> None: 131 """Sets the coefficient of `var` to `coef` in the objective.""" 132 133 @abc.abstractmethod 134 def get_linear_coefficient(self, var: variables.Variable) -> float: 135 """Returns the coefficinet of `var` (or zero if unset).""" 136 137 @abc.abstractmethod 138 def linear_terms(self) -> Iterator[variables.LinearTerm]: 139 """Yields variable coefficient pairs for variables with nonzero objective coefficient in undefined order.""" 140 141 @abc.abstractmethod 142 def set_quadratic_coefficient( 143 self, 144 first_variable: variables.Variable, 145 second_variable: variables.Variable, 146 coef: float, 147 ) -> None: 148 """Sets the coefficient for product of variables (see class description).""" 149 150 @abc.abstractmethod 151 def get_quadratic_coefficient( 152 self, 153 first_variable: variables.Variable, 154 second_variable: variables.Variable, 155 ) -> float: 156 """Gets the coefficient for product of variables (see class description).""" 157 158 @abc.abstractmethod 159 def quadratic_terms(self) -> Iterator[variables.QuadraticTerm]: 160 """Yields quadratic terms with nonzero objective coefficient in undefined order.""" 161 162 @abc.abstractmethod 163 def clear(self) -> None: 164 """Clears objective coefficients and offset. Does not change direction.""" 165 166 def as_linear_expression(self) -> variables.LinearExpression: 167 """Returns an equivalent LinearExpression, or errors if quadratic.""" 168 if any(self.quadratic_terms()): 169 raise TypeError("Cannot get a quadratic objective as a linear expression") 170 return variables.as_flat_linear_expression( 171 self.offset + variables.LinearSum(self.linear_terms()) 172 ) 173 174 def as_quadratic_expression(self) -> variables.QuadraticExpression: 175 """Returns an equivalent QuadraticExpression to this objetive.""" 176 return variables.as_flat_quadratic_expression( 177 self.offset 178 + variables.LinearSum(self.linear_terms()) 179 + variables.QuadraticSum(self.quadratic_terms()) 180 ) 181 182 def add(self, objective: variables.QuadraticTypes) -> None: 183 """Adds the provided expression `objective` to the objective function. 184 185 For a compile time guarantee that the objective remains linear, use 186 add_linear() instead. 187 188 Args: 189 objective: the expression to add to the objective function. 190 """ 191 if isinstance(objective, (variables.LinearBase, int, float)): 192 self.add_linear(objective) 193 elif isinstance(objective, variables.QuadraticBase): 194 self.add_quadratic(objective) 195 else: 196 raise TypeError( 197 "unsupported type in objective argument for " 198 f"Objective.add(): {type(objective).__name__!r}" 199 ) 200 201 def add_linear(self, objective: variables.LinearTypes) -> None: 202 """Adds the provided linear expression `objective` to the objective function.""" 203 if not isinstance(objective, (variables.LinearBase, int, float)): 204 raise TypeError( 205 "unsupported type in objective argument for " 206 f"Objective.add_linear(): {type(objective).__name__!r}" 207 ) 208 objective_expr = variables.as_flat_linear_expression(objective) 209 self.offset += objective_expr.offset 210 for var, coefficient in objective_expr.terms.items(): 211 self.set_linear_coefficient( 212 var, self.get_linear_coefficient(var) + coefficient 213 ) 214 215 def add_quadratic(self, objective: variables.QuadraticTypes) -> None: 216 """Adds the provided quadratic expression `objective` to the objective function.""" 217 if not isinstance( 218 objective, (variables.QuadraticBase, variables.LinearBase, int, float) 219 ): 220 raise TypeError( 221 "unsupported type in objective argument for " 222 f"Objective.add(): {type(objective).__name__!r}" 223 ) 224 objective_expr = variables.as_flat_quadratic_expression(objective) 225 self.offset += objective_expr.offset 226 for var, coefficient in objective_expr.linear_terms.items(): 227 self.set_linear_coefficient( 228 var, self.get_linear_coefficient(var) + coefficient 229 ) 230 for key, coefficient in objective_expr.quadratic_terms.items(): 231 self.set_quadratic_coefficient( 232 key.first_var, 233 key.second_var, 234 self.get_quadratic_coefficient(key.first_var, key.second_var) 235 + coefficient, 236 ) 237 238 def set_to_linear_expression(self, linear_expr: variables.LinearTypes) -> None: 239 """Sets the objective to optimize to `linear_expr`.""" 240 if not isinstance(linear_expr, (variables.LinearBase, int, float)): 241 raise TypeError( 242 "unsupported type in objective argument for " 243 f"set_to_linear_expression: {type(linear_expr).__name__!r}" 244 ) 245 self.clear() 246 objective_expr = variables.as_flat_linear_expression(linear_expr) 247 self.offset = objective_expr.offset 248 for var, coefficient in objective_expr.terms.items(): 249 self.set_linear_coefficient(var, coefficient) 250 251 def set_to_quadratic_expression( 252 self, quadratic_expr: variables.QuadraticTypes 253 ) -> None: 254 """Sets the objective to optimize the `quadratic_expr`.""" 255 if not isinstance( 256 quadratic_expr, 257 (variables.QuadraticBase, variables.LinearBase, int, float), 258 ): 259 raise TypeError( 260 "unsupported type in objective argument for " 261 f"set_to_quadratic_expression: {type(quadratic_expr).__name__!r}" 262 ) 263 self.clear() 264 objective_expr = variables.as_flat_quadratic_expression(quadratic_expr) 265 self.offset = objective_expr.offset 266 for var, coefficient in objective_expr.linear_terms.items(): 267 self.set_linear_coefficient(var, coefficient) 268 for quad_key, coefficient in objective_expr.quadratic_terms.items(): 269 self.set_quadratic_coefficient( 270 quad_key.first_var, quad_key.second_var, coefficient 271 ) 272 273 def set_to_expression(self, expr: variables.QuadraticTypes) -> None: 274 """Sets the objective to optimize the `expr`.""" 275 if isinstance(expr, (variables.LinearBase, int, float)): 276 self.set_to_linear_expression(expr) 277 elif isinstance(expr, variables.QuadraticBase): 278 self.set_to_quadratic_expression(expr) 279 else: 280 raise TypeError( 281 "unsupported type in objective argument for " 282 f"set_to_expression: {type(expr).__name__!r}" 283 ) 284 285 286class PrimaryObjective(Objective): 287 """The main objective, but users should program against Objective directly.""" 288 289 __slots__ = () 290 291 @property 292 def name(self) -> str: 293 return self._elemental.primary_objective_name 294 295 @property 296 def is_maximize(self) -> bool: 297 return self._elemental.get_attr(enums.BoolAttr0.MAXIMIZE, ()) 298 299 @is_maximize.setter 300 def is_maximize(self, is_maximize: bool) -> None: 301 self._elemental.set_attr(enums.BoolAttr0.MAXIMIZE, (), is_maximize) 302 303 @property 304 def offset(self) -> float: 305 return self._elemental.get_attr(enums.DoubleAttr0.OBJECTIVE_OFFSET, ()) 306 307 @offset.setter 308 def offset(self, value: float) -> None: 309 self._elemental.set_attr(enums.DoubleAttr0.OBJECTIVE_OFFSET, (), value) 310 311 @property 312 def priority(self) -> int: 313 return self._elemental.get_attr(enums.IntAttr0.OBJECTIVE_PRIORITY, ()) 314 315 @priority.setter 316 def priority(self, value: int) -> None: 317 self._elemental.set_attr(enums.IntAttr0.OBJECTIVE_PRIORITY, (), value) 318 319 def set_linear_coefficient(self, var: variables.Variable, coef: float) -> None: 320 from_model.model_is_same(self, var) 321 self._elemental.set_attr( 322 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT, (var.id,), coef 323 ) 324 325 def get_linear_coefficient(self, var: variables.Variable) -> float: 326 from_model.model_is_same(self, var) 327 return self._elemental.get_attr( 328 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT, (var.id,) 329 ) 330 331 def linear_terms(self) -> Iterator[variables.LinearTerm]: 332 keys = self._elemental.get_attr_non_defaults( 333 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT 334 ) 335 var_index = 0 336 coefs = self._elemental.get_attrs( 337 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT, keys 338 ) 339 for i in range(len(keys)): 340 yield variables.LinearTerm( 341 variable=variables.Variable(self._elemental, int(keys[i, var_index])), 342 coefficient=float(coefs[i]), 343 ) 344 345 def set_quadratic_coefficient( 346 self, 347 first_variable: variables.Variable, 348 second_variable: variables.Variable, 349 coef: float, 350 ) -> None: 351 from_model.model_is_same(self, first_variable) 352 from_model.model_is_same(self, second_variable) 353 self._elemental.set_attr( 354 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT, 355 (first_variable.id, second_variable.id), 356 coef, 357 ) 358 359 def get_quadratic_coefficient( 360 self, 361 first_variable: variables.Variable, 362 second_variable: variables.Variable, 363 ) -> float: 364 from_model.model_is_same(self, first_variable) 365 from_model.model_is_same(self, second_variable) 366 return self._elemental.get_attr( 367 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT, 368 (first_variable.id, second_variable.id), 369 ) 370 371 def quadratic_terms(self) -> Iterator[variables.QuadraticTerm]: 372 keys = self._elemental.get_attr_non_defaults( 373 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT 374 ) 375 coefs = self._elemental.get_attrs( 376 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT, keys 377 ) 378 for i in range(len(keys)): 379 yield variables.QuadraticTerm( 380 variables.QuadraticTermKey( 381 variables.Variable(self._elemental, int(keys[i, 0])), 382 variables.Variable(self._elemental, int(keys[i, 1])), 383 ), 384 coefficient=float(coefs[i]), 385 ) 386 387 def clear(self) -> None: 388 self._elemental.clear_attr(enums.DoubleAttr0.OBJECTIVE_OFFSET) 389 self._elemental.clear_attr(enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT) 390 self._elemental.clear_attr( 391 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT 392 ) 393 394 def __eq__(self, other: Any) -> bool: 395 if isinstance(other, PrimaryObjective): 396 return self._elemental is other._elemental 397 return False 398 399 def __hash__(self) -> int: 400 return hash(self._elemental) 401 402 403class AuxiliaryObjective(Objective): 404 """An additional objective that can be optimized after objectives.""" 405 406 __slots__ = ("_id",) 407 408 def __init__(self, elem: elemental.Elemental, obj_id: int) -> None: 409 """Internal only, prefer Model functions (add_auxiliary_objective() and get_auxiliary_objective()).""" 410 super().__init__(elem) 411 if not isinstance(obj_id, int): 412 raise TypeError( 413 f"obj_id type should be int, was: {type(obj_id).__name__!r}" 414 ) 415 self._id: int = obj_id 416 417 @property 418 def name(self) -> str: 419 return self._elemental.get_element_name( 420 enums.ElementType.AUXILIARY_OBJECTIVE, self._id 421 ) 422 423 @property 424 def id(self) -> int: 425 """Returns the id of this objective.""" 426 return self._id 427 428 @property 429 def is_maximize(self) -> bool: 430 return self._elemental.get_attr( 431 enums.BoolAttr1.AUXILIARY_OBJECTIVE_MAXIMIZE, (self._id,) 432 ) 433 434 @is_maximize.setter 435 def is_maximize(self, is_maximize: bool) -> None: 436 self._elemental.set_attr( 437 enums.BoolAttr1.AUXILIARY_OBJECTIVE_MAXIMIZE, 438 (self._id,), 439 is_maximize, 440 ) 441 442 @property 443 def offset(self) -> float: 444 return self._elemental.get_attr( 445 enums.DoubleAttr1.AUXILIARY_OBJECTIVE_OFFSET, (self._id,) 446 ) 447 448 @offset.setter 449 def offset(self, value: float) -> None: 450 self._elemental.set_attr( 451 enums.DoubleAttr1.AUXILIARY_OBJECTIVE_OFFSET, 452 (self._id,), 453 value, 454 ) 455 456 @property 457 def priority(self) -> int: 458 return self._elemental.get_attr( 459 enums.IntAttr1.AUXILIARY_OBJECTIVE_PRIORITY, (self._id,) 460 ) 461 462 @priority.setter 463 def priority(self, value: int) -> None: 464 self._elemental.set_attr( 465 enums.IntAttr1.AUXILIARY_OBJECTIVE_PRIORITY, 466 (self._id,), 467 value, 468 ) 469 470 def set_linear_coefficient(self, var: variables.Variable, coef: float) -> None: 471 from_model.model_is_same(self, var) 472 self._elemental.set_attr( 473 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT, 474 (self._id, var.id), 475 coef, 476 ) 477 478 def get_linear_coefficient(self, var: variables.Variable) -> float: 479 from_model.model_is_same(self, var) 480 return self._elemental.get_attr( 481 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT, 482 ( 483 self._id, 484 var.id, 485 ), 486 ) 487 488 def linear_terms(self) -> Iterator[variables.LinearTerm]: 489 keys = self._elemental.slice_attr( 490 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT, 491 0, 492 self._id, 493 ) 494 var_index = 1 495 coefs = self._elemental.get_attrs( 496 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT, keys 497 ) 498 for i in range(len(keys)): 499 yield variables.LinearTerm( 500 variable=variables.Variable(self._elemental, int(keys[i, var_index])), 501 coefficient=float(coefs[i]), 502 ) 503 504 def set_quadratic_coefficient( 505 self, 506 first_variable: variables.Variable, 507 second_variable: variables.Variable, 508 coef: float, 509 ) -> None: 510 raise ValueError("Quadratic auxiliary objectives are not supported.") 511 512 def get_quadratic_coefficient( 513 self, 514 first_variable: variables.Variable, 515 second_variable: variables.Variable, 516 ) -> float: 517 from_model.model_is_same(self, first_variable) 518 from_model.model_is_same(self, second_variable) 519 if not self._elemental.element_exists( 520 enums.ElementType.VARIABLE, first_variable.id 521 ): 522 raise ValueError(f"Variable {first_variable} does not exist") 523 if not self._elemental.element_exists( 524 enums.ElementType.VARIABLE, second_variable.id 525 ): 526 raise ValueError(f"Variable {second_variable} does not exist") 527 return 0.0 528 529 def quadratic_terms(self) -> Iterator[variables.QuadraticTerm]: 530 return iter(()) 531 532 def clear(self) -> None: 533 """Clears objective coefficients and offset. Does not change direction.""" 534 self._elemental.clear_attr(enums.DoubleAttr1.AUXILIARY_OBJECTIVE_OFFSET) 535 self._elemental.clear_attr( 536 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT 537 ) 538 539 def __eq__(self, other: Any) -> bool: 540 if isinstance(other, AuxiliaryObjective): 541 return self._elemental is other._elemental and self._id == other._id 542 return False 543 544 def __hash__(self) -> int: 545 return hash((self._elemental, self._id))
26class Objective(from_model.FromModel, metaclass=abc.ABCMeta): 27 """The objective for an optimization model. 28 29 An objective is either of the form: 30 min o + sum_{i in I} c_i * x_i + sum_{i, j in I, i <= j} q_i,j * x_i * x_j 31 or 32 max o + sum_{i in I} c_i * x_i + sum_{(i, j) in Q} q_i,j * x_i * x_j 33 where x_i are the decision variables of the problem and where all pairs (i, j) 34 in Q satisfy i <= j. The values of o, c_i and q_i,j should be finite and not 35 NaN. 36 37 The objective can be configured as follows: 38 * offset: a float property, o above. Should be finite and not NaN. 39 * is_maximize: a bool property, if the objective is to maximize or minimize. 40 * set_linear_coefficient and get_linear_coefficient control the c_i * x_i 41 terms. The variables must be from the same model as this objective, and 42 the c_i must be finite and not NaN. The coefficient for any variable not 43 set is 0.0, and setting a coefficient to 0.0 removes it from I above. 44 * set_quadratic_coefficient and get_quadratic_coefficient control the 45 q_i,j * x_i * x_j terms. The variables must be from the same model as this 46 objective, and the q_i,j must be finite and not NaN. The coefficient for 47 any pair of variables not set is 0.0, and setting a coefficient to 0.0 48 removes the associated (i,j) from Q above. 49 50 Do not create an Objective directly, use Model.objective to access the 51 objective instead (or Model.add_auxiliary_objective()). Two Objective objects 52 can represent the same objective (for the same model). They will have the same 53 underlying Objective.elemental for storing the data. The Objective class is 54 simply a reference to an Elemental. 55 56 The objective is linear if only linear coefficients are set. This can be 57 useful to avoid solve-time errors with solvers that do not accept quadratic 58 objectives. To facilitate this linear objective guarantee we provide three 59 functions to add to the objective: 60 * add(), which accepts linear or quadratic expressions, 61 * add_quadratic(), which also accepts linear or quadratic expressions and 62 can be used to signal a quadratic objective is possible, and 63 * add_linear(), which only accepts linear expressions and can be used to 64 guarantee the objective remains linear. 65 66 For quadratic terms, the order that variables are provided does not matter, 67 we always canonicalize to first_var <= second_var. So if you set (x1, x2) to 7 68 then: 69 * getting (x2, x1) returns 7 70 * setting (x2, x1) to 10 overwrites the value of 7. 71 Likewise, when we return nonzero quadratic coefficients, we always use the 72 form first_var <= second_var. 73 74 Most problems have only a single objective, but hierarchical objectives are 75 supported (see Model.add_auxiliary_objective()). Note that quadratic Auxiliary 76 objectives are not supported. 77 """ 78 79 __slots__ = ("_elemental",) 80 81 def __init__(self, elem: elemental.Elemental) -> None: 82 """Do not invoke directly, prefer Model.objective.""" 83 self._elemental: elemental.Elemental = elem 84 85 @property 86 def elemental(self) -> elemental.Elemental: 87 """The underlying data structure for the model, for internal use only.""" 88 return self._elemental 89 90 @property 91 @abc.abstractmethod 92 def name(self) -> str: 93 """The immutable name of this objective, for display only.""" 94 95 @property 96 @abc.abstractmethod 97 def is_maximize(self) -> bool: 98 """If true, the direction is maximization, otherwise minimization.""" 99 100 @is_maximize.setter 101 @abc.abstractmethod 102 def is_maximize(self, is_maximize: bool) -> None: ... 103 104 @property 105 @abc.abstractmethod 106 def offset(self) -> float: 107 """A constant added to the objective.""" 108 109 @offset.setter 110 @abc.abstractmethod 111 def offset(self, value: float) -> None: ... 112 113 @property 114 @abc.abstractmethod 115 def priority(self) -> int: 116 """For hierarchical problems, determines the order to apply objectives. 117 118 The objectives are applied from lowest priority to highest. 119 120 The default priority for the primary objective is zero, and auxiliary 121 objectives must specific a priority at creation time. 122 123 Priority has no effect for problems with only one objective. 124 """ 125 126 @priority.setter 127 @abc.abstractmethod 128 def priority(self, value: int) -> None: ... 129 130 @abc.abstractmethod 131 def set_linear_coefficient(self, var: variables.Variable, coef: float) -> None: 132 """Sets the coefficient of `var` to `coef` in the objective.""" 133 134 @abc.abstractmethod 135 def get_linear_coefficient(self, var: variables.Variable) -> float: 136 """Returns the coefficinet of `var` (or zero if unset).""" 137 138 @abc.abstractmethod 139 def linear_terms(self) -> Iterator[variables.LinearTerm]: 140 """Yields variable coefficient pairs for variables with nonzero objective coefficient in undefined order.""" 141 142 @abc.abstractmethod 143 def set_quadratic_coefficient( 144 self, 145 first_variable: variables.Variable, 146 second_variable: variables.Variable, 147 coef: float, 148 ) -> None: 149 """Sets the coefficient for product of variables (see class description).""" 150 151 @abc.abstractmethod 152 def get_quadratic_coefficient( 153 self, 154 first_variable: variables.Variable, 155 second_variable: variables.Variable, 156 ) -> float: 157 """Gets the coefficient for product of variables (see class description).""" 158 159 @abc.abstractmethod 160 def quadratic_terms(self) -> Iterator[variables.QuadraticTerm]: 161 """Yields quadratic terms with nonzero objective coefficient in undefined order.""" 162 163 @abc.abstractmethod 164 def clear(self) -> None: 165 """Clears objective coefficients and offset. Does not change direction.""" 166 167 def as_linear_expression(self) -> variables.LinearExpression: 168 """Returns an equivalent LinearExpression, or errors if quadratic.""" 169 if any(self.quadratic_terms()): 170 raise TypeError("Cannot get a quadratic objective as a linear expression") 171 return variables.as_flat_linear_expression( 172 self.offset + variables.LinearSum(self.linear_terms()) 173 ) 174 175 def as_quadratic_expression(self) -> variables.QuadraticExpression: 176 """Returns an equivalent QuadraticExpression to this objetive.""" 177 return variables.as_flat_quadratic_expression( 178 self.offset 179 + variables.LinearSum(self.linear_terms()) 180 + variables.QuadraticSum(self.quadratic_terms()) 181 ) 182 183 def add(self, objective: variables.QuadraticTypes) -> None: 184 """Adds the provided expression `objective` to the objective function. 185 186 For a compile time guarantee that the objective remains linear, use 187 add_linear() instead. 188 189 Args: 190 objective: the expression to add to the objective function. 191 """ 192 if isinstance(objective, (variables.LinearBase, int, float)): 193 self.add_linear(objective) 194 elif isinstance(objective, variables.QuadraticBase): 195 self.add_quadratic(objective) 196 else: 197 raise TypeError( 198 "unsupported type in objective argument for " 199 f"Objective.add(): {type(objective).__name__!r}" 200 ) 201 202 def add_linear(self, objective: variables.LinearTypes) -> None: 203 """Adds the provided linear expression `objective` to the objective function.""" 204 if not isinstance(objective, (variables.LinearBase, int, float)): 205 raise TypeError( 206 "unsupported type in objective argument for " 207 f"Objective.add_linear(): {type(objective).__name__!r}" 208 ) 209 objective_expr = variables.as_flat_linear_expression(objective) 210 self.offset += objective_expr.offset 211 for var, coefficient in objective_expr.terms.items(): 212 self.set_linear_coefficient( 213 var, self.get_linear_coefficient(var) + coefficient 214 ) 215 216 def add_quadratic(self, objective: variables.QuadraticTypes) -> None: 217 """Adds the provided quadratic expression `objective` to the objective function.""" 218 if not isinstance( 219 objective, (variables.QuadraticBase, variables.LinearBase, int, float) 220 ): 221 raise TypeError( 222 "unsupported type in objective argument for " 223 f"Objective.add(): {type(objective).__name__!r}" 224 ) 225 objective_expr = variables.as_flat_quadratic_expression(objective) 226 self.offset += objective_expr.offset 227 for var, coefficient in objective_expr.linear_terms.items(): 228 self.set_linear_coefficient( 229 var, self.get_linear_coefficient(var) + coefficient 230 ) 231 for key, coefficient in objective_expr.quadratic_terms.items(): 232 self.set_quadratic_coefficient( 233 key.first_var, 234 key.second_var, 235 self.get_quadratic_coefficient(key.first_var, key.second_var) 236 + coefficient, 237 ) 238 239 def set_to_linear_expression(self, linear_expr: variables.LinearTypes) -> None: 240 """Sets the objective to optimize to `linear_expr`.""" 241 if not isinstance(linear_expr, (variables.LinearBase, int, float)): 242 raise TypeError( 243 "unsupported type in objective argument for " 244 f"set_to_linear_expression: {type(linear_expr).__name__!r}" 245 ) 246 self.clear() 247 objective_expr = variables.as_flat_linear_expression(linear_expr) 248 self.offset = objective_expr.offset 249 for var, coefficient in objective_expr.terms.items(): 250 self.set_linear_coefficient(var, coefficient) 251 252 def set_to_quadratic_expression( 253 self, quadratic_expr: variables.QuadraticTypes 254 ) -> None: 255 """Sets the objective to optimize the `quadratic_expr`.""" 256 if not isinstance( 257 quadratic_expr, 258 (variables.QuadraticBase, variables.LinearBase, int, float), 259 ): 260 raise TypeError( 261 "unsupported type in objective argument for " 262 f"set_to_quadratic_expression: {type(quadratic_expr).__name__!r}" 263 ) 264 self.clear() 265 objective_expr = variables.as_flat_quadratic_expression(quadratic_expr) 266 self.offset = objective_expr.offset 267 for var, coefficient in objective_expr.linear_terms.items(): 268 self.set_linear_coefficient(var, coefficient) 269 for quad_key, coefficient in objective_expr.quadratic_terms.items(): 270 self.set_quadratic_coefficient( 271 quad_key.first_var, quad_key.second_var, coefficient 272 ) 273 274 def set_to_expression(self, expr: variables.QuadraticTypes) -> None: 275 """Sets the objective to optimize the `expr`.""" 276 if isinstance(expr, (variables.LinearBase, int, float)): 277 self.set_to_linear_expression(expr) 278 elif isinstance(expr, variables.QuadraticBase): 279 self.set_to_quadratic_expression(expr) 280 else: 281 raise TypeError( 282 "unsupported type in objective argument for " 283 f"set_to_expression: {type(expr).__name__!r}" 284 )
The objective for an optimization model.
An objective is either of the form:
min o + sum_{i in I} c_i * x_i + sum_{i, j in I, i <= j} q_i,j * x_i * x_j
or max o + sum_{i in I} c_i * x_i + sum_{(i, j) in Q} q_i,j * x_i * x_j where x_i are the decision variables of the problem and where all pairs (i, j) in Q satisfy i <= j. The values of o, c_i and q_i,j should be finite and not NaN.
The objective can be configured as follows:
- offset: a float property, o above. Should be finite and not NaN.
- is_maximize: a bool property, if the objective is to maximize or minimize.
- set_linear_coefficient and get_linear_coefficient control the c_i * x_i terms. The variables must be from the same model as this objective, and the c_i must be finite and not NaN. The coefficient for any variable not set is 0.0, and setting a coefficient to 0.0 removes it from I above.
- set_quadratic_coefficient and get_quadratic_coefficient control the q_i,j * x_i * x_j terms. The variables must be from the same model as this objective, and the q_i,j must be finite and not NaN. The coefficient for any pair of variables not set is 0.0, and setting a coefficient to 0.0 removes the associated (i,j) from Q above.
Do not create an Objective directly, use Model.objective to access the objective instead (or Model.add_auxiliary_objective()). Two Objective objects can represent the same objective (for the same model). They will have the same underlying Objective.elemental for storing the data. The Objective class is simply a reference to an Elemental.
The objective is linear if only linear coefficients are set. This can be useful to avoid solve-time errors with solvers that do not accept quadratic objectives. To facilitate this linear objective guarantee we provide three functions to add to the objective:
- add(), which accepts linear or quadratic expressions,
- add_quadratic(), which also accepts linear or quadratic expressions and can be used to signal a quadratic objective is possible, and
- add_linear(), which only accepts linear expressions and can be used to guarantee the objective remains linear.
For quadratic terms, the order that variables are provided does not matter, we always canonicalize to first_var <= second_var. So if you set (x1, x2) to 7 then:
- getting (x2, x1) returns 7
- setting (x2, x1) to 10 overwrites the value of 7. Likewise, when we return nonzero quadratic coefficients, we always use the form first_var <= second_var.
Most problems have only a single objective, but hierarchical objectives are supported (see Model.add_auxiliary_objective()). Note that quadratic Auxiliary objectives are not supported.
81 def __init__(self, elem: elemental.Elemental) -> None: 82 """Do not invoke directly, prefer Model.objective.""" 83 self._elemental: elemental.Elemental = elem
Do not invoke directly, prefer Model.objective.
85 @property 86 def elemental(self) -> elemental.Elemental: 87 """The underlying data structure for the model, for internal use only.""" 88 return self._elemental
The underlying data structure for the model, for internal use only.
90 @property 91 @abc.abstractmethod 92 def name(self) -> str: 93 """The immutable name of this objective, for display only."""
The immutable name of this objective, for display only.
95 @property 96 @abc.abstractmethod 97 def is_maximize(self) -> bool: 98 """If true, the direction is maximization, otherwise minimization."""
If true, the direction is maximization, otherwise minimization.
104 @property 105 @abc.abstractmethod 106 def offset(self) -> float: 107 """A constant added to the objective."""
A constant added to the objective.
113 @property 114 @abc.abstractmethod 115 def priority(self) -> int: 116 """For hierarchical problems, determines the order to apply objectives. 117 118 The objectives are applied from lowest priority to highest. 119 120 The default priority for the primary objective is zero, and auxiliary 121 objectives must specific a priority at creation time. 122 123 Priority has no effect for problems with only one objective. 124 """
For hierarchical problems, determines the order to apply objectives.
The objectives are applied from lowest priority to highest.
The default priority for the primary objective is zero, and auxiliary objectives must specific a priority at creation time.
Priority has no effect for problems with only one objective.
130 @abc.abstractmethod 131 def set_linear_coefficient(self, var: variables.Variable, coef: float) -> None: 132 """Sets the coefficient of `var` to `coef` in the objective."""
Sets the coefficient of var
to coef
in the objective.
134 @abc.abstractmethod 135 def get_linear_coefficient(self, var: variables.Variable) -> float: 136 """Returns the coefficinet of `var` (or zero if unset)."""
Returns the coefficinet of var
(or zero if unset).
138 @abc.abstractmethod 139 def linear_terms(self) -> Iterator[variables.LinearTerm]: 140 """Yields variable coefficient pairs for variables with nonzero objective coefficient in undefined order."""
Yields variable coefficient pairs for variables with nonzero objective coefficient in undefined order.
142 @abc.abstractmethod 143 def set_quadratic_coefficient( 144 self, 145 first_variable: variables.Variable, 146 second_variable: variables.Variable, 147 coef: float, 148 ) -> None: 149 """Sets the coefficient for product of variables (see class description)."""
Sets the coefficient for product of variables (see class description).
151 @abc.abstractmethod 152 def get_quadratic_coefficient( 153 self, 154 first_variable: variables.Variable, 155 second_variable: variables.Variable, 156 ) -> float: 157 """Gets the coefficient for product of variables (see class description)."""
Gets the coefficient for product of variables (see class description).
159 @abc.abstractmethod 160 def quadratic_terms(self) -> Iterator[variables.QuadraticTerm]: 161 """Yields quadratic terms with nonzero objective coefficient in undefined order."""
Yields quadratic terms with nonzero objective coefficient in undefined order.
163 @abc.abstractmethod 164 def clear(self) -> None: 165 """Clears objective coefficients and offset. Does not change direction."""
Clears objective coefficients and offset. Does not change direction.
167 def as_linear_expression(self) -> variables.LinearExpression: 168 """Returns an equivalent LinearExpression, or errors if quadratic.""" 169 if any(self.quadratic_terms()): 170 raise TypeError("Cannot get a quadratic objective as a linear expression") 171 return variables.as_flat_linear_expression( 172 self.offset + variables.LinearSum(self.linear_terms()) 173 )
Returns an equivalent LinearExpression, or errors if quadratic.
175 def as_quadratic_expression(self) -> variables.QuadraticExpression: 176 """Returns an equivalent QuadraticExpression to this objetive.""" 177 return variables.as_flat_quadratic_expression( 178 self.offset 179 + variables.LinearSum(self.linear_terms()) 180 + variables.QuadraticSum(self.quadratic_terms()) 181 )
Returns an equivalent QuadraticExpression to this objetive.
183 def add(self, objective: variables.QuadraticTypes) -> None: 184 """Adds the provided expression `objective` to the objective function. 185 186 For a compile time guarantee that the objective remains linear, use 187 add_linear() instead. 188 189 Args: 190 objective: the expression to add to the objective function. 191 """ 192 if isinstance(objective, (variables.LinearBase, int, float)): 193 self.add_linear(objective) 194 elif isinstance(objective, variables.QuadraticBase): 195 self.add_quadratic(objective) 196 else: 197 raise TypeError( 198 "unsupported type in objective argument for " 199 f"Objective.add(): {type(objective).__name__!r}" 200 )
Adds the provided expression objective
to the objective function.
For a compile time guarantee that the objective remains linear, use add_linear() instead.
Arguments:
- objective: the expression to add to the objective function.
202 def add_linear(self, objective: variables.LinearTypes) -> None: 203 """Adds the provided linear expression `objective` to the objective function.""" 204 if not isinstance(objective, (variables.LinearBase, int, float)): 205 raise TypeError( 206 "unsupported type in objective argument for " 207 f"Objective.add_linear(): {type(objective).__name__!r}" 208 ) 209 objective_expr = variables.as_flat_linear_expression(objective) 210 self.offset += objective_expr.offset 211 for var, coefficient in objective_expr.terms.items(): 212 self.set_linear_coefficient( 213 var, self.get_linear_coefficient(var) + coefficient 214 )
Adds the provided linear expression objective
to the objective function.
216 def add_quadratic(self, objective: variables.QuadraticTypes) -> None: 217 """Adds the provided quadratic expression `objective` to the objective function.""" 218 if not isinstance( 219 objective, (variables.QuadraticBase, variables.LinearBase, int, float) 220 ): 221 raise TypeError( 222 "unsupported type in objective argument for " 223 f"Objective.add(): {type(objective).__name__!r}" 224 ) 225 objective_expr = variables.as_flat_quadratic_expression(objective) 226 self.offset += objective_expr.offset 227 for var, coefficient in objective_expr.linear_terms.items(): 228 self.set_linear_coefficient( 229 var, self.get_linear_coefficient(var) + coefficient 230 ) 231 for key, coefficient in objective_expr.quadratic_terms.items(): 232 self.set_quadratic_coefficient( 233 key.first_var, 234 key.second_var, 235 self.get_quadratic_coefficient(key.first_var, key.second_var) 236 + coefficient, 237 )
Adds the provided quadratic expression objective
to the objective function.
239 def set_to_linear_expression(self, linear_expr: variables.LinearTypes) -> None: 240 """Sets the objective to optimize to `linear_expr`.""" 241 if not isinstance(linear_expr, (variables.LinearBase, int, float)): 242 raise TypeError( 243 "unsupported type in objective argument for " 244 f"set_to_linear_expression: {type(linear_expr).__name__!r}" 245 ) 246 self.clear() 247 objective_expr = variables.as_flat_linear_expression(linear_expr) 248 self.offset = objective_expr.offset 249 for var, coefficient in objective_expr.terms.items(): 250 self.set_linear_coefficient(var, coefficient)
Sets the objective to optimize to linear_expr
.
252 def set_to_quadratic_expression( 253 self, quadratic_expr: variables.QuadraticTypes 254 ) -> None: 255 """Sets the objective to optimize the `quadratic_expr`.""" 256 if not isinstance( 257 quadratic_expr, 258 (variables.QuadraticBase, variables.LinearBase, int, float), 259 ): 260 raise TypeError( 261 "unsupported type in objective argument for " 262 f"set_to_quadratic_expression: {type(quadratic_expr).__name__!r}" 263 ) 264 self.clear() 265 objective_expr = variables.as_flat_quadratic_expression(quadratic_expr) 266 self.offset = objective_expr.offset 267 for var, coefficient in objective_expr.linear_terms.items(): 268 self.set_linear_coefficient(var, coefficient) 269 for quad_key, coefficient in objective_expr.quadratic_terms.items(): 270 self.set_quadratic_coefficient( 271 quad_key.first_var, quad_key.second_var, coefficient 272 )
Sets the objective to optimize the quadratic_expr
.
274 def set_to_expression(self, expr: variables.QuadraticTypes) -> None: 275 """Sets the objective to optimize the `expr`.""" 276 if isinstance(expr, (variables.LinearBase, int, float)): 277 self.set_to_linear_expression(expr) 278 elif isinstance(expr, variables.QuadraticBase): 279 self.set_to_quadratic_expression(expr) 280 else: 281 raise TypeError( 282 "unsupported type in objective argument for " 283 f"set_to_expression: {type(expr).__name__!r}" 284 )
Sets the objective to optimize the expr
.
287class PrimaryObjective(Objective): 288 """The main objective, but users should program against Objective directly.""" 289 290 __slots__ = () 291 292 @property 293 def name(self) -> str: 294 return self._elemental.primary_objective_name 295 296 @property 297 def is_maximize(self) -> bool: 298 return self._elemental.get_attr(enums.BoolAttr0.MAXIMIZE, ()) 299 300 @is_maximize.setter 301 def is_maximize(self, is_maximize: bool) -> None: 302 self._elemental.set_attr(enums.BoolAttr0.MAXIMIZE, (), is_maximize) 303 304 @property 305 def offset(self) -> float: 306 return self._elemental.get_attr(enums.DoubleAttr0.OBJECTIVE_OFFSET, ()) 307 308 @offset.setter 309 def offset(self, value: float) -> None: 310 self._elemental.set_attr(enums.DoubleAttr0.OBJECTIVE_OFFSET, (), value) 311 312 @property 313 def priority(self) -> int: 314 return self._elemental.get_attr(enums.IntAttr0.OBJECTIVE_PRIORITY, ()) 315 316 @priority.setter 317 def priority(self, value: int) -> None: 318 self._elemental.set_attr(enums.IntAttr0.OBJECTIVE_PRIORITY, (), value) 319 320 def set_linear_coefficient(self, var: variables.Variable, coef: float) -> None: 321 from_model.model_is_same(self, var) 322 self._elemental.set_attr( 323 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT, (var.id,), coef 324 ) 325 326 def get_linear_coefficient(self, var: variables.Variable) -> float: 327 from_model.model_is_same(self, var) 328 return self._elemental.get_attr( 329 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT, (var.id,) 330 ) 331 332 def linear_terms(self) -> Iterator[variables.LinearTerm]: 333 keys = self._elemental.get_attr_non_defaults( 334 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT 335 ) 336 var_index = 0 337 coefs = self._elemental.get_attrs( 338 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT, keys 339 ) 340 for i in range(len(keys)): 341 yield variables.LinearTerm( 342 variable=variables.Variable(self._elemental, int(keys[i, var_index])), 343 coefficient=float(coefs[i]), 344 ) 345 346 def set_quadratic_coefficient( 347 self, 348 first_variable: variables.Variable, 349 second_variable: variables.Variable, 350 coef: float, 351 ) -> None: 352 from_model.model_is_same(self, first_variable) 353 from_model.model_is_same(self, second_variable) 354 self._elemental.set_attr( 355 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT, 356 (first_variable.id, second_variable.id), 357 coef, 358 ) 359 360 def get_quadratic_coefficient( 361 self, 362 first_variable: variables.Variable, 363 second_variable: variables.Variable, 364 ) -> float: 365 from_model.model_is_same(self, first_variable) 366 from_model.model_is_same(self, second_variable) 367 return self._elemental.get_attr( 368 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT, 369 (first_variable.id, second_variable.id), 370 ) 371 372 def quadratic_terms(self) -> Iterator[variables.QuadraticTerm]: 373 keys = self._elemental.get_attr_non_defaults( 374 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT 375 ) 376 coefs = self._elemental.get_attrs( 377 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT, keys 378 ) 379 for i in range(len(keys)): 380 yield variables.QuadraticTerm( 381 variables.QuadraticTermKey( 382 variables.Variable(self._elemental, int(keys[i, 0])), 383 variables.Variable(self._elemental, int(keys[i, 1])), 384 ), 385 coefficient=float(coefs[i]), 386 ) 387 388 def clear(self) -> None: 389 self._elemental.clear_attr(enums.DoubleAttr0.OBJECTIVE_OFFSET) 390 self._elemental.clear_attr(enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT) 391 self._elemental.clear_attr( 392 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT 393 ) 394 395 def __eq__(self, other: Any) -> bool: 396 if isinstance(other, PrimaryObjective): 397 return self._elemental is other._elemental 398 return False 399 400 def __hash__(self) -> int: 401 return hash(self._elemental)
The main objective, but users should program against Objective directly.
296 @property 297 def is_maximize(self) -> bool: 298 return self._elemental.get_attr(enums.BoolAttr0.MAXIMIZE, ())
If true, the direction is maximization, otherwise minimization.
304 @property 305 def offset(self) -> float: 306 return self._elemental.get_attr(enums.DoubleAttr0.OBJECTIVE_OFFSET, ())
A constant added to the objective.
312 @property 313 def priority(self) -> int: 314 return self._elemental.get_attr(enums.IntAttr0.OBJECTIVE_PRIORITY, ())
For hierarchical problems, determines the order to apply objectives.
The objectives are applied from lowest priority to highest.
The default priority for the primary objective is zero, and auxiliary objectives must specific a priority at creation time.
Priority has no effect for problems with only one objective.
320 def set_linear_coefficient(self, var: variables.Variable, coef: float) -> None: 321 from_model.model_is_same(self, var) 322 self._elemental.set_attr( 323 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT, (var.id,), coef 324 )
Sets the coefficient of var
to coef
in the objective.
326 def get_linear_coefficient(self, var: variables.Variable) -> float: 327 from_model.model_is_same(self, var) 328 return self._elemental.get_attr( 329 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT, (var.id,) 330 )
Returns the coefficinet of var
(or zero if unset).
332 def linear_terms(self) -> Iterator[variables.LinearTerm]: 333 keys = self._elemental.get_attr_non_defaults( 334 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT 335 ) 336 var_index = 0 337 coefs = self._elemental.get_attrs( 338 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT, keys 339 ) 340 for i in range(len(keys)): 341 yield variables.LinearTerm( 342 variable=variables.Variable(self._elemental, int(keys[i, var_index])), 343 coefficient=float(coefs[i]), 344 )
Yields variable coefficient pairs for variables with nonzero objective coefficient in undefined order.
346 def set_quadratic_coefficient( 347 self, 348 first_variable: variables.Variable, 349 second_variable: variables.Variable, 350 coef: float, 351 ) -> None: 352 from_model.model_is_same(self, first_variable) 353 from_model.model_is_same(self, second_variable) 354 self._elemental.set_attr( 355 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT, 356 (first_variable.id, second_variable.id), 357 coef, 358 )
Sets the coefficient for product of variables (see class description).
360 def get_quadratic_coefficient( 361 self, 362 first_variable: variables.Variable, 363 second_variable: variables.Variable, 364 ) -> float: 365 from_model.model_is_same(self, first_variable) 366 from_model.model_is_same(self, second_variable) 367 return self._elemental.get_attr( 368 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT, 369 (first_variable.id, second_variable.id), 370 )
Gets the coefficient for product of variables (see class description).
372 def quadratic_terms(self) -> Iterator[variables.QuadraticTerm]: 373 keys = self._elemental.get_attr_non_defaults( 374 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT 375 ) 376 coefs = self._elemental.get_attrs( 377 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT, keys 378 ) 379 for i in range(len(keys)): 380 yield variables.QuadraticTerm( 381 variables.QuadraticTermKey( 382 variables.Variable(self._elemental, int(keys[i, 0])), 383 variables.Variable(self._elemental, int(keys[i, 1])), 384 ), 385 coefficient=float(coefs[i]), 386 )
Yields quadratic terms with nonzero objective coefficient in undefined order.
388 def clear(self) -> None: 389 self._elemental.clear_attr(enums.DoubleAttr0.OBJECTIVE_OFFSET) 390 self._elemental.clear_attr(enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT) 391 self._elemental.clear_attr( 392 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT 393 )
Clears objective coefficients and offset. Does not change direction.
404class AuxiliaryObjective(Objective): 405 """An additional objective that can be optimized after objectives.""" 406 407 __slots__ = ("_id",) 408 409 def __init__(self, elem: elemental.Elemental, obj_id: int) -> None: 410 """Internal only, prefer Model functions (add_auxiliary_objective() and get_auxiliary_objective()).""" 411 super().__init__(elem) 412 if not isinstance(obj_id, int): 413 raise TypeError( 414 f"obj_id type should be int, was: {type(obj_id).__name__!r}" 415 ) 416 self._id: int = obj_id 417 418 @property 419 def name(self) -> str: 420 return self._elemental.get_element_name( 421 enums.ElementType.AUXILIARY_OBJECTIVE, self._id 422 ) 423 424 @property 425 def id(self) -> int: 426 """Returns the id of this objective.""" 427 return self._id 428 429 @property 430 def is_maximize(self) -> bool: 431 return self._elemental.get_attr( 432 enums.BoolAttr1.AUXILIARY_OBJECTIVE_MAXIMIZE, (self._id,) 433 ) 434 435 @is_maximize.setter 436 def is_maximize(self, is_maximize: bool) -> None: 437 self._elemental.set_attr( 438 enums.BoolAttr1.AUXILIARY_OBJECTIVE_MAXIMIZE, 439 (self._id,), 440 is_maximize, 441 ) 442 443 @property 444 def offset(self) -> float: 445 return self._elemental.get_attr( 446 enums.DoubleAttr1.AUXILIARY_OBJECTIVE_OFFSET, (self._id,) 447 ) 448 449 @offset.setter 450 def offset(self, value: float) -> None: 451 self._elemental.set_attr( 452 enums.DoubleAttr1.AUXILIARY_OBJECTIVE_OFFSET, 453 (self._id,), 454 value, 455 ) 456 457 @property 458 def priority(self) -> int: 459 return self._elemental.get_attr( 460 enums.IntAttr1.AUXILIARY_OBJECTIVE_PRIORITY, (self._id,) 461 ) 462 463 @priority.setter 464 def priority(self, value: int) -> None: 465 self._elemental.set_attr( 466 enums.IntAttr1.AUXILIARY_OBJECTIVE_PRIORITY, 467 (self._id,), 468 value, 469 ) 470 471 def set_linear_coefficient(self, var: variables.Variable, coef: float) -> None: 472 from_model.model_is_same(self, var) 473 self._elemental.set_attr( 474 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT, 475 (self._id, var.id), 476 coef, 477 ) 478 479 def get_linear_coefficient(self, var: variables.Variable) -> float: 480 from_model.model_is_same(self, var) 481 return self._elemental.get_attr( 482 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT, 483 ( 484 self._id, 485 var.id, 486 ), 487 ) 488 489 def linear_terms(self) -> Iterator[variables.LinearTerm]: 490 keys = self._elemental.slice_attr( 491 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT, 492 0, 493 self._id, 494 ) 495 var_index = 1 496 coefs = self._elemental.get_attrs( 497 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT, keys 498 ) 499 for i in range(len(keys)): 500 yield variables.LinearTerm( 501 variable=variables.Variable(self._elemental, int(keys[i, var_index])), 502 coefficient=float(coefs[i]), 503 ) 504 505 def set_quadratic_coefficient( 506 self, 507 first_variable: variables.Variable, 508 second_variable: variables.Variable, 509 coef: float, 510 ) -> None: 511 raise ValueError("Quadratic auxiliary objectives are not supported.") 512 513 def get_quadratic_coefficient( 514 self, 515 first_variable: variables.Variable, 516 second_variable: variables.Variable, 517 ) -> float: 518 from_model.model_is_same(self, first_variable) 519 from_model.model_is_same(self, second_variable) 520 if not self._elemental.element_exists( 521 enums.ElementType.VARIABLE, first_variable.id 522 ): 523 raise ValueError(f"Variable {first_variable} does not exist") 524 if not self._elemental.element_exists( 525 enums.ElementType.VARIABLE, second_variable.id 526 ): 527 raise ValueError(f"Variable {second_variable} does not exist") 528 return 0.0 529 530 def quadratic_terms(self) -> Iterator[variables.QuadraticTerm]: 531 return iter(()) 532 533 def clear(self) -> None: 534 """Clears objective coefficients and offset. Does not change direction.""" 535 self._elemental.clear_attr(enums.DoubleAttr1.AUXILIARY_OBJECTIVE_OFFSET) 536 self._elemental.clear_attr( 537 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT 538 ) 539 540 def __eq__(self, other: Any) -> bool: 541 if isinstance(other, AuxiliaryObjective): 542 return self._elemental is other._elemental and self._id == other._id 543 return False 544 545 def __hash__(self) -> int: 546 return hash((self._elemental, self._id))
An additional objective that can be optimized after objectives.
409 def __init__(self, elem: elemental.Elemental, obj_id: int) -> None: 410 """Internal only, prefer Model functions (add_auxiliary_objective() and get_auxiliary_objective()).""" 411 super().__init__(elem) 412 if not isinstance(obj_id, int): 413 raise TypeError( 414 f"obj_id type should be int, was: {type(obj_id).__name__!r}" 415 ) 416 self._id: int = obj_id
Internal only, prefer Model functions (add_auxiliary_objective() and get_auxiliary_objective()).
418 @property 419 def name(self) -> str: 420 return self._elemental.get_element_name( 421 enums.ElementType.AUXILIARY_OBJECTIVE, self._id 422 )
The immutable name of this objective, for display only.
424 @property 425 def id(self) -> int: 426 """Returns the id of this objective.""" 427 return self._id
Returns the id of this objective.
429 @property 430 def is_maximize(self) -> bool: 431 return self._elemental.get_attr( 432 enums.BoolAttr1.AUXILIARY_OBJECTIVE_MAXIMIZE, (self._id,) 433 )
If true, the direction is maximization, otherwise minimization.
443 @property 444 def offset(self) -> float: 445 return self._elemental.get_attr( 446 enums.DoubleAttr1.AUXILIARY_OBJECTIVE_OFFSET, (self._id,) 447 )
A constant added to the objective.
457 @property 458 def priority(self) -> int: 459 return self._elemental.get_attr( 460 enums.IntAttr1.AUXILIARY_OBJECTIVE_PRIORITY, (self._id,) 461 )
For hierarchical problems, determines the order to apply objectives.
The objectives are applied from lowest priority to highest.
The default priority for the primary objective is zero, and auxiliary objectives must specific a priority at creation time.
Priority has no effect for problems with only one objective.
471 def set_linear_coefficient(self, var: variables.Variable, coef: float) -> None: 472 from_model.model_is_same(self, var) 473 self._elemental.set_attr( 474 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT, 475 (self._id, var.id), 476 coef, 477 )
Sets the coefficient of var
to coef
in the objective.
479 def get_linear_coefficient(self, var: variables.Variable) -> float: 480 from_model.model_is_same(self, var) 481 return self._elemental.get_attr( 482 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT, 483 ( 484 self._id, 485 var.id, 486 ), 487 )
Returns the coefficinet of var
(or zero if unset).
489 def linear_terms(self) -> Iterator[variables.LinearTerm]: 490 keys = self._elemental.slice_attr( 491 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT, 492 0, 493 self._id, 494 ) 495 var_index = 1 496 coefs = self._elemental.get_attrs( 497 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT, keys 498 ) 499 for i in range(len(keys)): 500 yield variables.LinearTerm( 501 variable=variables.Variable(self._elemental, int(keys[i, var_index])), 502 coefficient=float(coefs[i]), 503 )
Yields variable coefficient pairs for variables with nonzero objective coefficient in undefined order.
505 def set_quadratic_coefficient( 506 self, 507 first_variable: variables.Variable, 508 second_variable: variables.Variable, 509 coef: float, 510 ) -> None: 511 raise ValueError("Quadratic auxiliary objectives are not supported.")
Sets the coefficient for product of variables (see class description).
513 def get_quadratic_coefficient( 514 self, 515 first_variable: variables.Variable, 516 second_variable: variables.Variable, 517 ) -> float: 518 from_model.model_is_same(self, first_variable) 519 from_model.model_is_same(self, second_variable) 520 if not self._elemental.element_exists( 521 enums.ElementType.VARIABLE, first_variable.id 522 ): 523 raise ValueError(f"Variable {first_variable} does not exist") 524 if not self._elemental.element_exists( 525 enums.ElementType.VARIABLE, second_variable.id 526 ): 527 raise ValueError(f"Variable {second_variable} does not exist") 528 return 0.0
Gets the coefficient for product of variables (see class description).
533 def clear(self) -> None: 534 """Clears objective coefficients and offset. Does not change direction.""" 535 self._elemental.clear_attr(enums.DoubleAttr1.AUXILIARY_OBJECTIVE_OFFSET) 536 self._elemental.clear_attr( 537 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT 538 )
Clears objective coefficients and offset. Does not change direction.