def add_model_soc_constr( self, model: ScipModel, variables: List, rows: Iterator, A: dok_matrix, b: ndarray, ) -> Tuple: """Adds SOC constraint to the model using the data from mat and vec. Return tuple contains (QConstr, list of Constr, and list of variables). """ from pyscipopt.scip import quicksum # Assume first expression (i.e. t) is nonzero. expr_list = {i: [] for i in rows} for (i, j), c in A.items(): v = variables[j] try: expr_list[i].append((c, v)) except Exception: pass # Make a variable and equality constraint for each term. soc_vars = [] for i in rows: lb = 0 if len(soc_vars) == 0 else None var = model.addVar( obj=0, name="soc_t_%d" % i, vtype=VariableTypes.CONTINUOUS, lb=lb, ub=None, ) soc_vars.append(var) lin_expr_list = [ b[i] - quicksum(coeff * var for coeff, var in expr_list[i]) for i in rows ] new_lin_constrs = [ model.addCons(soc_vars[i] == lin_expr_list[i]) for i, _ in enumerate(lin_expr_list) ] # Interesting because only <=? t_term = soc_vars[0] * soc_vars[0] x_term = quicksum([var * var for var in soc_vars[1:]]) constraint = model.addCons(x_term <= t_term) return ( constraint, new_lin_constrs, soc_vars, )
def test_objective(model): m, x, y, z = model # setting linear objective m.setObjective(x + y) # using quicksum m.setObjective(quicksum(2 * v for v in [x, y, z])) # setting affine objective m.setObjective(x + y + 1) assert m.getObjoffset() == 1 # setting nonlinear objective with pytest.raises(ValueError): m.setObjective(x**2 - y * z)
def test_objective(model): m, x, y, z = model # setting linear objective m.setObjective(x + y) # using quicksum m.setObjective(quicksum(2 * v for v in [x, y, z])) # setting affine objective m.setObjective(x + y + 1) assert m.getObjoffset() == 1 # setting nonlinear objective with pytest.raises(ValueError): m.setObjective(x ** 2 - y * z)
def add_model_lin_constr( self, model: ScipModel, variables: List, rows: Iterator, ctype: str, A: dok_matrix, b: ndarray, ) -> List: """Adds EQ/LEQ constraints to the model using the data from mat and vec. Return list contains constraints. """ from pyscipopt.scip import quicksum constraints = [] expr_list = {i: [] for i in rows} for (i, j), c in A.items(): v = variables[j] try: expr_list[i].append((c, v)) except Exception: pass for i in rows: # Ignore empty constraints. if expr_list[i]: expression = quicksum(coeff * var for coeff, var in expr_list[i]) constraint = model.addCons(( expression == b[i] ) if ctype == ConstraintTypes.EQUAL else (expression <= b[i])) constraints.append(constraint) else: constraints.append(None) return constraints
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 hooker_cost_formulation( number_of_facilities, number_of_tasks, processing_times, capacities, assignment_costs, release_dates, deadlines, resource_requirements, name="Hooker Cost Scheduling Formulation", ): """Generates scheduling MIP formulation according to [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] J. N. Hooker, A hybrid method for planning and scheduling, CP 2004. """ model = scip.Model(name) time_steps = range(min(release_dates), int(max(deadlines))) # objective function 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=assignment_costs[j, i], name=f"x_{j}_{i}_{t}", vtype="B") x[j, i, t] = var # add constraints # constraints (a) for j in range(number_of_tasks): model.addCons( scip.quicksum(x[j, i, t] for i, t in itertools.product( range(number_of_facilities), time_steps)) == 1) # constraints (b) for i, t in itertools.product(range(number_of_facilities), time_steps): model.addCons( scip.quicksum(resource_requirements[j, i] * x[j, i, t] for j in range(number_of_tasks)) <= capacities[i]) # constraints (c) for j, i, t in itertools.product(range(number_of_tasks), range(number_of_facilities), time_steps): if (deadlines[j] - processing_times[j, i] < t < release_dates[j] or t > number_of_tasks - processing_times[j, i]): model.addCons(x[j, i, t] == 0) 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