Repository: CharJon/GeCO

Issue:
Make facility location compliant to develop.md
For this the parameter generator needs to be split from the mip generator.

Discussion:

Patch:
diff --git a/geco/mips/facility_location.py b/geco/mips/facility_location.py
index 7139f3e..e02f2ab 100644
--- a/geco/mips/facility_location.py
+++ b/geco/mips/facility_location.py
@@ -6,7 +6,12 @@ from networkx.utils import py_random_state
 
 
 @py_random_state(3)
-def capacitated_facility_location(n_customers, n_facilities, ratio, seed=0):
+def cornuejols_instance(n_customers, n_facilities, ratio, seed=0):
+    return capacitated_facility_location(n_customers, n_facilities,
+                                         *cornuejols_instance_params(n_customers, n_facilities, ratio, seed))
+
+
+def capacitated_facility_location(n_customers, n_facilities, transportation_cost, demands, fixed_costs, capacities):
     """
     Generate a Capacited Facility Location problem following
         Cornuejols G, Sridharan R, Thizy J-M (1991)
@@ -19,37 +24,16 @@ def capacitated_facility_location(n_customers, n_facilities, ratio, seed=0):
         The desired number of customers.
     n_facilities: int
         The desired number of facilities.
-    ratio: float
-        The desired capacity / demand ratio.
-    seed: integer, random_state, or None
-        Indicator of random number generation state.
+    transportation_cost: numpy array [float]
+        Matrix of transportation costs from customer i to facility j [i,j]
+    demands: numpy array [int]
+        Demands of each customer.
+    fixed_costs: numpy array [int]
+        Fixed costs of operating each facility.
+    capacities: numpy array [int]
+        Capacities of each facility.
     """
-    # locations for customers
-    c_x = np.array([seed.random() for _ in range(n_customers)])
-    c_y = np.array([seed.random() for _ in range(n_customers)])
-
-    # locations for facilities
-    f_x = np.array([seed.random() for _ in range(n_facilities)])
-    f_y = np.array([seed.random() for _ in range(n_facilities)])
-
-    demands = np.array(seed.sample(range(5, 35 + 1), k=n_customers))
-    capacities = np.array(seed.sample(range(10, 160 + 1), k=n_facilities))
-    fixed_costs = np.array(seed.sample(range(100, 110 + 1), k=n_facilities) * np.sqrt(capacities)) \
-                  + np.array(seed.sample(range(90 + 1), k=n_facilities))
-    fixed_costs = fixed_costs.astype(int)
-
     total_demand = demands.sum()
-    total_capacity = capacities.sum()
-
-    # adjust capacities according to ratio
-    capacities = capacities * ratio * total_demand / total_capacity
-    capacities = capacities.astype(int)
-    total_capacity = capacities.sum()
-
-    # transportation costs
-    trans_costs = np.sqrt(
-        (c_x.reshape((-1, 1)) - f_x.reshape((1, -1))) ** 2 \
-        + (c_y.reshape((-1, 1)) - f_y.reshape((1, -1))) ** 2) * 10 * demands.reshape((-1, 1))
 
     model = scip.Model("Capacitated Facility Location")
 
@@ -59,7 +43,7 @@ def capacitated_facility_location(n_customers, n_facilities, ratio, seed=0):
     facility_vars = []
     # add customer-facility vars
     for i, j in itertools.product(range(n_customers), range(n_facilities)):
-        var = model.addVar(lb=0, ub=1, obj=trans_costs[i, j], name=f"x_{i}_{j}", vtype="B")
+        var = model.addVar(lb=0, ub=1, obj=transportation_cost[i, j], name=f"x_{i}_{j}", vtype="B")
         customer_facility_vars[i, j] = var
     # add facility vars
     for j in range(n_facilities):
@@ -84,3 +68,32 @@ def capacitated_facility_location(n_customers, n_facilities, ratio, seed=0):
         model.addCons(customer_facility_vars[i, j] <= facility_vars[j])
 
     return model
+
+
+@py_random_state(3)
+def cornuejols_instance_params(n_customers, n_facilities, ratio, seed):
+    # locations for customers
+    c_x = np.array([seed.random() for _ in range(n_customers)])
+    c_y = np.array([seed.random() for _ in range(n_customers)])
+
+    # locations for facilities
+    f_x = np.array([seed.random() for _ in range(n_facilities)])
+    f_y = np.array([seed.random() for _ in range(n_facilities)])
+
+    demands = np.array(seed.sample(range(5, 35 + 1), k=n_customers))
+    capacities = np.array(seed.sample(range(10, 160 + 1), k=n_facilities))
+    fixed_costs = np.array(seed.sample(range(100, 110 + 1), k=n_facilities) * np.sqrt(capacities)) + np.array(
+        seed.sample(range(90 + 1), k=n_facilities))
+    fixed_costs = fixed_costs.astype(int)
+
+    # adjust capacities according to ratio
+    total_demand = demands.sum()
+    total_capacity = capacities.sum()
+    capacities = capacities * ratio * total_demand / total_capacity
+    capacities = capacities.astype(int)
+
+    # transportation cost
+    trans_costs = np.sqrt(
+        (c_x.reshape((-1, 1)) - f_x.reshape((1, -1))) ** 2 \
+        + (c_y.reshape((-1, 1)) - f_y.reshape((1, -1))) ** 2) * 10 * demands.reshape((-1, 1))
+    return trans_costs, demands, fixed_costs, capacities


Test patch:
diff --git a/geco/mips/tests/test_facility_location.py b/geco/mips/tests/test_facility_location.py
index d21b2b4..22cc821 100644
--- a/geco/mips/tests/test_facility_location.py
+++ b/geco/mips/tests/test_facility_location.py
@@ -2,8 +2,9 @@ from geco.mips.facility_location import *
 
 
 def test_capacitated_facility_location():
-    n_customers, n_facilities, ratio, seed = 25, 10, 2, 0
-    model = capacitated_facility_location(n_customers, n_facilities, ratio, seed)
+    n_customers, n_facilities, ratio = 25, 10, 2
+    instance_params = cornuejols_instance_params(n_customers, n_facilities, ratio, seed=0)
+    model = capacitated_facility_location(n_customers, n_facilities, *instance_params)
     assert model.getNVars() == n_customers * n_facilities + n_facilities
     assert model.getNConss() == n_customers + n_facilities + 1 + n_customers * n_facilities
     assert model.getObjectiveSense() == "minimize"
diff --git a/geco/tests/test_generator.py b/geco/tests/test_generator.py
index ffa959f..fe33add 100644
--- a/geco/tests/test_generator.py
+++ b/geco/tests/test_generator.py
@@ -5,8 +5,8 @@ from geco.generator import *
 
 
 def test_generator():
-    from geco.mips.facility_location import capacitated_facility_location
-    gen = Generator(capacitated_facility_location, n_customers=10, n_facilities=3, ratio=2)
+    from geco.mips.facility_location import cornuejols_instance
+    gen = Generator(cornuejols_instance, n_customers=10, n_facilities=3, ratio=2)
     gen.seed(0)
     for model in itertools.islice(gen, 10):
         assert type(model) == scip.Model