ortools.math_opt.python.model_storage

An interface for in memory storage of optimization problems.

  1# Copyright 2010-2024 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 interface for in memory storage of optimization problems."""
 15
 16import abc
 17import dataclasses
 18from typing import Iterator, Optional, Type, TypeVar
 19
 20from ortools.math_opt import model_pb2
 21from ortools.math_opt import model_update_pb2
 22
 23
 24# TODO(b/231426528): remove __slots__ and set slots=True when Python 3.10 is
 25# available.
 26@dataclasses.dataclass(frozen=True)
 27class LinearConstraintMatrixIdEntry:
 28    __slots__ = "linear_constraint_id", "variable_id", "coefficient"
 29    linear_constraint_id: int
 30    variable_id: int
 31    coefficient: float
 32
 33
 34# TODO(b/231426528): remove __slots__ and set slots=True when Python 3.10 is
 35# available.
 36@dataclasses.dataclass(frozen=True)
 37class LinearObjectiveEntry:
 38    __slots__ = "variable_id", "coefficient"
 39    variable_id: int
 40    coefficient: float
 41
 42
 43# TODO(b/231426528): remove __slots__ and set slots=True when Python 3.10 is
 44# available.
 45@dataclasses.dataclass(frozen=True)
 46class QuadraticTermIdKey:
 47    """An ordered pair of ints used as a key for quadratic terms.
 48
 49    QuadraticTermIdKey.id1 <= QuadraticTermIdKey.id2.
 50    """
 51
 52    __slots__ = "id1", "id2"
 53    id1: int
 54    id2: int
 55
 56    def __init__(self, a: int, b: int):
 57        """Ints a and b will be ordered internally."""
 58        id1 = a
 59        id2 = b
 60        if id1 > id2:
 61            id1, id2 = id2, id1
 62        object.__setattr__(self, "id1", id1)
 63        object.__setattr__(self, "id2", id2)
 64
 65
 66# TODO(b/231426528): remove __slots__ and set slots=True when Python 3.10 is
 67# available.
 68@dataclasses.dataclass(frozen=True)
 69class QuadraticEntry:
 70    """Represents an id-indexed quadratic term."""
 71
 72    __slots__ = "id_key", "coefficient"
 73    id_key: QuadraticTermIdKey
 74    coefficient: float
 75
 76
 77class StorageUpdateTracker(abc.ABC):
 78    """Tracks updates to an optimization model from a ModelStorage.
 79
 80    Do not instantiate directly, instead create through
 81    ModelStorage.add_update_tracker().
 82
 83    Interacting with an update tracker after it has been removed from the model
 84    will result in an UsedUpdateTrackerAfterRemovalError error.
 85
 86    Example:
 87      mod = model_storage.ModelStorage()
 88      x = mod.add_variable(0.0, 1.0, True, 'x')
 89      y = mod.add_variable(0.0, 1.0, True, 'y')
 90      tracker = mod.add_update_tracker()
 91      mod.set_variable_ub(x, 3.0)
 92      tracker.export_update()
 93        => "variable_updates: {upper_bounds: {ids: [0], values[3.0] }"
 94      mod.set_variable_ub(y, 2.0)
 95      tracker.export_update()
 96        => "variable_updates: {upper_bounds: {ids: [0, 1], values[3.0, 2.0] }"
 97      tracker.advance_checkpoint()
 98      tracker.export_update()
 99        => ""
100      mod.set_variable_ub(y, 4.0)
101      tracker.export_update()
102        => "variable_updates: {upper_bounds: {ids: [1], values[4.0] }"
103      tracker.advance_checkpoint()
104      mod.remove_update_tracker(tracker)
105        => ""
106    """
107
108    @abc.abstractmethod
109    def export_update(self) -> Optional[model_update_pb2.ModelUpdateProto]:
110        """Returns changes to the model since last call to checkpoint/creation, or None if no changes occurred."""
111        pass
112
113    @abc.abstractmethod
114    def advance_checkpoint(self) -> None:
115        """Track changes to the model only after this function call."""
116        pass
117
118
119class UsedUpdateTrackerAfterRemovalError(RuntimeError):
120
121    def __init__(self):
122        super().__init__(
123            "Attempted to use update tracker after removing it from model storage."
124        )
125
126
127class BadVariableIdError(LookupError):
128    """Raised by ModelStorage when a bad variable id is given."""
129
130    def __init__(self, variable_id):
131        super().__init__(f"Unexpected variable id: {variable_id}")
132        self.id = variable_id
133
134
135class BadLinearConstraintIdError(LookupError):
136    """Raised by ModelStorage when a bad linear constraint id is given."""
137
138    def __init__(self, linear_constraint_id):
139        super().__init__(f"Unexpected linear constraint id: {linear_constraint_id}")
140        self.id = linear_constraint_id
141
142
143class ModelStorage(abc.ABC):
144    """An interface for in memory storage of an optimization model.
145
146    Most users should not use this class directly and use Model defined in
147    model.py.
148
149    Stores an mixed integer programming problem of the form:
150
151    {max/min} c*x + d
152    s.t.  lb_c <= A * x <= ub_c
153          lb_v <=     x <= ub_v
154                      x_i integer for i in I
155
156    where x is a vector of n decision variables, d is a number, lb_v, ub_v, and c
157    are vectors of n numbers, lb_c and ub_c are vectors of m numbers, A is a
158    m by n matrix, and I is a subset of {1,..., n}.
159
160    Each of the n variables and m constraints have an integer id that you use to
161    get/set the problem data (c, A, lb_c etc.). Ids begin at zero and increase
162    sequentially. They are not reused after deletion. Note that if a variable is
163    deleted, your model has nonconsecutive variable ids.
164
165    For all methods taking an id (e.g. set_variable_lb), providing a bad id
166    (including the id of a deleted variable) will raise a BadVariableIdError or
167    BadLinearConstraintIdError. Further, the ModelStorage instance is assumed to
168    be in a bad state after any such error and there are no guarantees on further
169    interactions.
170
171    All implementations must have a constructor taking a str argument for the
172    model name with a default value of the empty string.
173
174    Any ModelStorage can be exported to model_pb2.ModelProto, the format consumed
175    by MathOpt solvers. Changes to a model can be exported to a
176    model_update_pb2.ModelUpdateProto with an UpdateTracker, see the UpdateTracker
177    documentation for details.
178
179    When solving this optimization problem we will additionally require that:
180      * No numbers are NaN,
181      * c, d, and A are all finite,
182      * lb_c and lb_v are not +inf,
183      * ub_c and ub_v are not -inf,
184    but those assumptions are not checked or enforced here (NaNs and infinite
185    values can be used anywhere).
186    """
187
188    @property
189    @abc.abstractmethod
190    def name(self) -> str:
191        pass
192
193    @abc.abstractmethod
194    def add_variable(self, lb: float, ub: float, is_integer: bool, name: str) -> int:
195        pass
196
197    @abc.abstractmethod
198    def delete_variable(self, variable_id: int) -> None:
199        pass
200
201    @abc.abstractmethod
202    def variable_exists(self, variable_id: int) -> bool:
203        pass
204
205    @abc.abstractmethod
206    def next_variable_id(self) -> int:
207        pass
208
209    @abc.abstractmethod
210    def set_variable_lb(self, variable_id: int, lb: float) -> None:
211        pass
212
213    @abc.abstractmethod
214    def set_variable_ub(self, variable_id: int, ub: float) -> None:
215        pass
216
217    @abc.abstractmethod
218    def set_variable_is_integer(self, variable_id: int, is_integer: bool) -> None:
219        pass
220
221    @abc.abstractmethod
222    def get_variable_lb(self, variable_id: int) -> float:
223        pass
224
225    @abc.abstractmethod
226    def get_variable_ub(self, variable_id: int) -> float:
227        pass
228
229    @abc.abstractmethod
230    def get_variable_is_integer(self, variable_id: int) -> bool:
231        pass
232
233    @abc.abstractmethod
234    def get_variable_name(self, variable_id: int) -> str:
235        pass
236
237    @abc.abstractmethod
238    def get_variables(self) -> Iterator[int]:
239        """Yields the variable ids in order of creation."""
240        pass
241
242    @abc.abstractmethod
243    def add_linear_constraint(self, lb: float, ub: float, name: str) -> int:
244        pass
245
246    @abc.abstractmethod
247    def delete_linear_constraint(self, linear_constraint_id: int) -> None:
248        pass
249
250    @abc.abstractmethod
251    def linear_constraint_exists(self, linear_constraint_id: int) -> bool:
252        pass
253
254    @abc.abstractmethod
255    def next_linear_constraint_id(self) -> int:
256        pass
257
258    @abc.abstractmethod
259    def set_linear_constraint_lb(self, linear_constraint_id: int, lb: float) -> None:
260        pass
261
262    @abc.abstractmethod
263    def set_linear_constraint_ub(self, linear_constraint_id: int, ub: float) -> None:
264        pass
265
266    @abc.abstractmethod
267    def get_linear_constraint_lb(self, linear_constraint_id: int) -> float:
268        pass
269
270    @abc.abstractmethod
271    def get_linear_constraint_ub(self, linear_constraint_id: int) -> float:
272        pass
273
274    @abc.abstractmethod
275    def get_linear_constraint_name(self, linear_constraint_id: int) -> str:
276        pass
277
278    @abc.abstractmethod
279    def get_linear_constraints(self) -> Iterator[int]:
280        """Yields the linear constraint ids in order of creation."""
281        pass
282
283    @abc.abstractmethod
284    def set_linear_constraint_coefficient(
285        self, linear_constraint_id: int, variable_id: int, lb: float
286    ) -> None:
287        pass
288
289    @abc.abstractmethod
290    def get_linear_constraint_coefficient(
291        self, linear_constraint_id: int, variable_id: int
292    ) -> float:
293        pass
294
295    @abc.abstractmethod
296    def get_linear_constraints_with_variable(self, variable_id: int) -> Iterator[int]:
297        """Yields the linear constraints with nonzero coefficient for a variable in undefined order."""
298        pass
299
300    @abc.abstractmethod
301    def get_variables_for_linear_constraint(
302        self, linear_constraint_id: int
303    ) -> Iterator[int]:
304        """Yields the variables with nonzero coefficient in a linear constraint in undefined order."""
305        pass
306
307    @abc.abstractmethod
308    def get_linear_constraint_matrix_entries(
309        self,
310    ) -> Iterator[LinearConstraintMatrixIdEntry]:
311        """Yields the nonzero elements of the linear constraint matrix in undefined order."""
312        pass
313
314    @abc.abstractmethod
315    def clear_objective(self) -> None:
316        """Clears objective coefficients and offset. Does not change direction."""
317
318    @abc.abstractmethod
319    def set_linear_objective_coefficient(self, variable_id: int, value: float) -> None:
320        pass
321
322    @abc.abstractmethod
323    def get_linear_objective_coefficient(self, variable_id: int) -> float:
324        pass
325
326    @abc.abstractmethod
327    def get_linear_objective_coefficients(self) -> Iterator[LinearObjectiveEntry]:
328        """Yields the nonzero linear objective terms in undefined order."""
329        pass
330
331    @abc.abstractmethod
332    def set_quadratic_objective_coefficient(
333        self, first_variable_id: int, second_variable_id: int, value: float
334    ) -> None:
335        """Sets the objective coefficient for the product of two variables.
336
337        The ordering of the input variables does not matter.
338
339        Args:
340          first_variable_id: The first variable in the product.
341          second_variable_id: The second variable in the product.
342          value: The value of the coefficient.
343
344        Raises:
345          BadVariableIdError if first_variable_id or second_variable_id are not in
346          the model.
347        """
348
349    @abc.abstractmethod
350    def get_quadratic_objective_coefficient(
351        self, first_variable_id: int, second_variable_id: int
352    ) -> float:
353        """Gets the objective coefficient for the product of two variables.
354
355        The ordering of the input variables does not matter.
356
357        Args:
358          first_variable_id: The first variable in the product.
359          second_variable_id: The second variable in the product.
360
361        Raises:
362          BadVariableIdError if first_variable_id or second_variable_id are not in
363          the model.
364
365        Returns:
366          The value of the coefficient.
367        """
368
369    @abc.abstractmethod
370    def get_quadratic_objective_coefficients(self) -> Iterator[QuadraticEntry]:
371        """Yields the nonzero quadratic objective terms in undefined order."""
372
373    @abc.abstractmethod
374    def get_quadratic_objective_adjacent_variables(
375        self, variable_id: int
376    ) -> Iterator[int]:
377        """Yields the variables multiplying a variable in the objective function.
378
379        Variables are returned in an unspecified order.
380
381        For example, if variables x and y have ids 0 and 1 respectively, and the
382        quadratic portion of the objective is x^2 + 2 x*y, then
383        get_quadratic_objective_adjacent_variables(0) = (0, 1).
384
385        Args:
386          variable_id: Function yields the variables multiplying variable_id in the
387            objective function.
388
389        Yields:
390          The variables multiplying variable_id in the objective function.
391
392        Raises:
393          BadVariableIdError if variable_id is not in the model.
394        """
395
396    @abc.abstractmethod
397    def set_is_maximize(self, is_maximize: bool) -> None:
398        pass
399
400    @abc.abstractmethod
401    def get_is_maximize(self) -> bool:
402        pass
403
404    @abc.abstractmethod
405    def set_objective_offset(self, offset: float) -> None:
406        pass
407
408    @abc.abstractmethod
409    def get_objective_offset(self) -> float:
410        pass
411
412    @abc.abstractmethod
413    def export_model(self) -> model_pb2.ModelProto:
414        pass
415
416    @abc.abstractmethod
417    def add_update_tracker(self) -> StorageUpdateTracker:
418        """Creates a StorageUpdateTracker registered with self to view model changes."""
419        pass
420
421    @abc.abstractmethod
422    def remove_update_tracker(self, tracker: StorageUpdateTracker):
423        """Stops tracker from getting updates on model changes in self.
424
425        An error will be raised if tracker is not a StorageUpdateTracker created by
426        this Model that has not previously been removed.
427
428        Using an UpdateTracker (via checkpoint or export_update) after it has been
429        removed will result in an error.
430
431        Args:
432          tracker: The StorageUpdateTracker to unregister.
433
434        Raises:
435          KeyError: The tracker was created by another model or was already removed.
436        """
437        pass
438
439
440ModelStorageImpl = TypeVar("ModelStorageImpl", bound=ModelStorage)
441ModelStorageImplClass = Type[ModelStorageImpl]
@dataclasses.dataclass(frozen=True)
class LinearConstraintMatrixIdEntry:
27@dataclasses.dataclass(frozen=True)
28class LinearConstraintMatrixIdEntry:
29    __slots__ = "linear_constraint_id", "variable_id", "coefficient"
30    linear_constraint_id: int
31    variable_id: int
32    coefficient: float
LinearConstraintMatrixIdEntry(linear_constraint_id: int, variable_id: int, coefficient: float)
linear_constraint_id: int
variable_id: int
coefficient: float
@dataclasses.dataclass(frozen=True)
class LinearObjectiveEntry:
37@dataclasses.dataclass(frozen=True)
38class LinearObjectiveEntry:
39    __slots__ = "variable_id", "coefficient"
40    variable_id: int
41    coefficient: float
LinearObjectiveEntry(variable_id: int, coefficient: float)
variable_id: int
coefficient: float
@dataclasses.dataclass(frozen=True)
class QuadraticTermIdKey:
46@dataclasses.dataclass(frozen=True)
47class QuadraticTermIdKey:
48    """An ordered pair of ints used as a key for quadratic terms.
49
50    QuadraticTermIdKey.id1 <= QuadraticTermIdKey.id2.
51    """
52
53    __slots__ = "id1", "id2"
54    id1: int
55    id2: int
56
57    def __init__(self, a: int, b: int):
58        """Ints a and b will be ordered internally."""
59        id1 = a
60        id2 = b
61        if id1 > id2:
62            id1, id2 = id2, id1
63        object.__setattr__(self, "id1", id1)
64        object.__setattr__(self, "id2", id2)

An ordered pair of ints used as a key for quadratic terms.

QuadraticTermIdKey.id1 <= QuadraticTermIdKey.id2.

QuadraticTermIdKey(a: int, b: int)
57    def __init__(self, a: int, b: int):
58        """Ints a and b will be ordered internally."""
59        id1 = a
60        id2 = b
61        if id1 > id2:
62            id1, id2 = id2, id1
63        object.__setattr__(self, "id1", id1)
64        object.__setattr__(self, "id2", id2)

Ints a and b will be ordered internally.

id1: int
id2: int
@dataclasses.dataclass(frozen=True)
class QuadraticEntry:
69@dataclasses.dataclass(frozen=True)
70class QuadraticEntry:
71    """Represents an id-indexed quadratic term."""
72
73    __slots__ = "id_key", "coefficient"
74    id_key: QuadraticTermIdKey
75    coefficient: float

Represents an id-indexed quadratic term.

QuadraticEntry( id_key: QuadraticTermIdKey, coefficient: float)
coefficient: float
class StorageUpdateTracker(abc.ABC):
 78class StorageUpdateTracker(abc.ABC):
 79    """Tracks updates to an optimization model from a ModelStorage.
 80
 81    Do not instantiate directly, instead create through
 82    ModelStorage.add_update_tracker().
 83
 84    Interacting with an update tracker after it has been removed from the model
 85    will result in an UsedUpdateTrackerAfterRemovalError error.
 86
 87    Example:
 88      mod = model_storage.ModelStorage()
 89      x = mod.add_variable(0.0, 1.0, True, 'x')
 90      y = mod.add_variable(0.0, 1.0, True, 'y')
 91      tracker = mod.add_update_tracker()
 92      mod.set_variable_ub(x, 3.0)
 93      tracker.export_update()
 94        => "variable_updates: {upper_bounds: {ids: [0], values[3.0] }"
 95      mod.set_variable_ub(y, 2.0)
 96      tracker.export_update()
 97        => "variable_updates: {upper_bounds: {ids: [0, 1], values[3.0, 2.0] }"
 98      tracker.advance_checkpoint()
 99      tracker.export_update()
100        => ""
101      mod.set_variable_ub(y, 4.0)
102      tracker.export_update()
103        => "variable_updates: {upper_bounds: {ids: [1], values[4.0] }"
104      tracker.advance_checkpoint()
105      mod.remove_update_tracker(tracker)
106        => ""
107    """
108
109    @abc.abstractmethod
110    def export_update(self) -> Optional[model_update_pb2.ModelUpdateProto]:
111        """Returns changes to the model since last call to checkpoint/creation, or None if no changes occurred."""
112        pass
113
114    @abc.abstractmethod
115    def advance_checkpoint(self) -> None:
116        """Track changes to the model only after this function call."""
117        pass

Tracks updates to an optimization model from a ModelStorage.

Do not instantiate directly, instead create through ModelStorage.add_update_tracker().

Interacting with an update tracker after it has been removed from the model will result in an UsedUpdateTrackerAfterRemovalError error.

Example:

mod = model_storage.ModelStorage() x = mod.add_variable(0.0, 1.0, True, 'x') y = mod.add_variable(0.0, 1.0, True, 'y') tracker = mod.add_update_tracker() mod.set_variable_ub(x, 3.0) tracker.export_update() => "variable_updates: {upper_bounds: {ids: [0], values[3.0] }" mod.set_variable_ub(y, 2.0) tracker.export_update() => "variable_updates: {upper_bounds: {ids: [0, 1], values[3.0, 2.0] }" tracker.advance_checkpoint() tracker.export_update() => "" mod.set_variable_ub(y, 4.0) tracker.export_update() => "variable_updates: {upper_bounds: {ids: [1], values[4.0] }" tracker.advance_checkpoint() mod.remove_update_tracker(tracker) => ""

@abc.abstractmethod
def export_update(self) -> Optional[ortools.math_opt.model_update_pb2.ModelUpdateProto]:
109    @abc.abstractmethod
110    def export_update(self) -> Optional[model_update_pb2.ModelUpdateProto]:
111        """Returns changes to the model since last call to checkpoint/creation, or None if no changes occurred."""
112        pass

Returns changes to the model since last call to checkpoint/creation, or None if no changes occurred.

@abc.abstractmethod
def advance_checkpoint(self) -> None:
114    @abc.abstractmethod
115    def advance_checkpoint(self) -> None:
116        """Track changes to the model only after this function call."""
117        pass

Track changes to the model only after this function call.

class UsedUpdateTrackerAfterRemovalError(builtins.RuntimeError):
120class UsedUpdateTrackerAfterRemovalError(RuntimeError):
121
122    def __init__(self):
123        super().__init__(
124            "Attempted to use update tracker after removing it from model storage."
125        )

Unspecified run-time error.

Inherited Members
builtins.BaseException
with_traceback
add_note
args
class BadVariableIdError(builtins.LookupError):
128class BadVariableIdError(LookupError):
129    """Raised by ModelStorage when a bad variable id is given."""
130
131    def __init__(self, variable_id):
132        super().__init__(f"Unexpected variable id: {variable_id}")
133        self.id = variable_id

Raised by ModelStorage when a bad variable id is given.

BadVariableIdError(variable_id)
131    def __init__(self, variable_id):
132        super().__init__(f"Unexpected variable id: {variable_id}")
133        self.id = variable_id
id
Inherited Members
builtins.BaseException
with_traceback
add_note
args
class BadLinearConstraintIdError(builtins.LookupError):
136class BadLinearConstraintIdError(LookupError):
137    """Raised by ModelStorage when a bad linear constraint id is given."""
138
139    def __init__(self, linear_constraint_id):
140        super().__init__(f"Unexpected linear constraint id: {linear_constraint_id}")
141        self.id = linear_constraint_id

Raised by ModelStorage when a bad linear constraint id is given.

BadLinearConstraintIdError(linear_constraint_id)
139    def __init__(self, linear_constraint_id):
140        super().__init__(f"Unexpected linear constraint id: {linear_constraint_id}")
141        self.id = linear_constraint_id
id
Inherited Members
builtins.BaseException
with_traceback
add_note
args
class ModelStorage(abc.ABC):
144class ModelStorage(abc.ABC):
145    """An interface for in memory storage of an optimization model.
146
147    Most users should not use this class directly and use Model defined in
148    model.py.
149
150    Stores an mixed integer programming problem of the form:
151
152    {max/min} c*x + d
153    s.t.  lb_c <= A * x <= ub_c
154          lb_v <=     x <= ub_v
155                      x_i integer for i in I
156
157    where x is a vector of n decision variables, d is a number, lb_v, ub_v, and c
158    are vectors of n numbers, lb_c and ub_c are vectors of m numbers, A is a
159    m by n matrix, and I is a subset of {1,..., n}.
160
161    Each of the n variables and m constraints have an integer id that you use to
162    get/set the problem data (c, A, lb_c etc.). Ids begin at zero and increase
163    sequentially. They are not reused after deletion. Note that if a variable is
164    deleted, your model has nonconsecutive variable ids.
165
166    For all methods taking an id (e.g. set_variable_lb), providing a bad id
167    (including the id of a deleted variable) will raise a BadVariableIdError or
168    BadLinearConstraintIdError. Further, the ModelStorage instance is assumed to
169    be in a bad state after any such error and there are no guarantees on further
170    interactions.
171
172    All implementations must have a constructor taking a str argument for the
173    model name with a default value of the empty string.
174
175    Any ModelStorage can be exported to model_pb2.ModelProto, the format consumed
176    by MathOpt solvers. Changes to a model can be exported to a
177    model_update_pb2.ModelUpdateProto with an UpdateTracker, see the UpdateTracker
178    documentation for details.
179
180    When solving this optimization problem we will additionally require that:
181      * No numbers are NaN,
182      * c, d, and A are all finite,
183      * lb_c and lb_v are not +inf,
184      * ub_c and ub_v are not -inf,
185    but those assumptions are not checked or enforced here (NaNs and infinite
186    values can be used anywhere).
187    """
188
189    @property
190    @abc.abstractmethod
191    def name(self) -> str:
192        pass
193
194    @abc.abstractmethod
195    def add_variable(self, lb: float, ub: float, is_integer: bool, name: str) -> int:
196        pass
197
198    @abc.abstractmethod
199    def delete_variable(self, variable_id: int) -> None:
200        pass
201
202    @abc.abstractmethod
203    def variable_exists(self, variable_id: int) -> bool:
204        pass
205
206    @abc.abstractmethod
207    def next_variable_id(self) -> int:
208        pass
209
210    @abc.abstractmethod
211    def set_variable_lb(self, variable_id: int, lb: float) -> None:
212        pass
213
214    @abc.abstractmethod
215    def set_variable_ub(self, variable_id: int, ub: float) -> None:
216        pass
217
218    @abc.abstractmethod
219    def set_variable_is_integer(self, variable_id: int, is_integer: bool) -> None:
220        pass
221
222    @abc.abstractmethod
223    def get_variable_lb(self, variable_id: int) -> float:
224        pass
225
226    @abc.abstractmethod
227    def get_variable_ub(self, variable_id: int) -> float:
228        pass
229
230    @abc.abstractmethod
231    def get_variable_is_integer(self, variable_id: int) -> bool:
232        pass
233
234    @abc.abstractmethod
235    def get_variable_name(self, variable_id: int) -> str:
236        pass
237
238    @abc.abstractmethod
239    def get_variables(self) -> Iterator[int]:
240        """Yields the variable ids in order of creation."""
241        pass
242
243    @abc.abstractmethod
244    def add_linear_constraint(self, lb: float, ub: float, name: str) -> int:
245        pass
246
247    @abc.abstractmethod
248    def delete_linear_constraint(self, linear_constraint_id: int) -> None:
249        pass
250
251    @abc.abstractmethod
252    def linear_constraint_exists(self, linear_constraint_id: int) -> bool:
253        pass
254
255    @abc.abstractmethod
256    def next_linear_constraint_id(self) -> int:
257        pass
258
259    @abc.abstractmethod
260    def set_linear_constraint_lb(self, linear_constraint_id: int, lb: float) -> None:
261        pass
262
263    @abc.abstractmethod
264    def set_linear_constraint_ub(self, linear_constraint_id: int, ub: float) -> None:
265        pass
266
267    @abc.abstractmethod
268    def get_linear_constraint_lb(self, linear_constraint_id: int) -> float:
269        pass
270
271    @abc.abstractmethod
272    def get_linear_constraint_ub(self, linear_constraint_id: int) -> float:
273        pass
274
275    @abc.abstractmethod
276    def get_linear_constraint_name(self, linear_constraint_id: int) -> str:
277        pass
278
279    @abc.abstractmethod
280    def get_linear_constraints(self) -> Iterator[int]:
281        """Yields the linear constraint ids in order of creation."""
282        pass
283
284    @abc.abstractmethod
285    def set_linear_constraint_coefficient(
286        self, linear_constraint_id: int, variable_id: int, lb: float
287    ) -> None:
288        pass
289
290    @abc.abstractmethod
291    def get_linear_constraint_coefficient(
292        self, linear_constraint_id: int, variable_id: int
293    ) -> float:
294        pass
295
296    @abc.abstractmethod
297    def get_linear_constraints_with_variable(self, variable_id: int) -> Iterator[int]:
298        """Yields the linear constraints with nonzero coefficient for a variable in undefined order."""
299        pass
300
301    @abc.abstractmethod
302    def get_variables_for_linear_constraint(
303        self, linear_constraint_id: int
304    ) -> Iterator[int]:
305        """Yields the variables with nonzero coefficient in a linear constraint in undefined order."""
306        pass
307
308    @abc.abstractmethod
309    def get_linear_constraint_matrix_entries(
310        self,
311    ) -> Iterator[LinearConstraintMatrixIdEntry]:
312        """Yields the nonzero elements of the linear constraint matrix in undefined order."""
313        pass
314
315    @abc.abstractmethod
316    def clear_objective(self) -> None:
317        """Clears objective coefficients and offset. Does not change direction."""
318
319    @abc.abstractmethod
320    def set_linear_objective_coefficient(self, variable_id: int, value: float) -> None:
321        pass
322
323    @abc.abstractmethod
324    def get_linear_objective_coefficient(self, variable_id: int) -> float:
325        pass
326
327    @abc.abstractmethod
328    def get_linear_objective_coefficients(self) -> Iterator[LinearObjectiveEntry]:
329        """Yields the nonzero linear objective terms in undefined order."""
330        pass
331
332    @abc.abstractmethod
333    def set_quadratic_objective_coefficient(
334        self, first_variable_id: int, second_variable_id: int, value: float
335    ) -> None:
336        """Sets the objective coefficient for the product of two variables.
337
338        The ordering of the input variables does not matter.
339
340        Args:
341          first_variable_id: The first variable in the product.
342          second_variable_id: The second variable in the product.
343          value: The value of the coefficient.
344
345        Raises:
346          BadVariableIdError if first_variable_id or second_variable_id are not in
347          the model.
348        """
349
350    @abc.abstractmethod
351    def get_quadratic_objective_coefficient(
352        self, first_variable_id: int, second_variable_id: int
353    ) -> float:
354        """Gets the objective coefficient for the product of two variables.
355
356        The ordering of the input variables does not matter.
357
358        Args:
359          first_variable_id: The first variable in the product.
360          second_variable_id: The second variable in the product.
361
362        Raises:
363          BadVariableIdError if first_variable_id or second_variable_id are not in
364          the model.
365
366        Returns:
367          The value of the coefficient.
368        """
369
370    @abc.abstractmethod
371    def get_quadratic_objective_coefficients(self) -> Iterator[QuadraticEntry]:
372        """Yields the nonzero quadratic objective terms in undefined order."""
373
374    @abc.abstractmethod
375    def get_quadratic_objective_adjacent_variables(
376        self, variable_id: int
377    ) -> Iterator[int]:
378        """Yields the variables multiplying a variable in the objective function.
379
380        Variables are returned in an unspecified order.
381
382        For example, if variables x and y have ids 0 and 1 respectively, and the
383        quadratic portion of the objective is x^2 + 2 x*y, then
384        get_quadratic_objective_adjacent_variables(0) = (0, 1).
385
386        Args:
387          variable_id: Function yields the variables multiplying variable_id in the
388            objective function.
389
390        Yields:
391          The variables multiplying variable_id in the objective function.
392
393        Raises:
394          BadVariableIdError if variable_id is not in the model.
395        """
396
397    @abc.abstractmethod
398    def set_is_maximize(self, is_maximize: bool) -> None:
399        pass
400
401    @abc.abstractmethod
402    def get_is_maximize(self) -> bool:
403        pass
404
405    @abc.abstractmethod
406    def set_objective_offset(self, offset: float) -> None:
407        pass
408
409    @abc.abstractmethod
410    def get_objective_offset(self) -> float:
411        pass
412
413    @abc.abstractmethod
414    def export_model(self) -> model_pb2.ModelProto:
415        pass
416
417    @abc.abstractmethod
418    def add_update_tracker(self) -> StorageUpdateTracker:
419        """Creates a StorageUpdateTracker registered with self to view model changes."""
420        pass
421
422    @abc.abstractmethod
423    def remove_update_tracker(self, tracker: StorageUpdateTracker):
424        """Stops tracker from getting updates on model changes in self.
425
426        An error will be raised if tracker is not a StorageUpdateTracker created by
427        this Model that has not previously been removed.
428
429        Using an UpdateTracker (via checkpoint or export_update) after it has been
430        removed will result in an error.
431
432        Args:
433          tracker: The StorageUpdateTracker to unregister.
434
435        Raises:
436          KeyError: The tracker was created by another model or was already removed.
437        """
438        pass

An interface for in memory storage of an optimization model.

Most users should not use this class directly and use Model defined in model.py.

Stores an mixed integer programming problem of the form:

{max/min} c*x + d s.t. lb_c <= A * x <= ub_c lb_v <= x <= ub_v x_i integer for i in I

where x is a vector of n decision variables, d is a number, lb_v, ub_v, and c are vectors of n numbers, lb_c and ub_c are vectors of m numbers, A is a m by n matrix, and I is a subset of {1,..., n}.

Each of the n variables and m constraints have an integer id that you use to get/set the problem data (c, A, lb_c etc.). Ids begin at zero and increase sequentially. They are not reused after deletion. Note that if a variable is deleted, your model has nonconsecutive variable ids.

For all methods taking an id (e.g. set_variable_lb), providing a bad id (including the id of a deleted variable) will raise a BadVariableIdError or BadLinearConstraintIdError. Further, the ModelStorage instance is assumed to be in a bad state after any such error and there are no guarantees on further interactions.

All implementations must have a constructor taking a str argument for the model name with a default value of the empty string.

Any ModelStorage can be exported to model_pb2.ModelProto, the format consumed by MathOpt solvers. Changes to a model can be exported to a model_update_pb2.ModelUpdateProto with an UpdateTracker, see the UpdateTracker documentation for details.

When solving this optimization problem we will additionally require that:
  • No numbers are NaN,
  • c, d, and A are all finite,
  • lb_c and lb_v are not +inf,
  • ub_c and ub_v are not -inf,

but those assumptions are not checked or enforced here (NaNs and infinite values can be used anywhere).

name: str
189    @property
190    @abc.abstractmethod
191    def name(self) -> str:
192        pass
@abc.abstractmethod
def add_variable(self, lb: float, ub: float, is_integer: bool, name: str) -> int:
194    @abc.abstractmethod
195    def add_variable(self, lb: float, ub: float, is_integer: bool, name: str) -> int:
196        pass
@abc.abstractmethod
def delete_variable(self, variable_id: int) -> None:
198    @abc.abstractmethod
199    def delete_variable(self, variable_id: int) -> None:
200        pass
@abc.abstractmethod
def variable_exists(self, variable_id: int) -> bool:
202    @abc.abstractmethod
203    def variable_exists(self, variable_id: int) -> bool:
204        pass
@abc.abstractmethod
def next_variable_id(self) -> int:
206    @abc.abstractmethod
207    def next_variable_id(self) -> int:
208        pass
@abc.abstractmethod
def set_variable_lb(self, variable_id: int, lb: float) -> None:
210    @abc.abstractmethod
211    def set_variable_lb(self, variable_id: int, lb: float) -> None:
212        pass
@abc.abstractmethod
def set_variable_ub(self, variable_id: int, ub: float) -> None:
214    @abc.abstractmethod
215    def set_variable_ub(self, variable_id: int, ub: float) -> None:
216        pass
@abc.abstractmethod
def set_variable_is_integer(self, variable_id: int, is_integer: bool) -> None:
218    @abc.abstractmethod
219    def set_variable_is_integer(self, variable_id: int, is_integer: bool) -> None:
220        pass
@abc.abstractmethod
def get_variable_lb(self, variable_id: int) -> float:
222    @abc.abstractmethod
223    def get_variable_lb(self, variable_id: int) -> float:
224        pass
@abc.abstractmethod
def get_variable_ub(self, variable_id: int) -> float:
226    @abc.abstractmethod
227    def get_variable_ub(self, variable_id: int) -> float:
228        pass
@abc.abstractmethod
def get_variable_is_integer(self, variable_id: int) -> bool:
230    @abc.abstractmethod
231    def get_variable_is_integer(self, variable_id: int) -> bool:
232        pass
@abc.abstractmethod
def get_variable_name(self, variable_id: int) -> str:
234    @abc.abstractmethod
235    def get_variable_name(self, variable_id: int) -> str:
236        pass
@abc.abstractmethod
def get_variables(self) -> Iterator[int]:
238    @abc.abstractmethod
239    def get_variables(self) -> Iterator[int]:
240        """Yields the variable ids in order of creation."""
241        pass

Yields the variable ids in order of creation.

@abc.abstractmethod
def add_linear_constraint(self, lb: float, ub: float, name: str) -> int:
243    @abc.abstractmethod
244    def add_linear_constraint(self, lb: float, ub: float, name: str) -> int:
245        pass
@abc.abstractmethod
def delete_linear_constraint(self, linear_constraint_id: int) -> None:
247    @abc.abstractmethod
248    def delete_linear_constraint(self, linear_constraint_id: int) -> None:
249        pass
@abc.abstractmethod
def linear_constraint_exists(self, linear_constraint_id: int) -> bool:
251    @abc.abstractmethod
252    def linear_constraint_exists(self, linear_constraint_id: int) -> bool:
253        pass
@abc.abstractmethod
def next_linear_constraint_id(self) -> int:
255    @abc.abstractmethod
256    def next_linear_constraint_id(self) -> int:
257        pass
@abc.abstractmethod
def set_linear_constraint_lb(self, linear_constraint_id: int, lb: float) -> None:
259    @abc.abstractmethod
260    def set_linear_constraint_lb(self, linear_constraint_id: int, lb: float) -> None:
261        pass
@abc.abstractmethod
def set_linear_constraint_ub(self, linear_constraint_id: int, ub: float) -> None:
263    @abc.abstractmethod
264    def set_linear_constraint_ub(self, linear_constraint_id: int, ub: float) -> None:
265        pass
@abc.abstractmethod
def get_linear_constraint_lb(self, linear_constraint_id: int) -> float:
267    @abc.abstractmethod
268    def get_linear_constraint_lb(self, linear_constraint_id: int) -> float:
269        pass
@abc.abstractmethod
def get_linear_constraint_ub(self, linear_constraint_id: int) -> float:
271    @abc.abstractmethod
272    def get_linear_constraint_ub(self, linear_constraint_id: int) -> float:
273        pass
@abc.abstractmethod
def get_linear_constraint_name(self, linear_constraint_id: int) -> str:
275    @abc.abstractmethod
276    def get_linear_constraint_name(self, linear_constraint_id: int) -> str:
277        pass
@abc.abstractmethod
def get_linear_constraints(self) -> Iterator[int]:
279    @abc.abstractmethod
280    def get_linear_constraints(self) -> Iterator[int]:
281        """Yields the linear constraint ids in order of creation."""
282        pass

Yields the linear constraint ids in order of creation.

@abc.abstractmethod
def set_linear_constraint_coefficient(self, linear_constraint_id: int, variable_id: int, lb: float) -> None:
284    @abc.abstractmethod
285    def set_linear_constraint_coefficient(
286        self, linear_constraint_id: int, variable_id: int, lb: float
287    ) -> None:
288        pass
@abc.abstractmethod
def get_linear_constraint_coefficient(self, linear_constraint_id: int, variable_id: int) -> float:
290    @abc.abstractmethod
291    def get_linear_constraint_coefficient(
292        self, linear_constraint_id: int, variable_id: int
293    ) -> float:
294        pass
@abc.abstractmethod
def get_linear_constraints_with_variable(self, variable_id: int) -> Iterator[int]:
296    @abc.abstractmethod
297    def get_linear_constraints_with_variable(self, variable_id: int) -> Iterator[int]:
298        """Yields the linear constraints with nonzero coefficient for a variable in undefined order."""
299        pass

Yields the linear constraints with nonzero coefficient for a variable in undefined order.

@abc.abstractmethod
def get_variables_for_linear_constraint(self, linear_constraint_id: int) -> Iterator[int]:
301    @abc.abstractmethod
302    def get_variables_for_linear_constraint(
303        self, linear_constraint_id: int
304    ) -> Iterator[int]:
305        """Yields the variables with nonzero coefficient in a linear constraint in undefined order."""
306        pass

Yields the variables with nonzero coefficient in a linear constraint in undefined order.

@abc.abstractmethod
def get_linear_constraint_matrix_entries( self) -> Iterator[LinearConstraintMatrixIdEntry]:
308    @abc.abstractmethod
309    def get_linear_constraint_matrix_entries(
310        self,
311    ) -> Iterator[LinearConstraintMatrixIdEntry]:
312        """Yields the nonzero elements of the linear constraint matrix in undefined order."""
313        pass

Yields the nonzero elements of the linear constraint matrix in undefined order.

@abc.abstractmethod
def clear_objective(self) -> None:
315    @abc.abstractmethod
316    def clear_objective(self) -> None:
317        """Clears objective coefficients and offset. Does not change direction."""

Clears objective coefficients and offset. Does not change direction.

@abc.abstractmethod
def set_linear_objective_coefficient(self, variable_id: int, value: float) -> None:
319    @abc.abstractmethod
320    def set_linear_objective_coefficient(self, variable_id: int, value: float) -> None:
321        pass
@abc.abstractmethod
def get_linear_objective_coefficient(self, variable_id: int) -> float:
323    @abc.abstractmethod
324    def get_linear_objective_coefficient(self, variable_id: int) -> float:
325        pass
@abc.abstractmethod
def get_linear_objective_coefficients( self) -> Iterator[LinearObjectiveEntry]:
327    @abc.abstractmethod
328    def get_linear_objective_coefficients(self) -> Iterator[LinearObjectiveEntry]:
329        """Yields the nonzero linear objective terms in undefined order."""
330        pass

Yields the nonzero linear objective terms in undefined order.

@abc.abstractmethod
def set_quadratic_objective_coefficient( self, first_variable_id: int, second_variable_id: int, value: float) -> None:
332    @abc.abstractmethod
333    def set_quadratic_objective_coefficient(
334        self, first_variable_id: int, second_variable_id: int, value: float
335    ) -> None:
336        """Sets the objective coefficient for the product of two variables.
337
338        The ordering of the input variables does not matter.
339
340        Args:
341          first_variable_id: The first variable in the product.
342          second_variable_id: The second variable in the product.
343          value: The value of the coefficient.
344
345        Raises:
346          BadVariableIdError if first_variable_id or second_variable_id are not in
347          the model.
348        """

Sets the objective coefficient for the product of two variables.

The ordering of the input variables does not matter.

Arguments:
  • first_variable_id: The first variable in the product.
  • second_variable_id: The second variable in the product.
  • value: The value of the coefficient.
Raises:
  • BadVariableIdError if first_variable_id or second_variable_id are not in
  • the model.
@abc.abstractmethod
def get_quadratic_objective_coefficient(self, first_variable_id: int, second_variable_id: int) -> float:
350    @abc.abstractmethod
351    def get_quadratic_objective_coefficient(
352        self, first_variable_id: int, second_variable_id: int
353    ) -> float:
354        """Gets the objective coefficient for the product of two variables.
355
356        The ordering of the input variables does not matter.
357
358        Args:
359          first_variable_id: The first variable in the product.
360          second_variable_id: The second variable in the product.
361
362        Raises:
363          BadVariableIdError if first_variable_id or second_variable_id are not in
364          the model.
365
366        Returns:
367          The value of the coefficient.
368        """

Gets the objective coefficient for the product of two variables.

The ordering of the input variables does not matter.

Arguments:
  • first_variable_id: The first variable in the product.
  • second_variable_id: The second variable in the product.
Raises:
  • BadVariableIdError if first_variable_id or second_variable_id are not in
  • the model.
Returns:

The value of the coefficient.

@abc.abstractmethod
def get_quadratic_objective_coefficients(self) -> Iterator[QuadraticEntry]:
370    @abc.abstractmethod
371    def get_quadratic_objective_coefficients(self) -> Iterator[QuadraticEntry]:
372        """Yields the nonzero quadratic objective terms in undefined order."""

Yields the nonzero quadratic objective terms in undefined order.

@abc.abstractmethod
def get_quadratic_objective_adjacent_variables(self, variable_id: int) -> Iterator[int]:
374    @abc.abstractmethod
375    def get_quadratic_objective_adjacent_variables(
376        self, variable_id: int
377    ) -> Iterator[int]:
378        """Yields the variables multiplying a variable in the objective function.
379
380        Variables are returned in an unspecified order.
381
382        For example, if variables x and y have ids 0 and 1 respectively, and the
383        quadratic portion of the objective is x^2 + 2 x*y, then
384        get_quadratic_objective_adjacent_variables(0) = (0, 1).
385
386        Args:
387          variable_id: Function yields the variables multiplying variable_id in the
388            objective function.
389
390        Yields:
391          The variables multiplying variable_id in the objective function.
392
393        Raises:
394          BadVariableIdError if variable_id is not in the model.
395        """

Yields the variables multiplying a variable in the objective function.

Variables are returned in an unspecified order.

For example, if variables x and y have ids 0 and 1 respectively, and the quadratic portion of the objective is x^2 + 2 x*y, then get_quadratic_objective_adjacent_variables(0) = (0, 1).

Arguments:
  • variable_id: Function yields the variables multiplying variable_id in the objective function.
Yields:

The variables multiplying variable_id in the objective function.

Raises:
  • BadVariableIdError if variable_id is not in the model.
@abc.abstractmethod
def set_is_maximize(self, is_maximize: bool) -> None:
397    @abc.abstractmethod
398    def set_is_maximize(self, is_maximize: bool) -> None:
399        pass
@abc.abstractmethod
def get_is_maximize(self) -> bool:
401    @abc.abstractmethod
402    def get_is_maximize(self) -> bool:
403        pass
@abc.abstractmethod
def set_objective_offset(self, offset: float) -> None:
405    @abc.abstractmethod
406    def set_objective_offset(self, offset: float) -> None:
407        pass
@abc.abstractmethod
def get_objective_offset(self) -> float:
409    @abc.abstractmethod
410    def get_objective_offset(self) -> float:
411        pass
@abc.abstractmethod
def export_model(self) -> ortools.math_opt.model_pb2.ModelProto:
413    @abc.abstractmethod
414    def export_model(self) -> model_pb2.ModelProto:
415        pass
@abc.abstractmethod
def add_update_tracker(self) -> StorageUpdateTracker:
417    @abc.abstractmethod
418    def add_update_tracker(self) -> StorageUpdateTracker:
419        """Creates a StorageUpdateTracker registered with self to view model changes."""
420        pass

Creates a StorageUpdateTracker registered with self to view model changes.

@abc.abstractmethod
def remove_update_tracker( self, tracker: StorageUpdateTracker):
422    @abc.abstractmethod
423    def remove_update_tracker(self, tracker: StorageUpdateTracker):
424        """Stops tracker from getting updates on model changes in self.
425
426        An error will be raised if tracker is not a StorageUpdateTracker created by
427        this Model that has not previously been removed.
428
429        Using an UpdateTracker (via checkpoint or export_update) after it has been
430        removed will result in an error.
431
432        Args:
433          tracker: The StorageUpdateTracker to unregister.
434
435        Raises:
436          KeyError: The tracker was created by another model or was already removed.
437        """
438        pass

Stops tracker from getting updates on model changes in self.

An error will be raised if tracker is not a StorageUpdateTracker created by this Model that has not previously been removed.

Using an UpdateTracker (via checkpoint or export_update) after it has been removed will result in an error.

Arguments:
  • tracker: The StorageUpdateTracker to unregister.
Raises:
  • KeyError: The tracker was created by another model or was already removed.
ModelStorageImplClass = typing.Type[~ModelStorageImpl]