Repository: CharJon/GeCO

Issue:
Add convenience wrapper for miplib.
A function to easily get all (easy, hard, open) instances that are (or are not) part of the benchmark set would be nice to have.

Discussion:

Patch:
diff --git a/geco/mips/miplib/base.py b/geco/mips/miplib/base.py
index 881f9cf..87f27b1 100644
--- a/geco/mips/miplib/base.py
+++ b/geco/mips/miplib/base.py
@@ -3,15 +3,10 @@ from urllib.request import urlretrieve, urlopen
 from urllib.error import URLError
 import pyscipopt as scip
 import os
+import pandas as pd
 
 
 class Loader:
-    INSTANCES_URLS = [
-        "https://miplib.zib.de/WebData/instances/",  # 2017 instances
-        "http://miplib2010.zib.de/download/",  # 2010 instances
-        "http://miplib2010.zib.de/miplib2003/download/",  # 2003 instance
-    ]
-
     def __init__(self, persistent_directory=None):
         """
         Initializes the MIPLIB loader object
@@ -114,3 +109,47 @@ class Loader:
         if self.dir is None:
             for path in self.instances_cache.values():
                 os.unlink(path)
+
+
+def benchmark_instances():
+    for instance in custom_list("https://miplib.zib.de/downloads/benchmark-v2.test"):
+        yield instance
+
+
+def easy_instances():
+    for instance in custom_list("https://miplib.zib.de/downloads/easy-v9.test"):
+        yield instance
+
+
+def hard_instances():
+    for instance in custom_list("https://miplib.zib.de/downloads/hard-v15.test"):
+        yield instance
+
+
+def open_instances():
+    for instance in custom_list("https://miplib.zib.de/downloads/open-v14.test"):
+        yield instance
+
+
+def custom_list(source, with_solution=False, loader=None):
+    """
+    Returns a generator of instances from the given list
+
+    Parameters
+    ----------
+    source: str
+        Path or URL for the instance list source
+    with_solution: bool
+        Whether to return the instance with the known solutions or not
+    loader: Loader
+        Loader object to download instances with
+
+    Returns
+    -------
+    A generator for the instances
+    """
+    df = pd.read_csv(source, names=["instance"])
+    if loader is None:
+        loader = Loader()
+    for instance in df["instance"]:
+        yield loader.load_instance(instance, with_solution=with_solution)
diff --git a/setup.py b/setup.py
index 1798615..7e5231b 100644
--- a/setup.py
+++ b/setup.py
@@ -13,5 +13,5 @@ setup(
     url="https://github.com/CharJon/GeCO",
     license="MIT License",
     packages=find_packages(exclude=("tests", "docs", "data", "notebooks", "examples")),
-    install_requires=["pyscipopt", "networkx", "numpy"]
+    install_requires=["pyscipopt", "networkx", "numpy", "pandas"]
 )


Test patch:
diff --git a/geco/mips/tests/test_miplib.py b/geco/mips/tests/test_miplib.py
index d89b4ed..c544035 100644
--- a/geco/mips/tests/test_miplib.py
+++ b/geco/mips/tests/test_miplib.py
@@ -1,5 +1,6 @@
 import pandas as pd
 import pytest
+import itertools
 
 from geco.mips.miplib.base import *
 
@@ -66,3 +67,29 @@ def _check_instance(instance_name, with_solution=False):
     if with_solution:
         sols = instance.getSols()
         assert len(sols) == 1
+
+
+def test_custom_list():
+    miplib_2017_list = "https://miplib.zib.de/downloads/collection-v1.test"
+    for instance in itertools.islice(custom_list(miplib_2017_list), 5):
+        assert isinstance(instance, scip.Model)
+
+
+def test_easy_instances():
+    for instance in itertools.islice(easy_instances(), 5):
+        assert isinstance(instance, scip.Model)
+
+
+def test_hard_instances():
+    for instance in itertools.islice(hard_instances(), 5):
+        assert isinstance(instance, scip.Model)
+
+
+def test_open_instances():
+    for instance in itertools.islice(open_instances(), 5):
+        assert isinstance(instance, scip.Model)
+
+
+def test_benchmark_instances():
+    for instance in itertools.islice(benchmark_instances(), 5):
+        assert isinstance(instance, scip.Model)