15"""An Objective for a MathOpt optimization model."""
18from typing
import Any, Iterator
27 """The objective for an optimization model.
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
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
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.
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.
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.
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
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.
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.
79 __slots__ = (
"_elemental",)
81 def __init__(self, elem: elemental.Elemental) ->
None:
82 """Do not invoke directly, prefer Model.objective."""
86 def elemental(self) -> elemental.Elemental:
87 """The underlying data structure for the model, for internal use only."""
93 """The immutable name of this objective, for display only."""
98 """If true, the direction is maximization, otherwise minimization."""
107 """A constant added to the objective."""
111 def offset(self, value: float) ->
None: ...
116 """For hierarchical problems, determines the order to apply objectives.
118 The objectives are applied from lowest priority to highest.
120 The default priority for the primary objective is zero, and auxiliary
121 objectives must specific a priority at creation time.
123 Priority has no effect for problems with only one objective.
132 """Sets the coefficient of `var` to `coef` in the objective."""
136 """Returns the coefficinet of `var` (or zero if unset)."""
140 """Yields variable coefficient pairs for variables with nonzero objective coefficient in undefined order."""
145 first_variable: variables.Variable,
149 """Sets the coefficient for product of variables (see class description)."""
154 first_variable: variables.Variable,
157 """Gets the coefficient for product of variables (see class description)."""
161 """Yields quadratic terms with nonzero objective coefficient in undefined order."""
165 """Clears objective coefficients and offset. Does not change direction."""
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())
176 """Returns an equivalent QuadraticExpression to this objetive."""
177 return variables.as_flat_quadratic_expression(
183 def add(self, objective: variables.QuadraticTypes) ->
None:
184 """Adds the provided expression `objective` to the objective function.
186 For a compile time guarantee that the objective remains linear, use
187 add_linear() instead.
190 objective: the expression to add to the objective function.
198 "unsupported type in objective argument for "
199 f
"Objective.add(): {type(objective).__name__!r}"
202 def add_linear(self, objective: variables.LinearTypes) ->
None:
203 """Adds the provided linear expression `objective` to the objective function."""
206 "unsupported type in objective argument for "
207 f
"Objective.add_linear(): {type(objective).__name__!r}"
209 objective_expr = variables.as_flat_linear_expression(objective)
210 self.
offset += objective_expr.offset
211 for var, coefficient
in objective_expr.terms.items():
217 """Adds the provided quadratic expression `objective` to the objective function."""
222 "unsupported type in objective argument for "
223 f
"Objective.add(): {type(objective).__name__!r}"
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():
231 for key, coefficient
in objective_expr.quadratic_terms.items():
240 """Sets the objective to optimize to `linear_expr`."""
243 "unsupported type in objective argument for "
244 f
"set_to_linear_expression: {type(linear_expr).__name__!r}"
247 objective_expr = variables.as_flat_linear_expression(linear_expr)
249 for var, coefficient
in objective_expr.terms.items():
253 self, quadratic_expr: variables.QuadraticTypes
255 """Sets the objective to optimize the `quadratic_expr`."""
261 "unsupported type in objective argument for "
262 f
"set_to_quadratic_expression: {type(quadratic_expr).__name__!r}"
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():
269 for quad_key, coefficient
in objective_expr.quadratic_terms.items():
271 quad_key.first_var, quad_key.second_var, coefficient
275 """Sets the objective to optimize the `expr`."""
282 "unsupported type in objective argument for "
283 f
"set_to_expression: {type(expr).__name__!r}"
288 """The main objective, but users should program against Objective directly."""
298 return self.
_elemental.get_attr(enums.BoolAttr0.MAXIMIZE, ())
302 self.
_elemental.set_attr(enums.BoolAttr0.MAXIMIZE, (), is_maximize)
306 return self.
_elemental.get_attr(enums.DoubleAttr0.OBJECTIVE_OFFSET, ())
310 self.
_elemental.set_attr(enums.DoubleAttr0.OBJECTIVE_OFFSET, (), value)
314 return self.
_elemental.get_attr(enums.IntAttr0.OBJECTIVE_PRIORITY, ())
318 self.
_elemental.set_attr(enums.IntAttr0.OBJECTIVE_PRIORITY, (), value)
321 from_model.model_is_same(self, var)
323 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT, (var.id,), coef
327 from_model.model_is_same(self, var)
329 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT, (var.id,)
334 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT
338 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT, keys
340 for i
in range(len(keys)):
343 coefficient=float(coefs[i]),
348 first_variable: variables.Variable,
352 from_model.model_is_same(self, first_variable)
353 from_model.model_is_same(self, second_variable)
355 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT,
356 (first_variable.id, second_variable.id),
362 first_variable: variables.Variable,
365 from_model.model_is_same(self, first_variable)
366 from_model.model_is_same(self, second_variable)
368 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT,
369 (first_variable.id, second_variable.id),
374 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT
377 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT, keys
379 for i
in range(len(keys)):
385 coefficient=float(coefs[i]),
389 self.
_elemental.clear_attr(enums.DoubleAttr0.OBJECTIVE_OFFSET)
390 self.
_elemental.clear_attr(enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT)
392 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT
396 if isinstance(other, PrimaryObjective):
405 """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())."""
412 if not isinstance(obj_id, int):
414 f
"obj_id type should be int, was: {type(obj_id).__name__!r}"
421 enums.ElementType.AUXILIARY_OBJECTIVE, self.
_id
426 """Returns the id of this objective."""
432 enums.BoolAttr1.AUXILIARY_OBJECTIVE_MAXIMIZE, (self.
_id,)
438 enums.BoolAttr1.AUXILIARY_OBJECTIVE_MAXIMIZE,
446 enums.DoubleAttr1.AUXILIARY_OBJECTIVE_OFFSET, (self.
_id,)
452 enums.DoubleAttr1.AUXILIARY_OBJECTIVE_OFFSET,
460 enums.IntAttr1.AUXILIARY_OBJECTIVE_PRIORITY, (self.
_id,)
466 enums.IntAttr1.AUXILIARY_OBJECTIVE_PRIORITY,
472 from_model.model_is_same(self, var)
474 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT,
480 from_model.model_is_same(self, var)
482 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT,
491 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT,
497 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT, keys
499 for i
in range(len(keys)):
502 coefficient=float(coefs[i]),
507 first_variable: variables.Variable,
511 raise ValueError(
"Quadratic auxiliary objectives are not supported.")
515 first_variable: variables.Variable,
518 from_model.model_is_same(self, first_variable)
519 from_model.model_is_same(self, second_variable)
521 enums.ElementType.VARIABLE, first_variable.id
523 raise ValueError(f
"Variable {first_variable} does not exist")
525 enums.ElementType.VARIABLE, second_variable.id
527 raise ValueError(f
"Variable {second_variable} does not exist")
534 """Clears objective coefficients and offset. Does not change direction."""
535 self.
_elemental.clear_attr(enums.DoubleAttr1.AUXILIARY_OBJECTIVE_OFFSET)
537 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT
541 if isinstance(other, AuxiliaryObjective):
542 return self.
_elemental is other._elemental
and self.
_id == other._id