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