Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support nonlinear constraints with Gurobi 12 #95

Merged
merged 10 commits into from
Nov 15, 2024
Prev Previous commit
Next Next commit
[#93] Add API level tests
  • Loading branch information
simonbowly committed Nov 14, 2024
commit c4a9d9724b6ba623cec75ad4ddd1500dd9c78621
62 changes: 62 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@
tests of data types, errors, etc, are done on the lower-level functions.
"""

import math
import unittest

import gurobipy as gp
import pandas as pd
from gurobipy import GRB
from pandas.testing import assert_index_equal, assert_series_equal

import gurobipy_pandas as gppd
from tests.utils import GurobiModelTestCase

GUROBIPY_MAJOR_VERSION, *_ = gp.gurobi.version()


class TestAddVars(GurobiModelTestCase):
def test_from_dataframe(self):
Expand Down Expand Up @@ -269,6 +275,62 @@ def test_sense_series(self):
self.assertEqual(constr.RHS, -1.0)


@unittest.skipIf(
GUROBIPY_MAJOR_VERSION < 12,
"Nonlinear constraints are only supported for Gurobi 12 and later",
)
class TestNonlinear(GurobiModelTestCase):
def assert_approx_equal(self, value, expected, tolerance=1e-6):
difference = abs(value - expected)
self.assertLessEqual(difference, tolerance)

def test_log(self):
# max sum y_i
# s.t. y_i = log(x_i)
# 1.0 <= x <= 2.0
from gurobipy import nlfunc

index = pd.RangeIndex(3)
x = gppd.add_vars(self.model, index, lb=1.0, ub=2.0, name="x")
y = gppd.add_vars(self.model, index, obj=1.0, name="y")
self.model.ModelSense = GRB.MAXIMIZE

gppd.add_constrs(self.model, y, GRB.EQUAL, x.apply(nlfunc.log), name="log_x")

self.model.optimize()
self.assert_approx_equal(self.model.ObjVal, 3 * math.log(2.0))

def test_inequality(self):
# max sum x_i
# s.t. log2(x_i^2 + 1) <= 2.0
# 0.0 <= x <= 1.0
#
# Formulated as
#
# max sum x_i
# s.t. log2(x_i^2 + 1) == z_i
# 0.0 <= x <= 1.0
# -GRB.INFINITY <= z_i <= 2
from gurobipy import nlfunc

index = pd.RangeIndex(3)
x = gppd.add_vars(self.model, index, name="x")
z = gppd.add_vars(self.model, index, lb=-GRB.INFINITY, ub=2.0, name="z")
self.model.setObjective(x.sum(), sense=GRB.MAXIMIZE)

gppd.add_constrs(self.model, z, GRB.EQUAL, (x**2 + 1).apply(nlfunc.log))

self.model.optimize()
self.model.write("model.lp")
self.model.write("model.sol")

x_sol = x.gppd.X
z_sol = z.gppd.X
for i in range(3):
self.assert_approx_equal(x_sol[i], math.sqrt(math.exp(2.0) - 1))
self.assert_approx_equal(z_sol[i], 2.0)


class TestDataValidation(GurobiModelTestCase):
# Test that we throw some more informative errors, instead of obscure
# ones from the underlying gurobipy library
Expand Down