diff --git a/pulp/apis/coin_api.py b/pulp/apis/coin_api.py index f0e68e6b..62191f9d 100644 --- a/pulp/apis/coin_api.py +++ b/pulp/apis/coin_api.py @@ -182,8 +182,10 @@ def solve_CBC(self, lp, use_mps=True): "Pulp: Error while trying to execute, use msg=True for more details" + self.path ) - if pipe: + try: pipe.close() + except: + pass if not os.path.exists(tmpSol): raise PulpSolverError("Pulp: Error while executing " + self.path) ( diff --git a/pulp/tests/run_tests.py b/pulp/tests/run_tests.py index c6dd7647..0a6abd2a 100644 --- a/pulp/tests/run_tests.py +++ b/pulp/tests/run_tests.py @@ -4,6 +4,10 @@ def pulpTestAll(test_docs=False): + all_solvers = pulp.listSolvers(onlyAvailable=False) + available = pulp.listSolvers(onlyAvailable=True) + print(f"Available solvers: {available}") + print(f"Unavailable solvers: {set(all_solvers) - set(available)}") runner = unittest.TextTestRunner() suite_all = get_test_suite(test_docs) # we run all tests at the same time diff --git a/pulp/tests/test_pulp.py b/pulp/tests/test_pulp.py index 119d6e30..d50de9e1 100644 --- a/pulp/tests/test_pulp.py +++ b/pulp/tests/test_pulp.py @@ -74,7 +74,7 @@ def dumpTestProblem(prob): prob.writeLP("debug.lp") prob.writeMPS("debug.mps") except: - print("(Failed to write the test problem.)") + pass class BaseSolverTest: @@ -84,7 +84,7 @@ class PuLPTest(unittest.TestCase): def setUp(self): self.solver = self.solveInst(msg=False) if not self.solver.available(): - self.skipTest(f"solver {self.solveInst} not available") + self.skipTest(f"solver {self.solveInst.name} not available") def tearDown(self): for ext in ["mst", "log", "lp", "mps", "sol"]: @@ -104,7 +104,6 @@ def test_variable_0_is_deleted(self): z = LpVariable("z", 0) c1 = x + y <= 5 c2 = c1 + z - z - print("\t Testing zero subtraction") assert str(c2) assert c2[z] == 0 @@ -122,7 +121,6 @@ def test_infeasible(self): prob += x + z >= 10, "c2" prob += -y + z == 7, "c3" prob += w >= 0, "c4" - print("\t Testing inconsistent lp solution") # this was a problem with use_mps=false if self.solver.__class__ in [PULP_CBC_CMD, COIN_CMD]: pulpTestCheck( @@ -157,7 +155,6 @@ def test_continuous(self): prob += x + z >= 10, "c2" prob += -y + z == 7, "c3" prob += w >= 0, "c4" - print("\t Testing continuous LP solution") pulpTestCheck( prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: 0} ) @@ -173,7 +170,6 @@ def test_continuous_max(self): prob += x + z >= 10, "c2" prob += -y + z == 7, "c3" prob += w >= 0, "c4" - print("\t Testing maximize continuous LP solution") pulpTestCheck( prob, self.solver, [const.LpStatusOptimal], {x: 4, y: 1, z: 8, w: 0} ) @@ -189,7 +185,6 @@ def test_unbounded(self): prob += x + z >= 10, "c2" prob += -y + z == 7, "c3" prob += w >= 0, "c4" - print("\t Testing unbounded continuous LP solution") if self.solver.__class__ in [GUROBI, CPLEX_CMD, YAPOSIB, MOSEK, COPT]: # These solvers report infeasible or unbounded pulpTestCheck( @@ -200,7 +195,6 @@ def test_unbounded(self): elif self.solver.__class__ in [COINMP_DLL, MIPCL_CMD]: # COINMP_DLL is just plain wrong # also MIPCL_CMD - print("\t\t Error in CoinMP and MIPCL_CMD: reports Optimal") pulpTestCheck(prob, self.solver, [const.LpStatusOptimal]) elif self.solver.__class__ is GLPK_CMD: # GLPK_CMD Does not report unbounded problems, correctly @@ -208,8 +202,9 @@ def test_unbounded(self): elif self.solver.__class__ in [GUROBI_CMD, SCIP_CMD, FSCIP_CMD, SCIP_PY]: # GUROBI_CMD has a very simple interface pulpTestCheck(prob, self.solver, [const.LpStatusNotSolved]) - elif self.solver.__class__ in [CHOCO_CMD]: + elif self.solver.__class__ in [CHOCO_CMD, HiGHS_CMD]: # choco bounds all variables. Would not return unbounded status + # highs_cmd is inconsistent pass else: pulpTestCheck(prob, self.solver, [const.LpStatusUnbounded]) @@ -225,7 +220,6 @@ def test_long_var_name(self): prob += x + z >= 10, "c2" prob += -y + z == 7, "c3" prob += w >= 0, "c4" - print("\t Testing Long Names") if self.solver.__class__ in [ CPLEX_CMD, GLPK_CMD, @@ -268,7 +262,6 @@ def test_repeated_name(self): prob += x + z >= 10, "c2" prob += -y + z == 7, "c3" prob += w >= 0, "c4" - print("\t Testing repeated Names") if self.solver.__class__ in [ COIN_CMD, COINMP_DLL, @@ -319,7 +312,6 @@ def test_zero_constraint(self): prob += -y + z == 7, "c3" prob += w >= 0, "c4" prob += lpSum([0, 0]) <= 0, "c5" - print("\t Testing zero constraint") pulpTestCheck( prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: 0} ) @@ -335,7 +327,6 @@ def test_no_objective(self): prob += -y + z == 7, "c3" prob += w >= 0, "c4" prob += lpSum([0, 0]) <= 0, "c5" - print("\t Testing zero objective") pulpTestCheck(prob, self.solver, [const.LpStatusOptimal]) def test_variable_as_objective(self): @@ -350,7 +341,6 @@ def test_variable_as_objective(self): prob += -y + z == 7, "c3" prob += w >= 0, "c4" prob += lpSum([0, 0]) <= 0, "c5" - print("\t Testing LpVariable (not LpAffineExpression) objective") pulpTestCheck(prob, self.solver, [const.LpStatusOptimal]) def test_longname_lp(self): @@ -365,7 +355,6 @@ def test_longname_lp(self): prob += -y + z == 7, "c3" prob += w >= 0, "c4" if self.solver.__class__ in [PULP_CBC_CMD, COIN_CMD]: - print("\t Testing Long lines in LP") pulpTestCheck( prob, self.solver, @@ -385,7 +374,6 @@ def test_divide(self): prob += x + z >= 10, "c2" prob += -y + z == 7, "c3" prob += w >= 0, "c4" - print("\t Testing LpAffineExpression divide") pulpTestCheck( prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: 0} ) @@ -399,7 +387,6 @@ def test_mip(self): prob += x + y <= 5, "c1" prob += x + z >= 10, "c2" prob += -y + z == 7.5, "c3" - print("\t Testing MIP solution") pulpTestCheck( prob, self.solver, [const.LpStatusOptimal], {x: 3, y: -0.5, z: 7} ) @@ -413,7 +400,6 @@ def test_mip_floats_objective(self): prob += x + y <= 5, "c1" prob += x + z >= 10, "c2" prob += -y + z == 7.5, "c3" - print("\t Testing MIP solution with floats in objective") pulpTestCheck( prob, self.solver, @@ -443,7 +429,6 @@ def test_initial_value(self): "HiGHS_CMD", ]: self.solver.optionsDict["warmStart"] = True - print("\t Testing Initial value in MIP solution") pulpTestCheck( prob, self.solver, [const.LpStatusOptimal], {x: 3, y: -0.5, z: 7} ) @@ -462,7 +447,6 @@ def test_fixed_value(self): v.setInitialValue(solution[v]) v.fixValue() self.solver.optionsDict["warmStart"] = True - print("\t Testing fixing value in MIP solution") pulpTestCheck(prob, self.solver, [const.LpStatusOptimal], solution) def test_relaxed_mip(self): @@ -475,7 +459,6 @@ def test_relaxed_mip(self): prob += x + z >= 10, "c2" prob += -y + z == 7.5, "c3" self.solver.mip = 0 - print("\t Testing MIP relaxation") if self.solver.__class__ in [ GUROBI_CMD, CHOCO_CMD, @@ -501,7 +484,6 @@ def test_feasibility_only(self): prob += x + y <= 5, "c1" prob += x + z >= 10, "c2" prob += -y + z == 7.5, "c3" - print("\t Testing feasibility problem (no objective)") pulpTestCheck(prob, self.solver, [const.LpStatusOptimal]) def test_infeasible_2(self): @@ -512,7 +494,6 @@ def test_infeasible_2(self): prob += x + y <= 5.2, "c1" prob += x + z >= 10.3, "c2" prob += -y + z == 17.5, "c3" - print("\t Testing an infeasible problem") if self.solver.__class__ is GLPK_CMD: # GLPK_CMD return codes are not informative enough pulpTestCheck(prob, self.solver, [const.LpStatusUndefined]) @@ -530,7 +511,6 @@ def test_integer_infeasible(self): prob += x + y <= 5.2, "c1" prob += x + z >= 10.3, "c2" prob += -y + z == 7.4, "c3" - print("\t Testing an integer infeasible problem") if self.solver.__class__ in [GLPK_CMD, COIN_CMD, PULP_CBC_CMD, MOSEK]: # GLPK_CMD returns InfeasibleOrUnbounded pulpTestCheck( @@ -541,7 +521,6 @@ def test_integer_infeasible(self): elif self.solver.__class__ in [COINMP_DLL]: # Currently there is an error in COINMP for problems where # presolve eliminates too many variables - print("\t\t Error in CoinMP to be fixed, reports Optimal") pulpTestCheck(prob, self.solver, [const.LpStatusOptimal]) elif self.solver.__class__ in [GUROBI_CMD, FSCIP_CMD]: pulpTestCheck(prob, self.solver, [const.LpStatusNotSolved]) @@ -558,7 +537,6 @@ def test_integer_infeasible_2(self): prob += dummy prob += c1 + c2 == 2 prob += c1 <= 0 - print("\t Testing another integer infeasible problem") if self.solver.__class__ in [GUROBI_CMD, SCIP_CMD, FSCIP_CMD, SCIP_PY]: pulpTestCheck(prob, self.solver, [const.LpStatusNotSolved]) elif self.solver.__class__ in [GLPK_CMD]: @@ -587,7 +565,6 @@ def test_column_based(self): x = LpVariable("x", 0, 4, const.LpContinuous, obj + a + b) y = LpVariable("y", -1, 1, const.LpContinuous, 4 * obj + a - c) z = LpVariable("z", 0, None, const.LpContinuous, 9 * obj + b + c) - print("\t Testing column based modelling") pulpTestCheck( prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6} ) @@ -609,7 +586,6 @@ def test_colum_based_empty_constraints(self): y = LpVariable("y", -1, 1, const.LpContinuous, 4 * obj - c) z = LpVariable("z", 0, None, const.LpContinuous, 9 * obj + b + c) if self.solver.__class__ in [CPLEX_CMD, COINMP_DLL, YAPOSIB, PYGLPK]: - print("\t Testing column based modelling with empty constraints") pulpTestCheck( prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6} ) @@ -638,7 +614,6 @@ def test_dual_variables_reduced_costs(self): YAPOSIB, PYGLPK, ]: - print("\t Testing dual variables and slacks reporting") pulpTestCheck( prob, self.solver, @@ -668,7 +643,6 @@ def test_column_based_modelling_resolve(self): prob.resolve() z = LpVariable("z", 0, None, const.LpContinuous, 9 * obj + b + c) if self.solver.__class__ in [COINMP_DLL]: - print("\t Testing resolve of problem") prob.resolve() # difficult to check this is doing what we want as the resolve is # overridden if it is not implemented @@ -689,7 +663,6 @@ def test_sequential_solve(self): prob += x <= 1, "c1" if self.solver.__class__ in [COINMP_DLL, GUROBI]: - print("\t Testing Sequential Solves") status = prob.sequentialSolve([obj1, obj2], solver=self.solver) pulpTestCheck( prob, @@ -714,7 +687,6 @@ def test_fractional_constraints(self): prob += -y + z == 7, "c3" prob += w >= 0, "c4" prob += LpFractionConstraint(x, z, const.LpConstraintEQ, 0.5, name="c5") - print("\t Testing fractional constraints") pulpTestCheck( prob, self.solver, @@ -736,7 +708,6 @@ def test_elastic_constraints(self): prob += x + z >= 10, "c2" prob += -y + z == 7, "c3" prob.extend((w >= -1).makeElasticSubProblem()) - print("\t Testing elastic constraints (no change)") pulpTestCheck( prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: -1} ) @@ -755,7 +726,6 @@ def test_elastic_constraints_2(self): prob += x + z >= 10, "c2" prob += -y + z == 7, "c3" prob.extend((w >= -1).makeElasticSubProblem(proportionFreeBound=0.1)) - print("\t Testing elastic constraints (freebound)") pulpTestCheck( prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: -1.1} ) @@ -774,7 +744,6 @@ def test_elastic_constraints_penalty_unchanged(self): prob += x + z >= 10, "c2" prob += -y + z == 7, "c3" prob.extend((w >= -1).makeElasticSubProblem(penalty=1.1)) - print("\t Testing elastic constraints (penalty unchanged)") pulpTestCheck( prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: -1.0} ) @@ -793,7 +762,6 @@ def test_elastic_constraints_penalty_unbounded(self): prob += x + z >= 10, "c2" prob += -y + z == 7, "c3" prob.extend((w >= -1).makeElasticSubProblem(penalty=0.9)) - print("\t Testing elastic constraints (penalty unbounded)") if self.solver.__class__ in [ COINMP_DLL, GUROBI, @@ -847,7 +815,6 @@ def test_pulpTestAll(self): """ Test the availability of the function pulpTestAll """ - print("\t Testing the availability of the function pulpTestAll") from pulp import pulpTestAll def test_export_dict_LP(self): @@ -864,7 +831,6 @@ def test_export_dict_LP(self): data = prob.toDict() var1, prob1 = LpProblem.fromDict(data) x, y, z, w = (var1[name] for name in ["x", "y", "z", "w"]) - print("\t Testing continuous LP solution - export dict") pulpTestCheck( prob1, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: 0} ) @@ -882,7 +848,6 @@ def test_export_dict_LP_no_obj(self): data = prob.toDict() var1, prob1 = LpProblem.fromDict(data) x, y, z, w = (var1[name] for name in ["x", "y", "z", "w"]) - print("\t Testing export dict for LP") pulpTestCheck( prob1, self.solver, [const.LpStatusOptimal], {x: 4, y: 1, z: 6, w: 0} ) @@ -907,7 +872,6 @@ def test_export_json_LP(self): except: pass x, y, z, w = (var1[name] for name in ["x", "y", "z", "w"]) - print("\t Testing continuous LP solution - export JSON") pulpTestCheck( prob1, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: 0} ) @@ -927,7 +891,6 @@ def test_export_dict_MIP(self): data_backup = copy.deepcopy(data) var1, prob1 = LpProblem.fromDict(data) x, y, z = (var1[name] for name in ["x", "y", "z"]) - print("\t Testing export dict MIP") pulpTestCheck( prob1, self.solver, [const.LpStatusOptimal], {x: 3, y: -0.5, z: 7} ) @@ -948,7 +911,6 @@ def test_export_dict_max(self): data = prob.toDict() var1, prob1 = LpProblem.fromDict(data) x, y, z, w = (var1[name] for name in ["x", "y", "z", "w"]) - print("\t Testing maximize continuous LP solution") pulpTestCheck( prob1, self.solver, [const.LpStatusOptimal], {x: 4, y: 1, z: 8, w: 0} ) @@ -966,7 +928,6 @@ def test_export_solver_dict_LP(self): prob += w >= 0, "c4" data = self.solver.toDict() solver1 = getSolverFromDict(data) - print("\t Testing continuous LP solution - export solver dict") pulpTestCheck( prob, solver1, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: 0} ) @@ -1006,7 +967,6 @@ def test_export_solver_json(self): os.remove(filename) except: pass - print("\t Testing continuous LP solution - export solver JSON") pulpTestCheck( prob, solver1, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: 0} ) @@ -1025,7 +985,6 @@ def test_timeLimit(self): prob += w >= 0, "c4" self.solver.timeLimit = 20 # CHOCO has issues when given a time limit - print("\t Testing timeLimit argument") if self.solver.name != "CHOCO_CMD": pulpTestCheck( prob, @@ -1035,7 +994,6 @@ def test_timeLimit(self): ) def test_assignInvalidStatus(self): - print("\t Testing invalid status") t = LpProblem("test") Invalid = -100 self.assertRaises(const.PulpError, lambda: t.assignStatus(Invalid)) @@ -1063,7 +1021,6 @@ def test_logPath(self): "PULP_CBC_CMD", "COIN_CMD", ]: - print("\t Testing logPath argument") pulpTestCheck( prob, self.solver, @@ -1084,7 +1041,6 @@ def test_makeDict_behavior(self): target = {"A": {"C": 1, "D": 2}, "B": {"C": 3, "D": 4}} dict_with_default = makeDict(headers, values, default=0) dict_without_default = makeDict(headers, values) - print("\t Testing makeDict general behavior") self.assertEqual(dict_with_default, target) self.assertEqual(dict_without_default, target) @@ -1096,7 +1052,6 @@ def test_makeDict_default_value(self): values = [[1, 2], [3, 4]] dict_with_default = makeDict(headers, values, default=0) dict_without_default = makeDict(headers, values) - print("\t Testing makeDict default value behavior") # Check if a default value is passed self.assertEqual(dict_with_default["X"]["Y"], 0) # Check if a KeyError is raised @@ -1120,7 +1075,6 @@ def test_importMPS_maximize(self): _vars, prob2 = LpProblem.fromMPS(filename, sense=prob.sense) _dict1 = getSortedDict(prob) _dict2 = getSortedDict(prob2) - print("\t Testing reading MPS files - maximize") self.assertDictEqual(_dict1, _dict2) def test_importMPS_noname(self): @@ -1140,7 +1094,6 @@ def test_importMPS_noname(self): _vars, prob2 = LpProblem.fromMPS(filename, sense=prob.sense) _dict1 = getSortedDict(prob) _dict2 = getSortedDict(prob2) - print("\t Testing reading MPS files - noname") self.assertDictEqual(_dict1, _dict2) def test_importMPS_integer(self): @@ -1158,7 +1111,6 @@ def test_importMPS_integer(self): _vars, prob2 = LpProblem.fromMPS(filename, sense=prob.sense) _dict1 = getSortedDict(prob) _dict2 = getSortedDict(prob2) - print("\t Testing reading MPS files - integer variable") self.assertDictEqual(_dict1, _dict2) def test_importMPS_binary(self): @@ -1177,7 +1129,6 @@ def test_importMPS_binary(self): ) _dict1 = getSortedDict(prob, keyCons="constant") _dict2 = getSortedDict(prob2, keyCons="constant") - print("\t Testing reading MPS files - binary variable, no constraint names") self.assertDictEqual(_dict1, _dict2) def test_importMPS_RHS_fields56(self): @@ -1192,20 +1143,10 @@ def test_importMPS_PL_bound(self): """Import MPS file with PL bound type.""" with tempfile.NamedTemporaryFile(delete=False) as h: h.write(str.encode(EXAMPLE_MPS_PL_BOUNDS)) - print("\t Testing reading MPS files - PL bound") _, problem = LpProblem.fromMPS(h.name) os.unlink(h.name) self.assertIsInstance(problem, LpProblem) - # def test_importMPS_2(self): - # name = self._testMethodName - # # filename = name + ".mps" - # filename = "/home/pchtsp/Downloads/test.mps" - # _vars, _prob = LpProblem.fromMPS(filename) - # _prob.solve() - # for k, v in _vars.items(): - # print(k, v.value()) - def test_unset_objective_value__is_valid(self): """Given a valid problem that does not converge, assert that it is still categorised as valid. @@ -1266,7 +1207,6 @@ def add_const(prob): @gurobi_test def test_measuring_solving_time(self): - print("\t Testing measuring optimization time") time_limit = 10 solver_settings = dict( @@ -1305,10 +1245,9 @@ def test_measuring_solving_time(self): @gurobi_test def test_time_limit_no_solution(self): - print("\t Test time limit with no solution") time_limit = 1 - solver_settings = dict(HiGHS_CMD=50, HiGHS=50, PULP_CBC_CMD=30, COIN_CMD=30) + solver_settings = dict(HiGHS_CMD=60, HiGHS=60, PULP_CBC_CMD=60, COIN_CMD=60) bins = solver_settings.get(self.solver.name) if bins is None: # not all solvers have timeLimit support @@ -1331,7 +1270,6 @@ def test_invalid_var_names(self): prob += x + z >= 10, "c2" prob += -y + z == 7, "c3" prob += w >= 0, "c4" - print("\t Testing invalid var names") if self.solver.name not in [ "GUROBI_CMD", # end is a key-word for LP files ]: @@ -1349,7 +1287,6 @@ def test_LpVariable_indexs_param(self): customers = [1, 2, 3] agents = ["A", "B", "C"] - print("\t Testing 'indexs' param continues to work for LpVariable.dicts") # explicit param creates a dict of type LpVariable assign_vars = LpVariable.dicts(name="test", indices=(customers, agents)) for k, v in assign_vars.items(): @@ -1362,7 +1299,6 @@ def test_LpVariable_indexs_param(self): for a, b in v.items(): self.assertIsInstance(b, LpVariable) - print("\t Testing 'indexs' param continues to work for LpVariable.matrix") # explicit param creates list of LpVariable assign_vars_matrix = LpVariable.matrix( name="test", indices=(customers, agents) @@ -1385,14 +1321,12 @@ def test_LpVariable_indices_param(self): customers = [1, 2, 3] agents = ["A", "B", "C"] - print("\t Testing 'indices' argument works in LpVariable.dicts") # explicit param creates a dict of type LpVariable assign_vars = LpVariable.dicts(name="test", indices=(customers, agents)) for k, v in assign_vars.items(): for a, b in v.items(): self.assertIsInstance(b, LpVariable) - print("\t Testing 'indices' param continues to work for LpVariable.matrix") # explicit param creates list of list of LpVariable assign_vars_matrix = LpVariable.matrix( name="test", indices=(customers, agents) @@ -1407,7 +1341,6 @@ def test_parse_cplex_mipopt_solution(self): """ from io import StringIO - print("\t Testing that `readsol` can parse CPLEX mipopt solution") # Example solution generated by CPLEX mipopt solver file_content = """ @@ -1472,7 +1405,6 @@ def test_options_parsing_SCIP_HIGHS(self): prob += -y + z == 7, "c3" prob += w >= 0, "c4" # CHOCO has issues when given a time limit - print("\t Testing options parsing") if self.solver.__class__ in [SCIP_CMD, FSCIP_CMD]: self.solver.options = ["limits/time", 20] pulpTestCheck(