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
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).
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.
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).
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.
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).
Inherited Members
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.
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).
Inherited Members
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).
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).
Inherited Members
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.
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).
Inherited Members
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.
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.