ortools.linear_solver.python.linear_solver_natural_api

Patch to the python wrapper of ../linear_solver.h providing an algebraic API.

This is directly imported, and use exclusively in ./linear_solver.swig. See that file. For examples leveraging the code defined here, see ./pywraplp_test.py and ../../../python/linear_programming.py.

  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"""Patch to the python wrapper of ../linear_solver.h providing an algebraic API.
 16
 17This is directly imported, and use exclusively in ./linear_solver.swig. See that
 18file.
 19For examples leveraging the code defined here, see ./pywraplp_test.py and
 20../../../python/linear_programming.py.
 21"""
 22
 23import collections
 24import numbers
 25
 26# The classes below allow linear expressions to be expressed naturally with the
 27# usual arithmetic operators +-*/ and with constant numbers, which makes the
 28# python API very intuitive. See the top-level comment for examples.
 29
 30
 31inf = float("inf")
 32
 33
 34class _FakeMPVariableRepresentingTheConstantOffset:
 35    """A dummy class for a singleton instance used to represent the constant.
 36
 37    To represent linear expressions, we store a dictionary
 38    MPVariable->coefficient. To represent the constant offset of the expression,
 39    we use this class as a substitute: its coefficient will be the offset. To
 40    properly be evaluated, its solution_value() needs to be 1.
 41    """
 42
 43    def solution_value(self):  # pylint: disable=invalid-name
 44        return 1
 45
 46    def __repr__(self):
 47        return "OFFSET_KEY"
 48
 49
 50OFFSET_KEY = _FakeMPVariableRepresentingTheConstantOffset()
 51
 52
 53def CastToLinExp(v):
 54    if isinstance(v, numbers.Number):
 55        return Constant(v)
 56    else:
 57        return v
 58
 59
 60class LinearExpr:
 61    """Holds linear expressions.
 62
 63    A linear expression is essentially an offset (floating-point value), and a
 64    dictionary mapping MPVariable objects to their coefficient (which is also a
 65    floating-point value).
 66    """
 67
 68    OVERRIDDEN_OPERATOR_METHODS = [
 69        "__%s__" % opname
 70        for opname in [
 71            "add",
 72            "radd",
 73            "sub",
 74            "rsub",
 75            "mul",
 76            "rmul",
 77            "div",
 78            "truediv",
 79            "neg",
 80            "eq",
 81            "ge",
 82            "le",
 83            "gt",
 84            "lt",
 85            "ne",
 86        ]
 87    ]
 88
 89    def solution_value(self):  # pylint: disable=invalid-name
 90        """Value of this linear expr, using the solution_value of its vars."""
 91        coeffs = self.GetCoeffs()
 92        return sum(var.solution_value() * coeff for var, coeff in coeffs.items())
 93
 94    def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
 95        """Private function used by GetCoeffs() to delegate processing.
 96
 97        Implementation must either update coeffs or push to the stack a
 98        sub-expression and the accumulated multiplier that applies to it.
 99
100        Args:
101          coeffs: A dictionary of variables' coefficients. It is a defaultdict that
102              initializes the new values to 0 by default.
103          multiplier: The current accumulated multiplier to apply to this
104              expression.
105          stack: A list to append to if the current expression is composed of
106              sub-expressions. The elements of the stack are pair tuples
107              (multiplier, linear_expression).
108        """
109        raise NotImplementedError
110
111    def GetCoeffs(self):
112        coeffs = collections.defaultdict(float)
113        stack = [(1.0, self)]
114        while stack:
115            current_multiplier, current_expression = stack.pop()
116            current_expression.AddSelfToCoeffMapOrStack(
117                coeffs, current_multiplier, stack
118            )
119        return coeffs
120
121    def __add__(self, expr):
122        return Sum(self, expr)
123
124    def __radd__(self, cst):
125        return Sum(self, cst)
126
127    def __sub__(self, expr):
128        return Sum(self, -expr)
129
130    def __rsub__(self, cst):
131        return Sum(-self, cst)
132
133    def __mul__(self, cst):
134        return ProductCst(self, cst)
135
136    def __rmul__(self, cst):
137        return ProductCst(self, cst)
138
139    def __div__(self, cst):
140        return ProductCst(self, 1.0 / cst)
141
142    def __truediv__(self, cst):
143        return ProductCst(self, 1.0 / cst)
144
145    def __neg__(self):
146        return ProductCst(self, -1)
147
148    def __eq__(self, arg):
149        if isinstance(arg, numbers.Number):
150            return LinearConstraint(self, arg, arg)
151        else:
152            return LinearConstraint(self - arg, 0.0, 0.0)
153
154    def __ge__(self, arg):
155        if isinstance(arg, numbers.Number):
156            return LinearConstraint(self, arg, inf)
157        else:
158            return LinearConstraint(self - arg, 0.0, inf)
159
160    def __le__(self, arg):
161        if isinstance(arg, numbers.Number):
162            return LinearConstraint(self, -inf, arg)
163        else:
164            return LinearConstraint(self - arg, -inf, 0.0)
165
166    def __lt__(self, arg):
167        raise ValueError('Operators "<" and ">" not supported with the linear solver')
168
169    def __gt__(self, arg):
170        raise ValueError('Operators "<" and ">" not supported with the linear solver')
171
172    def __ne__(self, arg):
173        raise ValueError('Operator "!=" not supported with the linear solver')
174
175
176class VariableExpr(LinearExpr):
177    """Represents a LinearExpr containing only a single variable."""
178
179    def __init__(self, mpvar):
180        self.__var = mpvar
181
182    def __str__(self):
183        return str(self.__var)
184
185    def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
186        coeffs[self.__var] += multiplier
187
188
189class ProductCst(LinearExpr):
190    """Represents the product of a LinearExpr by a constant."""
191
192    def __init__(self, expr, coef):
193        self.__expr = CastToLinExp(expr)
194        if isinstance(coef, numbers.Number):
195            self.__coef = coef
196        else:
197            raise TypeError
198
199    def __str__(self):
200        if self.__coef == -1:
201            return "-" + str(self.__expr)
202        else:
203            return "(" + str(self.__coef) + " * " + str(self.__expr) + ")"
204
205    def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
206        current_multiplier = multiplier * self.__coef
207        if current_multiplier:
208            stack.append((current_multiplier, self.__expr))
209
210
211class Constant(LinearExpr):
212
213    def __init__(self, val):
214        self.__val = val
215
216    def __str__(self):
217        return str(self.__val)
218
219    def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
220        coeffs[OFFSET_KEY] += self.__val * multiplier
221
222
223class SumArray(LinearExpr):
224    """Represents the sum of a list of LinearExpr."""
225
226    def __init__(self, array):
227        self.__array = [CastToLinExp(elem) for elem in array]
228
229    def __str__(self):
230        parts = []
231        for term in map(str, self.__array):
232            if not parts:
233                parts.append(term)
234                continue
235            if term[0] == "-":
236                parts.append(" - " + term[1:])
237            else:
238                parts.append(" + " + term)
239        return f'({"".join(parts)})'
240
241    def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
242        # Append elements in reversed order so that the first popped from the stack
243        # in the next iteration of the evaluation loop will be the first item of the
244        # array. This keeps the end result of the floating point computation
245        # predictable from user perspective.
246        for arg in reversed(self.__array):
247            stack.append((multiplier, arg))
248
249
250def Sum(*args):
251    return SumArray(args)
252
253
254SumCst = Sum  # pylint: disable=invalid-name
255
256
257class LinearConstraint:
258    """Represents a linear constraint: LowerBound <= LinearExpr <= UpperBound."""
259
260    def __init__(self, expr, lb, ub):
261        self.__expr = expr
262        self.__lb = lb
263        self.__ub = ub
264
265    def __str__(self):
266        if self.__lb > -inf and self.__ub < inf:
267            if self.__lb == self.__ub:
268                return str(self.__expr) + " == " + str(self.__lb)
269            else:
270                return (
271                    str(self.__lb) + " <= " + str(self.__expr) + " <= " + str(self.__ub)
272                )
273        elif self.__lb > -inf:
274            return str(self.__expr) + " >= " + str(self.__lb)
275        elif self.__ub < inf:
276            return str(self.__expr) + " <= " + str(self.__ub)
277        else:
278            return "Trivial inequality (always true)"
279
280    def Extract(self, solver, name=""):
281        """Performs the actual creation of the constraint object."""
282        coeffs = self.__expr.GetCoeffs()
283        constant = coeffs.pop(OFFSET_KEY, 0.0)
284        lb = -solver.infinity()
285        ub = solver.infinity()
286        if self.__lb > -inf:
287            lb = self.__lb - constant
288        if self.__ub < inf:
289            ub = self.__ub - constant
290
291        constraint = solver.RowConstraint(lb, ub, name)
292        for (
293            v,
294            c,
295        ) in coeffs.items():
296            constraint.SetCoefficient(v, float(c))
297        return constraint
inf = inf
OFFSET_KEY = OFFSET_KEY
def CastToLinExp(v):
54def CastToLinExp(v):
55    if isinstance(v, numbers.Number):
56        return Constant(v)
57    else:
58        return v
class LinearExpr:
 61class LinearExpr:
 62    """Holds linear expressions.
 63
 64    A linear expression is essentially an offset (floating-point value), and a
 65    dictionary mapping MPVariable objects to their coefficient (which is also a
 66    floating-point value).
 67    """
 68
 69    OVERRIDDEN_OPERATOR_METHODS = [
 70        "__%s__" % opname
 71        for opname in [
 72            "add",
 73            "radd",
 74            "sub",
 75            "rsub",
 76            "mul",
 77            "rmul",
 78            "div",
 79            "truediv",
 80            "neg",
 81            "eq",
 82            "ge",
 83            "le",
 84            "gt",
 85            "lt",
 86            "ne",
 87        ]
 88    ]
 89
 90    def solution_value(self):  # pylint: disable=invalid-name
 91        """Value of this linear expr, using the solution_value of its vars."""
 92        coeffs = self.GetCoeffs()
 93        return sum(var.solution_value() * coeff for var, coeff in coeffs.items())
 94
 95    def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
 96        """Private function used by GetCoeffs() to delegate processing.
 97
 98        Implementation must either update coeffs or push to the stack a
 99        sub-expression and the accumulated multiplier that applies to it.
100
101        Args:
102          coeffs: A dictionary of variables' coefficients. It is a defaultdict that
103              initializes the new values to 0 by default.
104          multiplier: The current accumulated multiplier to apply to this
105              expression.
106          stack: A list to append to if the current expression is composed of
107              sub-expressions. The elements of the stack are pair tuples
108              (multiplier, linear_expression).
109        """
110        raise NotImplementedError
111
112    def GetCoeffs(self):
113        coeffs = collections.defaultdict(float)
114        stack = [(1.0, self)]
115        while stack:
116            current_multiplier, current_expression = stack.pop()
117            current_expression.AddSelfToCoeffMapOrStack(
118                coeffs, current_multiplier, stack
119            )
120        return coeffs
121
122    def __add__(self, expr):
123        return Sum(self, expr)
124
125    def __radd__(self, cst):
126        return Sum(self, cst)
127
128    def __sub__(self, expr):
129        return Sum(self, -expr)
130
131    def __rsub__(self, cst):
132        return Sum(-self, cst)
133
134    def __mul__(self, cst):
135        return ProductCst(self, cst)
136
137    def __rmul__(self, cst):
138        return ProductCst(self, cst)
139
140    def __div__(self, cst):
141        return ProductCst(self, 1.0 / cst)
142
143    def __truediv__(self, cst):
144        return ProductCst(self, 1.0 / cst)
145
146    def __neg__(self):
147        return ProductCst(self, -1)
148
149    def __eq__(self, arg):
150        if isinstance(arg, numbers.Number):
151            return LinearConstraint(self, arg, arg)
152        else:
153            return LinearConstraint(self - arg, 0.0, 0.0)
154
155    def __ge__(self, arg):
156        if isinstance(arg, numbers.Number):
157            return LinearConstraint(self, arg, inf)
158        else:
159            return LinearConstraint(self - arg, 0.0, inf)
160
161    def __le__(self, arg):
162        if isinstance(arg, numbers.Number):
163            return LinearConstraint(self, -inf, arg)
164        else:
165            return LinearConstraint(self - arg, -inf, 0.0)
166
167    def __lt__(self, arg):
168        raise ValueError('Operators "<" and ">" not supported with the linear solver')
169
170    def __gt__(self, arg):
171        raise ValueError('Operators "<" and ">" not supported with the linear solver')
172
173    def __ne__(self, arg):
174        raise ValueError('Operator "!=" not supported with the linear solver')

Holds linear expressions.

A linear expression is essentially an offset (floating-point value), and a dictionary mapping MPVariable objects to their coefficient (which is also a floating-point value).

OVERRIDDEN_OPERATOR_METHODS = ['__add__', '__radd__', '__sub__', '__rsub__', '__mul__', '__rmul__', '__div__', '__truediv__', '__neg__', '__eq__', '__ge__', '__le__', '__gt__', '__lt__', '__ne__']
def solution_value(self):
90    def solution_value(self):  # pylint: disable=invalid-name
91        """Value of this linear expr, using the solution_value of its vars."""
92        coeffs = self.GetCoeffs()
93        return sum(var.solution_value() * coeff for var, coeff in coeffs.items())

Value of this linear expr, using the solution_value of its vars.

def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
 95    def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
 96        """Private function used by GetCoeffs() to delegate processing.
 97
 98        Implementation must either update coeffs or push to the stack a
 99        sub-expression and the accumulated multiplier that applies to it.
100
101        Args:
102          coeffs: A dictionary of variables' coefficients. It is a defaultdict that
103              initializes the new values to 0 by default.
104          multiplier: The current accumulated multiplier to apply to this
105              expression.
106          stack: A list to append to if the current expression is composed of
107              sub-expressions. The elements of the stack are pair tuples
108              (multiplier, linear_expression).
109        """
110        raise NotImplementedError

Private function used by GetCoeffs() to delegate processing.

Implementation must either update coeffs or push to the stack a sub-expression and the accumulated multiplier that applies to it.

Arguments:
  • coeffs: A dictionary of variables' coefficients. It is a defaultdict that initializes the new values to 0 by default.
  • multiplier: The current accumulated multiplier to apply to this expression.
  • stack: A list to append to if the current expression is composed of sub-expressions. The elements of the stack are pair tuples (multiplier, linear_expression).
def GetCoeffs(self):
112    def GetCoeffs(self):
113        coeffs = collections.defaultdict(float)
114        stack = [(1.0, self)]
115        while stack:
116            current_multiplier, current_expression = stack.pop()
117            current_expression.AddSelfToCoeffMapOrStack(
118                coeffs, current_multiplier, stack
119            )
120        return coeffs
class VariableExpr(LinearExpr):
177class VariableExpr(LinearExpr):
178    """Represents a LinearExpr containing only a single variable."""
179
180    def __init__(self, mpvar):
181        self.__var = mpvar
182
183    def __str__(self):
184        return str(self.__var)
185
186    def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
187        coeffs[self.__var] += multiplier

Represents a LinearExpr containing only a single variable.

VariableExpr(mpvar)
180    def __init__(self, mpvar):
181        self.__var = mpvar
def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
186    def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
187        coeffs[self.__var] += multiplier

Private function used by GetCoeffs() to delegate processing.

Implementation must either update coeffs or push to the stack a sub-expression and the accumulated multiplier that applies to it.

Arguments:
  • coeffs: A dictionary of variables' coefficients. It is a defaultdict that initializes the new values to 0 by default.
  • multiplier: The current accumulated multiplier to apply to this expression.
  • stack: A list to append to if the current expression is composed of sub-expressions. The elements of the stack are pair tuples (multiplier, linear_expression).
class ProductCst(LinearExpr):
190class ProductCst(LinearExpr):
191    """Represents the product of a LinearExpr by a constant."""
192
193    def __init__(self, expr, coef):
194        self.__expr = CastToLinExp(expr)
195        if isinstance(coef, numbers.Number):
196            self.__coef = coef
197        else:
198            raise TypeError
199
200    def __str__(self):
201        if self.__coef == -1:
202            return "-" + str(self.__expr)
203        else:
204            return "(" + str(self.__coef) + " * " + str(self.__expr) + ")"
205
206    def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
207        current_multiplier = multiplier * self.__coef
208        if current_multiplier:
209            stack.append((current_multiplier, self.__expr))

Represents the product of a LinearExpr by a constant.

ProductCst(expr, coef)
193    def __init__(self, expr, coef):
194        self.__expr = CastToLinExp(expr)
195        if isinstance(coef, numbers.Number):
196            self.__coef = coef
197        else:
198            raise TypeError
def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
206    def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
207        current_multiplier = multiplier * self.__coef
208        if current_multiplier:
209            stack.append((current_multiplier, self.__expr))

Private function used by GetCoeffs() to delegate processing.

Implementation must either update coeffs or push to the stack a sub-expression and the accumulated multiplier that applies to it.

Arguments:
  • coeffs: A dictionary of variables' coefficients. It is a defaultdict that initializes the new values to 0 by default.
  • multiplier: The current accumulated multiplier to apply to this expression.
  • stack: A list to append to if the current expression is composed of sub-expressions. The elements of the stack are pair tuples (multiplier, linear_expression).
class Constant(LinearExpr):
212class Constant(LinearExpr):
213
214    def __init__(self, val):
215        self.__val = val
216
217    def __str__(self):
218        return str(self.__val)
219
220    def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
221        coeffs[OFFSET_KEY] += self.__val * multiplier

Holds linear expressions.

A linear expression is essentially an offset (floating-point value), and a dictionary mapping MPVariable objects to their coefficient (which is also a floating-point value).

Constant(val)
214    def __init__(self, val):
215        self.__val = val
def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
220    def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
221        coeffs[OFFSET_KEY] += self.__val * multiplier

Private function used by GetCoeffs() to delegate processing.

Implementation must either update coeffs or push to the stack a sub-expression and the accumulated multiplier that applies to it.

Arguments:
  • coeffs: A dictionary of variables' coefficients. It is a defaultdict that initializes the new values to 0 by default.
  • multiplier: The current accumulated multiplier to apply to this expression.
  • stack: A list to append to if the current expression is composed of sub-expressions. The elements of the stack are pair tuples (multiplier, linear_expression).
class SumArray(LinearExpr):
224class SumArray(LinearExpr):
225    """Represents the sum of a list of LinearExpr."""
226
227    def __init__(self, array):
228        self.__array = [CastToLinExp(elem) for elem in array]
229
230    def __str__(self):
231        parts = []
232        for term in map(str, self.__array):
233            if not parts:
234                parts.append(term)
235                continue
236            if term[0] == "-":
237                parts.append(" - " + term[1:])
238            else:
239                parts.append(" + " + term)
240        return f'({"".join(parts)})'
241
242    def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
243        # Append elements in reversed order so that the first popped from the stack
244        # in the next iteration of the evaluation loop will be the first item of the
245        # array. This keeps the end result of the floating point computation
246        # predictable from user perspective.
247        for arg in reversed(self.__array):
248            stack.append((multiplier, arg))

Represents the sum of a list of LinearExpr.

SumArray(array)
227    def __init__(self, array):
228        self.__array = [CastToLinExp(elem) for elem in array]
def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
242    def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack):
243        # Append elements in reversed order so that the first popped from the stack
244        # in the next iteration of the evaluation loop will be the first item of the
245        # array. This keeps the end result of the floating point computation
246        # predictable from user perspective.
247        for arg in reversed(self.__array):
248            stack.append((multiplier, arg))

Private function used by GetCoeffs() to delegate processing.

Implementation must either update coeffs or push to the stack a sub-expression and the accumulated multiplier that applies to it.

Arguments:
  • coeffs: A dictionary of variables' coefficients. It is a defaultdict that initializes the new values to 0 by default.
  • multiplier: The current accumulated multiplier to apply to this expression.
  • stack: A list to append to if the current expression is composed of sub-expressions. The elements of the stack are pair tuples (multiplier, linear_expression).
def Sum(*args):
251def Sum(*args):
252    return SumArray(args)
def SumCst(*args):
251def Sum(*args):
252    return SumArray(args)
class LinearConstraint:
258class LinearConstraint:
259    """Represents a linear constraint: LowerBound <= LinearExpr <= UpperBound."""
260
261    def __init__(self, expr, lb, ub):
262        self.__expr = expr
263        self.__lb = lb
264        self.__ub = ub
265
266    def __str__(self):
267        if self.__lb > -inf and self.__ub < inf:
268            if self.__lb == self.__ub:
269                return str(self.__expr) + " == " + str(self.__lb)
270            else:
271                return (
272                    str(self.__lb) + " <= " + str(self.__expr) + " <= " + str(self.__ub)
273                )
274        elif self.__lb > -inf:
275            return str(self.__expr) + " >= " + str(self.__lb)
276        elif self.__ub < inf:
277            return str(self.__expr) + " <= " + str(self.__ub)
278        else:
279            return "Trivial inequality (always true)"
280
281    def Extract(self, solver, name=""):
282        """Performs the actual creation of the constraint object."""
283        coeffs = self.__expr.GetCoeffs()
284        constant = coeffs.pop(OFFSET_KEY, 0.0)
285        lb = -solver.infinity()
286        ub = solver.infinity()
287        if self.__lb > -inf:
288            lb = self.__lb - constant
289        if self.__ub < inf:
290            ub = self.__ub - constant
291
292        constraint = solver.RowConstraint(lb, ub, name)
293        for (
294            v,
295            c,
296        ) in coeffs.items():
297            constraint.SetCoefficient(v, float(c))
298        return constraint

Represents a linear constraint: LowerBound <= LinearExpr <= UpperBound.

LinearConstraint(expr, lb, ub)
261    def __init__(self, expr, lb, ub):
262        self.__expr = expr
263        self.__lb = lb
264        self.__ub = ub
def Extract(self, solver, name=''):
281    def Extract(self, solver, name=""):
282        """Performs the actual creation of the constraint object."""
283        coeffs = self.__expr.GetCoeffs()
284        constant = coeffs.pop(OFFSET_KEY, 0.0)
285        lb = -solver.infinity()
286        ub = solver.infinity()
287        if self.__lb > -inf:
288            lb = self.__lb - constant
289        if self.__ub < inf:
290            ub = self.__ub - constant
291
292        constraint = solver.RowConstraint(lb, ub, name)
293        for (
294            v,
295            c,
296        ) in coeffs.items():
297            constraint.SetCoefficient(v, float(c))
298        return constraint

Performs the actual creation of the constraint object.