diff --git a/CHANGELOG.md b/CHANGELOG.md index b36a7bfa5..0a6677e2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Add SCIP function SCIPgetSolTime and wrapper getSolTime - Add convenience methods relax and getVarDict - Add SCIP functions hasPrimalRay, getPrimalRay, getPrimalRayVal +- Add multiple tests for constraints and for solutions ### Fixed - Fixed typo in documentation of chgRhs - Pricer plugin fundamental callbacks now raise an error if not implemented diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 302bc7261..707586581 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -2722,7 +2722,6 @@ cdef class Model: return pyCons - def addConsIndicator(self, cons, binvar=None, activeone=True, name="IndicatorCons", initial=True, separate=True, enforce=True, check=True, propagate=True, local=False, dynamic=False, @@ -3323,7 +3322,6 @@ cdef class Model: return _dualsol - def optimize(self): """Optimize the problem.""" PY_SCIP_CALL(SCIPsolve(self._scip)) diff --git a/tests/test_cons.py b/tests/test_cons.py index 0192cec82..19c0551d1 100644 --- a/tests/test_cons.py +++ b/tests/test_cons.py @@ -1,30 +1,33 @@ from pyscipopt import Model, quicksum import random +import pytest def test_getConsNVars(): - n_vars = random.randint(100,1000) + n_vars = random.randint(100, 1000) m = Model() x = {} for i in range(n_vars): - x[i] = m.addVar("%i"%i) - + x[i] = m.addVar("%i" % i) + c = m.addCons(quicksum(x[i] for i in x) <= 10) assert m.getConsNVars(c) == n_vars m.optimize() assert m.getConsNVars(c) == n_vars + def test_getConsVars(): - n_vars = random.randint(100,1000) + n_vars = random.randint(100, 1000) m = Model() x = {} for i in range(n_vars): - x[i] = m.addVar("%i"%i) - + x[i] = m.addVar("%i" % i) + c = m.addCons(quicksum(x[i] for i in x) <= 1) assert m.getConsVars(c) == [x[i] for i in x] - + + def test_constraint_option_setting(): m = Model() x = m.addVar() @@ -39,4 +42,119 @@ def test_constraint_option_setting(): assert c.isChecked() == option assert c.isEnforced() == option assert c.isRemovable() == option - assert c.isInitial() == option \ No newline at end of file + assert c.isInitial() == option + + +def test_cons_logical(): + m = Model() + x1 = m.addVar(vtype="B") + x2 = m.addVar(vtype="B") + x3 = m.addVar(vtype="B") + x4 = m.addVar(vtype="B") + result1 = m.addVar(vtype="B") + result2 = m.addVar(vtype="B") + + m.addCons(x3 == 1 - x1) + m.addCons(x4 == 1 - x2) + + # result1 true + m.addConsAnd([x1, x2], result1) + m.addConsOr([x1, x2], result1) + m.addConsXor([x1, x3], True) + + # result2 false + m.addConsOr([x3, x4], result2) + m.addConsAnd([x1, x3], result2) + m.addConsXor([x1, x2], False) + + m.optimize() + + assert m.isEQ(m.getVal(result1), 1) + assert m.isEQ(m.getVal(result2), 0) + + +def test_SOScons(): + m = Model() + x = {} + for i in range(6): + x[i] = m.addVar(vtype="B", obj=-i) + + c1 = m.addConsSOS1([x[0]], [1]) + c2 = m.addConsSOS2([x[1]], [1]) + + m.addVarSOS1(c1, x[2], 1) + m.addVarSOS2(c2, x[3], 1) + + m.appendVarSOS1(c1, x[4]) + m.appendVarSOS2(c2, x[5]) + + m.optimize() + + assert m.isEQ(m.getVal(x[0]), 0) + assert m.isEQ(m.getVal(x[1]), 0) + assert m.isEQ(m.getVal(x[2]), 0) + assert m.isEQ(m.getVal(x[3]), 1) + assert m.isEQ(m.getVal(x[4]), 1) + assert m.isEQ(m.getVal(x[5]), 1) + + +def test_cons_indicator(): + m = Model() + x = m.addVar(lb=0) + binvar = m.addVar(vtype="B", lb=1) + + c = m.addConsIndicator(x >= 1, binvar) + + slack = m.getSlackVarIndicator(c) + + m.optimize() + + assert m.isEQ(m.getVal(slack), 0) + assert m.isEQ(m.getVal(binvar), 1) + assert m.isEQ(m.getVal(x), 1) + + +@pytest.mark.xfail(reason="addConsIndicator doesn't behave as expected when binary variable is False. See Issue #717.") +def test_cons_indicator_fail(): + m = Model() + binvar = m.addVar(vtype="B") + x = m.addVar(vtype="C", lb=1, ub=3) + m.addConsIndicator(x <= 2, binvar) + + m.setObjective(x, "maximize") + + sol = m.createSol(None) + m.setSolVal(sol, x, 3) + m.setSolVal(sol, binvar, 0) + assert m.checkSol(sol) # solution should be feasible + + +def test_addConsCardinality(): + m = Model() + x = {} + for i in range(5): + x[i] = m.addVar(ub=1, obj=-1) + + m.addConsCardinality([x[i] for i in range(5)], 3) + m.optimize() + + assert m.isEQ(m.getVal(quicksum(x[i] for i in range(5))), 3) + + +def test_printCons(): + m = Model() + x = m.addVar() + y = m.addVar() + c = m.addCons(x * y <= 5) + + m.printCons(c) + + +@pytest.mark.skip(reason="TODO: test getValsLinear()") +def test_getValsLinear(): + assert True + + +@pytest.mark.skip(reason="TODO: test getRowLinear()") +def test_getRowLinear(): + assert True diff --git a/tests/test_logical.py b/tests/test_logical.py index 8c46f87ef..dbf99d3a6 100644 --- a/tests/test_logical.py +++ b/tests/test_logical.py @@ -42,7 +42,7 @@ def getVarByName(m, name): return [v for v in m.getVars() if name == v.name][0] except IndexError: return None - + def getAllVarsByName(m, name): try: diff --git a/tests/test_model.py b/tests/test_model.py index 991d95e1b..14424e108 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -40,6 +40,7 @@ def test_model(): assert s.getRhs(c) == 6.0 # solve problem + s.presolve() # to test presolve method s.optimize() solution = s.getBestSol() @@ -118,7 +119,6 @@ def assert_conss_eq(a, b): s.freeProb() m.freeProb() - def test_multiple_cons_names(): m = Model() x = m.addVar("x", vtype = 'C', obj = 1.0) @@ -251,3 +251,20 @@ def test_getVarsDict(): for v in x.values(): assert v.name in var_dict assert model.getVal(v) == var_dict[v.name] + +def test_objLim(): + m = Model() + + x = m.addVar(obj=1, lb=2) + m.setObjlimit(1) + + m.optimize() + assert m.getNLimSolsFound() == 0 + + m = Model() + x = m.addVar(obj=1, lb=2) + + m.setObjlimit(2) + m.optimize() + assert m.getNLimSolsFound() == 1 + \ No newline at end of file diff --git a/tests/test_pricer.py b/tests/test_pricer.py index ebae4eb46..1b8fd1184 100644 --- a/tests/test_pricer.py +++ b/tests/test_pricer.py @@ -38,6 +38,10 @@ def pricerredcost(self): objval = 1 + subMIP.getObjVal() + assert type(self.model.getNSolsFound()) == int + assert type(self.model.getNBestSolsFound()) == int + assert self.model.getNBestSolsFound() <= self.model.getNSolsFound() + self.model.data["nSols"] = self.model.getNSolsFound() # Adding the column to the master problem if objval < -1e-08: @@ -74,6 +78,8 @@ def test_cuttingstock(): s = Model("CuttingStock") s.setPresolve(0) + s.data = {} + s.data["nSols"] = 0 # creating a pricer pricer = CutPricer() @@ -149,6 +155,8 @@ def test_cuttingstock(): print('\t\t\tTotal Output:\t', '\t'.join(str(e) for e in widthOutput)) assert s.getObjVal() == 452.25 + assert type(s.getNSols()) == int + assert s.getNSols() == s.data["nSols"] def test_incomplete_pricer(): class IncompletePricer(Pricer): diff --git a/tests/test_solution.py b/tests/test_solution.py index 8dcba59dc..d1b7c9d33 100644 --- a/tests/test_solution.py +++ b/tests/test_solution.py @@ -2,12 +2,13 @@ import pytest from pyscipopt import Model, scip, SCIP_PARAMSETTING, quicksum, quickprod + def test_solution_getbest(): m = Model() x = m.addVar("x", lb=0, ub=2, obj=-1) y = m.addVar("y", lb=0, ub=4, obj=0) - m.addCons(x*x <= y) + m.addCons(x * x <= y) m.optimize() @@ -28,20 +29,31 @@ def test_solution_create(): x = m.addVar("x", lb=0, ub=2, obj=-1) y = m.addVar("y", lb=0, ub=4, obj=0) - m.addCons(x*x <= y) + m.addCons(x * x <= y) s = m.createSol() s[x] = 2.0 - s[y] = 4.0 + s[y] = 5.0 + assert not m.checkSol(s) assert m.addSol(s, free=True) + s1 = m.createSol() + m.setSolVal(s1, x, 1.0) + m.setSolVal(s1, y, 2.0) + assert m.checkSol(s1) + + m.optimize() + + assert m.getSolObjVal(s1) == -1 + m.freeSol(s1) + def test_solution_evaluation(): m = Model() x = m.addVar("x", lb=0, ub=2, obj=-1) y = m.addVar("y", lb=0, ub=4, obj=0) - m.addCons(x*x <= y) + m.addCons(x * x <= y) m.optimize() @@ -52,16 +64,21 @@ def test_solution_evaluation(): assert round(sol[y]) == 4.0 # Expression evaluation - expr = x*x + 2*x*y + y*y + expr = x * x + 2 * x * y + y * y expr2 = x + 1 assert round(sol[expr]) == 36.0 assert round(sol[expr2]) == 3.0 # Check consistency with Models's getVal method - assert sol[x] == m.getVal(x) - assert sol[y] == m.getVal(y) - assert sol[expr] == m.getVal(expr) - assert sol[expr2] == m.getVal(expr2) + assert m.isEQ(sol[x], m.getVal(x)) + assert m.isEQ(m.getSolVal(sol, x), m.getVal(x)) + assert m.isEQ(sol[y], m.getVal(y)) + assert m.isEQ(m.getSolVal(sol, y), m.getVal(y)) + assert m.isEQ(sol[expr], m.getVal(expr)) + assert m.isEQ(m.getSolVal(sol, expr), m.getVal(expr)) + assert m.isEQ(sol[expr2], m.getVal(expr2)) + assert m.isEQ(m.getSolVal(sol, expr2), m.getVal(expr2)) + def test_getSolTime(): m = Model() @@ -71,60 +88,65 @@ def test_getSolTime(): for i in range(20): x[i] = m.addVar(ub=i) - for i in range(1,6): - m.addCons(quicksum(x[j] for j in range(20) if j%i==0) >= i) - m.addCons(quickprod(x[j] for j in range(20) if j%i==0) <= i**3) - + for i in range(1, 6): + m.addCons(quicksum(x[j] for j in range(20) if j % i == 0) >= i) + m.addCons(quickprod(x[j] for j in range(20) if j % i == 0) <= i**3) + m.setObjective(quicksum(x[i] for i in range(20))) m.optimize() for s in m.getSols(): assert m.getSolTime(s) >= 0 + def test_hasPrimalRay(): m = Model() x = m.addVar() m.setObjective(x, "maximize") m.setPresolve(SCIP_PARAMSETTING.OFF) - + m.optimize() - + assert m.hasPrimalRay() m = Model() - x = m.addVar(lb = 0) # for readability + x = m.addVar(lb=0) # for readability m.setPresolve(SCIP_PARAMSETTING.OFF) m.optimize() assert not m.hasPrimalRay() - + + def test_getPrimalRayVal(): m = Model() x = m.addVar() m.setObjective(x, "maximize") m.setPresolve(SCIP_PARAMSETTING.OFF) - + m.hideOutput() m.optimize() - + assert m.getPrimalRayVal(x) == 1 - + + def test_getPrimalRay(): m = Model() x = m.addVar() y = m.addVar() m.setObjective(x, "maximize") m.setPresolve(SCIP_PARAMSETTING.OFF) - + m.hideOutput() m.optimize() - assert m.getPrimalRay() == [1,0] + assert m.getPrimalRay() == [1, 0] + def test_create_solution(): with pytest.raises(ValueError): scip.Solution() + def test_print_solution(): m = Model() @@ -132,7 +154,8 @@ def test_print_solution(): m.optimize() solution_str = str(m.getBestSol()) - print(re.match(r"{'x': -?\d+\.?\d*}", solution_str) is not None) + assert re.match(r"{'x': -?\d+\.?\d*}", solution_str) is not None + def test_getSols(): m = Model() @@ -141,4 +164,4 @@ def test_getSols(): m.optimize() assert len(m.getSols()) >= 1 - assert any(sol[x] == 0.0 for sol in m.getSols()) \ No newline at end of file + assert any(m.isEQ(sol[x], 0.0) for sol in m.getSols())