ortools.math_opt.python.elemental.elemental

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

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

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

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:
62    @classmethod
63    def from_model_proto(cls, proto: model_pb2.ModelProto) -> Self:
64        """Returns an Elemental equivalent to the input proto."""

Returns an Elemental equivalent to the input proto.

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

Returns a copy of this model with no associated diffs.

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

The name of the model.

primary_objective_name: str
73    @property
74    def primary_objective_name(self) -> str:
75        """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:
77    def add_element(self, element_type: enums.ElementType, name: str) -> int:
78        """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]]:
80    def add_elements(
81        self, element_type: enums.ElementType, num: int
82    ) -> np.typing.NDArray[np.int64]:
83        """Adds `num` `element_type`s to the model and returns their ids.
84
85        All elements added will have the name ''.
86
87        Args:
88          element_type: The ElementType of elements to add to the model.
89          num: How many elements are added.
90
91        Returns:
92          A numpy array with shape (num,) with the ids of the newly added elements.
93        """

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]]:
 95    def add_named_elements(
 96        self,
 97        element_type: enums.ElementType,
 98        names: np.typing.NDArray,
 99    ) -> np.typing.NDArray[np.int64]:
100        """Adds an element of `element_type` for each name in names and returns ids.
101
102        Args:
103          element_type: The ElementType of elements to add to the model.
104          names: The names the elements, must have shape (n,) and string values.
105
106        Returns:
107          A numpy array with shape (n,) with the ids of the newly added elements.
108        """

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

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:
139    def get_element_name(self, element_type: enums.ElementType, element_id: int) -> str:
140        """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]]:
142    def get_element_names(
143        self,
144        element_type: enums.ElementType,
145        elements: np.typing.NDArray[np.int64],
146    ) -> np.typing.NDArray:
147        """Returns the name of each element in `elements`.
148
149        Note that elements have a default name of '' if no name is provided.
150
151        Args:
152          element_type: The ElementType of elements to get the names for.
153          elements: The ids of the elements, must have shape (n,).
154
155        Returns:
156          A numpy array with shape (n,) containing the names.
157
158        Raises:
159           ValueError: if any id from `elements` is not in the model.
160        """

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:
162    def element_exists(self, element_type: enums.ElementType, element_id: int) -> bool:
163        """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]]:
165    def elements_exist(
166        self,
167        element_type: enums.ElementType,
168        elements: np.typing.NDArray[np.int64],
169    ) -> np.typing.NDArray[np.bool_]:
170        """Returns if each id in `elements` is an element in the model.
171
172        Args:
173          element_type: The ElementType to check.
174          elements: The ids to look for, must have shape (n,).
175
176        Returns:
177          A numpy array with shape (n,) containing true if each element is in the
178          model (the id has been created and not deleted).
179        """

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:
181    def get_next_element_id(self, element_type: enums.ElementType) -> int:
182        """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:
184    def get_num_elements(self, element_type: enums.ElementType) -> int:
185        """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]]:
187    def get_elements(
188        self, element_type: enums.ElementType
189    ) -> np.typing.NDArray[np.int64]:
190        """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:
192    def ensure_next_element_id_at_least(
193        self, element_type: enums.ElementType, element_id: int
194    ) -> None:
195        """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:
197    def set_attr(
198        self,
199        attr: enums.PyAttr[enums.AttrPyValueType],
200        key: Sequence[int],
201        values: enums.AttrPyValueType,
202    ) -> None:
203        """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:
205    def set_attrs(
206        self,
207        attr: enums.Attr[enums.AttrValueType],
208        keys: np.typing.NDArray[np.int64],
209        values: np.typing.NDArray[enums.AttrValueType],
210    ) -> None:
211        """Sets the value of an attribute for a list of keys.
212
213        Args:
214          attr: The attribute to modify, with k elements in each key.
215          keys: An (n, k) array of n keys to set this attribute for.
216          values: An array with shape (n,), the values to set for each key.
217
218        Raises:
219          ValueError: if (1) any key is repeated (2) any key references an element
220            not in the model, (3) the shape of keys are values is invalid, or (4)
221            the shape of keys and values is inconsistent. No changes are applied for
222            any key if the operation fails.
223        """

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:
225    def get_attr(
226        self, attr: enums.PyAttr[enums.AttrPyValueType], key: Sequence[int]
227    ) -> enums.AttrPyValueType:
228        """Returns the attribute value for a key.
229
230        The type of the attribute determines the number of elements in the key and
231        return type. E.g. when attr=DoubleAttr1.VARIABLE_LOWER_BOUND, key should
232        have size one (the element id of the variable) and the return type is float.
233
234        Args:
235          attr: The attribute to query, which implies the key size and return type.
236          key: A sequence of k ints, the element ids of the key.
237
238        Returns:
239          The value for the key, or the default value for the attribute if the key
240          is not set.
241
242        Raises:
243          ValueError: if key is of the wrong size or key refers to an element id
244            is not in the model.
245        """

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]]:
247    def get_attrs(
248        self,
249        attr: enums.Attr[enums.AttrValueType],
250        keys: np.typing.NDArray[np.int64],
251    ) -> np.typing.NDArray[enums.AttrValueType]:
252        """Returns the values of an attribute for a list of keys.
253
254        Repeated keys are okay.
255
256        Args:
257          attr: The attribute to query, with k elements in each key.
258          keys: An (n, k) array of n keys to read.
259
260        Returns:
261          An array with shape (n,) with the values for each key. The default value
262          of the attribute is returned if it was never set for the key.
263
264        Raises:
265          ValueError: if (1) any key references an element not in the model or (2)
266            the shape of keys is invalid.
267        """

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

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.
297    def get_attr_non_defaults(self, attr: enums.AnyAttr) -> np.typing.NDArray[np.int64]:
298        """Returns the keys with a non-default value for an attribute.
299
300        Args:
301          attr: The attribute to query, with k elements in each key.
302
303        Returns:
304          An array with shape (n, k) if there are n keys with a non-default value
305          for this attribute.
306        """

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.

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

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.
328    def get_attr_slice_size(
329        self, attr: enums.AnyAttr, key_index: int, element_id: int
330    ) -> int:
331        """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:
333    def export_model(self, *, remove_names: bool = False) -> model_pb2.ModelProto:
334        """Returns a ModelProto equivalent to this model.
335
336        Args:
337          remove_names: If True, exclude names (e.g. variable names, the model name)
338            from the returned proto.
339
340        Returns:
341          The equivalent ModelProto.
342
343        Raises:
344          ValueError: if the model is too big to fit into the proto, see class
345            description for details.
346        """

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:
348    def add_diff(self) -> int:
349        """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:
351    def delete_diff(self, diff_id: int) -> None:
352        """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:
354    def advance_diff(self, diff_id: int) -> None:
355        """Discards any previously tracked changes for this Diff.
356
357        The diff will now track changes from the point onward.
358
359        Args:
360          diff_id: The id of to the Diff to advance.
361
362        Raises:
363          ValueError: if diff_id does not reference a Diff for this model (e.g.,
364            the Diff was already deleted).
365        """

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

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:
389    def apply_update(self, update_proto: model_update_pb2.ModelUpdateProto) -> None:
390        """Modifies the model to apply the changes from `update_proto`.
391
392        Args:
393          update_proto: the changes to apply to the model.
394
395        Raises:
396          ValueError: if the update proto is invalid for the current model, or if
397           the implementation must serialize the proto and it is too large (see
398           class description).
399        """

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).