Skip to content

Commit 775b6e1

Browse files
committed
Skip tests that would fail due to unavailable solver features
1 parent e5fec98 commit 775b6e1

File tree

10 files changed

+217
-33
lines changed

10 files changed

+217
-33
lines changed

test/dbft_test.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from os import environ
55
from itertools import product
66
import pytest
7+
import mip.gurobi
78
import mip.highs
89
from mip import (
910
Model,
@@ -16,11 +17,12 @@
1617
GUROBI,
1718
HIGHS,
1819
)
20+
from util import skip_on
1921

2022
TOL = 1e-4
2123

2224
SOLVERS = [CBC]
23-
if "GUROBI_HOME" in environ:
25+
if mip.gurobi.has_gurobi and "GUROBI_HOME" in environ:
2426
SOLVERS += [GUROBI]
2527
if mip.highs.has_highs:
2628
SOLVERS += [HIGHS]
@@ -477,6 +479,7 @@ def create_model(solver, N, tMax):
477479
return m
478480

479481

482+
@skip_on(NotImplementedError)
480483
@pytest.mark.parametrize("pdata", PDATA.keys())
481484
@pytest.mark.parametrize("solver", SOLVERS)
482485
def test_dbft_mip(solver, pdata):

test/examples_test.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,22 @@
1111
import mip.gurobi
1212
import mip.highs
1313
from mip import CBC, GUROBI, HIGHS
14+
from util import skip_on
1415

1516
EXAMPLES = glob(join("..", "examples", "*.py")) + glob(join(".", "examples", "*.py"))
1617

1718
SOLVERS = [CBC]
18-
if mip.gurobi.has_gurobi:
19+
if mip.gurobi.has_gurobi and "GUROBI_HOME" in environ:
1920
SOLVERS += [GUROBI]
2021
if mip.highs.has_highs:
2122
SOLVERS += [HIGHS]
2223

2324

25+
@skip_on(NotImplementedError)
2426
@pytest.mark.parametrize("solver, example", product(SOLVERS, EXAMPLES))
2527
def test_examples(solver, example):
2628
"""Executes a given example with using solver 'solver'"""
2729
environ["SOLVER_NAME"] = solver
2830
loader = importlib.machinery.SourceFileLoader("example", example)
2931
mod = types.ModuleType(loader.name)
30-
try:
31-
loader.exec_module(mod)
32-
except NotImplementedError as e:
33-
print("Skipping test for example '{}': {}".format(example, e))
32+
loader.exec_module(mod)

test/mip_files_test.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22

33

44
from glob import glob
5+
from itertools import product
56
from os import environ
67
from os.path import basename, dirname, join, exists
7-
from itertools import product
8+
89
import pytest
9-
import mip
10-
from mip import Model, OptimizationStatus, GUROBI, CBC
10+
11+
import mip.gurobi
12+
import mip.highs
13+
from mip import Model, OptimizationStatus, CBC, GUROBI, HIGHS
14+
from util import skip_on
1115

1216
# for each MIP in test/data, best lower and upper bounds
1317
# to be used when checking optimization results
@@ -87,8 +91,10 @@
8791
MAX_NODES = 10
8892

8993
SOLVERS = [CBC]
90-
if "GUROBI_HOME" in environ:
94+
if mip.gurobi.has_gurobi and "GUROBI_HOME" in environ:
9195
SOLVERS += [GUROBI]
96+
if mip.highs.has_highs:
97+
SOLVERS += [HIGHS]
9298

9399
# check availability of test data
94100
DATA_DIR = join(join(dirname(mip.__file__)[0:-3], "test"), "data")
@@ -107,6 +113,7 @@
107113
INSTS = INSTS + glob(join(DATA_DIR, "*" + exti))
108114

109115

116+
@skip_on(NotImplementedError)
110117
@pytest.mark.parametrize("solver, instance", product(SOLVERS, INSTS))
111118
def test_mip_file(solver: str, instance: str):
112119
"""Tests optimization of MIP models stored in .mps or .lp files"""

test/mip_test.py

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
"""Tests for Python-MIP"""
2+
import math
23
from itertools import product
3-
import pytest
4+
from os import environ
5+
46
import networkx as nx
7+
import mip.gurobi
58
import mip.highs
69
from mip import Model, xsum, OptimizationStatus, MAXIMIZE, BINARY, INTEGER
7-
from mip import ConstrsGenerator, CutPool, maximize, CBC, GUROBI, HIGHS, Column
10+
from mip import ConstrsGenerator, CutPool, maximize, CBC, GUROBI, HIGHS, Column, Constr
811
from os import environ
12+
from util import skip_on
913
import math
14+
import pytest
1015

1116
TOL = 1e-4
1217

1318
SOLVERS = [CBC]
14-
if "GUROBI_HOME" in environ:
19+
if mip.gurobi.has_gurobi and "GUROBI_HOME" in environ:
1520
SOLVERS += [GUROBI]
1621
if mip.highs.has_highs:
1722
SOLVERS += [HIGHS]
1823

1924

25+
@skip_on(NotImplementedError)
2026
@pytest.mark.parametrize("solver", SOLVERS)
2127
def test_column_generation(solver: str):
2228
L = 250 # bar length
@@ -86,6 +92,7 @@ def test_column_generation(solver: str):
8692
assert round(master.objective_value) == 3
8793

8894

95+
@skip_on(NotImplementedError)
8996
@pytest.mark.parametrize("solver", SOLVERS)
9097
def test_cutting_stock(solver: str):
9198
n = 10 # maximum number of bars
@@ -122,6 +129,7 @@ def test_cutting_stock(solver: str):
122129
assert sum(x.x for x in model.vars) >= 5
123130

124131

132+
@skip_on(NotImplementedError)
125133
@pytest.mark.parametrize("solver", SOLVERS)
126134
def test_knapsack(solver: str):
127135
p = [10, 13, 18, 31, 7, 15]
@@ -153,6 +161,7 @@ def test_knapsack(solver: str):
153161
assert abs(m.objective.expr[x[1]] - 28) <= 1e-10
154162

155163

164+
@skip_on(NotImplementedError)
156165
@pytest.mark.parametrize("solver", SOLVERS)
157166
def test_queens(solver: str):
158167
"""MIP model n-queens"""
@@ -207,6 +216,7 @@ def test_queens(solver: str):
207216
assert rows_with_queens == n # "feasible solution"
208217

209218

219+
@skip_on(NotImplementedError)
210220
@pytest.mark.parametrize("solver", SOLVERS)
211221
def test_tsp(solver: str):
212222
"""tsp related tests"""
@@ -312,6 +322,7 @@ def generate_constrs(self, model: Model, depth: int = 0, npass: int = 0):
312322
model.add_cut(cut)
313323

314324

325+
@skip_on(NotImplementedError)
315326
@pytest.mark.parametrize("solver", SOLVERS)
316327
def test_tsp_cuts(solver: str):
317328
"""tsp related tests"""
@@ -383,9 +394,8 @@ def test_tsp_cuts(solver: str):
383394
assert abs(m.objective_value - 262) <= TOL # "mip model objective"
384395

385396

386-
# Exclude HiGHS solver, which doesn't support MIP start.
387-
SOLVERS_WITH_MIPSTART = [s for s in SOLVERS if s != HIGHS]
388-
@pytest.mark.parametrize("solver", SOLVERS_WITH_MIPSTART)
397+
@skip_on(NotImplementedError)
398+
@pytest.mark.parametrize("solver", SOLVERS)
389399
def test_tsp_mipstart(solver: str):
390400
"""tsp related tests"""
391401
N = ["a", "b", "c", "d", "e", "f", "g"]
@@ -568,6 +578,7 @@ def test_obj_const2(self, solver: str):
568578
assert model.objective_const == 1
569579

570580

581+
@skip_on(NotImplementedError)
571582
@pytest.mark.parametrize("val", range(1, 4))
572583
@pytest.mark.parametrize("solver", SOLVERS)
573584
def test_variable_bounds(solver: str, val: int):
@@ -583,6 +594,7 @@ def test_variable_bounds(solver: str, val: int):
583594
assert round(y.x) == val
584595

585596

597+
@skip_on(NotImplementedError)
586598
@pytest.mark.parametrize("val", range(1, 4))
587599
@pytest.mark.parametrize("solver", SOLVERS)
588600
def test_linexpr_x(solver: str, val: int):
@@ -611,6 +623,7 @@ def test_linexpr_x(solver: str, val: int):
611623
assert abs((x + 2 * y + x + 1 + x / 2).x - (x.x + 2 * y.x + x.x + 1 + x.x / 2)) < TOL
612624

613625

626+
@skip_on(NotImplementedError)
614627
@pytest.mark.parametrize("solver", SOLVERS)
615628
def test_add_column(solver: str):
616629
"""Simple test which add columns in a specific way"""
@@ -640,6 +653,7 @@ def test_add_column(solver: str):
640653
assert x in example_constr1.expr.expr
641654

642655

656+
@skip_on(NotImplementedError)
643657
@pytest.mark.parametrize("val", range(1, 4))
644658
@pytest.mark.parametrize("solver", SOLVERS)
645659
def test_float(solver: str, val: int):
@@ -658,3 +672,39 @@ def test_float(solver: str, val: int):
658672
assert y.x == float(y)
659673
# test linear expressions.
660674
assert float(x + y) == (x + y).x
675+
676+
677+
@skip_on(NotImplementedError)
678+
@pytest.mark.parametrize("solver", SOLVERS)
679+
def test_empty_useless_constraint_is_considered(solver: str):
680+
m = Model("empty_constraint", solver_name=solver)
681+
x = m.add_var(name="x")
682+
y = m.add_var(name="y")
683+
m.add_constr(xsum([]) <= 1, name="c_empty") # useless, empty constraint
684+
m.add_constr(x + y <= 5, name="c1")
685+
m.add_constr(2 * x + y <= 6, name="c2")
686+
m.objective = maximize(x + 2 * y)
687+
m.optimize()
688+
# check objective
689+
assert m.status == OptimizationStatus.OPTIMAL
690+
assert abs(m.objective.x - 10) < TOL
691+
# check that all names of constraints could be queried
692+
assert {c.name for c in m.constrs} == {"c1", "c2", "c_empty"}
693+
assert all(isinstance(m.constr_by_name(c_name), Constr) for c_name in ("c1", "c2", "c_empty"))
694+
695+
696+
@skip_on(NotImplementedError)
697+
@pytest.mark.parametrize("solver", SOLVERS)
698+
def test_empty_contradictory_constraint_is_considered(solver: str):
699+
m = Model("empty_constraint", solver_name=solver)
700+
x = m.add_var(name="x")
701+
y = m.add_var(name="y")
702+
m.add_constr(xsum([]) <= -1, name="c_contra") # contradictory empty constraint
703+
m.add_constr(x + y <= 5, name="c1")
704+
m.objective = maximize(x + 2 * y)
705+
m.optimize()
706+
# assert infeasibility of problem
707+
assert m.status in (OptimizationStatus.INF_OR_UNBD, OptimizationStatus.INFEASIBLE)
708+
# check that all names of constraints could be queried
709+
assert {c.name for c in m.constrs} == {"c1", "c_contra"}
710+
assert all(isinstance(m.constr_by_name(c_name), Constr) for c_name in ("c1", "c_contra"))

test/numpy_test.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
from itertools import product
2-
import pytest
31
import numpy as np
4-
from mip import Model, xsum, OptimizationStatus, MAXIMIZE, BINARY, INTEGER
5-
from mip import ConstrsGenerator, CutPool, maximize, CBC, GUROBI, Column
2+
from mip import Model, OptimizationStatus
63
from mip.ndarray import LinExprTensor
7-
from os import environ
84
import time
9-
import sys
5+
6+
from util import skip_on
107

118

129
def test_numpy():
@@ -32,6 +29,7 @@ def test_numpy():
3229
assert result == OptimizationStatus.OPTIMAL
3330

3431

32+
@skip_on(NotImplementedError)
3533
def test_LinExprTensor():
3634
model = Model()
3735
x = model.add_var_tensor(shape=(3,), name="x")

test/rcpsp_test.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
11
"""Set of tests for solving the LP relaxation"""
22

3-
from glob import glob
4-
from os import environ
53
import json
4+
from glob import glob
65
from itertools import product
6+
from os import environ
7+
78
import pytest
8-
from mip import CBC, GUROBI, OptimizationStatus
9+
10+
import mip.gurobi
11+
import mip.highs
12+
from mip import CBC, GUROBI, HIGHS, OptimizationStatus
913
from mip_rcpsp import create_mip
14+
from util import skip_on
1015

1116
INSTS = glob("./data/rcpsp*.json") + glob("./test/data/rcpsp*.json")
1217

1318
TOL = 1e-4
1419

1520
SOLVERS = [CBC]
16-
if "GUROBI_HOME" in environ:
21+
if mip.gurobi.has_gurobi and "GUROBI_HOME" in environ:
1722
SOLVERS += [GUROBI]
23+
if mip.highs.has_highs:
24+
SOLVERS += [HIGHS]
1825

1926

27+
@skip_on(NotImplementedError)
2028
@pytest.mark.parametrize("solver, instance", product(SOLVERS, INSTS))
2129
def test_rcpsp_relax(solver: str, instance: str):
2230
"""tests the solution of the LP relaxation of different rcpsp instances"""
@@ -37,6 +45,7 @@ def test_rcpsp_relax(solver: str, instance: str):
3745
assert abs(z_relax - mip.objective_value) <= TOL
3846

3947

48+
@skip_on(NotImplementedError)
4049
@pytest.mark.parametrize("solver, instance", product(SOLVERS, INSTS))
4150
def test_rcpsp_relax_mip(solver: str, instance: str):
4251
"""tests the solution of the LP relaxation of different rcpsp instances"""
@@ -58,6 +67,7 @@ def test_rcpsp_relax_mip(solver: str, instance: str):
5867
assert abs(z_relax - mip.objective_value) <= TOL
5968

6069

70+
@skip_on(NotImplementedError)
6171
@pytest.mark.parametrize("solver, instance", product(SOLVERS, INSTS))
6272
def test_rcpsp_mip(solver: str, instance: str):
6373
"""tests the solution of different RCPSP MIPs"""
@@ -97,6 +107,8 @@ def test_rcpsp_mip(solver: str, instance: str):
97107
xOn = [v for v in mip.vars if v.x >= 0.99 and v.name.startswith("x(")]
98108
assert len(xOn) == len(J)
99109

110+
111+
@skip_on(NotImplementedError)
100112
@pytest.mark.parametrize("solver, instance", product(SOLVERS, INSTS))
101113
def test_rcpsp_mipstart(solver: str, instance: str):
102114
"""tests the solution of different rcpsps MIPs with uwing MIPStarts"""
@@ -118,6 +130,6 @@ def test_rcpsp_mipstart(solver: str, instance: str):
118130
mip.cut_passes = 0
119131
mip.start = [(mip.var_by_name(n), v) for (n, v) in mipstart]
120132
mip.optimize(max_nodes=3)
121-
assert mip.status in [OptimizationStatus.FEASIBLE,
133+
assert mip.status in [OptimizationStatus.FEASIBLE,
122134
OptimizationStatus.OPTIMAL]
123135
assert abs(mip.objective_value - z_ub) <= TOL

test/test_conflict.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
import random
66
import numpy as np
77

8+
from util import skip_on
89

10+
11+
@skip_on(NotImplementedError)
912
def test_conflict_finder():
1013
mdl = mip.Model(name="infeasible_model_continuous")
1114
var = mdl.add_var(name="x", var_type=mip.CONTINUOUS, lb=-mip.INF, ub=mip.INF)
@@ -18,6 +21,7 @@ def test_conflict_finder():
1821
assert set(["lower_bound", "upper_bound"]) == iis_names
1922

2023

24+
@skip_on(NotImplementedError)
2125
def test_conflict_finder_iis():
2226
mdl = mip.Model(name="infeasible_model_continuous")
2327
var_x = mdl.add_var(name="x", var_type=mip.CONTINUOUS, lb=-mip.INF, ub=mip.INF)
@@ -32,6 +36,7 @@ def test_conflict_finder_iis():
3236
assert set(["lower_bound", "upper_bound"]) == iis_names
3337

3438

39+
@skip_on(NotImplementedError)
3540
def test_conflict_finder_iis_additive_method():
3641
mdl = mip.Model(name="infeasible_model_continuous")
3742
var_x = mdl.add_var(name="x", var_type=mip.CONTINUOUS, lb=-mip.INF, ub=mip.INF)
@@ -46,6 +51,7 @@ def test_conflict_finder_iis_additive_method():
4651
assert set(["lower_bound", "upper_bound"]) == iis_names
4752

4853

54+
@skip_on(NotImplementedError)
4955
def test_conflict_finder_iis_additive_method_two_options():
5056
mdl = mip.Model(name="infeasible_model_continuous")
5157
var_x = mdl.add_var(name="x", var_type=mip.CONTINUOUS, lb=-mip.INF, ub=mip.INF)
@@ -62,6 +68,7 @@ def test_conflict_finder_iis_additive_method_two_options():
6268
)
6369

6470

71+
@skip_on(NotImplementedError)
6572
def test_conflict_finder_feasible():
6673
mdl = mip.Model(name="feasible_model")
6774
var = mdl.add_var(name="x", var_type=mip.CONTINUOUS, lb=-mip.INF, ub=mip.INF)
@@ -98,6 +105,7 @@ def build_infeasible_cont_model(
98105
return mdl
99106

100107

108+
@skip_on(NotImplementedError)
101109
def test_coflict_relaxer():
102110
# logger config
103111
# handler = logging.StreamHandler(sys.stdout)

0 commit comments

Comments
 (0)