ortools.math_opt.python.elemental.elemental

An API for storing a MathOpt model and tracking model modifications.

  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 API for storing a MathOpt model and tracking model modifications."""
 16
 17from collections.abc import Sequence
 18from typing import Optional, Protocol
 19
 20import numpy as np
 21
 22# typing.Self is only in python 3.11+, for OR-tools supports down to 3.8.
 23from typing_extensions import Self
 24
 25from ortools.math_opt import model_pb2
 26from ortools.math_opt import model_update_pb2
 27from ortools.math_opt.elemental.python import enums
 28
 29
 30class Elemental(Protocol):
 31    """An API for building, modifying, and tracking changes to a MathOpt model.
 32
 33    On functions that return protocol buffers: These functions can fail for two
 34    reasons:
 35     (1) The data is too large for proto's in memory representation. Specifically,
 36         any repeated field can have at most 2^31 entries (~2 billion). So if your
 37         model has this many nonzeros in the constraint matrix, we cannot build a
 38         proto for it (we can potentially export to a text format still).
 39     (2) The particular combination of Elemental and Proto you are using must
 40         serialize your message (typically to cross a Python/C++ language
 41         boundary). Proto has a limit of 2GB for serialized messages, which is
 42         generally hit much earlier than the repeated field limit.
 43
 44    Note that for users solving locally, they can avoid needing to serialize
 45    their proto by:
 46     - using the C++ implementation of Elemental
 47     - using the upb or cpp implementations of proto for python and compile
 48       correctly, see go/fastpythonproto and
 49       https://github.com/protocolbuffers/protobuf/blob/main/python/README.md.
 50    """
 51
 52    def __init__(
 53        self, *, model_name: str = "", primary_objective_name: str = ""
 54    ) -> None:
 55        """Creates an empty optimization model.
 56
 57        Args:
 58          model_name: The name of the model, used for logging and export only.
 59          primary_objective_name: The name of the main objective of the problem.
 60            Typically used only for multi-objective problems.
 61        """
 62
 63    @classmethod
 64    def from_model_proto(cls, proto: model_pb2.ModelProto) -> Self:
 65        """Returns an Elemental equivalent to the input proto."""
 66
 67    def clone(self, *, new_model_name: Optional[str] = None) -> Self:
 68        """Returns a copy of this model with no associated diffs."""
 69
 70    @property
 71    def model_name(self) -> str:
 72        """The name of the model."""
 73
 74    @property
 75    def primary_objective_name(self) -> str:
 76        """The name of the primary objective of the model (rarely used)."""
 77
 78    def add_element(self, element_type: enums.ElementType, name: str) -> int:
 79        """Adds an element of `element_type` to the model and returns its id."""
 80
 81    def add_elements(
 82        self, element_type: enums.ElementType, num: int
 83    ) -> np.typing.NDArray[np.int64]:
 84        """Adds `num` `element_type`s to the model and returns their ids.
 85
 86        All elements added will have the name ''.
 87
 88        Args:
 89          element_type: The ElementType of elements to add to the model.
 90          num: How many elements are added.
 91
 92        Returns:
 93          A numpy array with shape (num,) with the ids of the newly added elements.
 94        """
 95
 96    def add_named_elements(
 97        self,
 98        element_type: enums.ElementType,
 99        names: np.typing.NDArray,
100    ) -> np.typing.NDArray[np.int64]:
101        """Adds an element of `element_type` for each name in names and returns ids.
102
103        Args:
104          element_type: The ElementType of elements to add to the model.
105          names: The names the elements, must have shape (n,) and string values.
106
107        Returns:
108          A numpy array with shape (n,) with the ids of the newly added elements.
109        """
110
111    def delete_element(self, element_type: enums.ElementType, element_id: int) -> bool:
112        """Deletes element `id` of `element_type` from model, returns success."""
113
114    def delete_elements(
115        self,
116        element_type: enums.ElementType,
117        elements: np.typing.NDArray[np.int64],
118    ) -> np.typing.NDArray[np.bool_]:
119        """Removes `elements` from the model, returning true elementwise on success.
120
121        A value of false is returned when an element was deleted in a previous call
122        to delete elements or the element was never in the model. Note that
123        repeating an id in `elements` for a single call to this function results in
124        an exception.
125
126        Args:
127          element_type: The ElementType of elements to delete from to the model.
128          elements: The ids of the elements to delete, must have shape (n,).
129
130        Returns:
131          A numpy array with shape (n,) indicating if each element was successfully
132          deleted. (Entries are false when the element id was previously deleted or
133          was never in the model.)
134
135        Raises:
136          ValueError: if elements contains any duplicates. No modifications to the
137            model will be applied when this exception is raised.
138        """
139
140    def get_element_name(self, element_type: enums.ElementType, element_id: int) -> str:
141        """Returns the name of the element `id` of ElementType `element_type`."""
142
143    def get_element_names(
144        self,
145        element_type: enums.ElementType,
146        elements: np.typing.NDArray[np.int64],
147    ) -> np.typing.NDArray:
148        """Returns the name of each element in `elements`.
149
150        Note that elements have a default name of '' if no name is provided.
151
152        Args:
153          element_type: The ElementType of elements to get the names for.
154          elements: The ids of the elements, must have shape (n,).
155
156        Returns:
157          A numpy array with shape (n,) containing the names.
158
159        Raises:
160           ValueError: if any id from `elements` is not in the model.
161        """
162
163    def element_exists(self, element_type: enums.ElementType, element_id: int) -> bool:
164        """Returns if element `id` of ElementType `element_type` is in the model."""
165
166    def elements_exist(
167        self,
168        element_type: enums.ElementType,
169        elements: np.typing.NDArray[np.int64],
170    ) -> np.typing.NDArray[np.bool_]:
171        """Returns if each id in `elements` is an element in the model.
172
173        Args:
174          element_type: The ElementType to check.
175          elements: The ids to look for, must have shape (n,).
176
177        Returns:
178          A numpy array with shape (n,) containing true if each element is in the
179          model (the id has been created and not deleted).
180        """
181
182    def get_next_element_id(self, element_type: enums.ElementType) -> int:
183        """Returns the next available element id of type `element_type`."""
184
185    def get_num_elements(self, element_type: enums.ElementType) -> int:
186        """Returns the number of elements of type `element_type` in the model."""
187
188    def get_elements(
189        self, element_type: enums.ElementType
190    ) -> np.typing.NDArray[np.int64]:
191        """Returns all element ids for type `element_type` in unspecified order."""
192
193    def ensure_next_element_id_at_least(
194        self, element_type: enums.ElementType, element_id: int
195    ) -> None:
196        """Increases next_element_id() to `element_id` if it is currently less."""
197
198    def set_attr(
199        self,
200        attr: enums.PyAttr[enums.AttrPyValueType],
201        key: Sequence[int],
202        values: enums.AttrPyValueType,
203    ) -> None:
204        """Sets an attribute to a value for a key."""
205
206    def set_attrs(
207        self,
208        attr: enums.Attr[enums.AttrValueType],
209        keys: np.typing.NDArray[np.int64],
210        values: np.typing.NDArray[enums.AttrValueType],
211    ) -> None:
212        """Sets the value of an attribute for a list of keys.
213
214        Args:
215          attr: The attribute to modify, with k elements in each key.
216          keys: An (n, k) array of n keys to set this attribute for.
217          values: An array with shape (n,), the values to set for each key.
218
219        Raises:
220          ValueError: if (1) any key is repeated (2) any key references an element
221            not in the model, (3) the shape of keys are values is invalid, or (4)
222            the shape of keys and values is inconsistent. No changes are applied for
223            any key if the operation fails.
224        """
225
226    def get_attr(
227        self, attr: enums.PyAttr[enums.AttrPyValueType], key: Sequence[int]
228    ) -> enums.AttrPyValueType:
229        """Returns the attribute value for a key.
230
231        The type of the attribute determines the number of elements in the key and
232        return type. E.g. when attr=DoubleAttr1.VARIABLE_LOWER_BOUND, key should
233        have size one (the element id of the variable) and the return type is float.
234
235        Args:
236          attr: The attribute to query, which implies the key size and return type.
237          key: A sequence of k ints, the element ids of the key.
238
239        Returns:
240          The value for the key, or the default value for the attribute if the key
241          is not set.
242
243        Raises:
244          ValueError: if key is of the wrong size or key refers to an element id
245            is not in the model.
246        """
247
248    def get_attrs(
249        self,
250        attr: enums.Attr[enums.AttrValueType],
251        keys: np.typing.NDArray[np.int64],
252    ) -> np.typing.NDArray[enums.AttrValueType]:
253        """Returns the values of an attribute for a list of keys.
254
255        Repeated keys are okay.
256
257        Args:
258          attr: The attribute to query, with k elements in each key.
259          keys: An (n, k) array of n keys to read.
260
261        Returns:
262          An array with shape (n,) with the values for each key. The default value
263          of the attribute is returned if it was never set for the key.
264
265        Raises:
266          ValueError: if (1) any key references an element not in the model or (2)
267            the shape of keys is invalid.
268        """
269
270    def clear_attr(self, attr: enums.AnyAttr) -> None:
271        """Restores an attribute to its default value for every key."""
272
273    def is_attr_non_default(self, attr: enums.AnyAttr, key: Sequence[int]) -> bool:
274        """Returns true if the attribute has a non-default value for key."""
275
276    def bulk_is_attr_non_default(
277        self, attr: enums.AnyAttr, keys: np.typing.NDArray[np.int64]
278    ) -> np.typing.NDArray[np.bool_]:
279        """Returns which keys take a value different from the attribute's default.
280
281        Repeated keys are okay.
282
283        Args:
284          attr: The attribute to query, with k elements in each key.
285          keys: An (n, k) array to of n keys to query.
286
287        Returns:
288          An array with shape (n,), for each key, if it had a non-default value.
289
290        Raises:
291          ValueError: if (1) any key references an element not in the model or (2)
292            the shape of keys is invalid.
293        """
294
295    def get_attr_num_non_defaults(self, attr: enums.AnyAttr) -> int:
296        """Returns the number of keys with a non-default value for an attribute."""
297
298    def get_attr_non_defaults(self, attr: enums.AnyAttr) -> np.typing.NDArray[np.int64]:
299        """Returns the keys with a non-default value for an attribute.
300
301        Args:
302          attr: The attribute to query, with k elements in each key.
303
304        Returns:
305          An array with shape (n, k) if there are n keys with a non-default value
306          for this attribute.
307        """
308
309    def slice_attr(
310        self, attr: enums.AnyAttr, key_index: int, element_id: int
311    ) -> np.typing.NDArray[np.int64]:
312        """Returns the keys with a non-default value for an attribute along a slice.
313
314        Args:
315          attr: The attribute to query, with k elements in each key.
316          key_index: The index of the key to slice on, in [0..k).
317          element_id: The value of the key to slice on, must be the id of an element
318            with type given by the `key_index` key for `attr`.
319
320        Returns:
321          An array with shape (n, k) if there are n keys along the slice with a
322          non-default value for this attribute.
323
324        Raises:
325          ValueError: if (1) `key_index` is not in [0..k) or (2) if no element with
326            `element_id` exists.
327        """
328
329    def get_attr_slice_size(
330        self, attr: enums.AnyAttr, key_index: int, element_id: int
331    ) -> int:
332        """Returns the number of keys in slice_attr(attr, key_index, element_id)."""
333
334    def export_model(self, *, remove_names: bool = False) -> model_pb2.ModelProto:
335        """Returns a ModelProto equivalent to this model.
336
337        Args:
338          remove_names: If True, exclude names (e.g. variable names, the model name)
339            from the returned proto.
340
341        Returns:
342          The equivalent ModelProto.
343
344        Raises:
345          ValueError: if the model is too big to fit into the proto, see class
346            description for details.
347        """
348
349    def add_diff(self) -> int:
350        """Creates a new Diff to track changes to the model and returns its id."""
351
352    def delete_diff(self, diff_id: int) -> None:
353        """Stop tracking changes to the model for the Diff with id `diff_id`."""
354
355    def advance_diff(self, diff_id: int) -> None:
356        """Discards any previously tracked changes for this Diff.
357
358        The diff will now track changes from the point onward.
359
360        Args:
361          diff_id: The id of to the Diff to advance.
362
363        Raises:
364          ValueError: if diff_id does not reference a Diff for this model (e.g.,
365            the Diff was already deleted).
366        """
367
368    def export_model_update(
369        self, diff_id: int, *, remove_names: bool = False
370    ) -> Optional[model_update_pb2.ModelUpdateProto]:
371        """Returns a ModelUpdateProto with the changes for the Diff `diff_id`.
372
373        Args:
374          diff_id: The id of the Diff to get changes for.
375          remove_names: If True, exclude names (e.g. variable names, the model name)
376            from the returned proto.
377
378        Returns:
379          All changes to the model since the most recent call to
380          `advance(diff_id)`, or since the Diff was created if it was never
381          advanced. Returns `None` instead of an empty proto when there are no
382          changes.
383
384        Raises:
385          ValueError: if the update is too big to fit into the proto (see class
386            description for details) or if diff_id does not reference a Diff for
387            this model (e.g., the id was already deleted).
388        """
389
390    def apply_update(self, update_proto: model_update_pb2.ModelUpdateProto) -> None:
391        """Modifies the model to apply the changes from `update_proto`.
392
393        Args:
394          update_proto: the changes to apply to the model.
395
396        Raises:
397          ValueError: if the update proto is invalid for the current model, or if
398           the implementation must serialize the proto and it is too large (see
399           class description).
400        """
class Elemental(typing.Protocol):
 31class Elemental(Protocol):
 32    """An API for building, modifying, and tracking changes to a MathOpt model.
 33
 34    On functions that return protocol buffers: These functions can fail for two
 35    reasons:
 36     (1) The data is too large for proto's in memory representation. Specifically,
 37         any repeated field can have at most 2^31 entries (~2 billion). So if your
 38         model has this many nonzeros in the constraint matrix, we cannot build a
 39         proto for it (we can potentially export to a text format still).
 40     (2) The particular combination of Elemental and Proto you are using must
 41         serialize your message (typically to cross a Python/C++ language
 42         boundary). Proto has a limit of 2GB for serialized messages, which is
 43         generally hit much earlier than the repeated field limit.
 44
 45    Note that for users solving locally, they can avoid needing to serialize
 46    their proto by:
 47     - using the C++ implementation of Elemental
 48     - using the upb or cpp implementations of proto for python and compile
 49       correctly, see go/fastpythonproto and
 50       https://github.com/protocolbuffers/protobuf/blob/main/python/README.md.
 51    """
 52
 53    def __init__(
 54        self, *, model_name: str = "", primary_objective_name: str = ""
 55    ) -> None:
 56        """Creates an empty optimization model.
 57
 58        Args:
 59          model_name: The name of the model, used for logging and export only.
 60          primary_objective_name: The name of the main objective of the problem.
 61            Typically used only for multi-objective problems.
 62        """
 63
 64    @classmethod
 65    def from_model_proto(cls, proto: model_pb2.ModelProto) -> Self:
 66        """Returns an Elemental equivalent to the input proto."""
 67
 68    def clone(self, *, new_model_name: Optional[str] = None) -> Self:
 69        """Returns a copy of this model with no associated diffs."""
 70
 71    @property
 72    def model_name(self) -> str:
 73        """The name of the model."""
 74
 75    @property
 76    def primary_objective_name(self) -> str:
 77        """The name of the primary objective of the model (rarely used)."""
 78
 79    def add_element(self, element_type: enums.ElementType, name: str) -> int:
 80        """Adds an element of `element_type` to the model and returns its id."""
 81
 82    def add_elements(
 83        self, element_type: enums.ElementType, num: int
 84    ) -> np.typing.NDArray[np.int64]:
 85        """Adds `num` `element_type`s to the model and returns their ids.
 86
 87        All elements added will have the name ''.
 88
 89        Args:
 90          element_type: The ElementType of elements to add to the model.
 91          num: How many elements are added.
 92
 93        Returns:
 94          A numpy array with shape (num,) with the ids of the newly added elements.
 95        """
 96
 97    def add_named_elements(
 98        self,
 99        element_type: enums.ElementType,
100        names: np.typing.NDArray,
101    ) -> np.typing.NDArray[np.int64]:
102        """Adds an element of `element_type` for each name in names and returns ids.
103
104        Args:
105          element_type: The ElementType of elements to add to the model.
106          names: The names the elements, must have shape (n,) and string values.
107
108        Returns:
109          A numpy array with shape (n,) with the ids of the newly added elements.
110        """
111
112    def delete_element(self, element_type: enums.ElementType, element_id: int) -> bool:
113        """Deletes element `id` of `element_type` from model, returns success."""
114
115    def delete_elements(
116        self,
117        element_type: enums.ElementType,
118        elements: np.typing.NDArray[np.int64],
119    ) -> np.typing.NDArray[np.bool_]:
120        """Removes `elements` from the model, returning true elementwise on success.
121
122        A value of false is returned when an element was deleted in a previous call
123        to delete elements or the element was never in the model. Note that
124        repeating an id in `elements` for a single call to this function results in
125        an exception.
126
127        Args:
128          element_type: The ElementType of elements to delete from to the model.
129          elements: The ids of the elements to delete, must have shape (n,).
130
131        Returns:
132          A numpy array with shape (n,) indicating if each element was successfully
133          deleted. (Entries are false when the element id was previously deleted or
134          was never in the model.)
135
136        Raises:
137          ValueError: if elements contains any duplicates. No modifications to the
138            model will be applied when this exception is raised.
139        """
140
141    def get_element_name(self, element_type: enums.ElementType, element_id: int) -> str:
142        """Returns the name of the element `id` of ElementType `element_type`."""
143
144    def get_element_names(
145        self,
146        element_type: enums.ElementType,
147        elements: np.typing.NDArray[np.int64],
148    ) -> np.typing.NDArray:
149        """Returns the name of each element in `elements`.
150
151        Note that elements have a default name of '' if no name is provided.
152
153        Args:
154          element_type: The ElementType of elements to get the names for.
155          elements: The ids of the elements, must have shape (n,).
156
157        Returns:
158          A numpy array with shape (n,) containing the names.
159
160        Raises:
161           ValueError: if any id from `elements` is not in the model.
162        """
163
164    def element_exists(self, element_type: enums.ElementType, element_id: int) -> bool:
165        """Returns if element `id` of ElementType `element_type` is in the model."""
166
167    def elements_exist(
168        self,
169        element_type: enums.ElementType,
170        elements: np.typing.NDArray[np.int64],
171    ) -> np.typing.NDArray[np.bool_]:
172        """Returns if each id in `elements` is an element in the model.
173
174        Args:
175          element_type: The ElementType to check.
176          elements: The ids to look for, must have shape (n,).
177
178        Returns:
179          A numpy array with shape (n,) containing true if each element is in the
180          model (the id has been created and not deleted).
181        """
182
183    def get_next_element_id(self, element_type: enums.ElementType) -> int:
184        """Returns the next available element id of type `element_type`."""
185
186    def get_num_elements(self, element_type: enums.ElementType) -> int:
187        """Returns the number of elements of type `element_type` in the model."""
188
189    def get_elements(
190        self, element_type: enums.ElementType
191    ) -> np.typing.NDArray[np.int64]:
192        """Returns all element ids for type `element_type` in unspecified order."""
193
194    def ensure_next_element_id_at_least(
195        self, element_type: enums.ElementType, element_id: int
196    ) -> None:
197        """Increases next_element_id() to `element_id` if it is currently less."""
198
199    def set_attr(
200        self,
201        attr: enums.PyAttr[enums.AttrPyValueType],
202        key: Sequence[int],
203        values: enums.AttrPyValueType,
204    ) -> None:
205        """Sets an attribute to a value for a key."""
206
207    def set_attrs(
208        self,
209        attr: enums.Attr[enums.AttrValueType],
210        keys: np.typing.NDArray[np.int64],
211        values: np.typing.NDArray[enums.AttrValueType],
212    ) -> None:
213        """Sets the value of an attribute for a list of keys.
214
215        Args:
216          attr: The attribute to modify, with k elements in each key.
217          keys: An (n, k) array of n keys to set this attribute for.
218          values: An array with shape (n,), the values to set for each key.
219
220        Raises:
221          ValueError: if (1) any key is repeated (2) any key references an element
222            not in the model, (3) the shape of keys are values is invalid, or (4)
223            the shape of keys and values is inconsistent. No changes are applied for
224            any key if the operation fails.
225        """
226
227    def get_attr(
228        self, attr: enums.PyAttr[enums.AttrPyValueType], key: Sequence[int]
229    ) -> enums.AttrPyValueType:
230        """Returns the attribute value for a key.
231
232        The type of the attribute determines the number of elements in the key and
233        return type. E.g. when attr=DoubleAttr1.VARIABLE_LOWER_BOUND, key should
234        have size one (the element id of the variable) and the return type is float.
235
236        Args:
237          attr: The attribute to query, which implies the key size and return type.
238          key: A sequence of k ints, the element ids of the key.
239
240        Returns:
241          The value for the key, or the default value for the attribute if the key
242          is not set.
243
244        Raises:
245          ValueError: if key is of the wrong size or key refers to an element id
246            is not in the model.
247        """
248
249    def get_attrs(
250        self,
251        attr: enums.Attr[enums.AttrValueType],
252        keys: np.typing.NDArray[np.int64],
253    ) -> np.typing.NDArray[enums.AttrValueType]:
254        """Returns the values of an attribute for a list of keys.
255
256        Repeated keys are okay.
257
258        Args:
259          attr: The attribute to query, with k elements in each key.
260          keys: An (n, k) array of n keys to read.
261
262        Returns:
263          An array with shape (n,) with the values for each key. The default value
264          of the attribute is returned if it was never set for the key.
265
266        Raises:
267          ValueError: if (1) any key references an element not in the model or (2)
268            the shape of keys is invalid.
269        """
270
271    def clear_attr(self, attr: enums.AnyAttr) -> None:
272        """Restores an attribute to its default value for every key."""
273
274    def is_attr_non_default(self, attr: enums.AnyAttr, key: Sequence[int]) -> bool:
275        """Returns true if the attribute has a non-default value for key."""
276
277    def bulk_is_attr_non_default(
278        self, attr: enums.AnyAttr, keys: np.typing.NDArray[np.int64]
279    ) -> np.typing.NDArray[np.bool_]:
280        """Returns which keys take a value different from the attribute's default.
281
282        Repeated keys are okay.
283
284        Args:
285          attr: The attribute to query, with k elements in each key.
286          keys: An (n, k) array to of n keys to query.
287
288        Returns:
289          An array with shape (n,), for each key, if it had a non-default value.
290
291        Raises:
292          ValueError: if (1) any key references an element not in the model or (2)
293            the shape of keys is invalid.
294        """
295
296    def get_attr_num_non_defaults(self, attr: enums.AnyAttr) -> int:
297        """Returns the number of keys with a non-default value for an attribute."""
298
299    def get_attr_non_defaults(self, attr: enums.AnyAttr) -> np.typing.NDArray[np.int64]:
300        """Returns the keys with a non-default value for an attribute.
301
302        Args:
303          attr: The attribute to query, with k elements in each key.
304
305        Returns:
306          An array with shape (n, k) if there are n keys with a non-default value
307          for this attribute.
308        """
309
310    def slice_attr(
311        self, attr: enums.AnyAttr, key_index: int, element_id: int
312    ) -> np.typing.NDArray[np.int64]:
313        """Returns the keys with a non-default value for an attribute along a slice.
314
315        Args:
316          attr: The attribute to query, with k elements in each key.
317          key_index: The index of the key to slice on, in [0..k).
318          element_id: The value of the key to slice on, must be the id of an element
319            with type given by the `key_index` key for `attr`.
320
321        Returns:
322          An array with shape (n, k) if there are n keys along the slice with a
323          non-default value for this attribute.
324
325        Raises:
326          ValueError: if (1) `key_index` is not in [0..k) or (2) if no element with
327            `element_id` exists.
328        """
329
330    def get_attr_slice_size(
331        self, attr: enums.AnyAttr, key_index: int, element_id: int
332    ) -> int:
333        """Returns the number of keys in slice_attr(attr, key_index, element_id)."""
334
335    def export_model(self, *, remove_names: bool = False) -> model_pb2.ModelProto:
336        """Returns a ModelProto equivalent to this model.
337
338        Args:
339          remove_names: If True, exclude names (e.g. variable names, the model name)
340            from the returned proto.
341
342        Returns:
343          The equivalent ModelProto.
344
345        Raises:
346          ValueError: if the model is too big to fit into the proto, see class
347            description for details.
348        """
349
350    def add_diff(self) -> int:
351        """Creates a new Diff to track changes to the model and returns its id."""
352
353    def delete_diff(self, diff_id: int) -> None:
354        """Stop tracking changes to the model for the Diff with id `diff_id`."""
355
356    def advance_diff(self, diff_id: int) -> None:
357        """Discards any previously tracked changes for this Diff.
358
359        The diff will now track changes from the point onward.
360
361        Args:
362          diff_id: The id of to the Diff to advance.
363
364        Raises:
365          ValueError: if diff_id does not reference a Diff for this model (e.g.,
366            the Diff was already deleted).
367        """
368
369    def export_model_update(
370        self, diff_id: int, *, remove_names: bool = False
371    ) -> Optional[model_update_pb2.ModelUpdateProto]:
372        """Returns a ModelUpdateProto with the changes for the Diff `diff_id`.
373
374        Args:
375          diff_id: The id of the Diff to get changes for.
376          remove_names: If True, exclude names (e.g. variable names, the model name)
377            from the returned proto.
378
379        Returns:
380          All changes to the model since the most recent call to
381          `advance(diff_id)`, or since the Diff was created if it was never
382          advanced. Returns `None` instead of an empty proto when there are no
383          changes.
384
385        Raises:
386          ValueError: if the update is too big to fit into the proto (see class
387            description for details) or if diff_id does not reference a Diff for
388            this model (e.g., the id was already deleted).
389        """
390
391    def apply_update(self, update_proto: model_update_pb2.ModelUpdateProto) -> None:
392        """Modifies the model to apply the changes from `update_proto`.
393
394        Args:
395          update_proto: the changes to apply to the model.
396
397        Raises:
398          ValueError: if the update proto is invalid for the current model, or if
399           the implementation must serialize the proto and it is too large (see
400           class description).
401        """

An API for building, modifying, and tracking changes to a MathOpt model.

On functions that return protocol buffers: These functions can fail for two reasons: (1) The data is too large for proto's in memory representation. Specifically, any repeated field can have at most 2^31 entries (~2 billion). So if your model has this many nonzeros in the constraint matrix, we cannot build a proto for it (we can potentially export to a text format still). (2) The particular combination of Elemental and Proto you are using must serialize your message (typically to cross a Python/C++ language boundary). Proto has a limit of 2GB for serialized messages, which is generally hit much earlier than the repeated field limit.

Note that for users solving locally, they can avoid needing to serialize their proto by:

Elemental(*, model_name: str = '', primary_objective_name: str = '')
53    def __init__(
54        self, *, model_name: str = "", primary_objective_name: str = ""
55    ) -> None:
56        """Creates an empty optimization model.
57
58        Args:
59          model_name: The name of the model, used for logging and export only.
60          primary_objective_name: The name of the main objective of the problem.
61            Typically used only for multi-objective problems.
62        """

Creates an empty optimization model.

Arguments:
  • model_name: The name of the model, used for logging and export only.
  • primary_objective_name: The name of the main objective of the problem. Typically used only for multi-objective problems.
@classmethod
def from_model_proto(cls, proto: ortools.math_opt.model_pb2.ModelProto) -> Self:
64    @classmethod
65    def from_model_proto(cls, proto: model_pb2.ModelProto) -> Self:
66        """Returns an Elemental equivalent to the input proto."""

Returns an Elemental equivalent to the input proto.

def clone(self, *, new_model_name: str | None = None) -> Self:
68    def clone(self, *, new_model_name: Optional[str] = None) -> Self:
69        """Returns a copy of this model with no associated diffs."""

Returns a copy of this model with no associated diffs.

model_name: str
71    @property
72    def model_name(self) -> str:
73        """The name of the model."""

The name of the model.

primary_objective_name: str
75    @property
76    def primary_objective_name(self) -> str:
77        """The name of the primary objective of the model (rarely used)."""

The name of the primary objective of the model (rarely used).

def add_element( self, element_type: ortools.math_opt.elemental.python.enums.ElementType, name: str) -> int:
79    def add_element(self, element_type: enums.ElementType, name: str) -> int:
80        """Adds an element of `element_type` to the model and returns its id."""

Adds an element of element_type to the model and returns its id.

def add_elements( self, element_type: ortools.math_opt.elemental.python.enums.ElementType, num: int) -> numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[numpy.int64]]:
82    def add_elements(
83        self, element_type: enums.ElementType, num: int
84    ) -> np.typing.NDArray[np.int64]:
85        """Adds `num` `element_type`s to the model and returns their ids.
86
87        All elements added will have the name ''.
88
89        Args:
90          element_type: The ElementType of elements to add to the model.
91          num: How many elements are added.
92
93        Returns:
94          A numpy array with shape (num,) with the ids of the newly added elements.
95        """

Adds num element_types to the model and returns their ids.

All elements added will have the name ''.

Arguments:
  • element_type: The ElementType of elements to add to the model.
  • num: How many elements are added.
Returns:

A numpy array with shape (num,) with the ids of the newly added elements.

def add_named_elements( self, element_type: ortools.math_opt.elemental.python.enums.ElementType, names: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]]) -> numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[numpy.int64]]:
 97    def add_named_elements(
 98        self,
 99        element_type: enums.ElementType,
100        names: np.typing.NDArray,
101    ) -> np.typing.NDArray[np.int64]:
102        """Adds an element of `element_type` for each name in names and returns ids.
103
104        Args:
105          element_type: The ElementType of elements to add to the model.
106          names: The names the elements, must have shape (n,) and string values.
107
108        Returns:
109          A numpy array with shape (n,) with the ids of the newly added elements.
110        """

Adds an element of element_type for each name in names and returns ids.

Arguments:
  • element_type: The ElementType of elements to add to the model.
  • names: The names the elements, must have shape (n,) and string values.
Returns:

A numpy array with shape (n,) with the ids of the newly added elements.

def delete_element( self, element_type: ortools.math_opt.elemental.python.enums.ElementType, element_id: int) -> bool:
112    def delete_element(self, element_type: enums.ElementType, element_id: int) -> bool:
113        """Deletes element `id` of `element_type` from model, returns success."""

Deletes element id of element_type from model, returns success.

def delete_elements( self, element_type: ortools.math_opt.elemental.python.enums.ElementType, elements: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[numpy.int64]]) -> numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[numpy.bool]]:
115    def delete_elements(
116        self,
117        element_type: enums.ElementType,
118        elements: np.typing.NDArray[np.int64],
119    ) -> np.typing.NDArray[np.bool_]:
120        """Removes `elements` from the model, returning true elementwise on success.
121
122        A value of false is returned when an element was deleted in a previous call
123        to delete elements or the element was never in the model. Note that
124        repeating an id in `elements` for a single call to this function results in
125        an exception.
126
127        Args:
128          element_type: The ElementType of elements to delete from to the model.
129          elements: The ids of the elements to delete, must have shape (n,).
130
131        Returns:
132          A numpy array with shape (n,) indicating if each element was successfully
133          deleted. (Entries are false when the element id was previously deleted or
134          was never in the model.)
135
136        Raises:
137          ValueError: if elements contains any duplicates. No modifications to the
138            model will be applied when this exception is raised.
139        """

Removes elements from the model, returning true elementwise on success.

A value of false is returned when an element was deleted in a previous call to delete elements or the element was never in the model. Note that repeating an id in elements for a single call to this function results in an exception.

Arguments:
  • element_type: The ElementType of elements to delete from to the model.
  • elements: The ids of the elements to delete, must have shape (n,).
Returns:

A numpy array with shape (n,) indicating if each element was successfully deleted. (Entries are false when the element id was previously deleted or was never in the model.)

Raises:
  • ValueError: if elements contains any duplicates. No modifications to the model will be applied when this exception is raised.
def get_element_name( self, element_type: ortools.math_opt.elemental.python.enums.ElementType, element_id: int) -> str:
141    def get_element_name(self, element_type: enums.ElementType, element_id: int) -> str:
142        """Returns the name of the element `id` of ElementType `element_type`."""

Returns the name of the element id of ElementType element_type.

def get_element_names( self, element_type: ortools.math_opt.elemental.python.enums.ElementType, elements: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[numpy.int64]]) -> numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~_ScalarT]]:
144    def get_element_names(
145        self,
146        element_type: enums.ElementType,
147        elements: np.typing.NDArray[np.int64],
148    ) -> np.typing.NDArray:
149        """Returns the name of each element in `elements`.
150
151        Note that elements have a default name of '' if no name is provided.
152
153        Args:
154          element_type: The ElementType of elements to get the names for.
155          elements: The ids of the elements, must have shape (n,).
156
157        Returns:
158          A numpy array with shape (n,) containing the names.
159
160        Raises:
161           ValueError: if any id from `elements` is not in the model.
162        """

Returns the name of each element in elements.

Note that elements have a default name of '' if no name is provided.

Arguments:
  • element_type: The ElementType of elements to get the names for.
  • elements: The ids of the elements, must have shape (n,).
Returns:

A numpy array with shape (n,) containing the names.

Raises:
  • ValueError: if any id from elements is not in the model.
def element_exists( self, element_type: ortools.math_opt.elemental.python.enums.ElementType, element_id: int) -> bool:
164    def element_exists(self, element_type: enums.ElementType, element_id: int) -> bool:
165        """Returns if element `id` of ElementType `element_type` is in the model."""

Returns if element id of ElementType element_type is in the model.

def elements_exist( self, element_type: ortools.math_opt.elemental.python.enums.ElementType, elements: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[numpy.int64]]) -> numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[numpy.bool]]:
167    def elements_exist(
168        self,
169        element_type: enums.ElementType,
170        elements: np.typing.NDArray[np.int64],
171    ) -> np.typing.NDArray[np.bool_]:
172        """Returns if each id in `elements` is an element in the model.
173
174        Args:
175          element_type: The ElementType to check.
176          elements: The ids to look for, must have shape (n,).
177
178        Returns:
179          A numpy array with shape (n,) containing true if each element is in the
180          model (the id has been created and not deleted).
181        """

Returns if each id in elements is an element in the model.

Arguments:
  • element_type: The ElementType to check.
  • elements: The ids to look for, must have shape (n,).
Returns:

A numpy array with shape (n,) containing true if each element is in the model (the id has been created and not deleted).

def get_next_element_id( self, element_type: ortools.math_opt.elemental.python.enums.ElementType) -> int:
183    def get_next_element_id(self, element_type: enums.ElementType) -> int:
184        """Returns the next available element id of type `element_type`."""

Returns the next available element id of type element_type.

def get_num_elements( self, element_type: ortools.math_opt.elemental.python.enums.ElementType) -> int:
186    def get_num_elements(self, element_type: enums.ElementType) -> int:
187        """Returns the number of elements of type `element_type` in the model."""

Returns the number of elements of type element_type in the model.

def get_elements( self, element_type: ortools.math_opt.elemental.python.enums.ElementType) -> numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[numpy.int64]]:
189    def get_elements(
190        self, element_type: enums.ElementType
191    ) -> np.typing.NDArray[np.int64]:
192        """Returns all element ids for type `element_type` in unspecified order."""

Returns all element ids for type element_type in unspecified order.

def ensure_next_element_id_at_least( self, element_type: ortools.math_opt.elemental.python.enums.ElementType, element_id: int) -> None:
194    def ensure_next_element_id_at_least(
195        self, element_type: enums.ElementType, element_id: int
196    ) -> None:
197        """Increases next_element_id() to `element_id` if it is currently less."""

Increases next_element_id() to element_id if it is currently less.

def set_attr( self, attr: ortools.math_opt.elemental.python.enums.PyAttr[~AttrPyValueType], key: Sequence[int], values: ~AttrPyValueType) -> None:
199    def set_attr(
200        self,
201        attr: enums.PyAttr[enums.AttrPyValueType],
202        key: Sequence[int],
203        values: enums.AttrPyValueType,
204    ) -> None:
205        """Sets an attribute to a value for a key."""

Sets an attribute to a value for a key.

def set_attrs( self, attr: ortools.math_opt.elemental.python.enums.Attr[~AttrValueType], keys: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[numpy.int64]], values: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~AttrValueType]]) -> None:
207    def set_attrs(
208        self,
209        attr: enums.Attr[enums.AttrValueType],
210        keys: np.typing.NDArray[np.int64],
211        values: np.typing.NDArray[enums.AttrValueType],
212    ) -> None:
213        """Sets the value of an attribute for a list of keys.
214
215        Args:
216          attr: The attribute to modify, with k elements in each key.
217          keys: An (n, k) array of n keys to set this attribute for.
218          values: An array with shape (n,), the values to set for each key.
219
220        Raises:
221          ValueError: if (1) any key is repeated (2) any key references an element
222            not in the model, (3) the shape of keys are values is invalid, or (4)
223            the shape of keys and values is inconsistent. No changes are applied for
224            any key if the operation fails.
225        """

Sets the value of an attribute for a list of keys.

Arguments:
  • attr: The attribute to modify, with k elements in each key.
  • keys: An (n, k) array of n keys to set this attribute for.
  • values: An array with shape (n,), the values to set for each key.
Raises:
  • ValueError: if (1) any key is repeated (2) any key references an element not in the model, (3) the shape of keys are values is invalid, or (4) the shape of keys and values is inconsistent. No changes are applied for any key if the operation fails.
def get_attr( self, attr: ortools.math_opt.elemental.python.enums.PyAttr[~AttrPyValueType], key: Sequence[int]) -> ~AttrPyValueType:
227    def get_attr(
228        self, attr: enums.PyAttr[enums.AttrPyValueType], key: Sequence[int]
229    ) -> enums.AttrPyValueType:
230        """Returns the attribute value for a key.
231
232        The type of the attribute determines the number of elements in the key and
233        return type. E.g. when attr=DoubleAttr1.VARIABLE_LOWER_BOUND, key should
234        have size one (the element id of the variable) and the return type is float.
235
236        Args:
237          attr: The attribute to query, which implies the key size and return type.
238          key: A sequence of k ints, the element ids of the key.
239
240        Returns:
241          The value for the key, or the default value for the attribute if the key
242          is not set.
243
244        Raises:
245          ValueError: if key is of the wrong size or key refers to an element id
246            is not in the model.
247        """

Returns the attribute value for a key.

The type of the attribute determines the number of elements in the key and return type. E.g. when attr=DoubleAttr1.VARIABLE_LOWER_BOUND, key should have size one (the element id of the variable) and the return type is float.

Arguments:
  • attr: The attribute to query, which implies the key size and return type.
  • key: A sequence of k ints, the element ids of the key.
Returns:

The value for the key, or the default value for the attribute if the key is not set.

Raises:
  • ValueError: if key is of the wrong size or key refers to an element id is not in the model.
def get_attrs( self, attr: ortools.math_opt.elemental.python.enums.Attr[~AttrValueType], keys: numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[numpy.int64]]) -> numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[~AttrValueType]]:
249    def get_attrs(
250        self,
251        attr: enums.Attr[enums.AttrValueType],
252        keys: np.typing.NDArray[np.int64],
253    ) -> np.typing.NDArray[enums.AttrValueType]:
254        """Returns the values of an attribute for a list of keys.
255
256        Repeated keys are okay.
257
258        Args:
259          attr: The attribute to query, with k elements in each key.
260          keys: An (n, k) array of n keys to read.
261
262        Returns:
263          An array with shape (n,) with the values for each key. The default value
264          of the attribute is returned if it was never set for the key.
265
266        Raises:
267          ValueError: if (1) any key references an element not in the model or (2)
268            the shape of keys is invalid.
269        """

Returns the values of an attribute for a list of keys.

Repeated keys are okay.

Arguments:
  • attr: The attribute to query, with k elements in each key.
  • keys: An (n, k) array of n keys to read.
Returns:

An array with shape (n,) with the values for each key. The default value of the attribute is returned if it was never set for the key.

Raises:
  • ValueError: if (1) any key references an element not in the model or (2) the shape of keys is invalid.
277    def bulk_is_attr_non_default(
278        self, attr: enums.AnyAttr, keys: np.typing.NDArray[np.int64]
279    ) -> np.typing.NDArray[np.bool_]:
280        """Returns which keys take a value different from the attribute's default.
281
282        Repeated keys are okay.
283
284        Args:
285          attr: The attribute to query, with k elements in each key.
286          keys: An (n, k) array to of n keys to query.
287
288        Returns:
289          An array with shape (n,), for each key, if it had a non-default value.
290
291        Raises:
292          ValueError: if (1) any key references an element not in the model or (2)
293            the shape of keys is invalid.
294        """

Returns which keys take a value different from the attribute's default.

Repeated keys are okay.

Arguments:
  • attr: The attribute to query, with k elements in each key.
  • keys: An (n, k) array to of n keys to query.
Returns:

An array with shape (n,), for each key, if it had a non-default value.

Raises:
  • ValueError: if (1) any key references an element not in the model or (2) the shape of keys is invalid.
299    def get_attr_non_defaults(self, attr: enums.AnyAttr) -> np.typing.NDArray[np.int64]:
300        """Returns the keys with a non-default value for an attribute.
301
302        Args:
303          attr: The attribute to query, with k elements in each key.
304
305        Returns:
306          An array with shape (n, k) if there are n keys with a non-default value
307          for this attribute.
308        """

Returns the keys with a non-default value for an attribute.

Arguments:
  • attr: The attribute to query, with k elements in each key.
Returns:

An array with shape (n, k) if there are n keys with a non-default value for this attribute.

310    def slice_attr(
311        self, attr: enums.AnyAttr, key_index: int, element_id: int
312    ) -> np.typing.NDArray[np.int64]:
313        """Returns the keys with a non-default value for an attribute along a slice.
314
315        Args:
316          attr: The attribute to query, with k elements in each key.
317          key_index: The index of the key to slice on, in [0..k).
318          element_id: The value of the key to slice on, must be the id of an element
319            with type given by the `key_index` key for `attr`.
320
321        Returns:
322          An array with shape (n, k) if there are n keys along the slice with a
323          non-default value for this attribute.
324
325        Raises:
326          ValueError: if (1) `key_index` is not in [0..k) or (2) if no element with
327            `element_id` exists.
328        """

Returns the keys with a non-default value for an attribute along a slice.

Arguments:
  • attr: The attribute to query, with k elements in each key.
  • key_index: The index of the key to slice on, in [0..k).
  • element_id: The value of the key to slice on, must be the id of an element with type given by the key_index key for attr.
Returns:

An array with shape (n, k) if there are n keys along the slice with a non-default value for this attribute.

Raises:
  • ValueError: if (1) key_index is not in [0..k) or (2) if no element with element_id exists.
330    def get_attr_slice_size(
331        self, attr: enums.AnyAttr, key_index: int, element_id: int
332    ) -> int:
333        """Returns the number of keys in slice_attr(attr, key_index, element_id)."""

Returns the number of keys in slice_attr(attr, key_index, element_id).

def export_model( self, *, remove_names: bool = False) -> ortools.math_opt.model_pb2.ModelProto:
335    def export_model(self, *, remove_names: bool = False) -> model_pb2.ModelProto:
336        """Returns a ModelProto equivalent to this model.
337
338        Args:
339          remove_names: If True, exclude names (e.g. variable names, the model name)
340            from the returned proto.
341
342        Returns:
343          The equivalent ModelProto.
344
345        Raises:
346          ValueError: if the model is too big to fit into the proto, see class
347            description for details.
348        """

Returns a ModelProto equivalent to this model.

Arguments:
  • remove_names: If True, exclude names (e.g. variable names, the model name) from the returned proto.
Returns:

The equivalent ModelProto.

Raises:
  • ValueError: if the model is too big to fit into the proto, see class description for details.
def add_diff(self) -> int:
350    def add_diff(self) -> int:
351        """Creates a new Diff to track changes to the model and returns its id."""

Creates a new Diff to track changes to the model and returns its id.

def delete_diff(self, diff_id: int) -> None:
353    def delete_diff(self, diff_id: int) -> None:
354        """Stop tracking changes to the model for the Diff with id `diff_id`."""

Stop tracking changes to the model for the Diff with id diff_id.

def advance_diff(self, diff_id: int) -> None:
356    def advance_diff(self, diff_id: int) -> None:
357        """Discards any previously tracked changes for this Diff.
358
359        The diff will now track changes from the point onward.
360
361        Args:
362          diff_id: The id of to the Diff to advance.
363
364        Raises:
365          ValueError: if diff_id does not reference a Diff for this model (e.g.,
366            the Diff was already deleted).
367        """

Discards any previously tracked changes for this Diff.

The diff will now track changes from the point onward.

Arguments:
  • diff_id: The id of to the Diff to advance.
Raises:
  • ValueError: if diff_id does not reference a Diff for this model (e.g., the Diff was already deleted).
def export_model_update( self, diff_id: int, *, remove_names: bool = False) -> ortools.math_opt.model_update_pb2.ModelUpdateProto | None:
369    def export_model_update(
370        self, diff_id: int, *, remove_names: bool = False
371    ) -> Optional[model_update_pb2.ModelUpdateProto]:
372        """Returns a ModelUpdateProto with the changes for the Diff `diff_id`.
373
374        Args:
375          diff_id: The id of the Diff to get changes for.
376          remove_names: If True, exclude names (e.g. variable names, the model name)
377            from the returned proto.
378
379        Returns:
380          All changes to the model since the most recent call to
381          `advance(diff_id)`, or since the Diff was created if it was never
382          advanced. Returns `None` instead of an empty proto when there are no
383          changes.
384
385        Raises:
386          ValueError: if the update is too big to fit into the proto (see class
387            description for details) or if diff_id does not reference a Diff for
388            this model (e.g., the id was already deleted).
389        """

Returns a ModelUpdateProto with the changes for the Diff diff_id.

Arguments:
  • diff_id: The id of the Diff to get changes for.
  • remove_names: If True, exclude names (e.g. variable names, the model name) from the returned proto.
Returns:

All changes to the model since the most recent call to advance(diff_id), or since the Diff was created if it was never advanced. Returns None instead of an empty proto when there are no changes.

Raises:
  • ValueError: if the update is too big to fit into the proto (see class description for details) or if diff_id does not reference a Diff for this model (e.g., the id was already deleted).
def apply_update( self, update_proto: ortools.math_opt.model_update_pb2.ModelUpdateProto) -> None:
391    def apply_update(self, update_proto: model_update_pb2.ModelUpdateProto) -> None:
392        """Modifies the model to apply the changes from `update_proto`.
393
394        Args:
395          update_proto: the changes to apply to the model.
396
397        Raises:
398          ValueError: if the update proto is invalid for the current model, or if
399           the implementation must serialize the proto and it is too large (see
400           class description).
401        """

Modifies the model to apply the changes from update_proto.

Arguments:
  • update_proto: the changes to apply to the model.
Raises:
  • ValueError: if the update proto is invalid for the current model, or if the implementation must serialize the proto and it is too large (see class description).