def callback_inner(model, where): if where == grb.GRB.Callback.MIPSOL: # Update sub-problem's RHS based on incumbent solution facility_cols = list(master.facility_to_column.values()) mp_facility_values = model.cbGetSolution(facility_cols) facility_names = master.facility_to_column.keys() sub_problem_rhs = {facility_name: -data.supply(facility_name) if utils.is_non_zero(val) else 0.0 for facility_name, val in zip(facility_names, mp_facility_values)} sub_problem.set_supply_constraint_rhs(sub_problem_rhs) # Solve sub-problem sub_problem.solve() logging.debug(f'Subproblem status: {sub_problem.model.getAttr(grb.GRB.Attr.Status)}') # Add cuts (lazy constraints) based on sub-problem status if sub_problem.status() == grb.GRB.Status.INFEASIBLE: # Add feasibility cut cut = [] for facility_name, row in sub_problem.facility_to_supply_constraint.items(): dual_farkas = row.getAttr(grb.GRB.Attr.FarkasDual) logging.debug(f'{facility_name}: {dual_farkas}') cut.append(dual_farkas * mapping[row]) for customer_name, row in sub_problem.customer_to_demand_constraint.items(): dual_farkas = row.getAttr(grb.GRB.Attr.FarkasDual) logging.debug(f'{customer_name}: {dual_farkas}') cut.append(dual_farkas * mapping[row]) logging.debug("Adding feasibility cut: ", grb.quicksum(cut) >= 0) model.cbLazy(grb.quicksum(cut) >= 0) elif sub_problem.status() == grb.GRB.Status.OPTIMAL: sub_problem_obj_val = sub_problem.model.getAttr(grb.GRB.Attr.ObjVal) z = master.name_to_column[master.aux_var_name] z_val = model.cbGetSolution(z) if utils.is_non_zero(sub_problem_obj_val - z_val): # Add optimality cut cut = [] for _, row in sub_problem.facility_to_supply_constraint.items(): dual_supply = row.getAttr(grb.GRB.Attr.Pi) cut.append(dual_supply * mapping[row]) for _, row in sub_problem.customer_to_demand_constraint.items(): dual_demand = row.getAttr(grb.GRB.Attr.Pi) cut.append(dual_demand * mapping[row]) cut.append(-z) model.cbLazy(grb.quicksum(cut) <= 0) logging.debug("Adding optimality cut: ", grb.quicksum(cut) <= 0)
def build_supply_constraints( data: InputData, model: grb.Model, facility_customer_pair_to_column: Dict[Tuple[str, str], grb.Var], facility_name_to_column: Dict[str, grb.Var]) -> Dict[str, grb.Constr]: """ Build constraints s_i y_i - \sum_{j=0}^{m} x_ij >= 0, for all i = 1, ..., n :param data: :param model: :param facility_customer_pair_to_column: :param facility_name_to_column: :return: """ facility_to_row = dict() for facility in data.facilities: lhs = [ -facility_customer_pair_to_column[(facility.name, customer_name)] for customer_name in facility.transport_cost.keys() ] facility_var = facility_name_to_column.get(facility.name, 1) lhs.append(1 * facility.supply * facility_var) lhs = grb.quicksum(lhs) name = f'supply_{facility.name}' row = model.addConstr(lhs >= 0.0, name=name) facility_to_row[facility.name] = row return facility_to_row
def adding_constraint( self, constraint_description: Union[ConstraintTaskIndividual, ConstraintWorkDuration], constraint_name: str = "", ): if isinstance(constraint_description, ConstraintTaskIndividual): if constraint_name == "": constraint_name = str(ConstraintTaskIndividual.__name__) for tupl in constraint_description.list_tuple: ressource, ressource_individual, task, has_to_do = tupl if ressource in self.ressource_id_usage: if ressource_individual in self.ressource_id_usage[ ressource]: if (task in self.ressource_id_usage[ressource] [ressource_individual]): if constraint_name not in self.constraint_additionnal: self.constraint_additionnal[ constraint_name] = [] self.constraint_additionnal[constraint_name] += [ self.model.addConstr( self.ressource_id_usage[ressource] [ressource_individual][task] == has_to_do) ] if isinstance(constraint_description, ConstraintWorkDuration): if constraint_name == "": constraint_name = str(ConstraintWorkDuration.__name__) if constraint_name not in self.constraint_additionnal: self.constraint_additionnal[constraint_name] = [] tasks_of_interest = [ t for t in self.rcpsp_schedule if t in self.ressource_id_usage.get( constraint_description.ressource, {}).get( constraint_description.individual, {}) and (constraint_description.time_bounds[0] <= self. rcpsp_schedule[t]["start_time"] <= constraint_description. time_bounds[1] or constraint_description.time_bounds[0] <= self.rcpsp_schedule[t]["end_time"] <= constraint_description.time_bounds[1]) ] print(tasks_of_interest) self.constraint_additionnal[constraint_name] += [ self.model.addConstr( gurobi.quicksum([ self.ressource_id_usage[ constraint_description.ressource][ constraint_description.individual][t] * (min( constraint_description.time_bounds[1], self.rcpsp_schedule[t]["end_time"], ) - max( constraint_description.time_bounds[0], self.rcpsp_schedule[t]["start_time"], )) for t in tasks_of_interest ]) <= constraint_description.working_time_upper_bound) ] self.model.update()
def add_gprs_cnf_weak_(tiger, model, gene_ub=None, gpr_suffix="__GPR", **kwargs): rxns = get_vars_by_name(model, tiger.rxns) if gene_ub is None: ub = GRB.INFINITY else: ub = gene_ub for gene in tiger.genes: model.addVar(lb=0.0, ub=ub, name=gene) model.update() g = get_vars_by_name(model, tiger.genes, "dict") for v, gpr in zip(rxns, tiger.gprs): if gpr.is_empty(): continue cnfs = parsing.make_cnf(gpr, names_only=True) for i, cnf in enumerate(cnfs): genes = [g[name] for name in cnf] if gene_ub is None: model.addConstr(v <= gp.quicksum(genes), name=v.varname + gpr_suffix + '-UB[' + str(i) + ']') model.addConstr(v >= -gp.quicksum(genes), name=v.varname + gpr_suffix + '-LB[' + str(i) + ']') else: model.addConstr(v <= v.ub / ub * gp.quicksum(genes), name=v.varname + gpr_suffix + '-UB[' + str(i) + ']') model.addConstr(v >= v.lb / ub * gp.quicksum(genes), name=v.varname + gpr_suffix + '-LB[' + str(i) + ']') model.update()
def build_demand_constraints(data: InputData, model: grb.Model, facility_customer_pair_to_column: Dict[Tuple[str, str], grb.Var]) \ -> Dict[str, grb.Constr]: customer_to_row = dict() for customer in data.customers: lhs = grb.quicksum([ facility_customer_pair_to_column[(facility.name, customer.name)] for facility in data.facilities ]) rhs = customer.demand name = f'demand_{customer.name}' row = model.addConstr(lhs >= rhs, name=name) customer_to_row[customer.name] = row return customer_to_row
def init_model(self, **args): warm_start = args.get('warm_start', {}) self.model = Model("Knapsack") self.variable_decision = {"x": {}} self.description_variable_description = { "x": { "shape": self.knapsack_model.nb_items, "type": bool, "descr": "dictionary with key the item index \ and value the boolean value corresponding \ to taking the item or not" } } self.description_constraint["weight"] = { "descr": "sum of weight of used items doesn't exceed max capacity" } weight = {} list_item = self.knapsack_model.list_items max_capacity = self.knapsack_model.max_capacity x = {} for item in list_item: i = item.index x[i] = self.model.addVar(vtype=GRB.BINARY, obj=item.value, name="x_" + str(i)) if i in warm_start: x[i].start = warm_start[i] x[i].varhinstval = warm_start[i] weight[i] = item.weight self.variable_decision["x"] = x self.model.update() self.constraints_dict["weight"] = self.model.addConstr( quicksum([weight[i] * x[i] for i in x]) <= max_capacity) self.model.update() self.model.setParam("TimeLimit", 200) self.model.modelSense = GRB.MAXIMIZE self.model.setParam(GRB.Param.PoolSolutions, 10000) self.model.setParam("MIPGapAbs", 0.00001) self.model.setParam("MIPGap", 0.00000001)
def init_model(self, **kwargs): greedy_start = kwargs.get("greedy_start", True) verbose = kwargs.get("verbose", False) use_cliques = kwargs.get("use_cliques", False) if greedy_start: if verbose: print("Computing greedy solution") greedy_solver = GreedyColoring(self.coloring_problem, params_objective_function=self.params_objective_function) result_store = greedy_solver.solve(strategy=NXGreedyColoringMethod.best, verbose=verbose) self.start_solution = result_store.get_best_solution_fit()[0] else: if verbose: print("Get dummy solution") solution = self.coloring_problem.get_dummy_solution() self.start_solution = solution nb_colors = self.start_solution.nb_color color_model = Model("color") colors_var = {} range_node = range(self.number_of_nodes) range_color = range(nb_colors) for node in self.nodes_name: for color in range_color: colors_var[node, color] = color_model.addVar(vtype=GRB.BINARY, obj=0, name="x_" + str((node, color))) one_color_constraints = {} for n in range_node: one_color_constraints[n] = color_model.addConstr(quicksum([colors_var[n, c] for c in range_color]) == 1) color_model.update() cliques = [] g = self.graph.to_networkx() if use_cliques: for c in nx.algorithms.clique.find_cliques(g): cliques += [c] cliques = sorted(cliques, key=lambda x: len(x), reverse=True) else: cliques = [[e[0], e[1]] for e in g.edges()] cliques_constraint = {} index_c = 0 opt = color_model.addVar(vtype=GRB.INTEGER, lb=0, ub=nb_colors, obj=1) if use_cliques: for c in cliques[:100]: cliques_constraint[index_c] = color_model.addConstr(quicksum([(color_i + 1) * colors_var[node, color_i] for node in c for color_i in range_color]) >= sum([i + 1 for i in range(len(c))])) cliques_constraint[(index_c, 1)] = color_model.addConstr(quicksum([colors_var[node, color_i] for node in c for color_i in range_color]) <= opt) index_c += 1 edges = g.edges() constraints_neighbors = {} for e in edges: for c in range_color: constraints_neighbors[(e[0], e[1], c)] = \ color_model.addConstr(colors_var[e[0], c] + colors_var[e[1], c] <= 1) for n in range_node: color_model.addConstr(quicksum([(color_i + 1) * colors_var[n, color_i] for color_i in range_color]) <= opt) color_model.update() color_model.modelSense = GRB.MINIMIZE color_model.setParam(GRB.Param.Threads, 8) color_model.setParam(GRB.Param.PoolSolutions, 10000) color_model.setParam(GRB.Param.Method, -1) color_model.setParam("MIPGapAbs", 0.001) color_model.setParam("MIPGap", 0.001) color_model.setParam("Heuristics", 0.01) self.model = color_model self.variable_decision = {"colors_var": colors_var} self.constraints_dict = {"one_color_constraints": one_color_constraints, "constraints_neighbors": constraints_neighbors} self.description_variable_description = {"colors_var": {"shape": (self.number_of_nodes, nb_colors), "type": bool, "descr": "for each node and each color," " a binary indicator"}} self.description_constraint["one_color_constraints"] = {"descr": "one and only one color " "should be assignated to a node"} self.description_constraint["constraints_neighbors"] = {"descr": "no neighbors can have same color"}
def init_model(self, **args): self.model = gurobi.Model("Gantt") self.ressource_id_usage = { k: {i: {} for i in range(len(self.rcpsp_model.calendar_details[k]))} for k in self.rcpsp_model.calendar_details.keys() } variables_per_task = {} variables_per_individual = {} constraints_ressource_need = {} for task in self.jobs: start = self.rcpsp_schedule[task]["start_time"] end = self.rcpsp_schedule[task]["end_time"] for k in self.ressource_id_usage: # typically worker needed_ressource = (self.rcpsp_model.mode_details[task][ self.modes_dict[task]][k] > 0) if needed_ressource: for individual in self.ressource_id_usage[k]: available = all([ self.rcpsp_model.calendar_details[k][individual] [time] for time in range(start, end) ]) if available: key_variable = (k, individual, task) self.ressource_id_usage[k][individual][ task] = self.model.addVar( name=str(key_variable), vtype=gurobi.GRB.BINARY) if task not in variables_per_task: variables_per_task[task] = set() if k not in variables_per_individual: variables_per_individual[k] = {} if individual not in variables_per_individual[k]: variables_per_individual[k][individual] = set() variables_per_task[task].add(key_variable) variables_per_individual[k][individual].add( key_variable) ressource_needed = self.rcpsp_model.mode_details[task][ self.modes_dict[task]][k] if k not in constraints_ressource_need: constraints_ressource_need[k] = {} constraints_ressource_need[k][task] = self.model.addConstr( gurobi.quicksum([ self.ressource_id_usage[k][key[1]][key[2]] for key in variables_per_task[task] if key[0] == k ]) == ressource_needed, name="ressource_" + str(k) + "_" + str(task), ) overlaps_constraints = {} for i in range(len(self.cliques)): tasks = set(self.cliques[i]) for k in variables_per_individual: for individual in variables_per_individual[k]: keys_variable = [ variable for variable in variables_per_individual[k][individual] if variable[2] in tasks ] if len(keys_variable) > 0: overlaps_constraints[( i, k, individual )] = self.model.addConstr( gurobi.quicksum([ self.ressource_id_usage[key[0]][key[1]][key[2]] for key in keys_variable ]) <= 1)
def init_model(self, **args): greedy_start = args.get("greedy_start", True) start_solution = args.get("start_solution", None) max_horizon = args.get("max_horizon", None) verbose = args.get("verbose", False) if start_solution is None: if greedy_start: if verbose: print("Computing greedy solution") if isinstance(self.rcpsp_model, RCPSPModelCalendar): greedy_solver = PileSolverRCPSP_Calendar(self.rcpsp_model) else: greedy_solver = PileSolverRCPSP(self.rcpsp_model) store_solution = greedy_solver.solve( greedy_choice=GreedyChoice.MOST_SUCCESSORS ) self.start_solution = store_solution.get_best_solution_fit()[0] makespan = self.rcpsp_model.evaluate(self.start_solution)["makespan"] else: if verbose: print("Get dummy solution") solution = self.rcpsp_model.get_dummy_solution() self.start_solution = solution makespan = self.rcpsp_model.evaluate(solution)["makespan"] else: self.start_solution = start_solution makespan = self.rcpsp_model.evaluate(start_solution)["makespan"] # p = [0, 3, 2, 5, 4, 2, 3, 4, 2, 4, 6, 0] sorted_tasks = sorted(self.rcpsp_model.mode_details.keys()) p = [ int( max( [ self.rcpsp_model.mode_details[key][mode]["duration"] for mode in self.rcpsp_model.mode_details[key] ] ) ) for key in sorted_tasks ] # c = [6, 8] c = [x for x in self.rcpsp_model.resources.values()] renewable = { r: self.rcpsp_model.resources[r] for r in self.rcpsp_model.resources if r not in self.rcpsp_model.non_renewable_resources } non_renewable = { r: self.rcpsp_model.resources[r] for r in self.rcpsp_model.non_renewable_resources } # print('c: ', c) # S = [[0, 1], [0, 2], [0, 3], [1, 4], [1, 5], [2, 9], [2, 10], [3, 8], [4, 6], # [4, 7], [5, 9], [5, 10], [6, 8], [6, 9], [7, 8], [8, 11], [9, 11], [10, 11]] S = [] print("successors: ", self.rcpsp_model.successors) for task in sorted_tasks: for suc in self.rcpsp_model.successors[task]: S.append([task, suc]) # print('S: ', S) (R, self.J, self.T) = (range(len(c)), range(len(p)), list(range(sum(p)))) # we have a better self.T to limit the number of variables : if self.start_solution.rcpsp_schedule_feasible: self.T = list(range(int(makespan + 1))) if max_horizon is not None: self.T = list(range(max_horizon + 1)) print("Hey") print(self.T) # model = Model() self.model = gurobi.Model("MRCPSP") self.x: Dict[gurobi.Var] = {} last_task = max(self.rcpsp_model.mode_details.keys()) variable_per_task = {} keys_for_t = {} for task in sorted_tasks: if task not in variable_per_task: variable_per_task[task] = [] for mode in self.rcpsp_model.mode_details[task]: for t in self.T: self.x[(task, mode, t)] = self.model.addVar( name="x({},{}, {})".format(task, mode, t), vtype=gurobi.GRB.BINARY, ) for tt in range( t, t + self.rcpsp_model.mode_details[task][mode]["duration"] ): if tt not in keys_for_t: keys_for_t[tt] = set() keys_for_t[tt].add((task, mode, t)) variable_per_task[task] += [(task, mode, t)] self.model.update() self.model.setObjective( gurobi.quicksum( self.x[key] * key[2] for key in variable_per_task[last_task] ) ) self.model.addConstrs( gurobi.quicksum(self.x[key] for key in variable_per_task[j]) == 1 for j in variable_per_task ) if isinstance(self.rcpsp_model, RCPSPModelCalendar): renewable_quantity = {r: renewable[r] for r in renewable} else: renewable_quantity = {r: [renewable[r]] * len(self.T) for r in renewable} if isinstance(self.rcpsp_model, RCPSPModelCalendar): non_renewable_quantity = {r: non_renewable[r] for r in non_renewable} else: non_renewable_quantity = { r: [non_renewable[r]] * len(self.T) for r in non_renewable } # for r, t in product(renewable, self.T): # self.model.addConstr(gurobi.quicksum(int(self.rcpsp_model.mode_details[key[0]][key[1]][r]) * self.x[key] # for key in keys_for_t[t]) # <= renewable_quantity[r][t]) # print(r, t) self.model.addConstrs( gurobi.quicksum( int(self.rcpsp_model.mode_details[key[0]][key[1]][r]) * self.x[key] for key in keys_for_t[t] ) <= renewable_quantity[r][t] for (r, t) in product(renewable, self.T) ) self.model.addConstrs( gurobi.quicksum( int(self.rcpsp_model.mode_details[key[0]][key[1]][r]) * self.x[key] for key in self.x ) <= non_renewable_quantity[r][0] for r in non_renewable ) self.model.update() durations = { j: self.model.addVar(name="duration_" + str(j), vtype=gurobi.GRB.INTEGER) for j in variable_per_task } self.durations = durations self.variable_per_task = variable_per_task self.model.addConstrs( gurobi.quicksum( self.rcpsp_model.mode_details[key[0]][key[1]]["duration"] * self.x[key] for key in variable_per_task[j] ) == durations[j] for j in variable_per_task ) self.model.addConstrs( gurobi.quicksum( [key[2] * self.x[key] for key in variable_per_task[s]] + [-key[2] * self.x[key] for key in variable_per_task[j]] ) >= durations[j] for (j, s) in S ) start = [] self.starts = {} for task in sorted_tasks: self.starts[task] = self.model.addVar( name="start({})".format(task), vtype=gurobi.GRB.INTEGER, lb=0, ub=self.T[-1], ) self.starts[task].start = self.start_solution.rcpsp_schedule[task][ "start_time" ] self.model.addConstr( gurobi.quicksum( [self.x[key] * key[2] for key in variable_per_task[task]] ) == self.starts[task] ) for j in self.start_solution.rcpsp_schedule: start_time_j = self.start_solution.rcpsp_schedule[j]["start_time"] mode_j = ( 1 if j == 1 or j == self.rcpsp_model.n_jobs + 2 else self.start_solution.rcpsp_modes[j - 2] ) start += [ ( self.durations[j], self.rcpsp_model.mode_details[j][mode_j]["duration"], ) ] for k in self.variable_per_task[j]: task, mode, time = k if start_time_j == time and mode == mode_j: start += [(self.x[k], 1)] self.x[k].start = 1 else: start += [(self.x[k], 0)] self.x[k].start = 0 # self.model.start = start p_s: Union[PartialSolution, None] = args.get("partial_solution", None) self.constraints_partial_solutions = [] self.model.update() if p_s is not None: constraints = [] if p_s.start_times is not None: constraints = self.model.addConstrs( gurobi.quicksum( [ self.x[k] for k in self.variable_per_task[task] if k[2] == p_s.start_times[task] ] ) == 1 for task in p_s.start_times ) if p_s.partial_permutation is not None: for t1, t2 in zip( p_s.partial_permutation[:-1], p_s.partial_permutation[1:] ): constraints += [ self.model.addConstr( gurobi.quicksum( [key[2] * self.x[key] for key in variable_per_task[t1]] + [ -key[2] * self.x[key] for key in variable_per_task[t2] ] ) <= 0 ) ] if p_s.list_partial_order is not None: for l in p_s.list_partial_order: for t1, t2 in zip(l[:-1], l[1:]): constraints += [ self.model.addConstr( gurobi.quicksum( [ key[2] * self.x[key] for key in variable_per_task[t1] ] + [ -key[2] * self.x[key] for key in variable_per_task[t2] ] ) <= 0 ) ] if p_s.start_at_end is not None: for i, j in p_s.start_at_end: constraints += [ self.model.addConstr( self.starts[j] == self.starts[i] + durations[i] ) ] if p_s.start_together is not None: for i, j in p_s.start_together: constraints += [ self.model.addConstr(self.starts[j] == self.starts[i]) ] if p_s.start_after_nunit is not None: for t1, t2, delta in p_s.start_after_nunit: constraints += [ self.model.addConstr(self.starts[t2] >= self.starts[t1] + delta) ] if p_s.start_at_end_plus_offset is not None: for t1, t2, delta in p_s.start_at_end_plus_offset: constraints += [ self.model.addConstr( self.starts[t2] >= self.starts[t1] + delta + durations[t1] ) ] self.constraints_partial_solutions = constraints print("Partial solution constraints : ", self.constraints_partial_solutions) self.model.update()
def init_model(self, **kwargs): nb_facilities = self.facility_problem.facility_count nb_customers = self.facility_problem.customer_count use_matrix_indicator_heuristic = kwargs.get("use_matrix_indicator_heuristic", True) if use_matrix_indicator_heuristic: n_shortest = kwargs.get("n_shortest", 10) n_cheapest = kwargs.get("n_cheapest", 10) matrix_fc_indicator, matrix_length = prune_search_space(self.facility_problem, n_cheapest=n_cheapest, n_shortest=n_shortest) else: matrix_fc_indicator, matrix_length = prune_search_space(self.facility_problem, n_cheapest=nb_facilities, n_shortest=nb_facilities) s = Model("facilities") x = {} for f in range(nb_facilities): for c in range(nb_customers): if matrix_fc_indicator[f, c] == 0: x[f, c] = 0 elif matrix_fc_indicator[f, c] == 1: x[f, c] = 1 elif matrix_fc_indicator[f, c] == 2: x[f, c] = s.addVar(vtype=GRB.BINARY, obj=0, name="x_" + str((f, c))) facilities = self.facility_problem.facilities customers = self.facility_problem.customers used = s.addVars(nb_facilities, vtype=GRB.BINARY, name="y") constraints_customer = {} for c in range(nb_customers): constraints_customer[c] = s.addConstr(quicksum([x[f, c] for f in range(nb_facilities)]) == 1) # one facility constraint_capacity = {} for f in range(nb_facilities): s.addConstrs(used[f] >= x[f, c] for c in range(nb_customers)) constraint_capacity[f] = s.addConstr(quicksum([x[f, c] * customers[c].demand for c in range(nb_customers)]) <= facilities[f].capacity) s.update() new_obj_f = LinExpr(0.) new_obj_f += quicksum([facilities[f].setup_cost * used[f] for f in range(nb_facilities)]) new_obj_f += quicksum([matrix_length[f, c] * x[f, c] for f in range(nb_facilities) for c in range(nb_customers)]) s.setObjective(new_obj_f) s.update() s.modelSense = GRB.MINIMIZE s.setParam(GRB.Param.Threads, 4) s.setParam(GRB.Param.PoolSolutions, 10000) s.setParam(GRB.Param.Method, 1) s.setParam("MIPGapAbs", 0.00001) s.setParam("MIPGap", 0.00000001) self.model = s self.variable_decision = {"x": x} self.constraints_dict = {"constraint_customer": constraints_customer, "constraint_capacity": constraint_capacity} self.description_variable_description = {"x": {"shape": (nb_facilities, nb_customers), "type": bool, "descr": "for each facility/customer indicate" " if the pair is active, meaning " "that the customer c is dealt with facility f"}} self.description_constraint = {"Im lazy."} print("Initialized")
def _create_model(job_num, machine_num, job_ids, r_times, d_times, p_intervals, p_cost): """ Prepare the index for decision variables """ # start time of process jobs = tuple(job_ids) # print("inside:", jobs) machines = tuple(range(machine_num)) # print(machines) # sequence of processing jobs: tuple list job_pairs = [(i, j) for i in jobs for j in jobs if i != j] # print(job_pairs) # assignment of jobs on machines job_machine_pairs = [(i, m) for i in jobs for m in machines] # print(job_machine_pairs) # dissimilar parallel machine-machine pair machine_pairs = [(m, n) for m in machines for n in machines if m != n] """ Parameters model (dictionary) """ # 1. release time release_time = dict(zip(jobs, tuple(r_times))) # print("release time:", release_time) # 2. due time due_time = dict(zip(jobs, tuple(d_times))) # print("due time:", due_time) # 3. processing time process_time = dict(zip(jobs, tuple(p_intervals))) # print("process time:", process_time) # 4. processing cost job_cost = dict(zip(jobs, tuple(p_cost))) # print("processing cost:", job_cost) # 5. define BigU U = sum([max(p_intervals[i]) for i in range(job_num)]) # print("test U:", U) """ Create model """ model = grb.Model("SSJSP") """ Create decision variables """ # 1. Assignments of jobs on machines x = model.addVars(job_machine_pairs, vtype=grb.GRB.BINARY, name="assign") # 2. Sequence (Order) of executing jobs y = model.addVars(job_pairs, vtype=grb.GRB.BINARY, name="sequence") # 3. Start time of executing each job (ts = time_start) ts = model.addVars(jobs, lb=0, name="start_time") """ Create the objective function """ model.setObjective(grb.quicksum([(job_cost[i][m]*x[(i,m)]) for m in machines for i in jobs]), sense=grb.GRB.MINIMIZE) """ Create constraints """ # 1. job release time constraint model.addConstrs((ts[i] >= release_time[i] for i in jobs), name="job release constraint") # 2. job due time constraint model.addConstrs((ts[i] <= due_time[i] - grb.quicksum([process_time[i][m]*x[(i,m)] for m in machines]) for i in jobs), name="job due constraint") # 3. one job is assigned to one and only one machine model.addConstrs((grb.quicksum([x[(i,m)] for m in machines]) == 1 for i in jobs), name="job non-splitting constraint") # 4. job 'j' is processed after job 'i' when both jobs are assigned to same machine model.addConstrs((y[(i,j)] + y[(j,i)] >= x[(i,m)] + x[(j,m)] - 1 for m in machines for (i,j) in job_pairs if j > i), name="assignment-sequencing vars constraint") # 5. sequencing constraint model.addConstrs((ts[j] >= ts[i] + grb.quicksum([process_time[i][m]*x[(i,m)] for m in machines]) - U*(1 - y[(i,j)]) for (i,j) in job_pairs), name="sequence constraint") # 6. either job 'i' is processed before job 'j' or vice versa model.addConstrs((y[(i,j)] + y[(j,i)] <= 1 for (i,j) in job_pairs if j > i), name="sequence of jobs") # 7. sequencing varibles = 0 when job 'i' and 'j' are assigned to different machines model.addConstrs((y[(i,j)] + y[(j,i)] + x[(i,m)] + x[(j,n)] <= 2 for (m,n) in machine_pairs for (i,j) in job_pairs if j > i), name="different machine constraint") return model, x, y, ts