From 3c9b1e2f44ed443fb82f57f8c3882ffdda8df1fc Mon Sep 17 00:00:00 2001 From: Alinson S Xavier Date: Sun, 26 Jan 2020 08:25:26 -0600 Subject: [PATCH] Improve stable set generator --- miplearn/problems/stab.py | 94 +++++++++++++++++++++++++--- miplearn/problems/tests/__init__.py | 0 miplearn/problems/tests/test_stab.py | 45 +++++++++++++ miplearn/tests/test_benchmark.py | 18 ++---- miplearn/tests/test_stab.py | 31 --------- miplearn/warmstart.py | 1 + 6 files changed, 135 insertions(+), 54 deletions(-) create mode 100644 miplearn/problems/tests/__init__.py create mode 100644 miplearn/problems/tests/test_stab.py delete mode 100644 miplearn/tests/test_stab.py diff --git a/miplearn/problems/stab.py b/miplearn/problems/stab.py index e864ba6..61f93f8 100644 --- a/miplearn/problems/stab.py +++ b/miplearn/problems/stab.py @@ -7,23 +7,99 @@ import pyomo.environ as pe import networkx as nx from miplearn import Instance import random +from scipy.stats import uniform, randint, bernoulli +from scipy.stats.distributions import rv_frozen -class MaxStableSetGenerator: - def __init__(self, graph, base_weights, perturbation_scale=1.0): - self.graph = graph - self.base_weights = base_weights - self.perturbation_scale = perturbation_scale +class MaxWeightStableSetChallengeA: + """ + - Fixed random graph (200 vertices, 5% density) + - Uniformly random weights in the [100., 125.] interval + - 500 training instances + - 100 test instances + """ + + def __init__(self): + self.generator = MaxWeightStableSetGenerator(w=uniform(loc=100., scale=25.), + n=randint(low=200, high=201), + density=uniform(loc=0.05, scale=0.0), + fix_graph=True) + + def get_training_instances(self): + return self.generator.generate(500) + + def get_test_instances(self): + return self.generator.generate(100) + + +class MaxWeightStableSetGenerator: + """Random instance generator for the Maximum-Weight Stable Set Problem. + + The generator has two modes of operation. When `fix_graph` is True, the random graph is + generated only once, during the constructor. Each instance is constructed by generating + random weights and by randomly deleting vertices and edges of this graph. When `fix_graph` + is False, a new random graph is created each time an instance is constructed. + """ + + def __init__(self, + w=uniform(loc=10.0, scale=1.0), + pe=bernoulli(1.), + pv=bernoulli(1.), + n=randint(low=250, high=251), + density=uniform(loc=0.05, scale=0.05), + fix_graph=True): + """Initializes the problem generator. + Parameters + ---------- + w: rv_continuous + Probability distribution for the vertex weights. + pe: rv_continuous + Probability of an edge being deleted. Only used when fix_graph=True. + pv: rv_continuous + Probability of a vertex being deleted. Only used when fix_graph=True. + n: rv_discrete + Probability distribution for the number of vertices in the random graph. + density: rv_continuous + Probability distribution for the density of the random graph. + """ + assert isinstance(w, rv_frozen), "w should be a SciPy probability distribution" + assert isinstance(pe, rv_frozen), "pe should be a SciPy probability distribution" + assert isinstance(pv, rv_frozen), "pv should be a SciPy probability distribution" + assert isinstance(n, rv_frozen), "n should be a SciPy probability distribution" + assert isinstance(density, rv_frozen), "density should be a SciPy probability distribution" + self.w = w + self.n = n + self.density = density + self.fix_graph = fix_graph + self.graph = None + if fix_graph: + self.graph = self._generate_graph() + def generate(self, n_samples): def _sample(): - perturbation = np.random.rand(self.graph.number_of_nodes()) * self.perturbation_scale - weights = self.base_weights + perturbation - return MaxStableSetInstance(self.graph, weights) + if self.graph is not None: + graph = self.graph + else: + graph = self._generate_graph() + weights = self.w.rvs(graph.number_of_nodes()) + return MaxWeightStableSetInstance(graph, weights) return [_sample() for _ in range(n_samples)] + + def _generate_graph(self): + return nx.generators.random_graphs.binomial_graph(self.n.rvs(), self.density.rvs()) -class MaxStableSetInstance(Instance): +class MaxWeightStableSetInstance(Instance): + """An instance of the Maximum-Weight Stable Set Problem. + + Given a graph G=(V,E) and a weight w_v for each vertex v, the problem asks for a stable + set S of G maximizing sum(w_v for v in S). A stable set (also called independent set) is + a subset of vertices, no two of which are adjacent. + + This is one of Karp's 21 NP-complete problems. + """ + def __init__(self, graph, weights): self.graph = graph self.weights = weights diff --git a/miplearn/problems/tests/__init__.py b/miplearn/problems/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/miplearn/problems/tests/test_stab.py b/miplearn/problems/tests/test_stab.py new file mode 100644 index 0000000..e203fdd --- /dev/null +++ b/miplearn/problems/tests/test_stab.py @@ -0,0 +1,45 @@ +# MIPLearn, an extensible framework for Learning-Enhanced Mixed-Integer Optimization +# Copyright (C) 2019-2020 Argonne National Laboratory. All rights reserved. +# Written by Alinson S. Xavier + +from miplearn import LearningSolver +from miplearn.problems.stab import MaxWeightStableSetInstance +from miplearn.problems.stab import MaxWeightStableSetGenerator +import networkx as nx +import numpy as np +from scipy.stats import uniform, randint + + +def test_stab(): + graph = nx.cycle_graph(5) + weights = [1., 2., 3., 4., 5.] + instance = MaxWeightStableSetInstance(graph, weights) + solver = LearningSolver() + solver.solve(instance) + assert instance.model.OBJ() == 8. + + +def test_stab_generator_fixed_graph(): + from miplearn.problems.stab import MaxWeightStableSetGenerator + gen = MaxWeightStableSetGenerator(w=uniform(loc=50., scale=10.), + n=randint(low=10, high=11), + density=uniform(loc=0.05, scale=0.), + fix_graph=True) + instances = gen.generate(1_000) + weights = np.array([instance.weights for instance in instances]) + weights_avg_actual = np.round(np.average(weights, axis=0)) + weights_avg_expected = [55.0] * 10 + assert list(weights_avg_actual) == weights_avg_expected + + +def test_stab_generator_random_graph(): + from miplearn.problems.stab import MaxWeightStableSetGenerator + gen = MaxWeightStableSetGenerator(w=uniform(loc=50., scale=10.), + n=randint(low=30, high=41), + density=uniform(loc=0.5, scale=0.), + fix_graph=False) + instances = gen.generate(1_000) + n_nodes = [instance.graph.number_of_nodes() for instance in instances] + n_edges = [instance.graph.number_of_edges() for instance in instances] + assert np.round(np.mean(n_nodes)) == 35. + assert np.round(np.mean(n_edges), -1) == 300. diff --git a/miplearn/tests/test_benchmark.py b/miplearn/tests/test_benchmark.py index 5184f72..c4bf6b0 100644 --- a/miplearn/tests/test_benchmark.py +++ b/miplearn/tests/test_benchmark.py @@ -4,27 +4,17 @@ from miplearn import LearningSolver, BenchmarkRunner from miplearn.warmstart import KnnWarmStartPredictor -from miplearn.problems.stab import MaxStableSetInstance, MaxStableSetGenerator -import networkx as nx +from miplearn.problems.stab import MaxWeightStableSetGenerator +from scipy.stats import randint import numpy as np import pyomo.environ as pe import os.path def test_benchmark(): - graph = nx.cycle_graph(10) - base_weights = np.random.rand(10) - # Generate training and test instances - train_instances = MaxStableSetGenerator(graph=graph, - base_weights=base_weights, - perturbation_scale=1.0, - ).generate(5) - - test_instances = MaxStableSetGenerator(graph=graph, - base_weights=base_weights, - perturbation_scale=1.0, - ).generate(3) + train_instances = MaxWeightStableSetGenerator(n=randint(low=25, high=26)).generate(5) + test_instances = MaxWeightStableSetGenerator(n=randint(low=25, high=26)).generate(3) # Training phase... training_solver = LearningSolver() diff --git a/miplearn/tests/test_stab.py b/miplearn/tests/test_stab.py deleted file mode 100644 index 7a97d78..0000000 --- a/miplearn/tests/test_stab.py +++ /dev/null @@ -1,31 +0,0 @@ -# MIPLearn, an extensible framework for Learning-Enhanced Mixed-Integer Optimization -# Copyright (C) 2019-2020 Argonne National Laboratory. All rights reserved. -# Written by Alinson S. Xavier - -from miplearn import LearningSolver -from miplearn.problems.stab import MaxStableSetInstance, MaxStableSetGenerator -import networkx as nx -import numpy as np - - -def test_stab(): - graph = nx.cycle_graph(5) - weights = [1.0, 2.0, 3.0, 4.0, 5.0] - instance = MaxStableSetInstance(graph, weights) - solver = LearningSolver() - solver.solve(instance) - assert instance.model.OBJ() == 8.0 - - -def test_stab_generator(): - graph = nx.cycle_graph(5) - base_weights = [1.0, 2.0, 3.0, 4.0, 5.0] - instances = MaxStableSetGenerator(graph=graph, - base_weights=base_weights, - perturbation_scale=1.0, - ).generate(100_000) - weights = np.array([instance.weights for instance in instances]) - weights_avg = np.round(np.average(weights, axis=0), 2) - weights_std = np.round(np.std(weights, axis=0), 2) - assert list(weights_avg) == [1.50, 2.50, 3.50, 4.50, 5.50] - assert list(weights_std) == [0.29] * 5 diff --git a/miplearn/warmstart.py b/miplearn/warmstart.py index addae9f..bf48bf1 100644 --- a/miplearn/warmstart.py +++ b/miplearn/warmstart.py @@ -18,6 +18,7 @@ class WarmStartPredictor(ABC): def fit(self, x_train, y_train): assert isinstance(x_train, np.ndarray) assert isinstance(y_train, np.ndarray) + y_train = y_train.astype(int) assert y_train.shape[0] == x_train.shape[0] assert y_train.shape[1] == 2 for i in [0,1]: