Adding Nonlinear Constraints

Gurobi 12 supports adding nonlinear constraints, using the NLExpr object to capture nonlinear expressions. gurobipy-pandas supports adding a Series of nonlinear constraints to a model via add_constrs. Note that gurobipy only supports nonlinear constraints of the form y=f(x¯) and gurobipy-pandas applies the same restriction.

Note

To add nonlinear constraints, you must have at least gurobipy version 12.0.0 installed.

Nonlinear Equality Constraints

This example builds the constraint set yi=log(1xi), for each i in the index.

>>> import pandas as pd
>>> import gurobipy as gp
>>> from gurobipy import GRB
>>> from gurobipy import nlfunc
>>> import gurobipy_pandas as gppd
>>> gppd.set_interactive()

>>> model = gp.Model()
>>> index = pd.RangeIndex(5)
>>> x = gppd.add_vars(model, index, lb=1.0, name="x")
>>> y = gppd.add_vars(model, index, lb=-GRB.INFINITY, name="y")

You can create nonlinear expressions using standard Python operators. A nonlinear expression is any expression which is not a polynomial of degree at most 2. For example, dividing a constant by a series of Var objects produces a series of nonlinear expressions.

>>> 1 / x
0    1.0 / x[0]
1    1.0 / x[1]
2    1.0 / x[2]
3    1.0 / x[3]
4    1.0 / x[4]
Name: x, dtype: object

The nlfunc module of gurobipy is used to create nonlinear expressions involving mathematical functions. You can use apply to construct a series representing the logarithm of the above result.

>>> (1 / x).apply(nlfunc.log)
0    log(1.0 / x[0])
1    log(1.0 / x[1])
2    log(1.0 / x[2])
3    log(1.0 / x[3])
4    log(1.0 / x[4])
Name: x, dtype: object

This series of expressions can be added as constraints using add_constrs. Note that you can only provide nonlinear constraints in the form y=f(x¯) where f may be a univariate or multivariate compound function. Therefore the lhs argument must be a series of Var objects, while the sense argument must be GRB.EQUAL.

>>> gppd.add_constrs(model, y, GRB.EQUAL, (1 / x).apply(nlfunc.log), name="log_x")
0    <gurobi.GenConstr 0>
1    <gurobi.GenConstr 1>
2    <gurobi.GenConstr 2>
3    <gurobi.GenConstr 3>
4    <gurobi.GenConstr 4>
Name: log_x, dtype: object

Nonlinear Inequality Constraints

As noted above, nonlinear constraints can only be provided in the form y=f(x¯). To formulate inequality constraints, you must introduce bounded intermediate variables. The following example formulates the set of constraints log(xi2+1)2 by introducing intermediate variables zi with no lower bound and an upper bound of 2.0.

>>> z = gppd.add_vars(model, index, lb=-GRB.INFINITY, ub=2.0, name="z")
>>> constrs = gppd.add_constrs(model, z, GRB.EQUAL, (x**2 + 1).apply(nlfunc.log))

To see the effect of this constraint, you can set a maximization objective i=04xi and solve the resulting model. You will find that the original variables xi are maximized to e21 in the optimal solution. The intermediate variables zi are at their upper bounds, indicating that the constraint is satisfied with equality.

>>> model.setObjective(x.sum(), sense=GRB.MAXIMIZE)
>>> model.optimize()  
Gurobi Optimizer ...
Optimal solution found ...
>>> x.gppd.X.round(3)
0    2.528
1    2.528
2    2.528
3    2.528
4    2.528
Name: x, dtype: float64
>>> z.gppd.X.round(3)
0    2.0
1    2.0
2    2.0
3    2.0
4    2.0
Name: z, dtype: float64