Repository: CharJon/GeCO

Issue:
More scheduling generators (Hookers c-instances)
Hooker lists additional parameters for generating scheduling instances [here](http://public.tepper.cmu.edu/jnh/instances.htm).

- [ ] c-instances generator
- [ ] check c-instance generation against supplied files (e.g. are the correct ranges [a,b] listed)


Discussion:

Patch:
diff --git a/geco/mips/scheduling/generic.py b/geco/mips/scheduling/generic.py
index b695be6..b4919e0 100644
--- a/geco/mips/scheduling/generic.py
+++ b/geco/mips/scheduling/generic.py
@@ -1,6 +1,225 @@
 import itertools
 
 from networkx.utils import py_random_state
+from pyscipopt import scip
+
+
+def late_tasks_formulation(
+    number_of_facilities,
+    number_of_tasks,
+    time_steps,
+    processing_times,
+    capacities,
+    assignment_costs,
+    release_dates,
+    deadlines,
+    name="Hooker Scheduling Late Tasks Formulation",
+):
+    """Generates late tasks mip formulation described in section 4 in [1].
+
+    Parameters
+    ----------
+    number_of_facilities: int
+        the number of facilities to schedule on
+    number_of_tasks: int
+        the number of tasks to assign to facilities
+    time_steps:
+        the number of time steps starting from 0 (corresponds to "N" in the paper)
+    processing_times: dict[int,int]
+        time steps to process each task
+    capacities: list[int]
+        capacity of each facility
+    assignment_costs: dict[int,int]
+        cost of assigning a task to a facility
+    release_dates: list[int]
+        time step at which a job is released
+    deadlines: dict[int, float]
+        deadline (time step) to finish a job
+    name: str
+        assigned name to generated instance
+
+    Returns
+    -------
+        model: SCIP model of the late tasks instance
+
+    References
+    ----------
+    .. [1] Hooker, John. (2005). Planning and Scheduling to Minimize
+     Tardiness. 314-327. 10.1007/11564751_25.
+    """
+    model = scip.Model(name)
+
+    start_time = min(release_dates)
+    time_steps = range(start_time, start_time + time_steps)
+
+    # add variables and their cost
+    L = []
+    for i in range(number_of_tasks):
+        var = model.addVar(lb=0, ub=1, obj=1, name=f"L_{i}", vtype="B")
+        L.append(var)
+
+    # assignment vars
+    x = {}
+    for j, i, t in itertools.product(
+        range(number_of_tasks), range(number_of_facilities), time_steps
+    ):
+        var = model.addVar(lb=0, ub=1, obj=0, name=f"x_{j}_{i}_{t}", vtype="B")
+        x[j, i, t] = var
+
+    # add constraints
+    # constraint (a)
+    for j, t in itertools.product(range(number_of_tasks), time_steps):
+        model.addCons(
+            len(time_steps) * L[j]
+            >= scip.quicksum(
+                (
+                    (t + processing_times[j, i]) * x[j, i, t] - deadlines[j]
+                    for i in range(number_of_facilities)
+                )
+            )
+        )
+
+    # constraint (b)
+    for j in range(number_of_tasks):
+        vars = (
+            x[j, i, t]
+            for i, t in itertools.product(range(number_of_facilities), time_steps)
+        )
+        model.addCons(scip.quicksum(vars) == 1)
+
+    # constraint (c)
+    for i, t in itertools.product(range(number_of_facilities), time_steps):
+        vars = []
+        for j in range(number_of_tasks):
+            vars += [
+                assignment_costs[j, i] * x[j, i, t_prime]
+                for t_prime in range(t - processing_times[j, i] + 1, t + 1)
+                if (j, i, t_prime) in x
+            ]
+        model.addCons(scip.quicksum(vars) <= capacities[i])
+
+    # constraint (d)
+    for i, j, t in itertools.product(
+        range(number_of_facilities), range(number_of_tasks), time_steps
+    ):
+        if t < release_dates[j] or t > len(time_steps) - processing_times[j, i]:
+            model.addCons(x[j, i, t] == 0)
+
+    model.setMinimize()
+
+    return model
+
+
+def heinz_formulation(
+    number_of_facilities,
+    number_of_tasks,
+    processing_times,
+    capacities,
+    assignment_costs,
+    release_dates,
+    deadlines,
+    resource_requirements,
+    name="Heinz Scheduling Formulation",
+):
+    """Generates scheduling MIP formulation according to Model 4 in [1].
+
+    Parameters
+    ----------
+    number_of_facilities: int
+        the number of facilities to schedule on
+    number_of_tasks: int
+        the number of tasks to assign to facilities
+    processing_times: dict[(int,int),int]
+        time steps to process each task
+    capacities: list[int]
+        capacity of each facility
+    assignment_costs: dict[(int,int),int]
+        cost of assigning a task to a facility
+    release_dates: list[int]
+        time step at which a job is released
+    deadlines: dict[int, float]
+        deadline (time step) to finish a job
+    resource_requirements: dict[(int,int),int]
+        resources required for each task assigned to a facility
+    name: str
+        assigned name to generated instance
+
+    Returns
+    -------
+        model: SCIP model of generated instance
+
+    References
+    ----------
+    .. [1] Heinz, J. (2013). Recent Improvements Using Constraint Integer Programming for Resource Allocation and Scheduling.
+    In Integration of AI and OR Techniques in Constraint Programming for Combinatorial Optimization Problems
+    (pp. 12–27). Springer Berlin Heidelberg.
+    """
+    model = scip.Model(name)
+
+    time_steps = range(min(release_dates), int(max(deadlines)))
+
+    # objective function
+    x = {}
+    for j, k in itertools.product(range(number_of_tasks), range(number_of_facilities)):
+        var = model.addVar(
+            lb=0, ub=1, obj=assignment_costs[j, k], name=f"x_{j}_{k}", vtype="B"
+        )
+        x[j, k] = var
+
+    # y vars
+    y = {}
+    for j, k, t in itertools.product(
+        range(number_of_tasks), range(number_of_facilities), time_steps
+    ):
+        if release_dates[j] <= t <= deadlines[j] - processing_times[j, k]:
+            var = model.addVar(lb=0, ub=1, obj=0, name=f"y_{j}_{k}_{t}", vtype="B")
+            y[j, k, t] = var
+
+    # add constraints
+    # constraint (12)
+    for j in range(number_of_tasks):
+        model.addCons(scip.quicksum(x[j, k] for k in range(number_of_facilities)) == 1)
+
+    # constraint (13)
+    for j, k in itertools.product(range(number_of_tasks), range(number_of_facilities)):
+        model.addCons(
+            scip.quicksum(
+                y[j, k, t]
+                for t in range(
+                    release_dates[j], int(deadlines[j]) - processing_times[j, k]
+                )
+                if t < len(time_steps)
+            )
+            == x[j, k]
+        )
+
+    # constraint (14)
+    for k, t in itertools.product(range(number_of_facilities), time_steps):
+        model.addCons(
+            scip.quicksum(
+                resource_requirements[j, k] * y[j, k, t_prime]
+                for j in range(number_of_tasks)
+                for t_prime in range(t - processing_times[j, k], t + 1)
+                if (j, k, t_prime) in y
+            )
+            <= capacities[k]
+        )
+
+    # constraint (15)
+    epsilon = filter(
+        lambda ts: ts[0] < ts[1], itertools.product(release_dates, deadlines)
+    )
+    for k, (t1, t2) in itertools.product(range(number_of_facilities), epsilon):
+        model.addCons(
+            scip.quicksum(
+                processing_times[j, k] * resource_requirements[j, k] * x[j, k]
+                for j in range(number_of_tasks)
+                if t1 <= release_dates[j] and t2 >= deadlines[j]
+            )
+            <= capacities[k] * (t2 - t1)
+        )
+
+    return model
 
 
 @py_random_state(-1)
diff --git a/geco/mips/scheduling/heinz.py b/geco/mips/scheduling/heinz.py
index 8e7a8d0..ba26732 100644
--- a/geco/mips/scheduling/heinz.py
+++ b/geco/mips/scheduling/heinz.py
@@ -1,3 +1,4 @@
+from networkx.utils import py_random_state
 import pyscipopt as scip
 
 from geco.mips.scheduling.generic import *
@@ -18,17 +19,17 @@ def heinz_params(number_of_facilities, number_of_tasks, seed=0):
 
     Returns
     -------
-    processing_times: dict[int,int]
+    processing_times: dict[(int,int),int]
         time steps to process each task
     capacities: list[int]
         capacity of each facility
-    assignment_costs: dict[int,int]
+    assignment_costs: dict[(int,int),int]
         cost of assigning a task to a facility
     release_times: list[int]
         time step at which a job is released
-    deadlines: dict[int, float]
+    deadlines: dict[int,int]
         deadline (time step) to finish a job
-    resource_requirements: dict[int,int]
+    resource_requirements: dict[(int,int),int]
         resources required for each task assigned to a facility
 
     References
diff --git a/geco/mips/scheduling/hooker.py b/geco/mips/scheduling/hooker.py
index bfa6342..e890f39 100644
--- a/geco/mips/scheduling/hooker.py
+++ b/geco/mips/scheduling/hooker.py
@@ -1,4 +1,5 @@
 import itertools
+import math
 
 import pyscipopt as scip
 from networkx.utils import py_random_state
@@ -61,7 +62,7 @@ def hooker_instance(number_of_facilities, number_of_tasks, time_steps, seed=0):
     .. [1] Hooker, John. (2005). Planning and Scheduling to Minimize
      Tardiness. 314-327. 10.1007/11564751_25.
     """
-    return hooker_formulation(
+    return late_tasks_formulation(
         number_of_facilities,
         number_of_tasks,
         time_steps,
@@ -70,18 +71,35 @@ def hooker_instance(number_of_facilities, number_of_tasks, time_steps, seed=0):
     )
 
 
-def hooker_formulation(
-    number_of_facilities,
-    number_of_tasks,
-    time_steps,
-    processing_times,
-    capacities,
-    assignment_costs,
-    release_dates,
-    deadlines,
-    name="Hooker Scheduling Late Tasks Formulation",
-):
-    """Generates late tasks mip formulation described in section 4 in [1].
+def generate_hookers_instances():
+    number_of_tasks = [10 + 2 * i for i in range(7)]
+    time_steps = [10, 100]
+    seeds = range(10)
+    for n, t, seed in itertools.product(number_of_tasks, time_steps, seeds):
+        params = 3, n, t, seed
+        yield params, hooker_instance(*params)
+
+
+def _common_hooker_params(number_of_facilities, number_of_tasks, seed):
+    capacities = [10] * number_of_facilities
+    resource_requirements = {}
+    for i in range(number_of_tasks):
+        cur_res_requirement = seed.randrange(1, 10)
+        for j in range(number_of_facilities):
+            resource_requirements[i, j] = cur_res_requirement
+    return capacities, resource_requirements
+
+
+@py_random_state(-1)
+def c_instance_params(seed=0):
+    for m, n in itertools.product(range(2, 4 + 1), range(10, 38 + 1, 2)):
+        yield c_params_generator(m, n, seed)
+
+
+@py_random_state(-1)
+def c_params_generator(number_of_facilities, number_of_tasks, seed=0):
+    """
+    Generate instance parameters for the c problem set mentioned in [1].
 
     Parameters
     ----------
@@ -89,97 +107,305 @@ def hooker_formulation(
         the number of facilities to schedule on
     number_of_tasks: int
         the number of tasks to assign to facilities
-    time_steps:
-        the number of time steps starting from 0 (corresponds to "N" in the paper)
-    processing_times: dict[int,int]
+    seed: int, random object or None
+        for randomization
+
+    Returns
+    -------
+    number_of_facilities: int
+        the number of facilities to schedule on
+    number_of_tasks: int
+        the number of tasks to assign to facilities
+    processing_times: dict[(int,int),int]
         time steps to process each task
     capacities: list[int]
         capacity of each facility
-    assignment_costs: dict[int,int]
+    assignment_costs: dict[(int,int),int]
         cost of assigning a task to a facility
-    release_dates: list[int]
+    release_times: list[int]
         time step at which a job is released
-    deadlines: dict[int, float]
+    deadlines: dict[int, int]
         deadline (time step) to finish a job
-    name: str
-        assigned name to generated instance
+    resource_requirements: dict[(int,int),int]
+        resources required for each task assigned to a facility
+
+    References
+    ----------
+    ..[1] http://public.tepper.cmu.edu/jnh/instances.htm
+    """
+    capacities, resource_requirements = _common_hooker_params(
+        number_of_facilities, number_of_tasks, seed
+    )
+
+    release_dates = [0] * number_of_tasks
+    due_dates = [
+        _due_date_helper(1 / 3, number_of_facilities, number_of_tasks)
+    ] * number_of_tasks
+
+    processing_times = {}
+    for i in range(number_of_facilities):
+        for j in range(number_of_tasks):
+            processing_times[j, i] = seed.randrange(i + 1, 10 * (i + 1))
+
+    processing_costs = {}
+    for i in range(number_of_facilities):
+        for j in range(number_of_tasks):
+            processing_costs[j, i] = seed.randrange(
+                2 * (number_of_facilities - i), 20 * (number_of_facilities - i)
+            )
+
+    return (
+        number_of_facilities,
+        number_of_tasks,
+        processing_times,
+        capacities,
+        processing_costs,
+        release_dates,
+        due_dates,
+        resource_requirements,
+    )
+
+
+@py_random_state(-1)
+def e_instance_params(seed=0):
+    for m in range(2, 10 + 1):
+        yield e_params_generator(m, 5 * m, seed)
+
+
+@py_random_state(-1)
+def e_params_generator(number_of_facilities, number_of_tasks, seed=0):
+    """
+    Generate instance parameters for the e problem set mentioned in [1].
+
+    Parameters
+    ----------
+    number_of_facilities: int
+        the number of facilities to schedule on
+    number_of_tasks: int
+        the number of tasks to assign to facilities
+    seed: int, random object or None
+        for randomization
 
     Returns
     -------
-        model: SCIP model of the late tasks instance
+    number_of_facilities: int
+        the number of facilities to schedule on
+    number_of_tasks: int
+        the number of tasks to assign to facilities
+    processing_times: dict[(int,int),int]
+        time steps to process each task
+    capacities: list[int]
+        capacity of each facility
+    assignment_costs: dict[(int,int),int]
+        cost of assigning a task to a facility
+    release_times: list[int]
+        time step at which a job is released
+    deadlines: dict[int, int]
+        deadline (time step) to finish a job
+    resource_requirements: dict[(int,int),int]
+        resources required for each task assigned to a facility
 
     References
     ----------
-    .. [1] Hooker, John. (2005). Planning and Scheduling to Minimize
-     Tardiness. 314-327. 10.1007/11564751_25.
+    ..[1] http://public.tepper.cmu.edu/jnh/instances.htm
     """
-    model = scip.Model(name)
+    capacities, resource_requirements = _common_hooker_params(
+        number_of_facilities, number_of_tasks, seed
+    )
 
-    start_time = min(release_dates)
-    time_steps = range(start_time, start_time + time_steps)
+    release_dates = [0] * number_of_tasks
+    due_dates = [33] * number_of_tasks
 
-    # add variables and their cost
-    L = []
-    for i in range(number_of_tasks):
-        var = model.addVar(lb=0, ub=1, obj=1, name=f"L_{i}", vtype="B")
-        L.append(var)
-
-    # assignment vars
-    x = {}
-    for j, i, t in itertools.product(
-        range(number_of_tasks), range(number_of_facilities), time_steps
-    ):
-        var = model.addVar(lb=0, ub=1, obj=0, name=f"x_{j}_{i}_{t}", vtype="B")
-        x[j, i, t] = var
-
-    # add constraints
-    # constraint (a)
-    for j, t in itertools.product(range(number_of_tasks), time_steps):
-        model.addCons(
-            len(time_steps) * L[j]
-            >= scip.quicksum(
-                (
-                    (t + processing_times[j, i]) * x[j, i, t] - deadlines[j]
-                    for i in range(number_of_facilities)
-                )
+    processing_times = {}
+    for i in range(number_of_facilities):
+        for j in range(number_of_tasks):
+            processing_times[j, i] = seed.randrange(
+                2, int(25 - i * (10 / (number_of_facilities - 1)))
             )
-        )
 
-    # constraint (b)
-    for j in range(number_of_tasks):
-        vars = (
-            x[j, i, t]
-            for i, t in itertools.product(range(number_of_facilities), time_steps)
+    processing_costs = {}
+    for i in range(number_of_facilities):
+        for j in range(number_of_tasks):
+            processing_costs[j, i] = seed.randrange(
+                math.floor(400 / (25 - i * (10 / (number_of_facilities - 1)))),
+                math.ceil(800 / (25 - i * (10 / (number_of_facilities - 1)))),
+            )
+
+    return (
+        number_of_facilities,
+        number_of_tasks,
+        processing_times,
+        capacities,
+        processing_costs,
+        release_dates,
+        due_dates,
+        resource_requirements,
+    )
+
+
+@py_random_state(-1)
+def de_instance_params(seed=0):
+    for n in range(14, 28 + 1, 2):
+        yield de_params_generator(3, n, seed)
+
+
+@py_random_state(-1)
+def de_params_generator(number_of_facilities, number_of_tasks, seed=0):
+    """
+    Generate instance parameters for the de problem set mentioned in [1].
+
+    Parameters
+    ----------
+    number_of_facilities: int
+        the number of facilities to schedule on
+    number_of_tasks: int
+        the number of tasks to assign to facilities
+    seed: int, random object or None
+        for randomization
+
+    Returns
+    -------
+    number_of_facilities: int
+        the number of facilities to schedule on
+    number_of_tasks: int
+        the number of tasks to assign to facilities
+    processing_times: dict[(int,int),int]
+        time steps to process each task
+    capacities: list[int]
+        capacity of each facility
+    assignment_costs: dict[(int,int),int]
+        cost of assigning a task to a facility
+    release_times: list[int]
+        time step at which a job is released
+    deadlines: dict[int, int]
+        deadline (time step) to finish a job
+    resource_requirements: dict[(int,int),int]
+        resources required for each task assigned to a facility
+
+    References
+    ----------
+    ..[1] http://public.tepper.cmu.edu/jnh/instances.htm
+    """
+    capacities, resource_requirements = _common_hooker_params(
+        number_of_facilities, number_of_tasks, seed
+    )
+
+    release_dates = [0] * number_of_tasks
+    due_dates = [
+        seed.randrange(
+            _due_date_helper((1 / 4) * (1 / 3), number_of_facilities, number_of_tasks),
+            _due_date_helper(1 / 3, number_of_facilities, number_of_tasks),
         )
-        model.addCons(scip.quicksum(vars) == 1)
+        for _ in range(number_of_tasks)
+    ]
 
-    # constraint (c)
-    for i, t in itertools.product(range(number_of_facilities), time_steps):
-        vars = []
+    processing_times = {}
+    range_start = 2 if number_of_facilities <= 20 else 5  # P1 in the reference website
+    for i in range(number_of_facilities):
         for j in range(number_of_tasks):
-            vars += [
-                assignment_costs[j, i] * x[j, i, t_prime]
-                for t_prime in range(t - processing_times[j, i] + 1, t + 1)
-                if (j, i, t_prime) in x
-            ]
-        model.addCons(scip.quicksum(vars) <= capacities[i])
+            processing_times[j, i] = seed.randrange(range_start, 30 - i * 5)
 
-    # constraint (d)
-    for i, j, t in itertools.product(
-        range(number_of_facilities), range(number_of_tasks), time_steps
-    ):
-        if t < release_dates[j] or t > len(time_steps) - processing_times[j, i]:
-            model.addCons(x[j, i, t] == 0)
+    processing_costs = {}
+    for i in range(number_of_facilities):
+        for j in range(number_of_tasks):
+            processing_costs[j, i] = seed.randrange(10 + 10 * i, 40 + 10 * i)
 
-    model.setMinimize()
+    return (
+        number_of_facilities,
+        number_of_tasks,
+        processing_times,
+        capacities,
+        processing_costs,
+        release_dates,
+        due_dates,
+        resource_requirements,
+    )
 
-    return model
 
+@py_random_state(-1)
+def df_instance_params(seed=0):
+    for n in range(14, 28 + 1, 2):
+        yield df_params_generator(3, n, seed)
 
-def generate_hookers_instances():
-    number_of_tasks = [10 + 2 * i for i in range(7)]
-    time_steps = [10, 100]
-    seeds = range(10)
-    for n, t, seed in itertools.product(number_of_tasks, time_steps, seeds):
-        params = 3, n, t, seed
-        yield params, hooker_instance(*params)
+
+@py_random_state(-1)
+def df_params_generator(number_of_facilities, number_of_tasks, seed=0):
+    """
+    Generate instance parameters for the df problem set mentioned in [1].
+
+    Parameters
+    ----------
+    number_of_facilities: int
+        the number of facilities to schedule on
+    number_of_tasks: int
+        the number of tasks to assign to facilities
+    seed: int, random object or None
+        for randomization
+
+    Returns
+    -------
+    number_of_facilities: int
+        the number of facilities to schedule on
+    number_of_tasks: int
+        the number of tasks to assign to facilities
+    processing_times: dict[(int,int),int]
+        time steps to process each task
+    capacities: list[int]
+        capacity of each facility
+    assignment_costs: dict[(int,int),int]
+        cost of assigning a task to a facility
+    release_times: list[int]
+        time step at which a job is released
+    deadlines: dict[int, int]
+        deadline (time step) to finish a job
+    resource_requirements: dict[(int,int),int]
+        resources required for each task assigned to a facility
+
+    References
+    ----------
+    ..[1] http://public.tepper.cmu.edu/jnh/instances.htm
+    """
+    capacities, resource_requirements = _common_hooker_params(
+        number_of_facilities, number_of_tasks, seed
+    )
+
+    release_dates = [0] * number_of_tasks
+
+    random_release_time = seed.choice(release_dates)
+    due_dates = [
+        seed.randrange(
+            random_release_time
+            + _due_date_helper(1 / 4 * 1 / 2, number_of_facilities, number_of_tasks),
+            random_release_time
+            + _due_date_helper(1 / 2, number_of_facilities, number_of_tasks),
+        )
+        for _ in range(number_of_tasks)
+    ]
+
+    processing_times = {}
+    range_start = 2 if number_of_facilities <= 20 else 5  # P1 in the reference website
+    for i in range(number_of_facilities):
+        for j in range(number_of_tasks):
+            processing_times[j, i] = seed.randrange(range_start, 30 - i * 5)
+
+    processing_costs = {}
+    for i in range(number_of_facilities):
+        for j in range(number_of_tasks):
+            processing_costs[j, i] = seed.randrange(10 + 10 * i, 40 + 10 * i)
+
+    return (
+        number_of_facilities,
+        number_of_tasks,
+        processing_times,
+        capacities,
+        processing_costs,
+        release_dates,
+        due_dates,
+        resource_requirements,
+    )
+
+
+def _due_date_helper(a, number_of_facilities, number_of_tasks):
+    return math.ceil(
+        5 * a * number_of_tasks * (number_of_facilities + 1) / number_of_facilities
+    )
diff --git a/geco/mips/utilities/generic.py b/geco/mips/utilities/generic.py
index a2de6c1..31380fc 100644
--- a/geco/mips/utilities/generic.py
+++ b/geco/mips/utilities/generic.py
@@ -1,3 +1,4 @@
+import itertools
 import tempfile
 
 import pyscipopt as scip
@@ -35,3 +36,24 @@ def shuffle(model, seed, cons=True, vars=True):
         shuffled.readProblem(temp.name)
         shuffled.setProbName(model.getProbName())
     return shuffled
+
+
+def expand_parameters(function, **parameter_lists):
+    """
+    Calls a function with every combination of params
+
+    Parameters
+    ----------
+    function: function
+    parameter_lists: dict[str,list]
+        Maps parameter name to all values it might take
+
+    Returns
+    -------
+    generator: Generator
+        Generator of returned values from function with each parameter combination
+    """
+    parameter_names = parameter_lists.keys()
+    all_possible_parameters = itertools.product(*parameter_lists.values())
+    for params in all_possible_parameters:
+        yield function(**{name: val for name, val in zip(parameter_names, params)})


Test patch:
diff --git a/geco/mips/tests/test_scheduling.py b/geco/mips/tests/test_scheduling.py
index 20a37a9..6b8981b 100644
--- a/geco/mips/tests/test_scheduling.py
+++ b/geco/mips/tests/test_scheduling.py
@@ -1,3 +1,5 @@
+import math
+
 import pytest
 
 from geco.mips.scheduling.heinz import *
@@ -67,7 +69,7 @@ def test_param_generation_seeding(n_resources, n_tasks, seed1, seed2):
 
 
 def test_hooker_simple_instance():
-    hooker_model = hooker_formulation(*_simple_instance_params()[:-1])
+    hooker_model = late_tasks_formulation(*_simple_instance_params()[:-1])
     hooker_model.hideOutput()
     hooker_model.optimize()
     assert hooker_model.getStatus() == "optimal"
@@ -145,3 +147,116 @@ def _check_hookers_instance(model, number_of_facilities, number_of_tasks, time_s
     )
     assert constraints_lowerbound <= model.getNConss() <= constraints_upperbound
     assert model.getObjectiveSense() == "minimize"
+
+
+def check_params_dimensions(params):
+    (
+        number_of_facilities,
+        number_of_tasks,
+        processing_times,
+        capacities,
+        assignment_costs,
+        release_times,
+        deadlines,
+        resource_requirements,
+    ) = params
+    facility_for_task_count = number_of_facilities * number_of_tasks
+    assert len(processing_times) == facility_for_task_count
+    assert len(assignment_costs) == facility_for_task_count
+    assert len(resource_requirements) == facility_for_task_count
+    assert len(release_times) == number_of_tasks
+    assert len(deadlines) == number_of_tasks
+    assert len(capacities) == number_of_facilities
+
+
+def check_params_ranges(params, params_ranges):
+    for param, (start, end) in zip(params, params_ranges):
+        if isinstance(param, int):
+            assert start <= param < end
+        elif isinstance(param, dict):
+            for val in param.values():
+                assert start <= val < end
+
+
+def test_c_params_generation():
+    n = 0
+    for params in c_instance_params():
+        n += 1
+        check_params_dimensions(params)
+        check_params_ranges(
+            params,
+            [
+                (2, 4 + 1),
+                (10, 38 + 1),
+                (1, 10 * 4),
+                (10, 10 + 1),
+                (2, 20 * 4),
+                (0, 0 + 1),
+                (21, 95 + 1),
+                (1, 10),
+            ],
+        )
+    assert n == 3 * 15
+
+
+def test_e_params_generation():
+    n = 0
+    for params in e_instance_params():
+        n += 1
+        check_params_dimensions(params)
+        check_params_ranges(
+            params,
+            [
+                (2, 10 + 1),
+                (10, 5 * 10 + 1),
+                (2, 25),
+                (10, 10 + 1),
+                (16, 53 + 1),
+                (0, 0 + 1),
+                (33, 33 + 1),
+                (1, 10),
+            ],
+        )
+    assert n == 9
+
+
+def test_de_params_generation():
+    n = 0
+    for params in de_instance_params():
+        n += 1
+        check_params_dimensions(params)
+        check_params_ranges(
+            params,
+            [
+                (3, 3 + 1),
+                (14, 28 + 1),
+                (2, 30),
+                (10, 10 + 1),
+                (10, 60),
+                (0, 0 + 1),
+                (6, 95 + 1),
+                (1, 10),
+            ],
+        )
+    assert n == 8
+
+
+def test_df_params_generation():
+    n = 0
+    for params in df_instance_params():
+        n += 1
+        check_params_dimensions(params)
+        check_params_ranges(
+            params,
+            [
+                (3, 3 + 1),
+                (14, 28 + 1),
+                (2, 30),
+                (10, 10 + 1),
+                (10, 60),
+                (0, 4 + 1),
+                (2, math.inf),
+                (1, 10),
+            ],
+        )
+    assert n == 8
diff --git a/geco/mips/tests/test_utilities.py b/geco/mips/tests/test_utilities.py
index e22971c..c12ec3f 100644
--- a/geco/mips/tests/test_utilities.py
+++ b/geco/mips/tests/test_utilities.py
@@ -21,3 +21,10 @@ def test_saving_shuffled_instance():
                 break
         else:
             assert False
+
+
+def test_expand_parameters():
+    def add(x, y):
+        return x + y
+
+    assert list(expand_parameters(add, x=[1, 2], y=[3, 4])) == [4, 5, 5, 6]