From 5148fe74ce5367cb301f65fc382d9342b54a30f4 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Fri, 18 Oct 2024 13:19:13 +0200 Subject: [PATCH 01/13] Minor fixes --- tests/helpers/utils.py | 6 +----- tests/test_model.py | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py index 0eaff8c2f..4299c39dd 100644 --- a/tests/helpers/utils.py +++ b/tests/helpers/utils.py @@ -77,19 +77,15 @@ def random_nlp_1(): return model -def knapsack_model(weights=[4, 2, 6, 3, 7, 5], costs=[7, 2, 5, 4, 3, 4]): +def knapsack_model(weights=[4, 2, 6, 3, 7, 5], costs=[7, 2, 5, 4, 3, 4], knapsackSize = 15): # create solver instance s = Model("Knapsack") - s.hideOutput() # setting the objective sense to maximise s.setMaximize() assert len(weights) == len(costs) - # knapsack size - knapsackSize = 15 - # adding the knapsack variables knapsackVars = [] varNames = [] diff --git a/tests/test_model.py b/tests/test_model.py index 365f3e919..f5dcd062a 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -476,4 +476,4 @@ def test_getObjVal(): assert m.getVal(x) == 0 assert m.getObjVal() == 16 - assert m.getVal(x) == 0 + assert m.getVal(x) == 0 \ No newline at end of file From 58ad6d1216fd8aed4dbafcd9204edf5afef49191 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Fri, 18 Oct 2024 13:19:39 +0200 Subject: [PATCH 02/13] Add primal_dual_evolution and test and plot --- .../recipes/primal_dual_evolution.py | 50 +++++++++++++++++++ tests/test_recipe_primal_dual_evolution.py | 20 ++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/pyscipopt/recipes/primal_dual_evolution.py create mode 100644 tests/test_recipe_primal_dual_evolution.py diff --git a/src/pyscipopt/recipes/primal_dual_evolution.py b/src/pyscipopt/recipes/primal_dual_evolution.py new file mode 100644 index 000000000..1aad4f45d --- /dev/null +++ b/src/pyscipopt/recipes/primal_dual_evolution.py @@ -0,0 +1,50 @@ +from pyscipopt import Model, Eventhdlr, SCIP_EVENTTYPE, Eventhdlr + +def get_primal_dual_evolution(model: Model): + + class gapEventhdlr(Eventhdlr): + + def eventinit(self): # we want to collect best primal solutions and best dual solutions + self.model.catchEvent(SCIP_EVENTTYPE.BESTSOLFOUND, self) + self.model.catchEvent(SCIP_EVENTTYPE.LPSOLVED, self) + self.model.catchEvent(SCIP_EVENTTYPE.NODESOLVED, self) + + + def eventexec(self, event): + # if a new best primal solution was found, we save when it was found and also its objective + if event.getType() == SCIP_EVENTTYPE.BESTSOLFOUND: + self.model.data["primal_solutions"].append((self.model.getSolvingTime(), self.model.getPrimalbound())) + + if not self.model.data["dual_solutions"]: + self.model.data["dual_solutions"].append((self.model.getSolvingTime(), self.model.getDualbound())) + + if self.model.getObjectiveSense() == "minimize": + if self.model.isGT(self.model.getDualbound(), self.model.data["dual_solutions"][-1][1]): + self.model.data["dual_solutions"].append((self.model.getSolvingTime(), self.model.getDualbound())) + else: + if self.model.isLT(self.model.getDualbound(), self.model.data["dual_solutions"][-1][1]): + self.model.data["dual_solutions"].append((self.model.getSolvingTime(), self.model.getDualbound())) + + if not hasattr(model, "data"): + model.data = {} + + model.data["primal_solutions"] = [] + model.data["dual_solutions"] = [] + hdlr = gapEventhdlr() + model.includeEventhdlr(hdlr, "gapEventHandler", "Event handler which collects primal and dual solution evolution") + + return model + +def plot_primal_dual_evolution(model: Model): + try: + from matplotlib import pyplot as plt + except ImportError: + raise("matplotlib is required to plot the solution. Try running `pip install matplotlib` in the command line.") + + time_primal, val_primal = zip(*model.data["primal_solutions"]) + plt.plot(time_primal, val_primal, label="Primal bound") + time_dual, val_dual = zip(*model.data["dual_solutions"]) + plt.plot(time_dual, val_dual, label="Dual bound") + + plt.legend(loc="best") + plt.show() \ No newline at end of file diff --git a/tests/test_recipe_primal_dual_evolution.py b/tests/test_recipe_primal_dual_evolution.py new file mode 100644 index 000000000..c5a67770f --- /dev/null +++ b/tests/test_recipe_primal_dual_evolution.py @@ -0,0 +1,20 @@ +from pyscipopt.recipes.primal_dual_evolution import get_primal_dual_evolution +from helpers.utils import bin_packing_model + +def test_primal_dual_evolution(): + from random import randint + + model = bin_packing_model(sizes=[randint(1,40) for _ in range(120)], capacity=50) + model.setParam("limits/time",5) + + model.data = {"test": True} + model = get_primal_dual_evolution(model) + + assert "test" in model.data + assert "primal_solutions" in model.data + + model.optimize() + + # these are required because the event handler doesn't capture the final state + model.data["primal_solutions"].append((model.getSolvingTime(), model.getPrimalbound())) + model.data["dual_solutions"].append((model.getSolvingTime(), model.getDualbound())) \ No newline at end of file From 1e5a56f7b770b48bb36f5994014b995b10db7889 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Fri, 18 Oct 2024 13:20:15 +0200 Subject: [PATCH 03/13] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ef5ff7e4..6b0d5f05b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased ### Added +- Added primal_dual_evolution recipe and a plot recipe - Expanded Statistics class to more problems. - Created Statistics class - Added parser to read .stats file From 53627d21c88b5dcdf20da9c597a41ab234823984 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Fri, 18 Oct 2024 13:33:57 +0200 Subject: [PATCH 04/13] More robust testing --- tests/test_recipe_primal_dual_evolution.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/test_recipe_primal_dual_evolution.py b/tests/test_recipe_primal_dual_evolution.py index c5a67770f..c7e911779 100644 --- a/tests/test_recipe_primal_dual_evolution.py +++ b/tests/test_recipe_primal_dual_evolution.py @@ -17,4 +17,20 @@ def test_primal_dual_evolution(): # these are required because the event handler doesn't capture the final state model.data["primal_solutions"].append((model.getSolvingTime(), model.getPrimalbound())) - model.data["dual_solutions"].append((model.getSolvingTime(), model.getDualbound())) \ No newline at end of file + model.data["dual_solutions"].append((model.getSolvingTime(), model.getDualbound())) + + for i in range(1, len(model.data["primal_solutions"])): + if model.getObjectiveSense() == "minimize": + assert model.data["primal_solutions"][i][1] <= model.data["primal_solutions"][i-1][1] + else: + assert model.data["primal_solutions"][i][1] >= model.data["primal_solutions"][i-1][1] + + for i in range(1, len(model.data["dual_solutions"])): + if model.getObjectiveSense() == "minimize": + assert model.data["dual_solutions"][i][1] >= model.data["dual_solutions"][i-1][1] + else: + assert model.data["dual_solutions"][i][1] <= model.data["dual_solutions"][i-1][1] + + # how to get a simple plot of the data + #from pyscipopt.recipes.primal_dual_evolution import plot_primal_dual_evolution + #plot_primal_dual_evolution(model) \ No newline at end of file From 7c23a54356ef46e96c411aeba84d5de93e01a66b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dion=C3=ADsio?= <57299939+Joao-Dionisio@users.noreply.github.com> Date: Sun, 27 Oct 2024 10:17:06 +0100 Subject: [PATCH 05/13] Update src/pyscipopt/recipes/primal_dual_evolution.py Co-authored-by: Mohammed Ghannam --- src/pyscipopt/recipes/primal_dual_evolution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/recipes/primal_dual_evolution.py b/src/pyscipopt/recipes/primal_dual_evolution.py index 1aad4f45d..670d4456c 100644 --- a/src/pyscipopt/recipes/primal_dual_evolution.py +++ b/src/pyscipopt/recipes/primal_dual_evolution.py @@ -2,7 +2,7 @@ def get_primal_dual_evolution(model: Model): - class gapEventhdlr(Eventhdlr): + class GapEventhdlr(Eventhdlr): def eventinit(self): # we want to collect best primal solutions and best dual solutions self.model.catchEvent(SCIP_EVENTTYPE.BESTSOLFOUND, self) From cca56ee1b77ea49ce19c6c6e02ea634864011a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dion=C3=ADsio?= <57299939+Joao-Dionisio@users.noreply.github.com> Date: Sun, 27 Oct 2024 10:17:14 +0100 Subject: [PATCH 06/13] Update tests/helpers/utils.py Co-authored-by: Mohammed Ghannam --- tests/helpers/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py index 4299c39dd..969ba4798 100644 --- a/tests/helpers/utils.py +++ b/tests/helpers/utils.py @@ -77,7 +77,7 @@ def random_nlp_1(): return model -def knapsack_model(weights=[4, 2, 6, 3, 7, 5], costs=[7, 2, 5, 4, 3, 4], knapsackSize = 15): +def knapsack_model(weights=[4, 2, 6, 3, 7, 5], costs=[7, 2, 5, 4, 3, 4], knapsack_size = 15): # create solver instance s = Model("Knapsack") From a3cddcb52489cdb9510d9c60c4a3c2073b5163a2 Mon Sep 17 00:00:00 2001 From: Dionisio Date: Mon, 28 Oct 2024 15:11:48 +0100 Subject: [PATCH 07/13] some comments --- .../finished/plot_primal_dual_evolution.py | 140 ++++++++++++++++++ .../recipes/primal_dual_evolution.py | 57 +++---- tests/test_recipe_primal_dual_evolution.py | 12 +- 3 files changed, 172 insertions(+), 37 deletions(-) create mode 100644 examples/finished/plot_primal_dual_evolution.py diff --git a/examples/finished/plot_primal_dual_evolution.py b/examples/finished/plot_primal_dual_evolution.py new file mode 100644 index 000000000..6db1755f2 --- /dev/null +++ b/examples/finished/plot_primal_dual_evolution.py @@ -0,0 +1,140 @@ +from pyscipopt import Model + +def plot_primal_dual_evolution(model: Model): + try: + from matplotlib import pyplot as plt + except ImportError: + raise("matplotlib is required to plot the solution. Try running `pip install matplotlib` in the command line.") + + time_primal, val_primal = zip(*model.data["primal_log"]) + plt.plot(time_primal, val_primal, label="Primal bound") + time_dual, val_dual = zip(*model.data["dual_log"]) + plt.plot(time_dual, val_dual, label="Dual bound") + + plt.legend(loc="best") + plt.show() + + +def gastrans_model(): + GASTEMP = 281.15 + RUGOSITY = 0.05 + DENSITY = 0.616 + COMPRESSIBILITY = 0.8 + nodes = [ + # name supplylo supplyup pressurelo pressureup cost + ("Anderlues", 0.0, 1.2, 0.0, 66.2, 0.0), # 0 + ("Antwerpen", None, -4.034, 30.0, 80.0, 0.0), # 1 + ("Arlon", None, -0.222, 0.0, 66.2, 0.0), # 2 + ("Berneau", 0.0, 0.0, 0.0, 66.2, 0.0), # 3 + ("Blaregnies", None, -15.616, 50.0, 66.2, 0.0), # 4 + ("Brugge", None, -3.918, 30.0, 80.0, 0.0), # 5 + ("Dudzele", 0.0, 8.4, 0.0, 77.0, 2.28), # 6 + ("Gent", None, -5.256, 30.0, 80.0, 0.0), # 7 + ("Liege", None, -6.385, 30.0, 66.2, 0.0), # 8 + ("Loenhout", 0.0, 4.8, 0.0, 77.0, 2.28), # 9 + ("Mons", None, -6.848, 0.0, 66.2, 0.0), # 10 + ("Namur", None, -2.120, 0.0, 66.2, 0.0), # 11 + ("Petange", None, -1.919, 25.0, 66.2, 0.0), # 12 + ("Peronnes", 0.0, 0.96, 0.0, 66.2, 1.68), # 13 + ("Sinsin", 0.0, 0.0, 0.0, 63.0, 0.0), # 14 + ("Voeren", 20.344, 22.012, 50.0, 66.2, 1.68), # 15 + ("Wanze", 0.0, 0.0, 0.0, 66.2, 0.0), # 16 + ("Warnand", 0.0, 0.0, 0.0, 66.2, 0.0), # 17 + ("Zeebrugge", 8.87, 11.594, 0.0, 77.0, 2.28), # 18 + ("Zomergem", 0.0, 0.0, 0.0, 80.0, 0.0) # 19 + ] + arcs = [ + # node1 node2 diameter length active */ + (18, 6, 890.0, 4.0, False), + (18, 6, 890.0, 4.0, False), + (6, 5, 890.0, 6.0, False), + (6, 5, 890.0, 6.0, False), + (5, 19, 890.0, 26.0, False), + (9, 1, 590.1, 43.0, False), + (1, 7, 590.1, 29.0, False), + (7, 19, 590.1, 19.0, False), + (19, 13, 890.0, 55.0, False), + (15, 3, 890.0, 5.0, True), + (15, 3, 395.0, 5.0, True), + (3, 8, 890.0, 20.0, False), + (3, 8, 395.0, 20.0, False), + (8, 17, 890.0, 25.0, False), + (8, 17, 395.0, 25.0, False), + (17, 11, 890.0, 42.0, False), + (11, 0, 890.0, 40.0, False), + (0, 13, 890.0, 5.0, False), + (13, 10, 890.0, 10.0, False), + (10, 4, 890.0, 25.0, False), + (17, 16, 395.5, 10.5, False), + (16, 14, 315.5, 26.0, True), + (14, 2, 315.5, 98.0, False), + (2, 12, 315.5, 6.0, False) + ] + + model = Model() + + # create flow variables + flow = {} + for arc in arcs: + flow[arc] = model.addVar("flow_%s_%s" % (nodes[arc[0]][0], nodes[arc[1]][0]), # names of nodes in arc + lb=0.0 if arc[4] else None) # no lower bound if not active + + # pressure difference variables + pressurediff = {} + for arc in arcs: + pressurediff[arc] = model.addVar("pressurediff_%s_%s" % (nodes[arc[0]][0], nodes[arc[1]][0]), + # names of nodes in arc + lb=None) + + # supply variables + supply = {} + for node in nodes: + supply[node] = model.addVar("supply_%s" % (node[0]), lb=node[1], ub=node[2], obj=node[5]) + + # square pressure variables + pressure = {} + for node in nodes: + pressure[node] = model.addVar("pressure_%s" % (node[0]), lb=node[3] ** 2, ub=node[4] ** 2) + + # node balance constrains, for each node i: outflows - inflows = supply + for nid, node in enumerate(nodes): + # find arcs that go or end at this node + flowbalance = 0 + for arc in arcs: + if arc[0] == nid: # arc is outgoing + flowbalance += flow[arc] + elif arc[1] == nid: # arc is incoming + flowbalance -= flow[arc] + else: + continue + + model.addCons(flowbalance == supply[node], name="flowbalance%s" % node[0]) + + # pressure difference constraints: pressurediff[node1 to node2] = pressure[node1] - pressure[node2] + for arc in arcs: + model.addCons(pressurediff[arc] == pressure[nodes[arc[0]]] - pressure[nodes[arc[1]]], + "pressurediffcons_%s_%s" % (nodes[arc[0]][0], nodes[arc[1]][0])) + + # pressure loss constraints: + from math import log10 + for arc in arcs: + coef = 96.074830e-15 * arc[2] ** 5 * (2.0 * log10(3.7 * arc[2] / RUGOSITY)) ** 2 / COMPRESSIBILITY / GASTEMP / \ + arc[3] / DENSITY + if arc[4]: # active + model.addCons(flow[arc] ** 2 + coef * pressurediff[arc] <= 0.0, + "pressureloss_%s_%s" % (nodes[arc[0]][0], nodes[arc[1]][0])) + else: + model.addCons(flow[arc] * abs(flow[arc]) - coef * pressurediff[arc] == 0.0, + "pressureloss_%s_%s" % (nodes[arc[0]][0], nodes[arc[1]][0])) + + return model + + +if __name__=="__main__": + from pyscipopt.recipes.primal_dual_evolution import attach_primal_dual_evolution_eventhdlr + + model = gastrans_model() + model.attach_primal_dual_evolution_eventhdlr() + + model.optimize() + plot_primal_dual_evolution(model) diff --git a/src/pyscipopt/recipes/primal_dual_evolution.py b/src/pyscipopt/recipes/primal_dual_evolution.py index 670d4456c..dd13d162e 100644 --- a/src/pyscipopt/recipes/primal_dual_evolution.py +++ b/src/pyscipopt/recipes/primal_dual_evolution.py @@ -1,7 +1,9 @@ from pyscipopt import Model, Eventhdlr, SCIP_EVENTTYPE, Eventhdlr -def get_primal_dual_evolution(model: Model): - +def attach_primal_dual_evolution_eventhdlr(model: Model): + """ + + """ class GapEventhdlr(Eventhdlr): def eventinit(self): # we want to collect best primal solutions and best dual solutions @@ -13,38 +15,39 @@ def eventinit(self): # we want to collect best primal solutions and best dual so def eventexec(self, event): # if a new best primal solution was found, we save when it was found and also its objective if event.getType() == SCIP_EVENTTYPE.BESTSOLFOUND: - self.model.data["primal_solutions"].append((self.model.getSolvingTime(), self.model.getPrimalbound())) + self.model.data["primal_log"].append((self.model.getSolvingTime(), self.model.getPrimalbound())) + + if not self.model.data["dual_log"]: + self.model.data["dual_log"].append((self.model.getSolvingTime(), self.model.getDualbound())) + + if self.model.getObjectiveSense() == "minimize": + if self.model.isGT(self.model.getDualbound(), self.model.data["dual_log"][-1][1]): + self.model.data["dual_log"].append((self.model.getSolvingTime(), self.model.getDualbound())) + else: + if self.model.isLT(self.model.getDualbound(), self.model.data["dual_log"][-1][1]): + self.model.data["dual_log"].append((self.model.getSolvingTime(), self.model.getDualbound())) + + def eventexitsol(self): + if self.model.data["primal_log"][-1] and self.model.getPrimalbound() != self.model.data["primal_log"][-1][1]: + self.model.data["primal_log"].append((self.model.getSolvingTime(), self.model.getPrimalbound())) - if not self.model.data["dual_solutions"]: - self.model.data["dual_solutions"].append((self.model.getSolvingTime(), self.model.getDualbound())) + if not self.model.data["dual_log"]: + self.model.data["dual_log"].append((self.model.getSolvingTime(), self.model.getDualbound())) if self.model.getObjectiveSense() == "minimize": - if self.model.isGT(self.model.getDualbound(), self.model.data["dual_solutions"][-1][1]): - self.model.data["dual_solutions"].append((self.model.getSolvingTime(), self.model.getDualbound())) + if self.model.isGT(self.model.getDualbound(), self.model.data["dual_log"][-1][1]): + self.model.data["dual_log"].append((self.model.getSolvingTime(), self.model.getDualbound())) else: - if self.model.isLT(self.model.getDualbound(), self.model.data["dual_solutions"][-1][1]): - self.model.data["dual_solutions"].append((self.model.getSolvingTime(), self.model.getDualbound())) + if self.model.isLT(self.model.getDualbound(), self.model.data["dual_log"][-1][1]): + self.model.data["dual_log"].append((self.model.getSolvingTime(), self.model.getDualbound())) + if not hasattr(model, "data"): model.data = {} - model.data["primal_solutions"] = [] - model.data["dual_solutions"] = [] - hdlr = gapEventhdlr() + model.data["primal_log"] = [] + model.data["dual_log"] = [] + hdlr = GapEventhdlr() model.includeEventhdlr(hdlr, "gapEventHandler", "Event handler which collects primal and dual solution evolution") - return model - -def plot_primal_dual_evolution(model: Model): - try: - from matplotlib import pyplot as plt - except ImportError: - raise("matplotlib is required to plot the solution. Try running `pip install matplotlib` in the command line.") - - time_primal, val_primal = zip(*model.data["primal_solutions"]) - plt.plot(time_primal, val_primal, label="Primal bound") - time_dual, val_dual = zip(*model.data["dual_solutions"]) - plt.plot(time_dual, val_dual, label="Dual bound") - - plt.legend(loc="best") - plt.show() \ No newline at end of file + return model \ No newline at end of file diff --git a/tests/test_recipe_primal_dual_evolution.py b/tests/test_recipe_primal_dual_evolution.py index c7e911779..f1b2632fc 100644 --- a/tests/test_recipe_primal_dual_evolution.py +++ b/tests/test_recipe_primal_dual_evolution.py @@ -1,4 +1,4 @@ -from pyscipopt.recipes.primal_dual_evolution import get_primal_dual_evolution +from pyscipopt.recipes.primal_dual_evolution import attach_primal_dual_evolution_eventhdlr from helpers.utils import bin_packing_model def test_primal_dual_evolution(): @@ -8,17 +8,13 @@ def test_primal_dual_evolution(): model.setParam("limits/time",5) model.data = {"test": True} - model = get_primal_dual_evolution(model) + model = attach_primal_dual_evolution_eventhdlr(model) assert "test" in model.data assert "primal_solutions" in model.data model.optimize() - # these are required because the event handler doesn't capture the final state - model.data["primal_solutions"].append((model.getSolvingTime(), model.getPrimalbound())) - model.data["dual_solutions"].append((model.getSolvingTime(), model.getDualbound())) - for i in range(1, len(model.data["primal_solutions"])): if model.getObjectiveSense() == "minimize": assert model.data["primal_solutions"][i][1] <= model.data["primal_solutions"][i-1][1] @@ -30,7 +26,3 @@ def test_primal_dual_evolution(): assert model.data["dual_solutions"][i][1] >= model.data["dual_solutions"][i-1][1] else: assert model.data["dual_solutions"][i][1] <= model.data["dual_solutions"][i-1][1] - - # how to get a simple plot of the data - #from pyscipopt.recipes.primal_dual_evolution import plot_primal_dual_evolution - #plot_primal_dual_evolution(model) \ No newline at end of file From 7c87182fd60a99928f11bf36eb09a72524fc9239 Mon Sep 17 00:00:00 2001 From: Dionisio Date: Mon, 28 Oct 2024 15:16:10 +0100 Subject: [PATCH 08/13] remove is_optimized_mode --- tests/test_memory.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_memory.py b/tests/test_memory.py index 2433c2c6a..a0c6d9363 100644 --- a/tests/test_memory.py +++ b/tests/test_memory.py @@ -1,15 +1,14 @@ import pytest from pyscipopt.scip import Model, is_memory_freed, print_memory_in_use -from helpers.utils import is_optimized_mode def test_not_freed(): - if is_optimized_mode(): + if is_memory_freed(): pytest.skip() m = Model() assert not is_memory_freed() def test_freed(): - if is_optimized_mode(): + if is_memory_freed(): pytest.skip() m = Model() del m From 06b4e724f282939d0e1e21e6f73508a479f30f10 Mon Sep 17 00:00:00 2001 From: Dionisio Date: Mon, 28 Oct 2024 15:29:52 +0100 Subject: [PATCH 09/13] add docstring. remove useless util --- src/pyscipopt/recipes/primal_dual_evolution.py | 9 ++++++++- tests/helpers/utils.py | 8 -------- tests/test_heur.py | 4 ++-- tests/test_recipe_primal_dual_evolution.py | 14 +++++++------- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/pyscipopt/recipes/primal_dual_evolution.py b/src/pyscipopt/recipes/primal_dual_evolution.py index dd13d162e..51878bd89 100644 --- a/src/pyscipopt/recipes/primal_dual_evolution.py +++ b/src/pyscipopt/recipes/primal_dual_evolution.py @@ -2,7 +2,14 @@ def attach_primal_dual_evolution_eventhdlr(model: Model): """ - + Attaches an event handler to a given SCIP model that collects primal and dual solutions, + along with the solving time when they were found. + The data is saved in model.data["primal_log"] and model.data["dual_log"]. They consist of + a list of tuples, each tuple containing the solving time and the corresponding solution. + + A usage example can be found in examples/finished/plot_primal_dual_evolution.py. The + example takes the information provided by this recipe and uses it to plot the evolution + of the dual and primal bounds over time. """ class GapEventhdlr(Eventhdlr): diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py index 969ba4798..4166c3027 100644 --- a/tests/helpers/utils.py +++ b/tests/helpers/utils.py @@ -1,14 +1,6 @@ from pyscipopt import Model, quicksum, SCIP_PARAMSETTING, exp, log, sqrt, sin from typing import List -from pyscipopt.scip import is_memory_freed - - -def is_optimized_mode(): - model = Model() - return is_memory_freed() - - def random_mip_1(disable_sepa=True, disable_huer=True, disable_presolve=True, node_lim=2000, small=False): model = Model() diff --git a/tests/test_heur.py b/tests/test_heur.py index 4af260e5b..9e9acd87f 100644 --- a/tests/test_heur.py +++ b/tests/test_heur.py @@ -6,7 +6,7 @@ from pyscipopt import Model, Heur, SCIP_RESULT, SCIP_PARAMSETTING, SCIP_HEURTIMING, SCIP_LPSOLSTAT from pyscipopt.scip import is_memory_freed -from helpers.utils import random_mip_1, is_optimized_mode +from helpers.utils import random_mip_1 class MyHeur(Heur): @@ -106,7 +106,7 @@ def test_heur(): assert round(sol[y]) == 0.0 def test_heur_memory(): - if is_optimized_mode(): + if is_memory_freed(): pytest.skip() def inner(): diff --git a/tests/test_recipe_primal_dual_evolution.py b/tests/test_recipe_primal_dual_evolution.py index f1b2632fc..d6d12d644 100644 --- a/tests/test_recipe_primal_dual_evolution.py +++ b/tests/test_recipe_primal_dual_evolution.py @@ -11,18 +11,18 @@ def test_primal_dual_evolution(): model = attach_primal_dual_evolution_eventhdlr(model) assert "test" in model.data - assert "primal_solutions" in model.data + assert "primal_log" in model.data model.optimize() - for i in range(1, len(model.data["primal_solutions"])): + for i in range(1, len(model.data["primal_log"])): if model.getObjectiveSense() == "minimize": - assert model.data["primal_solutions"][i][1] <= model.data["primal_solutions"][i-1][1] + assert model.data["primal_log"][i][1] <= model.data["primal_log"][i-1][1] else: - assert model.data["primal_solutions"][i][1] >= model.data["primal_solutions"][i-1][1] + assert model.data["primal_log"][i][1] >= model.data["primal_log"][i-1][1] - for i in range(1, len(model.data["dual_solutions"])): + for i in range(1, len(model.data["dual_log"])): if model.getObjectiveSense() == "minimize": - assert model.data["dual_solutions"][i][1] >= model.data["dual_solutions"][i-1][1] + assert model.data["dual_log"][i][1] >= model.data["dual_log"][i-1][1] else: - assert model.data["dual_solutions"][i][1] <= model.data["dual_solutions"][i-1][1] + assert model.data["dual_log"][i][1] <= model.data["dual_log"][i-1][1] From 3886f4af48ee386058c95839bf1f265b880cb50f Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Thu, 7 Nov 2024 15:07:22 +0100 Subject: [PATCH 10/13] Clean up code and example --- .../finished/plot_primal_dual_evolution.py | 158 ++++-------------- .../recipes/primal_dual_evolution.py | 26 +-- 2 files changed, 42 insertions(+), 142 deletions(-) diff --git a/examples/finished/plot_primal_dual_evolution.py b/examples/finished/plot_primal_dual_evolution.py index 6db1755f2..8d4c966bb 100644 --- a/examples/finished/plot_primal_dual_evolution.py +++ b/examples/finished/plot_primal_dual_evolution.py @@ -1,140 +1,54 @@ +""" +This example show how to retrieve the primal and dual solutions during the optimization process +and plot them as a function of time. The model is about gas transportation and can be found in +PySCIPOpt/tests/helpers/utils.py + +It makes use of the attach_primal_dual_evolution_eventhdlr recipe. + +Requires matplotlib, and may require PyQt6 to show the plot. +""" + from pyscipopt import Model def plot_primal_dual_evolution(model: Model): try: from matplotlib import pyplot as plt except ImportError: - raise("matplotlib is required to plot the solution. Try running `pip install matplotlib` in the command line.") - - time_primal, val_primal = zip(*model.data["primal_log"]) + raise ImportError("matplotlib is required to plot the solution. Try running `pip install matplotlib` in the command line.\ + You may also need to install PyQt6 to show the plot.") + + assert model.data["primal_log"], "Could not find any feasible solutions" + time_primal, val_primal = map(list,zip(*model.data["primal_log"])) + time_dual, val_dual = map(list,zip(*model.data["dual_log"])) + + + if time_primal[-1] < time_dual[-1]: + time_primal.append(time_dual[-1]) + val_primal.append(val_primal[-1]) + + if time_primal[-1] > time_dual[-1]: + time_dual.append(time_primal[-1]) + val_dual.append(val_dual[-1]) + plt.plot(time_primal, val_primal, label="Primal bound") - time_dual, val_dual = zip(*model.data["dual_log"]) plt.plot(time_dual, val_dual, label="Dual bound") plt.legend(loc="best") plt.show() - -def gastrans_model(): - GASTEMP = 281.15 - RUGOSITY = 0.05 - DENSITY = 0.616 - COMPRESSIBILITY = 0.8 - nodes = [ - # name supplylo supplyup pressurelo pressureup cost - ("Anderlues", 0.0, 1.2, 0.0, 66.2, 0.0), # 0 - ("Antwerpen", None, -4.034, 30.0, 80.0, 0.0), # 1 - ("Arlon", None, -0.222, 0.0, 66.2, 0.0), # 2 - ("Berneau", 0.0, 0.0, 0.0, 66.2, 0.0), # 3 - ("Blaregnies", None, -15.616, 50.0, 66.2, 0.0), # 4 - ("Brugge", None, -3.918, 30.0, 80.0, 0.0), # 5 - ("Dudzele", 0.0, 8.4, 0.0, 77.0, 2.28), # 6 - ("Gent", None, -5.256, 30.0, 80.0, 0.0), # 7 - ("Liege", None, -6.385, 30.0, 66.2, 0.0), # 8 - ("Loenhout", 0.0, 4.8, 0.0, 77.0, 2.28), # 9 - ("Mons", None, -6.848, 0.0, 66.2, 0.0), # 10 - ("Namur", None, -2.120, 0.0, 66.2, 0.0), # 11 - ("Petange", None, -1.919, 25.0, 66.2, 0.0), # 12 - ("Peronnes", 0.0, 0.96, 0.0, 66.2, 1.68), # 13 - ("Sinsin", 0.0, 0.0, 0.0, 63.0, 0.0), # 14 - ("Voeren", 20.344, 22.012, 50.0, 66.2, 1.68), # 15 - ("Wanze", 0.0, 0.0, 0.0, 66.2, 0.0), # 16 - ("Warnand", 0.0, 0.0, 0.0, 66.2, 0.0), # 17 - ("Zeebrugge", 8.87, 11.594, 0.0, 77.0, 2.28), # 18 - ("Zomergem", 0.0, 0.0, 0.0, 80.0, 0.0) # 19 - ] - arcs = [ - # node1 node2 diameter length active */ - (18, 6, 890.0, 4.0, False), - (18, 6, 890.0, 4.0, False), - (6, 5, 890.0, 6.0, False), - (6, 5, 890.0, 6.0, False), - (5, 19, 890.0, 26.0, False), - (9, 1, 590.1, 43.0, False), - (1, 7, 590.1, 29.0, False), - (7, 19, 590.1, 19.0, False), - (19, 13, 890.0, 55.0, False), - (15, 3, 890.0, 5.0, True), - (15, 3, 395.0, 5.0, True), - (3, 8, 890.0, 20.0, False), - (3, 8, 395.0, 20.0, False), - (8, 17, 890.0, 25.0, False), - (8, 17, 395.0, 25.0, False), - (17, 11, 890.0, 42.0, False), - (11, 0, 890.0, 40.0, False), - (0, 13, 890.0, 5.0, False), - (13, 10, 890.0, 10.0, False), - (10, 4, 890.0, 25.0, False), - (17, 16, 395.5, 10.5, False), - (16, 14, 315.5, 26.0, True), - (14, 2, 315.5, 98.0, False), - (2, 12, 315.5, 6.0, False) - ] - - model = Model() - - # create flow variables - flow = {} - for arc in arcs: - flow[arc] = model.addVar("flow_%s_%s" % (nodes[arc[0]][0], nodes[arc[1]][0]), # names of nodes in arc - lb=0.0 if arc[4] else None) # no lower bound if not active - - # pressure difference variables - pressurediff = {} - for arc in arcs: - pressurediff[arc] = model.addVar("pressurediff_%s_%s" % (nodes[arc[0]][0], nodes[arc[1]][0]), - # names of nodes in arc - lb=None) - - # supply variables - supply = {} - for node in nodes: - supply[node] = model.addVar("supply_%s" % (node[0]), lb=node[1], ub=node[2], obj=node[5]) - - # square pressure variables - pressure = {} - for node in nodes: - pressure[node] = model.addVar("pressure_%s" % (node[0]), lb=node[3] ** 2, ub=node[4] ** 2) - - # node balance constrains, for each node i: outflows - inflows = supply - for nid, node in enumerate(nodes): - # find arcs that go or end at this node - flowbalance = 0 - for arc in arcs: - if arc[0] == nid: # arc is outgoing - flowbalance += flow[arc] - elif arc[1] == nid: # arc is incoming - flowbalance -= flow[arc] - else: - continue - - model.addCons(flowbalance == supply[node], name="flowbalance%s" % node[0]) - - # pressure difference constraints: pressurediff[node1 to node2] = pressure[node1] - pressure[node2] - for arc in arcs: - model.addCons(pressurediff[arc] == pressure[nodes[arc[0]]] - pressure[nodes[arc[1]]], - "pressurediffcons_%s_%s" % (nodes[arc[0]][0], nodes[arc[1]][0])) - - # pressure loss constraints: - from math import log10 - for arc in arcs: - coef = 96.074830e-15 * arc[2] ** 5 * (2.0 * log10(3.7 * arc[2] / RUGOSITY)) ** 2 / COMPRESSIBILITY / GASTEMP / \ - arc[3] / DENSITY - if arc[4]: # active - model.addCons(flow[arc] ** 2 + coef * pressurediff[arc] <= 0.0, - "pressureloss_%s_%s" % (nodes[arc[0]][0], nodes[arc[1]][0])) - else: - model.addCons(flow[arc] * abs(flow[arc]) - coef * pressurediff[arc] == 0.0, - "pressureloss_%s_%s" % (nodes[arc[0]][0], nodes[arc[1]][0])) - - return model - - if __name__=="__main__": from pyscipopt.recipes.primal_dual_evolution import attach_primal_dual_evolution_eventhdlr - + import os + import sys + + # just a way to import files from different folders, not important + sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../tests/helpers'))) + + from utils import gastrans_model + model = gastrans_model() - model.attach_primal_dual_evolution_eventhdlr() + model.data = {} + attach_primal_dual_evolution_eventhdlr(model) model.optimize() plot_primal_dual_evolution(model) diff --git a/src/pyscipopt/recipes/primal_dual_evolution.py b/src/pyscipopt/recipes/primal_dual_evolution.py index 51878bd89..3e86a03d9 100644 --- a/src/pyscipopt/recipes/primal_dual_evolution.py +++ b/src/pyscipopt/recipes/primal_dual_evolution.py @@ -22,34 +22,20 @@ def eventinit(self): # we want to collect best primal solutions and best dual so def eventexec(self, event): # if a new best primal solution was found, we save when it was found and also its objective if event.getType() == SCIP_EVENTTYPE.BESTSOLFOUND: - self.model.data["primal_log"].append((self.model.getSolvingTime(), self.model.getPrimalbound())) + self.model.data["primal_log"].append([self.model.getSolvingTime(), self.model.getPrimalbound()]) if not self.model.data["dual_log"]: - self.model.data["dual_log"].append((self.model.getSolvingTime(), self.model.getDualbound())) + self.model.data["dual_log"].append([self.model.getSolvingTime(), self.model.getDualbound()]) if self.model.getObjectiveSense() == "minimize": if self.model.isGT(self.model.getDualbound(), self.model.data["dual_log"][-1][1]): - self.model.data["dual_log"].append((self.model.getSolvingTime(), self.model.getDualbound())) + self.model.data["dual_log"].append([self.model.getSolvingTime(), self.model.getDualbound()]) else: if self.model.isLT(self.model.getDualbound(), self.model.data["dual_log"][-1][1]): - self.model.data["dual_log"].append((self.model.getSolvingTime(), self.model.getDualbound())) + self.model.data["dual_log"].append([self.model.getSolvingTime(), self.model.getDualbound()]) + - def eventexitsol(self): - if self.model.data["primal_log"][-1] and self.model.getPrimalbound() != self.model.data["primal_log"][-1][1]: - self.model.data["primal_log"].append((self.model.getSolvingTime(), self.model.getPrimalbound())) - - if not self.model.data["dual_log"]: - self.model.data["dual_log"].append((self.model.getSolvingTime(), self.model.getDualbound())) - - if self.model.getObjectiveSense() == "minimize": - if self.model.isGT(self.model.getDualbound(), self.model.data["dual_log"][-1][1]): - self.model.data["dual_log"].append((self.model.getSolvingTime(), self.model.getDualbound())) - else: - if self.model.isLT(self.model.getDualbound(), self.model.data["dual_log"][-1][1]): - self.model.data["dual_log"].append((self.model.getSolvingTime(), self.model.getDualbound())) - - - if not hasattr(model, "data"): + if not hasattr(model, "data") or model.data==None: model.data = {} model.data["primal_log"] = [] From e011945b7f21d8b729787114bd0059438202bc74 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Thu, 7 Nov 2024 15:07:31 +0100 Subject: [PATCH 11/13] Add inits for ease of import later --- examples/finished/__init__.py | 0 tests/__init__.py | 0 tests/helpers/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/finished/__init__.py create mode 100644 tests/__init__.py create mode 100644 tests/helpers/__init__.py diff --git a/examples/finished/__init__.py b/examples/finished/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py new file mode 100644 index 000000000..e69de29bb From 9db8870dbcb30c4e46824d68ce2ea509084b8fd9 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Tue, 12 Nov 2024 17:17:53 +0100 Subject: [PATCH 12/13] Restore optimized test --- tests/test_heur.py | 4 ++-- tests/test_memory.py | 6 +++++- tests/test_model.py | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_heur.py b/tests/test_heur.py index 9e9acd87f..454f11c74 100644 --- a/tests/test_heur.py +++ b/tests/test_heur.py @@ -4,7 +4,7 @@ import pytest from pyscipopt import Model, Heur, SCIP_RESULT, SCIP_PARAMSETTING, SCIP_HEURTIMING, SCIP_LPSOLSTAT -from pyscipopt.scip import is_memory_freed +from test_memory import is_optimized_mode from helpers.utils import random_mip_1 @@ -106,7 +106,7 @@ def test_heur(): assert round(sol[y]) == 0.0 def test_heur_memory(): - if is_memory_freed(): + if is_optimized_mode(): pytest.skip() def inner(): diff --git a/tests/test_memory.py b/tests/test_memory.py index a0c6d9363..f67a77f6a 100644 --- a/tests/test_memory.py +++ b/tests/test_memory.py @@ -15,4 +15,8 @@ def test_freed(): assert is_memory_freed() def test_print_memory_in_use(): - print_memory_in_use() \ No newline at end of file + print_memory_in_use() + +def is_optimized_mode(): + model = Model() + return is_memory_freed() \ No newline at end of file diff --git a/tests/test_model.py b/tests/test_model.py index f5dcd062a..800ae7ab8 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -3,6 +3,7 @@ import itertools from pyscipopt import Model, SCIP_STAGE, SCIP_PARAMSETTING, quicksum +from pyscipopt.scip import is_memory_freed def test_model(): # create solver instance From 15f7e1bcfd8802060b08442c5aaa51a58b2eb4c0 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Fri, 15 Nov 2024 15:28:52 +0100 Subject: [PATCH 13/13] finally working --- tests/__init__.py | 0 tests/data/readStatistics.stats | 172 ++++++++++++++++++++++++++++++++ tests/helpers/__init__.py | 0 tests/test_memory.py | 4 +- tests/test_model.py | 1 - 5 files changed, 174 insertions(+), 3 deletions(-) delete mode 100644 tests/__init__.py create mode 100644 tests/data/readStatistics.stats delete mode 100644 tests/helpers/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/data/readStatistics.stats b/tests/data/readStatistics.stats new file mode 100644 index 000000000..c98992f6f --- /dev/null +++ b/tests/data/readStatistics.stats @@ -0,0 +1,172 @@ +SCIP Status : solving was interrupted [solution limit reached] +Total Time : 0.00 + solving : 0.00 + presolving : 0.00 (included in solving) + reading : 0.00 + copying : 0.00 (0 times copied the problem) +Original Problem : + Problem name : model + Variables : 1 (0 binary, 0 integer, 0 implicit integer, 1 continuous) + Constraints : 0 initial, 0 maximal + Objective : minimize, 0 non-zeros (abs.min = 1e+20, abs.max = -1e+20) +Presolved Problem : + Problem name : t_model + Variables : 1 (0 binary, 0 integer, 0 implicit integer, 1 continuous) + Constraints : 0 initial, 0 maximal + Objective : minimize, 0 non-zeros (abs.min = 1e+20, abs.max = -1e+20) + Nonzeros : 0 constraint, 0 clique table +Presolvers : ExecTime SetupTime Calls FixedVars AggrVars ChgTypes ChgBounds AddHoles DelCons AddCons ChgSides ChgCoefs + boundshift : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + convertinttobin : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + domcol : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + dualagg : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + dualcomp : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + dualinfer : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + dualsparsify : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + gateextraction : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + implics : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + inttobinary : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + qpkktref : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + redvub : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + sparsify : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + stuffing : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + trivial : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + tworowbnd : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + dualfix : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + genvbounds : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + probing : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + pseudoobj : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + symmetry : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + vbounds : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + benders : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + components : 0.00 0.00 0 0 0 0 0 0 0 0 0 0 + root node : - - - 0 - - 0 - - - - - +Constraints : Number MaxNumber #Separate #Propagate #EnfoLP #EnfoRelax #EnfoPS #Check #ResProp Cutoffs DomReds Cuts Applied Conss Children + benderslp : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + integral : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + benders : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + fixedvar : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + countsols : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + components : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Constraint Timings : TotalTime SetupTime Separate Propagate EnfoLP EnfoPS EnfoRelax Check ResProp SB-Prop + benderslp : 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 + integral : 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 + benders : 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 + fixedvar : 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 + countsols : 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 + components : 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 +Propagators : #Propagate #ResProp Cutoffs DomReds + dualfix : 0 0 0 0 + genvbounds : 0 0 0 0 + nlobbt : 0 0 0 0 + obbt : 0 0 0 0 + probing : 0 0 0 0 + pseudoobj : 0 0 0 0 + redcost : 0 0 0 0 + rootredcost : 0 0 0 0 + symmetry : 0 0 0 0 + vbounds : 0 0 0 0 +Propagator Timings : TotalTime SetupTime Presolve Propagate ResProp SB-Prop + dualfix : 0.00 0.00 0.00 0.00 0.00 0.00 + genvbounds : 0.00 0.00 0.00 0.00 0.00 0.00 + nlobbt : 0.00 0.00 0.00 0.00 0.00 0.00 + obbt : 0.00 0.00 0.00 0.00 0.00 0.00 + probing : 0.00 0.00 0.00 0.00 0.00 0.00 + pseudoobj : 0.00 0.00 0.00 0.00 0.00 0.00 + redcost : 0.00 0.00 0.00 0.00 0.00 0.00 + rootredcost : 0.00 0.00 0.00 0.00 0.00 0.00 + symmetry : 0.00 0.00 0.00 0.00 0.00 0.00 + vbounds : 0.00 0.00 0.00 0.00 0.00 0.00 +Conflict Analysis : Time Calls Success DomReds Conflicts Literals Reconvs ReconvLits Dualrays Nonzeros LP Iters (pool size: [--,--]) + propagation : 0.00 0 0 - 0 0.0 0 0.0 - - - + infeasible LP : 0.00 0 0 - 0 0.0 0 0.0 0 0.0 0 + bound exceed. LP : 0.00 0 0 - 0 0.0 0 0.0 0 0.0 0 + strong branching : 0.00 0 0 - 0 0.0 0 0.0 - - 0 + pseudo solution : 0.00 0 0 - 0 0.0 0 0.0 - - - + applied globally : 0.00 - - 0 0 0.0 - - 0 - - + applied locally : - - - 0 0 0.0 - - 0 - - +Primal Heuristics : ExecTime SetupTime Calls Found Best + LP solutions : 0.00 - - 0 0 + relax solutions : 0.00 - - 0 0 + pseudo solutions : 0.00 - - 0 0 + strong branching : 0.00 - - 0 0 + actconsdiving : 0.00 0.00 0 0 0 + adaptivediving : 0.00 0.00 0 0 0 + alns : 0.00 0.00 0 0 0 + bound : 0.00 0.00 0 0 0 + clique : 0.00 0.00 0 0 0 + coefdiving : 0.00 0.00 0 0 0 + completesol : 0.00 0.00 0 0 0 + conflictdiving : 0.00 0.00 0 0 0 + crossover : 0.00 0.00 0 0 0 + dins : 0.00 0.00 0 0 0 + distributiondivin: 0.00 0.00 0 0 0 + dps : 0.00 0.00 0 0 0 + dualval : 0.00 0.00 0 0 0 + farkasdiving : 0.00 0.00 0 0 0 + feaspump : 0.00 0.00 0 0 0 + fixandinfer : 0.00 0.00 0 0 0 + fracdiving : 0.00 0.00 0 0 0 + gins : 0.00 0.00 0 0 0 + guideddiving : 0.00 0.00 0 0 0 + indicator : 0.00 0.00 0 0 0 + indicatordiving : 0.00 0.00 0 0 0 + intdiving : 0.00 0.00 0 0 0 + intshifting : 0.00 0.00 0 0 0 + linesearchdiving : 0.00 0.00 0 0 0 + localbranching : 0.00 0.00 0 0 0 + locks : 0.00 0.00 0 0 0 + lpface : 0.00 0.00 0 0 0 + mpec : 0.00 0.00 0 0 0 + multistart : 0.00 0.00 0 0 0 + mutation : 0.00 0.00 0 0 0 + nlpdiving : 0.00 0.00 0 0 0 + objpscostdiving : 0.00 0.00 0 0 0 + octane : 0.00 0.00 0 0 0 + ofins : 0.00 0.00 0 0 0 + oneopt : 0.00 0.00 0 0 0 + padm : 0.00 0.00 0 0 0 + proximity : 0.00 0.00 0 0 0 + pscostdiving : 0.00 0.00 0 0 0 + randrounding : 0.00 0.00 0 0 0 + rens : 0.00 0.00 0 0 0 + reoptsols : 0.00 0.00 0 0 0 + repair : 0.00 0.00 0 0 0 + rins : 0.00 0.00 0 0 0 + rootsoldiving : 0.00 0.00 0 0 0 + rounding : 0.00 0.00 0 0 0 + scheduler : 0.00 0.00 0 0 0 + shiftandpropagate: 0.00 0.00 0 0 0 + shifting : 0.00 0.00 0 0 0 + simplerounding : 0.00 0.00 0 0 0 + subnlp : 0.00 0.00 0 0 0 + trivial : 0.00 0.00 0 0 0 + trivialnegation : 0.00 0.00 0 0 0 + trustregion : 0.00 0.00 0 0 0 + trysol : 0.00 0.00 0 0 0 + twoopt : 0.00 0.00 0 0 0 + undercover : 0.00 0.00 0 0 0 + vbounds : 0.00 0.00 0 0 0 + veclendiving : 0.00 0.00 0 0 0 + zeroobj : 0.00 0.00 0 0 0 + zirounding : 0.00 0.00 0 0 0 + other solutions : - - - 0 - +LNS (Scheduler) : Calls SetupTime SolveTime SolveNodes Sols Best Exp3 Exp3-IX EpsGreedy UCB TgtFixRate Opt Inf Node Stal Sol Usr Othr Actv + rens : 0 0.00 0.00 0 0 0 0.00000 0.00000 -1.00000 1.00000 0.900 0 0 0 0 0 0 0 1 + rins : 0 0.00 0.00 0 0 0 0.00000 0.00000 -1.00000 1.00000 0.900 0 0 0 0 0 0 0 1 + mutation : 0 0.00 0.00 0 0 0 0.00000 0.00000 -1.00000 1.00000 0.900 0 0 0 0 0 0 0 1 + localbranching : 0 0.00 0.00 0 0 0 0.00000 0.00000 -1.00000 1.00000 0.900 0 0 0 0 0 0 0 1 + crossover : 0 0.00 0.00 0 0 0 0.00000 0.00000 -1.00000 1.00000 0.900 0 0 0 0 0 0 0 1 + proximity : 0 0.00 0.00 0 0 0 0.00000 0.00000 -1.00000 1.00000 0.900 0 0 0 0 0 0 0 1 + zeroobjective : 0 0.00 0.00 0 0 0 0.00000 0.00000 -1.00000 1.00000 0.900 0 0 0 0 0 0 0 1 + dins : 0 0.00 0.00 0 0 0 0.00000 0.00000 -1.00000 1.00000 0.900 0 0 0 0 0 0 0 1 + trustregion : 0 0.00 0.00 0 0 0 0.00000 0.00000 -1.00000 1.00000 0.900 0 0 0 0 0 0 0 1 +Solution : + Solutions found : 0 (0 improvements) + Primal Bound : - + Dual Bound : - + Gap : infinite +Integrals : Total Avg% + primal-dual : 0.02 100.00 + primal-ref : - - (not evaluated) + dual-ref : - - (not evaluated) diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_memory.py b/tests/test_memory.py index f67a77f6a..f73070d25 100644 --- a/tests/test_memory.py +++ b/tests/test_memory.py @@ -2,13 +2,13 @@ from pyscipopt.scip import Model, is_memory_freed, print_memory_in_use def test_not_freed(): - if is_memory_freed(): + if is_optimized_mode(): pytest.skip() m = Model() assert not is_memory_freed() def test_freed(): - if is_memory_freed(): + if is_optimized_mode(): pytest.skip() m = Model() del m diff --git a/tests/test_model.py b/tests/test_model.py index 800ae7ab8..f5dcd062a 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -3,7 +3,6 @@ import itertools from pyscipopt import Model, SCIP_STAGE, SCIP_PARAMSETTING, quicksum -from pyscipopt.scip import is_memory_freed def test_model(): # create solver instance