Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
elemental.py
Go to the documentation of this file.
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 """
enums.AttrPyValueType get_attr(self, enums.PyAttr[enums.AttrPyValueType] attr, Sequence[int] key)
Definition elemental.py:228
np.typing.NDArray[np.int64] get_attr_non_defaults(self, enums.AnyAttr attr)
Definition elemental.py:298
None set_attr(self, enums.PyAttr[enums.AttrPyValueType] attr, Sequence[int] key, enums.AttrPyValueType values)
Definition elemental.py:203
np.typing.NDArray get_element_names(self, enums.ElementType element_type, np.typing.NDArray[np.int64] elements)
Definition elemental.py:147
np.typing.NDArray[np.int64] get_elements(self, enums.ElementType element_type)
Definition elemental.py:190
bool is_attr_non_default(self, enums.AnyAttr attr, Sequence[int] key)
Definition elemental.py:273
bool delete_element(self, enums.ElementType element_type, int element_id)
Definition elemental.py:111
None set_attrs(self, enums.Attr[enums.AttrValueType] attr, np.typing.NDArray[np.int64] keys, np.typing.NDArray[enums.AttrValueType] values)
Definition elemental.py:211
int get_attr_num_non_defaults(self, enums.AnyAttr attr)
Definition elemental.py:295
int get_num_elements(self, enums.ElementType element_type)
Definition elemental.py:185
Self from_model_proto(cls, model_pb2.ModelProto proto)
Definition elemental.py:64
np.typing.NDArray[np.bool_] bulk_is_attr_non_default(self, enums.AnyAttr attr, np.typing.NDArray[np.int64] keys)
Definition elemental.py:278
int add_element(self, enums.ElementType element_type, str name)
Definition elemental.py:78
int get_attr_slice_size(self, enums.AnyAttr attr, int key_index, int element_id)
Definition elemental.py:331
np.typing.NDArray[np.int64] slice_attr(self, enums.AnyAttr attr, int key_index, int element_id)
Definition elemental.py:311
bool element_exists(self, enums.ElementType element_type, int element_id)
Definition elemental.py:163
None __init__(self, *, str model_name="", str primary_objective_name="")
Definition elemental.py:54
str get_element_name(self, enums.ElementType element_type, int element_id)
Definition elemental.py:140
np.typing.NDArray[np.int64] add_named_elements(self, enums.ElementType element_type, np.typing.NDArray names)
Definition elemental.py:100
Self clone(self, *, Optional[str] new_model_name=None)
Definition elemental.py:67
int get_next_element_id(self, enums.ElementType element_type)
Definition elemental.py:182
np.typing.NDArray[np.int64] add_elements(self, enums.ElementType element_type, int num)
Definition elemental.py:83
model_pb2.ModelProto export_model(self, *, bool remove_names=False)
Definition elemental.py:334
np.typing.NDArray[np.bool_] elements_exist(self, enums.ElementType element_type, np.typing.NDArray[np.int64] elements)
Definition elemental.py:170
None apply_update(self, model_update_pb2.ModelUpdateProto update_proto)
Definition elemental.py:390
Optional[model_update_pb2.ModelUpdateProto] export_model_update(self, int diff_id, *, bool remove_names=False)
Definition elemental.py:370
None ensure_next_element_id_at_least(self, enums.ElementType element_type, int element_id)
Definition elemental.py:195
np.typing.NDArray[enums.AttrValueType] get_attrs(self, enums.Attr[enums.AttrValueType] attr, np.typing.NDArray[np.int64] keys)
Definition elemental.py:252
np.typing.NDArray[np.bool_] delete_elements(self, enums.ElementType element_type, np.typing.NDArray[np.int64] elements)
Definition elemental.py:118