Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
elemental.py
Go to the documentation of this file.
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 """
enums.AttrPyValueType get_attr(self, enums.PyAttr[enums.AttrPyValueType] attr, Sequence[int] key)
Definition elemental.py:226
np.typing.NDArray[np.int64] get_attr_non_defaults(self, enums.AnyAttr attr)
Definition elemental.py:296
None set_attr(self, enums.PyAttr[enums.AttrPyValueType] attr, Sequence[int] key, enums.AttrPyValueType values)
Definition elemental.py:201
np.typing.NDArray get_element_names(self, enums.ElementType element_type, np.typing.NDArray[np.int64] elements)
Definition elemental.py:145
np.typing.NDArray[np.int64] get_elements(self, enums.ElementType element_type)
Definition elemental.py:188
bool is_attr_non_default(self, enums.AnyAttr attr, Sequence[int] key)
Definition elemental.py:271
bool delete_element(self, enums.ElementType element_type, int element_id)
Definition elemental.py:109
None set_attrs(self, enums.Attr[enums.AttrValueType] attr, np.typing.NDArray[np.int64] keys, np.typing.NDArray[enums.AttrValueType] values)
Definition elemental.py:209
int get_attr_num_non_defaults(self, enums.AnyAttr attr)
Definition elemental.py:293
int get_num_elements(self, enums.ElementType element_type)
Definition elemental.py:183
Self from_model_proto(cls, model_pb2.ModelProto proto)
Definition elemental.py:62
np.typing.NDArray[np.bool_] bulk_is_attr_non_default(self, enums.AnyAttr attr, np.typing.NDArray[np.int64] keys)
Definition elemental.py:276
int add_element(self, enums.ElementType element_type, str name)
Definition elemental.py:76
int get_attr_slice_size(self, enums.AnyAttr attr, int key_index, int element_id)
Definition elemental.py:329
np.typing.NDArray[np.int64] slice_attr(self, enums.AnyAttr attr, int key_index, int element_id)
Definition elemental.py:309
bool element_exists(self, enums.ElementType element_type, int element_id)
Definition elemental.py:161
None __init__(self, *, str model_name="", str primary_objective_name="")
Definition elemental.py:52
str get_element_name(self, enums.ElementType element_type, int element_id)
Definition elemental.py:138
np.typing.NDArray[np.int64] add_named_elements(self, enums.ElementType element_type, np.typing.NDArray names)
Definition elemental.py:98
Self clone(self, *, Optional[str] new_model_name=None)
Definition elemental.py:65
int get_next_element_id(self, enums.ElementType element_type)
Definition elemental.py:180
np.typing.NDArray[np.int64] add_elements(self, enums.ElementType element_type, int num)
Definition elemental.py:81
model_pb2.ModelProto export_model(self, *, bool remove_names=False)
Definition elemental.py:332
np.typing.NDArray[np.bool_] elements_exist(self, enums.ElementType element_type, np.typing.NDArray[np.int64] elements)
Definition elemental.py:168
None apply_update(self, model_update_pb2.ModelUpdateProto update_proto)
Definition elemental.py:388
Optional[model_update_pb2.ModelUpdateProto] export_model_update(self, int diff_id, *, bool remove_names=False)
Definition elemental.py:368
None ensure_next_element_id_at_least(self, enums.ElementType element_type, int element_id)
Definition elemental.py:193
np.typing.NDArray[enums.AttrValueType] get_attrs(self, enums.Attr[enums.AttrValueType] attr, np.typing.NDArray[np.int64] keys)
Definition elemental.py:250
np.typing.NDArray[np.bool_] delete_elements(self, enums.ElementType element_type, np.typing.NDArray[np.int64] elements)
Definition elemental.py:116