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 """
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:
- using the C++ implementation of Elemental
- using the upb or cpp implementations of proto for python and compile correctly, see go/fastpythonproto and https://github.com/protocolbuffers/protobuf/blob/main/python/README.md.
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.
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.
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.
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).
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.
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_type
s 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.
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.
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.
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.
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
.
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.
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.
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).
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
.
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.
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.
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.
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.
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.
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.
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.
269 def clear_attr(self, attr: enums.AnyAttr) -> None: 270 """Restores an attribute to its default value for every key."""
Restores an attribute to its default value for every key.
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."""
Returns true if the attribute has a non-default value for key.
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.
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."""
Returns the number of keys with a non-default value for an attribute.
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 forattr
.
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 withelement_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).
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.
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.
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
.
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).
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. ReturnsNone
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).
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).