14"""An Objective for a MathOpt optimization model."""
17from typing
import Any, Iterator
26 """The objective for an optimization model.
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
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
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.
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.
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.
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
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.
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.
78 __slots__ = (
"_elemental",)
80 def __init__(self, elem: elemental.Elemental) ->
None:
81 """Do not invoke directly, prefer Model.objective."""
85 def elemental(self) -> elemental.Elemental:
86 """The underlying data structure for the model, for internal use only."""
92 """The immutable name of this objective, for display only."""
97 """If true, the direction is maximization, otherwise minimization."""
106 """A constant added to the objective."""
110 def offset(self, value: float) ->
None: ...
115 """For hierarchical problems, determines the order to apply objectives.
117 The objectives are applied from lowest priority to highest.
119 The default priority for the primary objective is zero, and auxiliary
120 objectives must specific a priority at creation time.
122 Priority has no effect for problems with only one objective.
131 """Sets the coefficient of `var` to `coef` in the objective."""
135 """Returns the coefficinet of `var` (or zero if unset)."""
139 """Yields variable coefficient pairs for variables with nonzero objective coefficient in undefined order."""
144 first_variable: variables.Variable,
148 """Sets the coefficient for product of variables (see class description)."""
153 first_variable: variables.Variable,
156 """Gets the coefficient for product of variables (see class description)."""
160 """Yields quadratic terms with nonzero objective coefficient in undefined order."""
164 """Clears objective coefficients and offset. Does not change direction."""
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())
175 """Returns an equivalent QuadraticExpression to this objetive."""
176 return variables.as_flat_quadratic_expression(
182 def add(self, objective: variables.QuadraticTypes) ->
None:
183 """Adds the provided expression `objective` to the objective function.
185 For a compile time guarantee that the objective remains linear, use
186 add_linear() instead.
189 objective: the expression to add to the objective function.
197 "unsupported type in objective argument for "
198 f
"Objective.add(): {type(objective).__name__!r}"
201 def add_linear(self, objective: variables.LinearTypes) ->
None:
202 """Adds the provided linear expression `objective` to the objective function."""
205 "unsupported type in objective argument for "
206 f
"Objective.add_linear(): {type(objective).__name__!r}"
208 objective_expr = variables.as_flat_linear_expression(objective)
209 self.
offset += objective_expr.offset
210 for var, coefficient
in objective_expr.terms.items():
216 """Adds the provided quadratic expression `objective` to the objective function."""
221 "unsupported type in objective argument for "
222 f
"Objective.add(): {type(objective).__name__!r}"
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():
230 for key, coefficient
in objective_expr.quadratic_terms.items():
239 """Sets the objective to optimize to `linear_expr`."""
242 "unsupported type in objective argument for "
243 f
"set_to_linear_expression: {type(linear_expr).__name__!r}"
246 objective_expr = variables.as_flat_linear_expression(linear_expr)
248 for var, coefficient
in objective_expr.terms.items():
252 self, quadratic_expr: variables.QuadraticTypes
254 """Sets the objective to optimize the `quadratic_expr`."""
260 "unsupported type in objective argument for "
261 f
"set_to_quadratic_expression: {type(quadratic_expr).__name__!r}"
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():
268 for quad_key, coefficient
in objective_expr.quadratic_terms.items():
270 quad_key.first_var, quad_key.second_var, coefficient
274 """Sets the objective to optimize the `expr`."""
281 "unsupported type in objective argument for "
282 f
"set_to_expression: {type(expr).__name__!r}"
287 """The main objective, but users should program against Objective directly."""
297 return self.
_elemental.get_attr(enums.BoolAttr0.MAXIMIZE, ())
301 self.
_elemental.set_attr(enums.BoolAttr0.MAXIMIZE, (), is_maximize)
305 return self.
_elemental.get_attr(enums.DoubleAttr0.OBJECTIVE_OFFSET, ())
309 self.
_elemental.set_attr(enums.DoubleAttr0.OBJECTIVE_OFFSET, (), value)
313 return self.
_elemental.get_attr(enums.IntAttr0.OBJECTIVE_PRIORITY, ())
317 self.
_elemental.set_attr(enums.IntAttr0.OBJECTIVE_PRIORITY, (), value)
320 from_model.model_is_same(self, var)
322 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT, (var.id,), coef
326 from_model.model_is_same(self, var)
328 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT, (var.id,)
333 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT
337 enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT, keys
339 for i
in range(len(keys)):
342 coefficient=float(coefs[i]),
347 first_variable: variables.Variable,
351 from_model.model_is_same(self, first_variable)
352 from_model.model_is_same(self, second_variable)
354 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT,
355 (first_variable.id, second_variable.id),
361 first_variable: variables.Variable,
364 from_model.model_is_same(self, first_variable)
365 from_model.model_is_same(self, second_variable)
367 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT,
368 (first_variable.id, second_variable.id),
373 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT
376 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT, keys
378 for i
in range(len(keys)):
384 coefficient=float(coefs[i]),
388 self.
_elemental.clear_attr(enums.DoubleAttr0.OBJECTIVE_OFFSET)
389 self.
_elemental.clear_attr(enums.DoubleAttr1.OBJECTIVE_LINEAR_COEFFICIENT)
391 enums.SymmetricDoubleAttr2.OBJECTIVE_QUADRATIC_COEFFICIENT
395 if isinstance(other, PrimaryObjective):
404 """An additional objective that can be optimized after objectives."""
408 def __init__(self, elem: elemental.Elemental, obj_id: int) ->
None:
409 """Internal only, prefer Model functions (add_auxiliary_objective() and get_auxiliary_objective())."""
411 if not isinstance(obj_id, int):
413 f
"obj_id type should be int, was: {type(obj_id).__name__!r}"
420 enums.ElementType.AUXILIARY_OBJECTIVE, self.
_id
425 """Returns the id of this objective."""
431 enums.BoolAttr1.AUXILIARY_OBJECTIVE_MAXIMIZE, (self.
_id,)
437 enums.BoolAttr1.AUXILIARY_OBJECTIVE_MAXIMIZE,
445 enums.DoubleAttr1.AUXILIARY_OBJECTIVE_OFFSET, (self.
_id,)
451 enums.DoubleAttr1.AUXILIARY_OBJECTIVE_OFFSET,
459 enums.IntAttr1.AUXILIARY_OBJECTIVE_PRIORITY, (self.
_id,)
465 enums.IntAttr1.AUXILIARY_OBJECTIVE_PRIORITY,
471 from_model.model_is_same(self, var)
473 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT,
479 from_model.model_is_same(self, var)
481 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT,
490 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT,
496 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT, keys
498 for i
in range(len(keys)):
501 coefficient=float(coefs[i]),
506 first_variable: variables.Variable,
510 raise ValueError(
"Quadratic auxiliary objectives are not supported.")
514 first_variable: variables.Variable,
517 from_model.model_is_same(self, first_variable)
518 from_model.model_is_same(self, second_variable)
520 enums.ElementType.VARIABLE, first_variable.id
522 raise ValueError(f
"Variable {first_variable} does not exist")
524 enums.ElementType.VARIABLE, second_variable.id
526 raise ValueError(f
"Variable {second_variable} does not exist")
533 """Clears objective coefficients and offset. Does not change direction."""
534 self.
_elemental.clear_attr(enums.DoubleAttr1.AUXILIARY_OBJECTIVE_OFFSET)
536 enums.DoubleAttr2.AUXILIARY_OBJECTIVE_LINEAR_COEFFICIENT
540 if isinstance(other, AuxiliaryObjective):
541 return self.
_elemental is other._elemental
and self.
_id == other._id