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