def test_add_column(solver: str): """Simple test which add columns in a specific way""" m = Model() x = m.add_var() example_constr1 = m.add_constr(x >= 1, "constr1") example_constr2 = m.add_constr(x <= 2, "constr2") column1 = Column() column1.constrs = [example_constr1] column1.coeffs = [1] second_var = m.add_var("second", column=column1) column2 = Column() column2.constrs = [example_constr2] column2.coeffs = [2] m.add_var("third", column=column2) vthird = m.vars["third"] assert vthird is not None assert len(vthird.column.coeffs) == len(vthird.column.constrs) assert len(vthird.column.coeffs) == 1 pconstr2 = m.constrs["constr2"] assert vthird.column.constrs[0].name == pconstr2.name assert len(example_constr1.expr.expr) == 2 assert second_var in example_constr1.expr.expr assert x in example_constr1.expr.expr
def relax_constraints(cls, relaxed_model: mip.Model, slack_dict: dict) -> mip.Model: """this method creates a modification of the model `relaxed_model` where all the constraints in the slack_dict are modified in order to add the slack values to make the IIS disappear Args: relaxed_model (mip.Model): model to relax slack_dict (dict): pairs {constraint_name: slack_var.value} Returns: mip.Model: a modification of the original model where all the constraints are modified with the slack values """ for crt_name in slack_dict.keys(): crt_original = relaxed_model.constr_by_name(crt_name) relax_expr = crt_original.expr + slack_dict[crt_name] relaxed_model.add_constr( relax_expr, name=crt_original.name, priority=crt_original.priority ) relaxed_model.remove(crt_original) # remove constraint return relaxed_model
def test_cutting_stock(solver: str): n = 10 # maximum number of bars L = 250 # bar length m = 4 # number of requests w = [187, 119, 74, 90] # size of each item b = [1, 2, 2, 1] # demand for each item # creating the model model = Model(solver_name=solver) x = {(i, j): model.add_var(obj=0, var_type=INTEGER, name="x[%d,%d]" % (i, j)) for i in range(m) for j in range(n)} y = { j: model.add_var(obj=1, var_type=BINARY, name="y[%d]" % j) for j in range(n) } # constraints for i in range(m): model.add_constr(xsum(x[i, j] for j in range(n)) >= b[i]) for j in range(n): model.add_constr(xsum(w[i] * x[i, j] for i in range(m)) <= L * y[j]) # additional constraints to reduce symmetry for j in range(1, n): model.add_constr(y[j - 1] >= y[j]) # optimizing the model model.optimize() # sanity tests assert model.status == OptimizationStatus.OPTIMAL assert abs(model.objective_value - 3) <= 1e-4 assert sum(x.x for x in model.vars) >= 5
def build_infeasible_cont_model(num_constraints: int = 10, num_infeasible_sets: int = 20) -> Model: # build an infeasible model, based on many redundant constraints mdl = Model(name='infeasible_model_continuous') var = mdl.add_var(name='var', var_type=CONTINUOUS, lb=-1000, ub=1000) for idx, rand_constraint in enumerate(np.linspace(1, 1000, num_constraints)): crt = mdl.add_constr(var >= rand_constraint, name='lower_bound_{}'.format(idx)) logger.debug('added {} to the model'.format(crt)) num_constraint_inf = int(num_infeasible_sets / num_constraints) for idx, rand_constraint in enumerate( np.linspace(-1000, -1, num_constraint_inf)): crt = mdl.add_constr(var <= rand_constraint, name='upper_bound_{}'.format(idx)) logger.debug('added {} to the model'.format(crt)) mdl.emphasis = 1 # feasibility mdl.preprocess = 1 # -1 automatic, 0 off, 1 on. # mdl.pump_passes TODO configure to feasibility emphasis return mdl
class NaiveMIPModel: m: Model = None customer_facility_map: List[List[Var]] = None facility_customer_map: List[List[Var]] = None def __init__(self, facilities: List[Facility], customers: List[Customer]): self.m = Model('NaiveFacilityMIP', solver_name=CBC) self.customer_facility_map = [[self.m.add_var(var_type=BINARY) for facility in range(len(facilities))] for customer in range(len(customers))] self.facility_customer_map = list(zip(*self.customer_facility_map)) for customer in range(len(customers)): self.m.add_constr(xsum(self.customer_facility_map[customer]) == 1) for facility in range(len(facilities)): self.m.add_constr(xsum([customers[index].demand * variable for index, variable in enumerate(self.facility_customer_map[facility])]) <= facilities[facility].capacity) facility_enabled = [self.m.add_var(var_type=BINARY) for facility in range(len(facilities))] for facility in range(len(facilities)): for customer in range(len(customers)): self.m.add_constr(self.facility_customer_map[facility][customer] <= facility_enabled[facility]) self.m.objective = xsum([facilities[f].setup_cost * facility_enabled[f] for f in range(len(facilities))]) + \ xsum([euclideam_length(facilities[facility].location, customers[customer].location) * self.facility_customer_map[facility][customer] for facility in range(len(facilities)) for customer in range(len(customers))]) self.m.verbose = True self.m.max_seconds = 60 * 20 def optimize(self): self.m.optimize() def get_best_score(self): return self.m.objective_value def get_solution(self): solution = [] for customer in self.customer_facility_map: for index, facility in enumerate(customer): if facility.x >= 0.99: solution.append(index) break return solution
class LP_MRCPSP(MilpSolver): def __init__( self, rcpsp_model: MultiModeRCPSPModel, lp_solver=LP_RCPSP_Solver.CBC, params_objective_function: ParamsObjectiveFunction = None, **kwargs ): self.rcpsp_model = rcpsp_model self.model: Model = None self.lp_solver = CBC if lp_solver == LP_RCPSP_Solver.GRB: self.lp_solver = GRB elif lp_solver == LP_RCPSP_Solver.CBC: self.lp_solver = CBC self.variable_decision = {} self.constraints_dict = {} self.constraints_dict["lns"] = [] ( self.aggreg_from_sol, self.aggreg_dict, self.params_objective_function, ) = build_aggreg_function_and_params_objective( problem=self.rcpsp_model, params_objective_function=params_objective_function, ) # self.description_variable_description = {} # self.description_constraint = {} def init_model(self, **args): greedy_start = args.get("greedy_start", True) start_solution = args.get("start_solution", None) verbose = args.get("verbose", False) if start_solution is None: if greedy_start: if verbose: print("Computing greedy solution") 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)), range(sum(p))) # we have a better self.T to limit the number of variables : if self.start_solution.rcpsp_schedule_feasible: self.T = range(int(makespan + 1)) # model = Model() self.model = Model(sense=MINIMIZE, solver_name=self.lp_solver) self.x: Dict[Var] = {} last_task = max(self.rcpsp_model.mode_details.keys()) variable_per_task = {} 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.add_var( name="x({},{}, {})".format(task, mode, t), var_type=BINARY ) variable_per_task[task] += [(task, mode, t)] self.model.objective = xsum( self.x[key] * key[2] for key in variable_per_task[last_task] ) for j in variable_per_task: self.model += xsum(self.x[key] for key in variable_per_task[j]) == 1 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.add_constr( xsum( int(self.rcpsp_model.mode_details[key[0]][key[1]][r]) * self.x[key] for key in self.x if key[2] <= t < key[2] + int(self.rcpsp_model.mode_details[key[0]][key[1]]["duration"]) ) <= renewable_quantity[r][t] ) print(r, t) for r in non_renewable: self.model.add_constr( xsum( int(self.rcpsp_model.mode_details[key[0]][key[1]][r]) * self.x[key] for key in self.x ) <= non_renewable_quantity[r][0] ) durations = { j: self.model.add_var(name="duration_" + str(j), var_type=INTEGER) for j in variable_per_task } self.durations = durations self.variable_per_task = variable_per_task for j in variable_per_task: self.model.add_constr( xsum( self.rcpsp_model.mode_details[key[0]][key[1]]["duration"] * self.x[key] for key in variable_per_task[j] ) == durations[j] ) for (j, s) in S: self.model.add_constr( xsum( [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] ) start = [] 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)] else: start += [(self.x[k], 0)] self.model.start = start p_s: Union[PartialSolution, None] = args.get("partial_solution", None) self.constraints_partial_solutions = [] if p_s is not None: constraints = [] if p_s.start_times is not None: for task in p_s.start_times: constraints += [ self.model.add_constr( xsum( [ self.x[k] for k in self.variable_per_task[task] if k[2] == p_s.start_times[task] ] ) == 1 ) ] 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.add_constr( xsum( [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.add_constr( xsum( [ 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 ) ] self.constraints_partial_solutions = constraints print("Partial solution constraints : ", self.constraints_partial_solutions) def retrieve_solutions(self, parameters_milp: ParametersMilp) -> ResultStorage: retrieve_all_solution = parameters_milp.retrieve_all_solution nb_solutions_max = parameters_milp.n_solutions_max nb_solution = min(nb_solutions_max, self.model.num_solutions) if not retrieve_all_solution: nb_solution = 1 list_solution_fits = [] print(nb_solution, " solutions found") for s in range(nb_solution): rcpsp_schedule = {} modes = {} objective = self.model.objective_values[s] for (task, mode, t) in self.x: value = self.x[(task, mode, t)].xi(s) if value >= 0.5: rcpsp_schedule[task] = { "start_time": t, "end_time": t + self.rcpsp_model.mode_details[task][mode]["duration"], } modes[task] = mode print("Size schedule : ", len(rcpsp_schedule.keys())) try: modes.pop(1) modes.pop(self.rcpsp_model.n_jobs + 2) modes_vec = [modes[k] for k in sorted(modes)] solution = RCPSPSolution( problem=self.rcpsp_model, rcpsp_schedule=rcpsp_schedule, rcpsp_modes=modes_vec, rcpsp_schedule_feasible=True, ) fit = self.aggreg_from_sol(solution) list_solution_fits += [(solution, fit)] except: pass return ResultStorage( list_solution_fits=list_solution_fits, best_solution=min(list_solution_fits, key=lambda x: x[1])[0], mode_optim=self.params_objective_function.sense_function, ) def solve( self, parameters_milp: ParametersMilp = ParametersMilp.default(), **kwargs ) -> ResultStorage: if self.model is None: self.init_model(greedy_start=False, **kwargs) limit_time_s = parameters_milp.TimeLimit self.model.sol_pool_size = parameters_milp.PoolSolutions self.model.max_mip_gap_abs = parameters_milp.MIPGapAbs self.model.max_mip_gap = parameters_milp.MIPGap self.model.optimize( max_seconds=limit_time_s, max_solutions=parameters_milp.n_solutions_max ) return self.retrieve_solutions(parameters_milp)
class LeaderModel: m: Model = None customer_facility: List[List[int]] = None facility_customer: List[List[int]] = None facilities: List[Facility] = None leaders: List[Facility] = None customers: List[Customer] = None var_store: Dict[Tuple[int, int], Var] = defaultdict() facilities_enabled: List[Var] = [] NEIGHBOURS = 5 RADIUS = 1 CAPACITY_LEADERS = 2 SETUP_LEADERS = 5 def __init__(self, facilities: List[Facility], customers: List[Customer]): self.facilities = facilities self.customers = customers self.leaders = self.get_capacity_leaders() self.leaders += self.get_setup_leaders() self.dedupe_leaders() customer_distance_leaders: List[Dict[ int, int]] = self.get_customer_distance_leaders() self.m = Model('LeaderModel', solver_name=CBC) self.initialize_customer_facility(customer_distance_leaders) self.initialize_facility_customer(customer_distance_leaders) self.model_adjustments() self.create_model_variables() self.add_necessary_constraints() self.construct_objective_fn() def model_adjustments(self): self.m.verbose = True self.m.max_seconds = 60 * 10 def initialize_facility_customer(self, customer_distance_leaders): self.facility_customer = [[]] * len(self.facilities) for facility in self.facilities: facility_vars = [] found = False for leader in self.leaders: if leader.index == facility.index: for customer in self.customers: facility_vars.append(customer.index) self.facility_customer[facility.index] = facility_vars found = True if found: continue for customer in self.customers: if facility.index in customer_distance_leaders[customer.index]: facility_vars.append(customer.index) self.facility_customer[facility.index] = facility_vars def construct_objective_fn(self): setup_expression = [] for facility in self.facilities: setup_expression.append(0.5 * facility.setup_cost * self.facilities_enabled[facility.index]) distance_expressions = [] for facility in self.facilities: for customer_selected in self.facility_customer[facility.index]: distance = euclidean_length( facility.location, self.customers[customer_selected].location) distance_expressions.append( distance * self.var_store[(customer_selected, facility.index)]) self.m.objective = xsum(setup_expression) + xsum(distance_expressions) def add_necessary_constraints(self): for customer in self.customers: self.m.add_constr( xsum( map(lambda x: self.var_store[(customer.index, x)], self.customer_facility[customer.index])) == 1) for facility in self.facilities: expressions = [] for customer_selected in self.facility_customer[facility.index]: expressions.append( self.customers[customer_selected].demand * self.var_store[(customer_selected, facility.index)]) self.m.add_constr( self.var_store[(customer_selected, facility.index)] <= self.facilities_enabled[facility.index]) self.m.add_constr(xsum(expressions) <= facility.capacity) def initialize_customer_facility(self, customer_distance_leaders): self.customer_facility = [0] * len(self.customers) for customer in self.customers: customer_vars = [] for facility in self.leaders: customer_vars.append(facility.index) for facility in customer_distance_leaders[customer.index]: customer_vars.append(facility) self.customer_facility[customer.index] = customer_vars def optimize(self): self.m.optimize() def get_best_score(self): return self.m.objective_value def get_solution(self): solution = [] for customer in self.customers: for facility_selected in self.customer_facility[customer.index]: variable = self.var_store[(customer.index, facility_selected)] if variable.x >= 0.99: solution.append(facility_selected) break return solution def get_capacity_leaders(self): return list( sorted(self.facilities, key=lambda facility: -facility.capacity) )[:self.CAPACITY_LEADERS] def get_setup_leaders(self): return list( sorted( self.facilities, key=lambda facility: facility.setup_cost))[:self.SETUP_LEADERS] def get_customer_distance_leaders(self) -> List[Dict[int, int]]: facilities_already_selected = set() for facility in self.leaders: facilities_already_selected.add(facility.index) facilities: List[Facility] = list( filter(lambda x: x.index not in facilities_already_selected, self.facilities)) facility_locations = map(lambda x: [x.location.x, x.location.y], facilities) customer_locations = map(lambda x: [x.location.x, x.location.y], self.customers) neigh = NearestNeighbors(n_neighbors=self.NEIGHBOURS, algorithm="ball_tree") neigh.fit(list(facility_locations)) neighbour_indices = neigh.kneighbors(list(customer_locations), return_distance=False) sorted_facilities_x = list( sorted(map(lambda x: (x.location.x, x.index), facilities))) sorted_facilities_y = list( sorted(map(lambda x: (x.location.y, x.index), facilities))) customer_facility_map = [0] * len(self.customers) for neighbours, customer in zip(neighbour_indices, self.customers): selections = set() for neighbour in neighbours: selections.add(facilities[neighbour].index) customer_facility_map[customer.index] = selections return customer_facility_map def dedupe_leaders(self): seen = set() leader_temp = [] for leader in self.leaders: if leader.index in seen: continue else: leader_temp.append(leader) seen.add(leader.index) self.leaders = leader_temp def create_model_variables(self): for customer in self.customers: facilities_selected = self.customer_facility[customer.index] for facility in facilities_selected: self.var_store[(customer.index, facility)] = self.m.add_var(var_type=BINARY) self.facilities_enabled = [0] * len(self.facilities) for facility in self.facilities: self.facilities_enabled[facility.index] = self.m.add_var( var_type=BINARY)
from mip import Model, INTEGER, minimize bus_input = "1789,37,47,1889" buses = bus_input.split(",") model = Model() t = model.add_var(var_type=INTEGER) # TIME - result x = [] n_buses = 0 prod_buses = 1 for i in range(len(buses)): if buses[i] != "x": buses[i] = int(buses[i]) x.append(model.add_var(var_type=INTEGER)) model.add_constr(t == buses[i] * x[n_buses] - i) n_buses += 1 prod_buses *= buses[i] model.objective = minimize(t) for i in range(len(x)): model.add_constr(x[i] >= 1) model.add_constr(t >= 1) model.add_constr(t <= prod_buses) model.optimize() print('') print('Objective value: {model.objective_value:.3}'.format(**locals())) print('Solution: ', end='')
def test_tsp_cuts(solver: str): """tsp related tests""" N = ["a", "b", "c", "d", "e", "f", "g"] n = len(N) i0 = N[0] A = { ("a", "d"): 56, ("d", "a"): 67, ("a", "b"): 49, ("b", "a"): 50, ("d", "b"): 39, ("b", "d"): 37, ("c", "f"): 35, ("f", "c"): 35, ("g", "b"): 35, ("b", "g"): 25, ("a", "c"): 80, ("c", "a"): 99, ("e", "f"): 20, ("f", "e"): 20, ("g", "e"): 38, ("e", "g"): 49, ("g", "f"): 37, ("f", "g"): 32, ("b", "e"): 21, ("e", "b"): 30, ("a", "g"): 47, ("g", "a"): 68, ("d", "c"): 37, ("c", "d"): 52, ("d", "e"): 15, ("e", "d"): 20, } # input and output arcs per node Aout = {n: [a for a in A if a[0] == n] for n in N} Ain = {n: [a for a in A if a[1] == n] for n in N} m = Model(solver_name=solver) m.verbose = 0 x = { a: m.add_var(name="x({},{})".format(a[0], a[1]), var_type=BINARY) for a in A } m.objective = xsum(c * x[a] for a, c in A.items()) for i in N: m += xsum(x[a] for a in Aout[i]) == 1, "out({})".format(i) m += xsum(x[a] for a in Ain[i]) == 1, "in({})".format(i) # continuous variable to prevent subtours: each # city will have a different "identifier" in the planned route y = {i: m.add_var(name="y({})".format(i), lb=0.0) for i in N} # subtour elimination for (i, j) in A: if i0 not in [i, j]: m.add_constr(y[i] - (n + 1) * x[(i, j)] >= y[j] - n) m.cuts_generator = SubTourCutGenerator() # tiny model, should be enough to find the optimal m.max_seconds = 10 m.max_nodes = 100 m.max_solutions = 1000 m.optimize() assert m.status == OptimizationStatus.OPTIMAL # "mip model status" assert abs(m.objective_value - 262) <= TOL # "mip model objective"
class InterfaceToSolver: """A wrapper for the mip model class, allows interaction with mip using pd.DataFrames.""" def __init__(self, solver_name='CBC'): self.variables = {} self.linear_mip_variables = {} self.solver_name = solver_name if solver_name == 'CBC': self.mip_model = Model("market", solver_name=CBC) self.linear_mip_model = Model("market", solver_name=CBC) elif solver_name == 'GUROBI': self.mip_model = Model("market", solver_name=GUROBI) self.linear_mip_model = Model("market", solver_name=GUROBI) else: raise ValueError("Solver '{}' not recognised.") self.mip_model.verbose = 0 self.mip_model.solver.set_mip_gap_abs(1e-10) self.mip_model.solver.set_mip_gap(1e-20) self.mip_model.lp_method = LP_Method.DUAL self.linear_mip_model.verbose = 0 self.linear_mip_model.solver.set_mip_gap_abs(1e-10) self.linear_mip_model.solver.set_mip_gap(1e-20) self.linear_mip_model.lp_method = LP_Method.DUAL def add_variables(self, decision_variables): """Add decision variables to the model. Examples -------- >>> decision_variables = pd.DataFrame({ ... 'variable_id': [0, 1], ... 'lower_bound': [0.0, 0.0], ... 'upper_bound': [6.0, 1.0], ... 'type': ['continuous', 'binary']}) >>> si = InterfaceToSolver() >>> si.add_variables(decision_variables) The underlying mip_model should now have 2 variables. >>> print(si.mip_model.num_cols) 2 The first one should have the following properties. >>> print(si.mip_model.var_by_name('0').var_type) C >>> print(si.mip_model.var_by_name('0').lb) 0.0 >>> print(si.mip_model.var_by_name('0').ub) 6.0 The second one should have the following properties. >>> print(si.mip_model.var_by_name('1').var_type) B >>> print(si.mip_model.var_by_name('1').lb) 0.0 >>> print(si.mip_model.var_by_name('1').ub) 1.0 """ # Create a mapping between the nempy level names for variable types and the mip representation. variable_types = {'continuous': CONTINUOUS, 'binary': BINARY} # Add each variable to the mip model. for variable_id, lower_bound, upper_bound, variable_type in zip( list(decision_variables['variable_id']), list(decision_variables['lower_bound']), list(decision_variables['upper_bound']), list(decision_variables['type'])): self.variables[variable_id] = self.mip_model.add_var( lb=lower_bound, ub=upper_bound, var_type=variable_types[variable_type], name=str(variable_id)) self.linear_mip_variables[ variable_id] = self.linear_mip_model.add_var( lb=lower_bound, ub=upper_bound, var_type=variable_types[variable_type], name=str(variable_id)) def add_sos_type_2(self, sos_variables, sos_id_columns, position_column): """Add groups of special ordered sets of type 2 two the mip model. Examples -------- >>> decision_variables = pd.DataFrame({ ... 'variable_id': [0, 1, 2, 3, 4, 5], ... 'lower_bound': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ... 'upper_bound': [5.0, 5.0, 5.0, 5.0, 5.0, 5.0], ... 'type': ['continuous', 'continuous', 'continuous', ... 'continuous', 'continuous', 'continuous']}) >>> sos_variables = pd.DataFrame({ ... 'variable_id': [0, 1, 2, 3, 4, 5], ... 'sos_id': ['A', 'A', 'A', 'B', 'B', 'B'], ... 'position': [0, 1, 2, 0, 1, 2]}) >>> si = InterfaceToSolver() >>> si.add_variables(decision_variables) >>> si.add_sos_type_2(sos_variables, 'sos_id', 'position') """ # Function that adds sets to mip model. def add_sos_vars(sos_group): self.mip_model.add_sos( list(zip(sos_group['vars'], sos_group[position_column])), 2) # For each variable_id get the variable object from the mip model sos_variables['vars'] = sos_variables['variable_id'].apply( lambda x: self.variables[x]) # Break up the sets based on their id and add them to the model separately. sos_variables.groupby(sos_id_columns).apply(add_sos_vars) # This is a hack to make sure mip knows there are binary constraints. self.mip_model.add_var(var_type=BINARY, obj=0.0) def add_sos_type_1(self, sos_variables): # Function that adds sets to mip model. def add_sos_vars(sos_group): self.mip_model.add_sos( list( zip(sos_group['vars'], [1.0 for i in range(len(sos_variables['vars']))])), 1) # For each variable_id get the variable object from the mip model sos_variables['vars'] = sos_variables['variable_id'].apply( lambda x: self.variables[x]) # Break up the sets based on their id and add them to the model separately. sos_variables.groupby('sos_id').apply(add_sos_vars) # This is a hack to make mip knows there are binary constraints. self.mip_model.add_var(var_type=BINARY, obj=0.0) def add_objective_function(self, objective_function): """Add the objective function to the mip model. Examples -------- >>> decision_variables = pd.DataFrame({ ... 'variable_id': [0, 1, 2, 3, 4, 5], ... 'lower_bound': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ... 'upper_bound': [5.0, 5.0, 5.0, 5.0, 5.0, 5.0], ... 'type': ['continuous', 'continuous', 'continuous', ... 'continuous', 'continuous', 'continuous']}) >>> objective_function = pd.DataFrame({ ... 'variable_id': [0, 1, 3, 4, 5], ... 'cost': [1.0, 2.0, -1.0, 5.0, 0.0]}) >>> si = InterfaceToSolver() >>> si.add_variables(decision_variables) >>> si.add_objective_function(objective_function) >>> print(si.mip_model.var_by_name('0').obj) 1.0 >>> print(si.mip_model.var_by_name('5').obj) 0.0 """ objective_function = objective_function.sort_values('variable_id') objective_function = objective_function.set_index('variable_id') obj = minimize( xsum(objective_function['cost'][i] * self.variables[i] for i in list(objective_function.index))) self.mip_model.objective = obj self.linear_mip_model.objective = obj def add_constraints(self, constraints_lhs, constraints_type_and_rhs): """Add constraints to the mip model. Examples -------- >>> decision_variables = pd.DataFrame({ ... 'variable_id': [0, 1, 2, 3, 4, 5], ... 'lower_bound': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ... 'upper_bound': [5.0, 5.0, 10.0, 10.0, 5.0, 5.0], ... 'type': ['continuous', 'continuous', 'continuous', ... 'continuous', 'continuous', 'continuous']}) >>> constraints_lhs = pd.DataFrame({ ... 'constraint_id': [1, 1, 2, 2], ... 'variable_id': [0, 1, 3, 4], ... 'coefficient': [1.0, 0.5, 1.0, 2.0]}) >>> constraints_type_and_rhs = pd.DataFrame({ ... 'constraint_id': [1, 2], ... 'type': ['<=', '='], ... 'rhs': [10.0, 20.0]}) >>> si = InterfaceToSolver() >>> si.add_variables(decision_variables) >>> si.add_constraints(constraints_lhs, constraints_type_and_rhs) >>> print(si.mip_model.constr_by_name('1')) 1: +1.0 0 +0.5 1 <= 10.0 >>> print(si.mip_model.constr_by_name('2')) 2: +1.0 3 +2.0 4 = 20.0 """ constraints_lhs = constraints_lhs.groupby( ['constraint_id', 'variable_id'], as_index=False).agg({'coefficient': 'sum'}) rows = constraints_lhs.groupby(['constraint_id'], as_index=False) # Make a dictionary so constraint rhs values can be accessed using the constraint id. rhs = dict( zip(constraints_type_and_rhs['constraint_id'], constraints_type_and_rhs['rhs'])) # Make a dictionary so constraint type can be accessed using the constraint id. enq_type = dict( zip(constraints_type_and_rhs['constraint_id'], constraints_type_and_rhs['type'])) var_ids = constraints_lhs['variable_id'].to_numpy() vars = np.asarray([ self.variables[k] if k in self.variables.keys() else None for k in range(0, max(var_ids) + 1) ]) coefficients = constraints_lhs['coefficient'].to_numpy() for row_id, row in rows.indices.items(): # Use the variable_ids to get mip variable objects present in the constraints lhs_variables = vars[var_ids[row]] # Use the positions of the non nan values to the lhs coefficients. lhs = coefficients[row] # Multiply and the variables by their coefficients and sum to create the lhs of the constraint. exp = lhs_variables * lhs exp = exp.tolist() exp = xsum(exp) # Add based on inequality type. if enq_type[row_id] == '<=': new_constraint = exp <= rhs[row_id] elif enq_type[row_id] == '>=': new_constraint = exp >= rhs[row_id] elif enq_type[row_id] == '=': new_constraint = exp == rhs[row_id] else: raise ValueError( "Constraint type not recognised should be one of '<=', '>=' or '='." ) self.mip_model.add_constr(new_constraint, name=str(row_id)) self.linear_mip_model.add_constr(new_constraint, name=str(row_id)) def optimize(self): """Optimize the mip model. If an optimal solution cannot be found and the investigate_infeasibility flag is set to True then remove constraints until a feasible solution is found. Examples -------- >>> decision_variables = pd.DataFrame({ ... 'variable_id': [0, 1, 2, 3, 4, 5], ... 'lower_bound': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ... 'upper_bound': [5.0, 5.0, 10.0, 10.0, 5.0, 5.0], ... 'type': ['continuous', 'continuous', 'continuous', ... 'continuous', 'continuous', 'continuous']}) >>> constraints_lhs = pd.DataFrame({ ... 'constraint_id': [1, 1, 2, 2], ... 'variable_id': [0, 1, 3, 4], ... 'coefficient': [1.0, 0.5, 1.0, 2.0]}) >>> constraints_type_and_rhs = pd.DataFrame({ ... 'constraint_id': [1, 2], ... 'type': ['<=', '='], ... 'rhs': [10.0, 20.0]}) >>> si = InterfaceToSolver() >>> si.add_variables(decision_variables) >>> si.add_constraints(constraints_lhs, constraints_type_and_rhs) >>> si.optimize() >>> decision_variables['value'] = si.get_optimal_values_of_decision_variables(decision_variables) >>> print(decision_variables) variable_id lower_bound upper_bound type value 0 0 0.0 5.0 continuous 0.0 1 1 0.0 5.0 continuous 0.0 2 2 0.0 10.0 continuous 0.0 3 3 0.0 10.0 continuous 10.0 4 4 0.0 5.0 continuous 5.0 5 5 0.0 5.0 continuous 0.0 """ status = self.mip_model.optimize() if status != OptimizationStatus.OPTIMAL: # Attempt find constraint causing infeasibility. print('Model infeasible attempting to find problem constraint.') con_index = find_problem_constraint(self.mip_model) print( 'Couldn\'t find an optimal solution, but removing con {} fixed INFEASIBLITY' .format(con_index)) raise ValueError('Linear program infeasible') def get_optimal_values_of_decision_variables(self, variable_definitions): """Get the optimal values for each decision variable. Examples -------- >>> decision_variables = pd.DataFrame({ ... 'variable_id': [0, 1, 2, 3, 4, 5], ... 'lower_bound': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ... 'upper_bound': [5.0, 5.0, 10.0, 10.0, 5.0, 5.0], ... 'type': ['continuous', 'continuous', 'continuous', ... 'continuous', 'continuous', 'continuous']}) >>> constraints_lhs = pd.DataFrame({ ... 'constraint_id': [1, 1, 2, 2], ... 'variable_id': [0, 1, 3, 4], ... 'coefficient': [1.0, 0.5, 1.0, 2.0]}) >>> constraints_type_and_rhs = pd.DataFrame({ ... 'constraint_id': [1, 2], ... 'type': ['<=', '='], ... 'rhs': [10.0, 20.0]}) >>> si = InterfaceToSolver() >>> si.add_variables(decision_variables) >>> si.add_constraints(constraints_lhs, constraints_type_and_rhs) >>> si.optimize() >>> decision_variables['value'] = si.get_optimal_values_of_decision_variables(decision_variables) >>> print(decision_variables) variable_id lower_bound upper_bound type value 0 0 0.0 5.0 continuous 0.0 1 1 0.0 5.0 continuous 0.0 2 2 0.0 10.0 continuous 0.0 3 3 0.0 10.0 continuous 10.0 4 4 0.0 5.0 continuous 5.0 5 5 0.0 5.0 continuous 0.0 """ values = variable_definitions['variable_id'].apply( lambda x: self.mip_model.var_by_name(str(x)).x, self.mip_model) return values def get_optimal_values_of_decision_variables_lin(self, variable_definitions): values = variable_definitions['variable_id'].apply( lambda x: self.linear_mip_model.var_by_name(str(x)).x, self.mip_model) return values def get_slack_in_constraints(self, constraints_type_and_rhs): """Get the slack values in each constraint. Examples -------- >>> decision_variables = pd.DataFrame({ ... 'variable_id': [0, 1, 2, 3, 4, 5], ... 'lower_bound': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ... 'upper_bound': [5.0, 5.0, 10.0, 10.0, 5.0, 5.0], ... 'type': ['continuous', 'continuous', 'continuous', ... 'continuous', 'continuous', 'continuous']}) >>> constraints_lhs = pd.DataFrame({ ... 'constraint_id': [1, 1, 2, 2], ... 'variable_id': [0, 1, 3, 4], ... 'coefficient': [1.0, 0.5, 1.0, 2.0]}) >>> constraints_type_and_rhs = pd.DataFrame({ ... 'constraint_id': [1, 2], ... 'type': ['<=', '='], ... 'rhs': [10.0, 20.0]}) >>> si = InterfaceToSolver() >>> si.add_variables(decision_variables) >>> si.add_constraints(constraints_lhs, constraints_type_and_rhs) >>> si.optimize() >>> constraints_type_and_rhs['slack'] = si.get_slack_in_constraints(constraints_type_and_rhs) >>> print(constraints_type_and_rhs) constraint_id type rhs slack 0 1 <= 10.0 10.0 1 2 = 20.0 0.0 """ slack = constraints_type_and_rhs['constraint_id'].apply( lambda x: self.mip_model.constr_by_name(str(x)).slack, self.mip_model) return slack def price_constraints(self, constraint_ids_to_price): """For each constraint_id find the marginal value of the constraint. This is done by incrementing the constraint by a value of 1.0 and re-optimizing the model, the marginal cost of the constraint is increase in the objective function value between model runs. Examples -------- >>> decision_variables = pd.DataFrame({ ... 'variable_id': [0, 1, 2, 3, 4, 5], ... 'lower_bound': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ... 'upper_bound': [5.0, 5.0, 10.0, 10.0, 5.0, 5.0], ... 'type': ['continuous', 'continuous', 'continuous', ... 'continuous', 'continuous', 'continuous']}) >>> objective_function = pd.DataFrame({ ... 'variable_id': [0, 1, 2, 3, 4, 5], ... 'cost': [1.0, 3.0, 10.0, 8.0, 9.0, 7.0]}) >>> constraints_lhs = pd.DataFrame({ ... 'constraint_id': [1, 1, 1, 1], ... 'variable_id': [0, 1, 3, 4], ... 'coefficient': [1.0, 1.0, 1.0, 1.0]}) >>> constraints_type_and_rhs = pd.DataFrame({ ... 'constraint_id': [1], ... 'type': ['='], ... 'rhs': [20.0]}) >>> si = InterfaceToSolver() >>> si.add_variables(decision_variables) >>> si.add_constraints(constraints_lhs, constraints_type_and_rhs) >>> si.add_objective_function(objective_function) >>> si.optimize() >>> si.linear_mip_model.optimize() <OptimizationStatus.OPTIMAL: 0> >>> prices = si.price_constraints([1]) >>> print(prices) {1: 8.0} >>> decision_variables['value'] = si.get_optimal_values_of_decision_variables(decision_variables) >>> print(decision_variables) variable_id lower_bound upper_bound type value 0 0 0.0 5.0 continuous 5.0 1 1 0.0 5.0 continuous 5.0 2 2 0.0 10.0 continuous 0.0 3 3 0.0 10.0 continuous 10.0 4 4 0.0 5.0 continuous 0.0 5 5 0.0 5.0 continuous 0.0 """ costs = {} for id in constraint_ids_to_price: costs[id] = self.linear_mip_model.constr_by_name(str(id)).pi return costs def update_rhs(self, constraint_id, violation_degree): constraint = self.linear_mip_model.constr_by_name(str(constraint_id)) constraint.rhs += violation_degree def update_variable_bounds(self, new_bounds): for variable_id, lb, ub in zip(new_bounds['variable_id'], new_bounds['lower_bound'], new_bounds['upper_bound']): self.mip_model.var_by_name(str(variable_id)).lb = lb self.mip_model.var_by_name(str(variable_id)).ub = ub def disable_variables(self, variables): for var_id in variables['variable_id']: var = self.linear_mip_model.var_by_name(str(var_id)) var.lb = 0.0 var.ub = 0.0
class AP: """ 指派问题求解。 """ ap_model = None # 模型 dec_vars = None # decision variable 决策变量 is_init = False # 是否初始化 opt_status = None # 求解状态 def __init__(self, profit_matrix: List[int]): """ 初始化 """ self.profit_matrix = profit_matrix def init_model(self): # 1. 选择求解器初始化 self.ap_model = Model(sense=MINIMIZE, solver_name=CBC) # 2. 定义决策变量 self.dec_vars = [[ self.ap_model.add_var(var_type=BINARY) for i in range(len(self.profit_matrix)) ] for j in range(len(self.profit_matrix))] # 3. 定义目标函数 self.ap_model.objective = minimize( xsum(self.dec_vars[i][j] * self.profit_matrix[i][j] for i in range(len(self.profit_matrix)) for j in range(len(self.profit_matrix)))) # 4. 定义约束条件 for i in range(len(self.profit_matrix)): # 每行只能有一个1 self.ap_model.add_constr( xsum(self.dec_vars[i][j] for j in range(len(self.profit_matrix))) == 1) for j in range(len(self.profit_matrix)): # 每列只能有一个1 self.ap_model.add_constr( xsum(self.dec_vars[i][j] for i in range(len(self.profit_matrix))) == 1) self.is_init = True def solve(self, max_seconds: int = 10): """ 设定约束时间开始求解 :param max_seconds: :return: """ if not self.is_init: self.init_model() self.opt_status = self.ap_model.optimize(max_seconds=max_seconds) return self.opt_status def get_optimum_val(self): """ 获取最优解值 :return: """ if self.opt_status and self.opt_status == OptimizationStatus.OPTIMAL: return self.ap_model.objective_value else: raise Exception('未求得解') def get_optimum_sol(self): """ 获取最优解变量 :return: """ if self.opt_status and self.opt_status == OptimizationStatus.OPTIMAL: return self.dec_vars else: raise Exception('未求得解')
def mip_solver(input_data): # Modify this code to run your optimization algorithm # parse the input lines = input_data.split('\n') firstLine = lines[0].split() item_count = int(firstLine[0]) capacity = int(firstLine[1]) items = [] for i in range(1, item_count+1): line = lines[i] parts = line.split() items.append(Item(i-1, int(parts[0]), int(parts[1]))) # a trivial algorithm for filling the knapsack # it takes items in-order until the knapsack is full value = 0 weight = 0 taken = [0]*len(items) # declare value vector val_vec = [item.value for item in items] # declare weight vector scaled by capacity wgt_vec = [item.weight/capacity for item in items] # start MIP solver m = Model(solver_name='GRB') print('-Model instatiated!',datetime.datetime.now()) # states search emphasis # - '0' (default) balanced approach # - '1' (feasibility) aggressively searches for feasible solutions # - '2' (optimality) explores search space to tighten dual gap m.emphasis = 2 # whenever the distance of the lower and upper bounds is less or # equal max_gap*100%, the search can be finished m.max_gap = 0.05 # specifies number of used threads # 0 uses solver default configuration, # -1 uses the number of available processing cores # ≥1 uses the specified number of threads. # An increased number of threads may improve the solution time but also increases # the memory consumption. Each thread needs to store a different model instance! m.threads = -1 # controls the generation of cutting planes # cutting planes usually improve the LP relaxation bound but also make the solution time of the LP relaxation larger # -1 means automatic # 0 disables completely # 1 (default) generates cutting planes in a moderate way # 2 generates cutting planes aggressively # 3 generates even more cutting planes m.cuts=-1 m.preprocess=1 m.pump_passes=10 m.sol_pool_size=1 # instantiates taken items variable vector taken = [m.add_var(name="it{}".format(i),var_type='B') for i in range(item_count)] print('-Variables declared!',datetime.datetime.now()) # instantiates objective function m.objective = maximize( xsum( val_vec[i]*taken[i] for i in range(item_count) ) ) print('-Objective declared!',datetime.datetime.now()) m.add_constr( xsum( wgt_vec[i]*taken[i] for i in range(item_count)) <=1 ) print('-Contraints processed!',datetime.datetime.now()) #Maximum time in seconds that the search can go on if a feasible solution #is available and it is not being improved mssi = 1000 #default = inf # specifies maximum number of nodes to be explored in the search tree (default = inf) mn = 40000 #default = 1073741824 # optimize model m within a processing time limit of 'ms' seconds ms = 3000 #default = inf print('-Optimizer start.',datetime.datetime.now()) # executes the optimization #status = m.optimize(max_seconds = ms,max_seconds_same_incumbent = mssi,max_nodes = mn) status = m.optimize(max_seconds = ms , max_seconds_same_incumbent = mssi) final_obj = m.objective_value print('Opt. Status:',status) print('MIP Sol. Obj.:',final_obj) print('Dual Bound:',m.objective_bound) print('Dual gap:',m.gap) sol = [round(it.x) for it in taken] value = int(final_obj) taken = sol # prepare the solution in the specified output format if status == OptimizationStatus.OPTIMAL: output_data = str(value) + ' ' + str(1) + '\n' output_data += ' '.join(map(str, taken)) elif status == OptimizationStatus.FEASIBLE: output_data = str(value) + ' ' + str(0) + '\n' output_data += ' '.join(map(str, taken)) if item_count == 10000: output_data=greedy(input_data) return output_data
def test_tsp(solver: str): """tsp related tests""" N = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] n = len(N) i0 = N[0] A = { ('a', 'd'): 56, ('d', 'a'): 67, ('a', 'b'): 49, ('b', 'a'): 50, ('d', 'b'): 39, ('b', 'd'): 37, ('c', 'f'): 35, ('f', 'c'): 35, ('g', 'b'): 35, ('b', 'g'): 25, ('a', 'c'): 80, ('c', 'a'): 99, ('e', 'f'): 20, ('f', 'e'): 20, ('g', 'e'): 38, ('e', 'g'): 49, ('g', 'f'): 37, ('f', 'g'): 32, ('b', 'e'): 21, ('e', 'b'): 30, ('a', 'g'): 47, ('g', 'a'): 68, ('d', 'c'): 37, ('c', 'd'): 52, ('d', 'e'): 15, ('e', 'd'): 20 } # input and output arcs per node Aout = {n: [a for a in A if a[0] == n] for n in N} Ain = {n: [a for a in A if a[1] == n] for n in N} m = Model(solver_name=solver) m.verbose = 1 x = { a: m.add_var(name='x({},{})'.format(a[0], a[1]), var_type=BINARY) for a in A } m.objective = xsum(c * x[a] for a, c in A.items()) for i in N: m += xsum(x[a] for a in Aout[i]) == 1, 'out({})'.format(i) m += xsum(x[a] for a in Ain[i]) == 1, 'in({})'.format(i) # continuous variable to prevent subtours: each # city will have a different "identifier" in the planned route y = {i: m.add_var(name='y({})'.format(i), lb=0.0) for i in N} # subtour elimination for (i, j) in A: if i0 not in [i, j]: m.add_constr(y[i] - (n + 1) * x[(i, j)] >= y[j] - n) m.relax() m.optimize() assert m.status == OptimizationStatus.OPTIMAL # "lp model status" assert abs(m.objective_value - 238.75) <= TOL # "lp model objective" # setting all variables to integer now for v in m.vars: v.var_type = INTEGER m.optimize() assert m.status == OptimizationStatus.OPTIMAL # "mip model status" assert abs(m.objective_value - 262) <= TOL # "mip model objective"
def milp(data, vocabulary, num_classes, vocab_size): """ Mixed-Integer Linear Programing :param data: matrix of one-hot representation of each document per class shape {num_classes: [None, vocab_size]} :param vocabulary: the super vector of the unique words :param num_classes: the number of classes :param vocab_size: the vocabulary size in terms number of words """ # Create the optimization model model = Model() print('Optimization model is done!') # IMLP Variable: (to optimize) shape [num_classes, vocab_size] V = [[model.add_var(var_type=BINARY) for _ in range(vocab_size)] for _ in range(num_classes)] print('Optimization variables are done!') # Optimization function # xsum(V[c][j] for c in range(num_classes) for j in range(vocab_size)) + model.objective = minimize( xsum(d[j] * V[c_prime][j] for c in range(num_classes) for d in data[c] for c_prime in range(num_classes) if c != c_prime for j in range(vocab_size))) print('Optimization objective function is done!') # Constraints # Equation (5) """ Each interpretation feature must belong to only one category (Tested) """ for j in range(vocab_size): model.add_constr(xsum(V[c][j] for c in range(num_classes)) <= 1) print('Optimization subjective equation (5) is done!') # Equation (6) """ Each document of class c must have at least one interpretation feature that belong to class c (Tested) """ for c in range(num_classes): for d in data[c]: model.add_constr( xsum(d[j] * V[c][j] for j in range(vocab_size)) >= 1) print('Optimization subjective equation (6) is done!') # run optimization status = model.optimize() print('Optimization is over!') # print results if status == OptimizationStatus.OPTIMAL: print('optimal solution cost {} found'.format(model.objective_value)) elif status == OptimizationStatus.FEASIBLE: print('sol.cost {} found, best possible: {}'.format( model.objective_value, model)) elif status == OptimizationStatus.NO_SOLUTION_FOUND: print('no feasible solution found, lower bound is: {}'.format( model.objective_bound)) milp_if = dict() if status == OptimizationStatus.OPTIMAL or status == OptimizationStatus.FEASIBLE: for i in range(num_classes): milp_if.setdefault(i, set()) for j in range(vocab_size): if V[i][j].x > 0: milp_if[i].add(vocabulary[j]) return milp_if
class LP_RCPSP(MilpSolver): def __init__( self, rcpsp_model: SingleModeRCPSPModel, lp_solver=LP_RCPSP_Solver.CBC, params_objective_function: ParamsObjectiveFunction = None, **kwargs ): self.rcpsp_model = rcpsp_model self.model: Model = None self.lp_solver = CBC if lp_solver == LP_RCPSP_Solver.GRB: self.lp_solver = GRB elif lp_solver == LP_RCPSP_Solver.CBC: self.lp_solver = CBC self.variable_decision = {} self.constraints_dict = {} self.constraints_dict["lns"] = [] ( self.aggreg_from_sol, self.aggreg_dict, self.params_objective_function, ) = build_aggreg_function_and_params_objective( problem=self.rcpsp_model, params_objective_function=params_objective_function, ) # self.description_variable_description = {} # self.description_constraint = {} def init_model(self, **args): greedy_start = args.get("greedy_start", True) start_solution = args.get("start_solution", None) verbose = args.get("verbose", False) if start_solution is None: if greedy_start: if verbose: print("Computing greedy solution") 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()) print(sorted_tasks) p = [ int(self.rcpsp_model.mode_details[key][1]["duration"]) for key in sorted_tasks ] # print('p:', p) # u = [[0, 0], [5, 1], [0, 4], [1, 4], [1, 3], [3, 2], [3, 1], [2, 4], # [4, 0], [5, 2], [2, 5], [0, 0]] u = [] for task in sorted_tasks: tmp = [] for r in self.rcpsp_model.resources.keys(): tmp.append(self.rcpsp_model.mode_details[task][1][r]) u.append(tmp) # print('u: ', u) # c = [6, 8] c = [x for x in self.rcpsp_model.resources.values()] # 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 - 1, suc - 1]) # print('S: ', S) (R, self.J, self.T) = (range(len(c)), range(len(p)), range(sum(p))) # we have a better self.T to limit the number of variables : self.T = range(int(makespan + 1)) # model = Model() self.model = Model(sense=MINIMIZE, solver_name=self.lp_solver) self.x: List[List[Var]] = [ [ self.model.add_var(name="x({},{})".format(j, t), var_type=BINARY) for t in self.T ] for j in self.J ] self.model.objective = xsum(self.x[len(self.J) - 1][t] * t for t in self.T) for j in self.J: self.model += xsum(self.x[j][t] for t in self.T) == 1 for (r, t) in product(R, self.T): self.model += ( xsum( u[j][r] * self.x[j][t2] for j in self.J for t2 in range(max(0, t - p[j] + 1), t + 1) ) <= c[r] ) for (j, s) in S: self.model += ( xsum(t * self.x[s][t] - t * self.x[j][t] for t in self.T) >= p[j] ) start = [] for j in self.J: for t in self.T: if self.start_solution.rcpsp_schedule[j + 1]["start_time"] == t: start += [(self.x[j][t], 1)] else: start += [(self.x[j][t], 0)] self.model.start = start p_s: Union[PartialSolution, None] = args.get("partial_solution", None) self.constraints_partial_solutions = [] if p_s is not None: constraints = [] if p_s.start_times is not None: for task in p_s.start_times: constraints += [ self.model.add_constr( xsum( [ j * self.x[task - 1][j] for j in range(len(self.x[task - 1])) ] ) == p_s.start_times[task] ) ] constraints += [ self.model.add_constr( self.x[task - 1][p_s.start_times[task]] == 1 ) ] 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.add_constr( xsum( [ t * self.x[t1 - 1][t] - t * self.x[t2 - 1][t] for t in self.T ] ) <= 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.add_constr( xsum( [ t * self.x[t1 - 1][t] - t * self.x[t2 - 1][t] for t in self.T ] ) <= 0 ) ] self.starts = {} for j in range(len(self.x)): self.starts[j] = self.model.add_var( name="start_" + str(j), lb=0, ub=makespan ) self.model.add_constr( xsum(t * self.x[j][t] for t in self.T) == self.starts[j] ) if p_s.start_at_end is not None: for i, j in p_s.start_at_end: constraints += [ self.model.add_constr( self.starts[j - 1] == self.starts[i - 1] + p[i - 1] ) ] if p_s.start_together is not None: for i, j in p_s.start_together: constraints += [ self.model.add_constr(self.starts[j - 1] == self.starts[i - 1]) ] if p_s.start_after_nunit is not None: for t1, t2, delta in p_s.start_after_nunit: constraints += [ self.model.add_constr( self.starts[t2 - 1] >= self.starts[t1 - 1] + 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.add_constr( self.starts[t2 - 1] >= self.starts[t1 - 1] + delta + p[t1 - 1] ) ] self.constraints_partial_solutions = constraints def retrieve_solutions(self, parameters_milp: ParametersMilp) -> ResultStorage: retrieve_all_solution = parameters_milp.retrieve_all_solution nb_solutions_max = parameters_milp.n_solutions_max nb_solution = min(nb_solutions_max, self.model.num_solutions) if not retrieve_all_solution: nb_solution = 1 list_solution_fits = [] print(nb_solution, " solutions found") for s in range(nb_solution): rcpsp_schedule = {} objective = self.model.objective_values[s] for (j, t) in product(self.J, self.T): value = self.x[j][t].xi(s) if value >= 0.5: rcpsp_schedule[j + 1] = { "start_time": t, "end_time": t + self.rcpsp_model.mode_details[j + 1][1]["duration"], } print("Size schedule : ", len(rcpsp_schedule.keys())) try: solution = RCPSPSolution( problem=self.rcpsp_model, rcpsp_schedule=rcpsp_schedule, rcpsp_schedule_feasible=True, ) fit = self.aggreg_from_sol(solution) list_solution_fits += [(solution, fit)] except: print("Problem =", rcpsp_schedule, len(rcpsp_schedule)) pass return ResultStorage( list_solution_fits=list_solution_fits, best_solution=min(list_solution_fits, key=lambda x: x[1])[0], mode_optim=self.params_objective_function.sense_function, ) def solve( self, parameters_milp: ParametersMilp = ParametersMilp.default(), **kwargs ) -> ResultStorage: if self.model is None: self.init_model() limit_time_s = parameters_milp.TimeLimit self.model.sol_pool_size = parameters_milp.PoolSolutions self.model.max_mip_gap_abs = parameters_milp.MIPGapAbs self.model.max_mip_gap = parameters_milp.MIPGap self.model.optimize( max_seconds=limit_time_s, max_solutions=parameters_milp.n_solutions_max ) return self.retrieve_solutions(parameters_milp)
class LP_MRCPSP_GANTT(MilpSolver): def __init__(self, rcpsp_model: RCPSPModelCalendar, rcpsp_solution: RCPSPSolution, lp_solver=MilpSolverName.CBC, **kwargs): self.rcpsp_model = rcpsp_model self.lp_solver = lp_solver self.rcpsp_solution = rcpsp_solution self.jobs = sorted(list(self.rcpsp_model.mode_details.keys())) self.modes_dict = { i + 2: self.rcpsp_solution.rcpsp_modes[i] for i in range(len(self.rcpsp_solution.rcpsp_modes)) } self.modes_dict[1] = 1 self.modes_dict[self.jobs[-1]] = 1 self.rcpsp_schedule = self.rcpsp_solution.rcpsp_schedule # self.set_start_times = set(self.rcpsp_schedule.values()) self.start_times_dict = {} for task in self.rcpsp_schedule: t = self.rcpsp_schedule[task]["start_time"] if t not in self.start_times_dict: self.start_times_dict[t] = set() self.start_times_dict[t].add((task, t)) self.graph_intersection_time = nx.Graph() for t in self.jobs: self.graph_intersection_time.add_node(t) for t in self.jobs: intersected_jobs = [ task for task in self.rcpsp_schedule if intersect( [ self.rcpsp_schedule[task]["start_time"], self.rcpsp_schedule[task]["end_time"], ], [ self.rcpsp_schedule[t]["start_time"], self.rcpsp_schedule[t]["end_time"], ], ) is not None and t != task ] for tt in intersected_jobs: self.graph_intersection_time.add_edge(t, tt) cliques = [c for c in nx.find_cliques(self.graph_intersection_time)] self.cliques = cliques def init_model(self, **args): self.model = Model(sense=MINIMIZE, solver_name=map_solver[self.lp_solver]) 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.add_var( name=str(key_variable), var_type=BINARY, obj=random.random(), ) 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.add_constr( xsum([ 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.add_constr( xsum([ self.ressource_id_usage[key[0]][key[1]][key[2]] for key in keys_variable ]) <= 1) def retrieve_solutions(self, parameters_milp: ParametersMilp) -> ResultStorage: retrieve_all_solution = parameters_milp.retrieve_all_solution nb_solutions_max = parameters_milp.n_solutions_max nb_solution = min(nb_solutions_max, self.model.num_solutions) if not retrieve_all_solution: nb_solution = 1 list_solution_fits = [] print(nb_solution, " solutions found") for s in range(nb_solution): rcpsp_schedule = {} modes = {} objective = self.model.objective_values[s] resource_id_usage = { k: { individual: { task: self.ressource_id_usage[k][individual][task].xi(s) for task in self.ressource_id_usage[k][individual] } for individual in self.ressource_id_usage[k] } for k in self.ressource_id_usage } print(resource_id_usage) def solve(self, parameters_milp: ParametersMilp = ParametersMilp.default(), **kwargs) -> ResultStorage: if self.model is None: self.init_model(greedy_start=False, **kwargs) limit_time_s = parameters_milp.TimeLimit self.model.sol_pool_size = parameters_milp.PoolSolutions self.model.max_mip_gap_abs = parameters_milp.MIPGapAbs self.model.max_mip_gap = parameters_milp.MIPGap self.model.optimize(max_seconds=limit_time_s, max_solutions=parameters_milp.n_solutions_max) return self.retrieve_solutions(parameters_milp)
def tsp_mip_solver(input_data): # parse the input lines = input_data.split('\n') nodeCount = int(lines[0]) points = [] for i in range(1, nodeCount + 1): line = lines[i] parts = line.split() points.append(Point(float(parts[0]), float(parts[1]))) print('Points parsed!') # calculate distance matrix d_m = [[length(q, p) for q in points] for p in points] print('Distance matrix ready!') # declare MIP model m = Model(solver_name='GRB') print('-Model instatiated!', datetime.datetime.now()) # states search emphasis # - '0' (default) balanced approach # - '1' (feasibility) aggressively searches for feasible solutions # - '2' (optimality) explores search space to tighten dual gap m.emphasis = 0 # whenever the distance of the lower and upper bounds is less or # equal max_gap*100%, the search can be finished m.max_gap = 0.05 # specifies number of used threads # 0 uses solver default configuration, # -1 uses the number of available processing cores # ≥1 uses the specified number of threads. # An increased number of threads may improve the solution time but also increases # the memory consumption. Each thread needs to store a different model instance! m.threads = 0 # controls the generation of cutting planes # cutting planes usually improve the LP relaxation bound but also make the solution time of the LP relaxation larger # -1 means automatic # 0 disables completely # 1 (default) generates cutting planes in a moderate way # 2 generates cutting planes aggressively # 3 generates even more cutting planes m.cuts = -1 m.preprocess = 1 m.pump_passes = 20 m.sol_pool_size = 1 nodes = set(range(nodeCount)) # instantiate "entering and leaving" variables x = [[m.add_var(name="x{}_{}".format(p, q), var_type='B') for q in nodes] for p in nodes] # instantiate subtour elimination variables y = [m.add_var(name="y{}".format(i)) for i in nodes] print('-Variables instantiated', datetime.datetime.now()) # declare objective function m.objective = minimize( xsum(d_m[i][j] * x[i][j] for i in nodes for j in nodes)) print('-Objective declared!', datetime.datetime.now()) # declare constraints # leave each city only once for i in tqdm(nodes): m.add_constr(xsum(x[i][j] for j in nodes - {i}) == 1) # enter each city only once for i in tqdm(nodes): m.add_constr(xsum(x[j][i] for j in nodes - {i}) == 1) # subtour elimination constraints for (i, j) in tqdm(product(nodes - {0}, nodes - {0})): if i != j: m.add_constr(y[i] - (nodeCount + 1) * x[i][j] >= y[j] - nodeCount) print('-Constraints declared!', datetime.datetime.now()) #Maximum time in seconds that the search can go on if a feasible solution #is available and it is not being improved mssi = 1000 #default = inf # specifies maximum number of nodes to be explored in the search tree (default = inf) mn = 1000000 #default = 1073741824 # optimize model m within a processing time limit of 'ms' seconds ms = 3000 #default = inf # executes the optimization print('-Optimizer start.', datetime.datetime.now()) #status = m.optimize(max_seconds = ms,max_seconds_same_incumbent = mssi,max_nodes = mn) status = m.optimize(max_seconds=ms, max_seconds_same_incumbent=mssi) print('Opt. Status:', status) print('MIP Sol. Obj.:', m.objective_value) print('Dual Bound:', m.objective_bound) print('Dual gap:', m.gap) sol = [0] c_node = 0 for j in range(nodeCount - 1): for i in range(nodeCount): if round(m.var_by_name("x{}_{}".format(c_node, i)).x) != 0: sol.append(i) c_node = i break obj = m.objective_value # prepare the solution in the specified output format if status == OptimizationStatus.OPTIMAL: output_data = '%.2f' % obj + ' ' + str(1) + '\n' output_data += ' '.join(map(str, sol)) elif status == OptimizationStatus.FEASIBLE: output_data = '%.2f' % obj + ' ' + str(0) + '\n' output_data += ' '.join(map(str, sol)) return output_data
def test_column_generation(solver: str): L = 250 # bar length m = 4 # number of requests w = [187, 119, 74, 90] # size of each item b = [1, 2, 2, 1] # demand for each item # creating master model master = Model(solver_name=solver) # creating an initial set of patterns which cut one item per bar # to provide the restricted master problem with a feasible solution lambdas = [ master.add_var(obj=1, name="lambda_%d" % (j + 1)) for j in range(m) ] # creating constraints constraints = [] for i in range(m): constraints.append( master.add_constr(lambdas[i] >= b[i], name="i_%d" % (i + 1))) # creating the pricing problem pricing = Model(solver_name=solver) # creating pricing variables a = [ pricing.add_var(obj=0, var_type=INTEGER, name="a_%d" % (i + 1)) for i in range(m) ] # creating pricing constraint pricing += xsum(w[i] * a[i] for i in range(m)) <= L, "bar_length" new_vars = True while new_vars: ########## # STEP 1: solving restricted master problem ########## master.optimize() ########## # STEP 2: updating pricing objective with dual values from master ########## pricing += 1 - xsum(constraints[i].pi * a[i] for i in range(m)) # solving pricing problem pricing.optimize() ########## # STEP 3: adding the new columns (if any is obtained with negative reduced cost) ########## # checking if columns with negative reduced cost were produced and # adding them into the restricted master problem if pricing.objective_value < -TOL: pattern = [a[i].x for i in range(m)] column = Column(constraints, pattern) lambdas.append( master.add_var(obj=1, column=column, name="lambda_%d" % (len(lambdas) + 1))) # if no column with negative reduced cost was produced, then linear # relaxation of the restricted master problem is solved else: new_vars = False # printing the solution assert len(lambdas) == 8 assert round(master.objective_value) == 3
class LP_Solver_MRSCPSP(MilpSolver): def __init__(self, rcpsp_model: MS_RCPSPModel, lp_solver: MilpSolverName = MilpSolverName.CBC, params_objective_function: ParamsObjectiveFunction = None, **kwargs): self.rcpsp_model = rcpsp_model self.model: Model = None self.lp_solver = lp_solver self.variable_decision = {} self.constraints_dict = {} self.constraints_dict["lns"] = [] self.aggreg_from_sol, self.aggreg_dict, self.params_objective_function = \ build_aggreg_function_and_params_objective(problem=self.rcpsp_model, params_objective_function= params_objective_function) # # def init_model(self, **args): # self.model = Model(name="mrcpsp", # sense=MINIMIZE, # solver_name=map_solver[self.lp_solver]) # sorted_tasks = sorted(self.rcpsp_model.mode_details.keys()) # max_duration = sum([int(max([self.rcpsp_model.mode_details[key][mode]['duration'] # for mode in self.rcpsp_model.mode_details[key]])) # for key in sorted_tasks])+10 # # # c = [6, 8] # renewable = {r: self.rcpsp_model.resources_availability[r] # for r in self.rcpsp_model.resources_availability # if r not in self.rcpsp_model.non_renewable_resources} # non_renewable = {r: self.rcpsp_model.resources_availability[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]] # list_edges = [] # print('successors: ', self.rcpsp_model.successors) # for task in sorted_tasks: # for suc in self.rcpsp_model.successors[task]: # list_edges.append([task, suc]) # self.x: Dict[Tuple, Var] = {} # self.employee_usage: Dict[Tuple, Var] = {} # self.task_mode: Dict[Tuple, Var] = {} # self.task_mode_without_t: Dict[Tuple, Var] = {} # last_task = max(self.rcpsp_model.mode_details.keys()) # all_vars = set() # all_vars_employee = set() # variable_per_task = {} # variable_per_task_mode = {} # variable_per_employee = {} # variable_per_employee_time = {} # variable_employee_usage = {} # tasks_only_ressource = [task for task in self.rcpsp_model.mode_details # if all([s not in self.rcpsp_model.skills_set # or (s in self.rcpsp_model.skills_set # and self.rcpsp_model.mode_details[task][mode][s] == 0) # for mode in self.rcpsp_model.mode_details[task] # for s in self.rcpsp_model.mode_details[task][mode]])] # for employee in self.rcpsp_model.employees: # skills_employee = [skill # for skill in self.rcpsp_model.employees[employee].dict_skill.keys() # if self.rcpsp_model.employees[employee].dict_skill[skill].skill_value > 0] # for task in sorted_tasks: # if task in tasks_only_ressource: # continue # for mode in self.rcpsp_model.mode_details[task]: # required_skills = [s # for s in self.rcpsp_model.mode_details[task][mode] # if s in self.rcpsp_model.skills_set # and self.rcpsp_model.mode_details[task][mode][s] > 0 # and s in skills_employee] # if len(required_skills) == 0: # # this employee will be useless anyway, pass # continue # for t in range(max_duration+1): # if (task, mode, t) not in self.task_mode: # if task not in variable_per_task_mode: # variable_per_task_mode[task] = [] # self.task_mode[(task, mode, t)] = self.model.add_var(name="mode_{},{},{}".format(task, # mode, t), # var_type=BINARY) # variable_per_task_mode[task].append((task, mode, t)) # self.x[(task, # employee, # mode, # t)] = self.model.add_var(name="x({},{},{},{})".format(task, employee, mode, t), # var_type=BINARY) # if task not in variable_per_task: # variable_per_task[task] = [] # if employee not in variable_per_employee: # variable_per_employee[employee] = [] # if employee not in variable_per_employee_time: # variable_per_employee_time[employee] = {} # if t not in variable_per_employee_time[employee]: # variable_per_employee_time[employee][t] = [] # variable_per_task[task] += [(task, employee, # mode, t)] # all_vars.add((task, employee, # mode, t)) # variable_per_employee[employee] += [(task, employee, # mode, t)] # variable_per_employee_time[employee][t] += [(task, # employee, # mode, # t)] # for s in required_skills: # if employee not in variable_employee_usage: # variable_employee_usage[employee] = [] # variable_employee_usage[employee] += [(task, employee, s)] # all_vars_employee.add((task, employee, s)) # self.employee_usage[(task, employee, s)] = \ # self.model.add_var(name="usage({},{},{})".format(task, employee, s), # var_type=BINARY) # # employee = "fake-employee" # for task in tasks_only_ressource: # for mode in self.rcpsp_model.mode_details[task]: # for t in range(max_duration + 1): # if (task, mode, t) not in self.task_mode: # if task not in variable_per_task_mode: # variable_per_task_mode[task] = [] # self.task_mode[(task, mode, t)] = self.model.add_var(name="mode_{},{},{}".format(task, mode, t), # var_type=BINARY) # variable_per_task_mode[task].append((task, mode, t)) # self.x[(task, employee, mode, t)] = self.model.add_var(name="x({},{},{},{})" # .format(task, employee, mode, t), # var_type=BINARY) # if task not in variable_per_task: # variable_per_task[task] = [] # if employee not in variable_per_employee: # variable_per_employee[employee] = [] # if employee not in variable_per_employee_time: # variable_per_employee_time[employee] = {} # if t not in variable_per_employee_time[employee]: # variable_per_employee_time[employee][t] = [] # variable_per_task[task] += [(task, employee, # mode, t)] # variable_per_employee[employee] += [(task, employee, # mode, t)] # all_vars.add((task, employee, # mode, t)) # variable_per_employee_time[employee][t] += [(task, # employee, # mode, # t)] # for task, mode, t in self.task_mode: # # all variables in self.x, with same task, mode, time # var_of_interest = [v for v in all_vars if v[0] == task and v[2] == mode and v[3] == t] # for v in var_of_interest: # self.model.add_constr(self.task_mode[(task, mode, t)] >= self.x[v]) # # tm = set([(x[0], x[1]) for x in self.task_mode]) # for t, m in tm: # self.task_mode_without_t[(t, m)] = self.model.add_var(name="mode_{},{}".format(t, m), # var_type=BINARY) # for x in self.task_mode: # self.model.add_constr(self.task_mode_without_t[(x[0], x[1])] >= self.task_mode[x]) # for t in sorted_tasks: # self.model.add_constr(xsum(self.task_mode_without_t[x] for x in self.task_mode_without_t # if x[0] == t) == 1) # # # at least one mode !!! # for task in sorted_tasks: # vars_task_mode = [v for v in self.task_mode if v[0] == task] # self.model.add_constr(xsum(self.task_mode[v] for v in vars_task_mode) >= 1) # # self.durations = {j: self.model.add_var(name="duration_" + str(j), # var_type=INTEGER) # for j in variable_per_task} # # for task in variable_per_task_mode.keys(): # self.model.add_constr(xsum(self.task_mode_without_t[key] # * self.rcpsp_model.mode_details[key[0]][key[1]]["duration"] # for key in self.task_mode_without_t if key[0] == task) # == self.durations[task]) # for task in variable_per_task_mode.keys(): # self.model.add_constr(xsum(self.task_mode[key] for key in variable_per_task_mode[task]) # == self.durations[task]+1) # self.start_time_bin: Dict[Tuple, Var] = {} # self.end_time_bin: Dict[Tuple, Var] = {} # # self.start_time: Dict[Tuple, Var] = {} # self.end_time: Dict[Tuple, Var] = {} # # for task in variable_per_task: # self.start_time[task] = self.model.add_var(name="start_{}".format(task), # var_type=INTEGER, # lb=0, # ub=max_duration) # self.end_time[task] = self.model.add_var(name="end_{}".format(task), # var_type=INTEGER, # lb=0, # ub=max_duration+10) # self.model.add_constr(self.start_time[task]+self.durations[task]-self.end_time[task] == 0) # for variable in variable_per_task_mode[task]: # time = variable[-1] # # Setting starting date. # self.model.add_constr(self.start_time[task] <= self.task_mode[variable]*time # + (1-self.task_mode[variable])*10000) # # # constraints on the employees activity : # for employee in variable_per_employee: # if employee == "fake-employee": # # no constraint on this. # continue # for time in variable_per_employee_time[employee]: # # the employee do only one stuff at a time. # self.model.add_constr(xsum(self.x[variable] # for variable in variable_per_employee_time[employee][time]) <= 1) # self.model.add_constr(xsum(self.x[variable] # for variable in variable_per_employee_time[employee][time]) # <= 1 if self.rcpsp_model.employees[employee].calendar_employee[time] else 0) # # (makes redundant the precedent one) # # for task in variable_per_task: # for employee in variable_per_employee: # if employee == "fake-employee": # continue # variable_task_employee = [v for v in variable_per_employee[employee] # if v[0] == task] # if len(variable_task_employee) > 0: # variable_skills = [v for v in variable_employee_usage[employee] if v[0] == task] # skills_dict = {} # for v in variable_skills: # if v[2] not in skills_dict: # skills_dict[v[2]] = [] # skills_dict[v[2]] += [v] # # for skill in skills_dict: # # self.model.add_constr(xsum([self.x[v] for v in variable_task_employee]) # # <= self.durations[task]) # Need to check that :/ # for v in variable_task_employee: # self.model.add_constr(xsum([self.employee_usage[v] # for v in variable_skills]) >= self.x[v]) # self.model.add_constr(xsum(self.x[v] for v in variable_task_employee) # >= xsum([self.employee_usage[v] # for v in variable_skills])) # # constraints on ressource usage : # from itertools import product # # # for (r, t) in product(renewable, range(max_duration+1)): # # self.model += (xsum(int(self.rcpsp_model.mode_details[key[0]][key[1]][r]) * self.task_mode[key] # # for key in self.task_mode) # # <= renewable[r][t]) # # # # for r in non_renewable: # # self.model.add_constr(xsum(int(self.rcpsp_model.mode_details[key[0]][key[1]][r]) * self.task_mode[key] # # for key in self.task_mode) <= non_renewable[r][0]) # # # constraint on skill: # done = set() # for (task, mode, time) in self.task_mode: # if (task, mode) not in done: # skills = [s # for s in self.rcpsp_model.mode_details[task][mode] # if s in self.rcpsp_model.skills_set # and self.rcpsp_model.mode_details[task][mode][s]>0] # print("Skilles : ", skills) # # if len(skills) > 0: # for s in skills: # print("Needs : ", [self.rcpsp_model.mode_details[task][mode][s] # for tmt in self.task_mode # if tmt[0] == task and tmt[1] == mode]) # variable = [v for v in all_vars_employee if v[0] == task and v[2] == s] # self.model.add_constr(xsum(self.employee_usage[v] # * self.rcpsp_model.employees[v[1]].dict_skill[v[2]].skill_value # for v in variable) >= # xsum(self.task_mode[tmt]*self.rcpsp_model.mode_details[task][mode][s] # for tmt in self.task_mode # if tmt[0] == task and tmt[1] == mode)) # else: # continue # done.add((task, mode)) # # Precedence = # for (j, s) in list_edges: # self.model.add_constr(self.start_time[s]-self.end_time[j] # >= self.durations[j]) # self.model.objective = self.start_time[max(self.start_time)] def init_model(self, **args): self.model = Model(name="mrcpsp", sense=MINIMIZE, solver_name=map_solver[self.lp_solver]) sorted_tasks = sorted(self.rcpsp_model.mode_details.keys()) max_duration = sum([ int( max([ self.rcpsp_model.mode_details[key][mode]['duration'] for mode in self.rcpsp_model.mode_details[key] ])) for key in sorted_tasks ]) + 10 # c = [6, 8] renewable = { r: self.rcpsp_model.resources_availability[r] for r in self.rcpsp_model.resources_availability if r not in self.rcpsp_model.non_renewable_resources } non_renewable = { r: self.rcpsp_model.resources_availability[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]] list_edges = [] print('successors: ', self.rcpsp_model.successors) for task in sorted_tasks: for suc in self.rcpsp_model.successors[task]: list_edges.append([task, suc]) times = range(max_duration + 1) self.modes = { task: { mode: self.model.add_var(name="mode_{},{}".format(task, mode), var_type=BINARY) for mode in self.rcpsp_model.mode_details[task] } for task in self.rcpsp_model.mode_details } self.start_times = { task: { mode: { t: self.model.add_var(name="start_{},{},{}".format( task, mode, t), var_type=BINARY) for t in times } for mode in self.rcpsp_model.mode_details[task] } for task in self.rcpsp_model.mode_details } # you have to choose one starting date : for task in self.start_times: self.model.add_constr( xsum(self.start_times[task][mode][t] for mode in self.start_times[task] for t in self.start_times[task][mode]) == 1) for mode in self.modes[task]: self.model.add_constr(self.modes[task][mode] == xsum( self.start_times[task][mode][t] for t in self.start_times[task][mode])) self.durations = { task: self.model.add_var(name="duration_" + str(task), var_type=INTEGER) for task in self.modes } self.start_times_task = { task: self.model.add_var(name="start_time_{}".format(task), var_type=INTEGER) for task in self.start_times } self.end_times_task = { task: self.model.add_var(name="end_time_{}".format(task), var_type=INTEGER) for task in self.start_times } for task in self.start_times: self.model.add_constr( xsum(self.start_times[task][mode][t] * t for mode in self.start_times[task] for t in self.start_times[task][mode]) == self.start_times_task[task]) self.model.add_constr(self.end_times_task[task] - self.start_times_task[task] - self.durations[task] == 0) for task in self.durations: self.model.add_constr( xsum(self.rcpsp_model.mode_details[task][mode]["duration"] * self.modes[task][mode] for mode in self.modes[task]) == self.durations[task]) self.employee_usage = tree() task_in_employee_usage = set() for employee in self.rcpsp_model.employees: skills_employee = [ skill for skill in self.rcpsp_model.employees[employee].dict_skill.keys() if self.rcpsp_model.employees[employee].dict_skill[skill]. skill_value > 0 ] for task in sorted_tasks: for mode in self.rcpsp_model.mode_details[task]: required_skills = [ s for s in self.rcpsp_model.mode_details[task][mode] if s in self.rcpsp_model.skills_set and self.rcpsp_model.mode_details[task][mode][s] > 0 and s in skills_employee ] if len(required_skills) == 0: # this employee will be useless anyway, pass continue for s in required_skills: for t in range(max_duration + 1): self.employee_usage[(employee, task, mode, t, s)] = \ self.model.add_var(name="employee_{}{}{}{}{}".format(employee, task, mode, t, s), var_type=BINARY) task_in_employee_usage.add(task) self.model.add_constr(self.employee_usage[ (employee, task, mode, t, s)] - self.modes[task][mode] <= 0) self.model.add_constr( self.employee_usage[ (employee, task, mode, t, s)] - self.start_times[task][mode][t] <= 0) if any(not self.rcpsp_model.employees[employee]. calendar_employee[tt] for tt in range( t, t + self.rcpsp_model.mode_details[task] [mode]["duration"])): # print((employee, task, mode, t, s), "will be 0") # print(self.rcpsp_model.employees[employee].calendar_employee) self.model.add_constr( self.employee_usage[(employee, task, mode, t, s)] == 0) employees = set([x[0] for x in self.employee_usage]) # for emp in employees: # all = [x for x in self.employee_usage if x[0] == emp] # for x1 in all: # for x2 in all: # if x1[0] == x2[0]: # self.model.add_constr(self.employee_usage[x1]+self.employee_usage[x2] <= 1) # if x1[0] != x2[0]: # if x1[3]+self.rcpsp_model.mode_details[x1[0]][x1[2]]["duration"]>=x2[3]: # self.model.add_constr(self.employee_usage[x1] + self.employee_usage[x2] <= 1) # print("Employee ", emp) from itertools import product # can't work on overlapping tasks. for emp, t in product(employees, times): self.model.add_constr( xsum(self.employee_usage[x] for x in self.employee_usage if x[0] == emp and x[3] <= t < x[3] + int(self.rcpsp_model.mode_details[x[1]][x[2]]["duration"]) ) <= 1) # ressource usage limit for (r, t) in product(renewable, times): self.model.add_constr( xsum( int(self.rcpsp_model.mode_details[task][mode][r]) * self.start_times[task][mode][time] for task in self.start_times for mode in self.start_times[task] for time in self.start_times[task][mode] if time <= t < time + int(self.rcpsp_model.mode_details[task][mode]["duration"])) <= renewable[r][t]) # for non renewable ones. for r in non_renewable: self.model.add_constr( xsum( int(self.rcpsp_model.mode_details[task][mode][r]) * self.start_times[task][mode][time] for task in self.start_times for mode in self.start_times[task] for time in self.start_times[task][mode]) <= non_renewable[r][0]) for task in self.start_times_task: required_skills = [ (s, mode, self.rcpsp_model.mode_details[task][mode][s]) for mode in self.rcpsp_model.mode_details[task] for s in self.rcpsp_model.mode_details[task][mode] if s in self.rcpsp_model.skills_set and self.rcpsp_model.mode_details[task][mode][s] > 0 ] skills = set([s[0] for s in required_skills]) for s in skills: employee_usage_keys = [ v for v in self.employee_usage if v[1] == task and v[4] == s ] self.model.add_constr( xsum(self.employee_usage[x] * self.rcpsp_model.employees[ x[0]].dict_skill[s].skill_value for x in employee_usage_keys) >= xsum(self.modes[task][mode] * self.rcpsp_model.mode_details[task][mode].get(s, 0) for mode in self.modes[task])) for (j, s) in list_edges: self.model.add_constr( self.start_times_task[s] - self.end_times_task[j] >= 0) self.model.objective = self.start_times_task[max( self.start_times_task)] def retrieve_solutions(self, parameters_milp: ParametersMilp) -> ResultStorage: retrieve_all_solution = parameters_milp.retrieve_all_solution nb_solutions_max = parameters_milp.n_solutions_max nb_solution = min(nb_solutions_max, self.model.num_solutions) if not retrieve_all_solution: nb_solution = 1 list_solution_fits = [] print(nb_solution, " solutions found") for s in range(nb_solution): rcpsp_schedule = {} modes = {} objective = self.model.objective_values[s] results = {} employee_usage = {} employee_usage_solution = {} for task in self.start_times: for mode in self.start_times[task]: for t in self.start_times[task][mode]: value = self.start_times[task][mode][t].xi(s) results[(task, mode, t)] = value if value >= 0.5: rcpsp_schedule[task] = { 'start_time': int(t), 'end_time': int(t + self.rcpsp_model.mode_details[task] [mode]['duration']) } modes[task] = mode for t in self.employee_usage: employee_usage[t] = self.employee_usage[t].xi(s) if employee_usage[t] >= 0.5: if t[1] not in employee_usage_solution: employee_usage_solution[t[1]] = {} if t[0] not in employee_usage_solution[t[1]]: employee_usage_solution[t[1]][t[0]] = set() employee_usage_solution[t[1]][t[0]].add(t[4]) # (employee, task, mode, time, skill) modes = {} modes_task = {} for t in self.modes: for m in self.modes[t]: modes[(t, m)] = self.modes[t][m].xi(s) if modes[(t, m)] >= 0.5: modes_task[t] = m durations = {} for t in self.durations: durations[t] = self.durations[t].xi(s) start_time = {} for t in self.start_times_task: start_time[t] = self.start_times_task[t].xi(s) end_time = {} for t in self.start_times_task: end_time[t] = self.end_times_task[t].xi(s) print("Size schedule : ", len(rcpsp_schedule.keys())) print("results", "(task, mode, time)", {x: results[x] for x in results if results[x] == 1.}) print( "Employee usage : ", "(employee, task, mode, time, skill)", { x: employee_usage[x] for x in employee_usage if employee_usage[x] == 1. }) print("task mode : ", "(task, mode)", {t: modes[t] for t in modes if modes[t] == 1.}) print("durations : ", durations) print("Start time ", start_time) print("End time ", end_time) solution = MS_RCPSPSolution(problem=self.rcpsp_model, modes=modes_task, schedule=rcpsp_schedule, employee_usage=employee_usage_solution) fit = self.aggreg_from_sol(solution) list_solution_fits += [(solution, fit)] return ResultStorage( list_solution_fits=list_solution_fits, mode_optim=self.params_objective_function.sense_function) def solve(self, parameters_milp: ParametersMilp, **args) -> ResultStorage: if self.model is None: import time print("Init LP model ") t = time.time() self.init_model(greedy_start=False) print("LP model initialized...in ", time.time() - t, " seconds") limit_time_s = parameters_milp.TimeLimit self.model.sol_pool_size = parameters_milp.PoolSolutions self.model.max_mip_gap_abs = parameters_milp.MIPGapAbs self.model.max_mip_gap = parameters_milp.MIPGap self.model.optimize(max_seconds=limit_time_s, max_solutions=parameters_milp.n_solutions_max) return self.retrieve_solutions(parameters_milp)
def test_tsp_mipstart(solver: str): """tsp related tests""" N = ["a", "b", "c", "d", "e", "f", "g"] n = len(N) i0 = N[0] A = { ("a", "d"): 56, ("d", "a"): 67, ("a", "b"): 49, ("b", "a"): 50, ("d", "b"): 39, ("b", "d"): 37, ("c", "f"): 35, ("f", "c"): 35, ("g", "b"): 35, ("b", "g"): 25, ("a", "c"): 80, ("c", "a"): 99, ("e", "f"): 20, ("f", "e"): 20, ("g", "e"): 38, ("e", "g"): 49, ("g", "f"): 37, ("f", "g"): 32, ("b", "e"): 21, ("e", "b"): 30, ("a", "g"): 47, ("g", "a"): 68, ("d", "c"): 37, ("c", "d"): 52, ("d", "e"): 15, ("e", "d"): 20, } # input and output arcs per node Aout = {n: [a for a in A if a[0] == n] for n in N} Ain = {n: [a for a in A if a[1] == n] for n in N} m = Model(solver_name=solver) m.verbose = 0 x = { a: m.add_var(name="x({},{})".format(a[0], a[1]), var_type=BINARY) for a in A } m.objective = xsum(c * x[a] for a, c in A.items()) for i in N: m += xsum(x[a] for a in Aout[i]) == 1, "out({})".format(i) m += xsum(x[a] for a in Ain[i]) == 1, "in({})".format(i) # continuous variable to prevent subtours: each # city will have a different "identifier" in the planned route y = {i: m.add_var(name="y({})".format(i), lb=0.0) for i in N} # subtour elimination for (i, j) in A: if i0 not in [i, j]: m.add_constr(y[i] - (n + 1) * x[(i, j)] >= y[j] - n) route = ["a", "g", "f", "c", "d", "e", "b", "a"] m.start = [(x[route[i - 1], route[i]], 1.0) for i in range(1, len(route))] m.optimize() assert m.status == OptimizationStatus.OPTIMAL assert abs(m.objective_value - 262) <= TOL
m = 4 # number of requests w = [187, 119, 74, 90] # size of each item b = [1, 2, 2, 1] # demand for each item # creating the model model = Model() x = {(i, j): model.add_var(obj=0, var_type=INTEGER, name="x[%d,%d]" % (i, j)) for i in range(m) for j in range(n)} y = { j: model.add_var(obj=1, var_type=BINARY, name="y[%d]" % j) for j in range(n) } # constraints for i in range(m): model.add_constr(xsum(x[i, j] for j in range(n)) >= b[i]) for j in range(n): model.add_constr(xsum(w[i] * x[i, j] for i in range(m)) <= L * y[j]) # additional constraints to reduce symmetry for j in range(1, n): model.add_constr(y[j - 1] >= y[j]) # optimizing the model model.optimize() # printing the solution print('') print('Objective value: {model.objective_value:.3}'.format(**locals())) print('Solution: ', end='') for v in model.vars:
m = 4 # number of requests w = [187, 119, 74, 90] # size of each item b = [1, 2, 2, 1] # demand for each item # creating master model master = Model() # creating an initial set of patterns which cut one item per bar # to provide the restricted master problem with a feasible solution lambdas = [master.add_var(obj=1, name='lambda_%d' % (j + 1)) for j in range(m)] # creating constraints constraints = [] for i in range(m): constraints.append( master.add_constr(lambdas[i] >= b[i], name='i_%d' % (i + 1))) # creating the pricing problem pricing = Model() # creating pricing variables a = [ pricing.add_var(obj=0, var_type=INTEGER, name='a_%d' % (i + 1)) for i in range(m) ] # creating pricing constraint pricing += xsum(w[i] * a[i] for i in range(m)) <= L, 'bar_length' new_vars = True while new_vars:
def dispatch(decision_variables, constraints_lhs, constraints_rhs_and_type, market_rhs_and_type, constraints_dynamic_rhs_and_type, objective_function): """Create and solve a linear program, returning prices of the market constraints and decision variables values. 0. Create the problem instance as a mip-python object instance 1. Create the decision variables 2. Create the objective function 3. Create the constraints 4. Solve the problem 5. Retrieve optimal values of each variable 6. Retrieve the shadow costs of market constraints :param decision_variables: dict of DataFrames each with the following columns variable_id: int lower_bound: float upper_bound: float type: str one of 'continuous', 'integer' or 'binary' :param constraints_lhs_coefficient: dict of DataFrames each with the following columns variable_id: int constraint_id: int coefficient: float :param constraints_rhs_and_type: dict of DataFrames each with the following columns constraint_id: int type: str one of '=', '<=', '>=' rhs: float :param market_constraints_lhs_coefficients: dict of DataFrames each with the following columns variable_id: int constraint_id: int coefficient: float :param market_rhs_and_type: dict of DataFrames each with the following columns constraint_id: int type: str one of '=', '<=', '>=' rhs: float :param objective_function: dict of DataFrames each with the following columns variable_id: int cost: float :return: decision_variables: dict of DataFrames each with the following columns variable_id: int lower_bound: float upper_bound: float type: str one of 'continuous', 'integer' or 'binary' value: float market_rhs_and_type: dict of DataFrames each with the following columns constraint_id: int type: str one of '=', '<=', '>=' rhs: float price: float """ # 0. Create the problem instance as a mip-python object instance prob = Model("market") prob.verbose = 0 sos_variables = None if 'interpolation_weights' in decision_variables.keys(): sos_variables = decision_variables['interpolation_weights'] # 1. Create the decision variables decision_variables = pd.concat(decision_variables) lp_variables = {} variable_types = {'continuous': CONTINUOUS, 'binary': BINARY} for variable_id, lower_bound, upper_bound, variable_type in zip( list(decision_variables['variable_id']), list(decision_variables['lower_bound']), list(decision_variables['upper_bound']), list(decision_variables['type'])): lp_variables[variable_id] = prob.add_var( lb=lower_bound, ub=upper_bound, var_type=variable_types[variable_type], name=str(variable_id)) def add_sos_vars(sos_group): prob.add_sos(list(zip(sos_group['vars'], sos_group['loss_segment'])), 2) if sos_variables is not None: sos_variables['vars'] = sos_variables['variable_id'].apply( lambda x: lp_variables[x]) sos_variables.groupby('interconnector').apply(add_sos_vars) # 2. Create the objective function if len(objective_function) > 0: objective_function = pd.concat(list(objective_function.values())) objective_function = objective_function.sort_values('variable_id') objective_function = objective_function.set_index('variable_id') prob.objective = minimize( xsum(objective_function['cost'][i] * lp_variables[i] for i in list(objective_function.index))) # 3. Create the constraints sos_constraints = [] if len(constraints_rhs_and_type) > 0: constraints_rhs_and_type = pd.concat( list(constraints_rhs_and_type.values())) else: constraints_rhs_and_type = pd.DataFrame({}) if len(constraints_dynamic_rhs_and_type) > 0: constraints_dynamic_rhs_and_type = pd.concat( list(constraints_dynamic_rhs_and_type.values())) constraints_dynamic_rhs_and_type['rhs'] = constraints_dynamic_rhs_and_type.\ apply(lambda x: lp_variables[x['rhs_variable_id']], axis=1) rhs_and_type = pd.concat([constraints_rhs_and_type] + list(market_rhs_and_type.values()) + [constraints_dynamic_rhs_and_type]) else: rhs_and_type = pd.concat([constraints_rhs_and_type] + list(market_rhs_and_type.values())) constraint_matrix = constraints_lhs.pivot('constraint_id', 'variable_id', 'coefficient') constraint_matrix = constraint_matrix.sort_index(axis=1) constraint_matrix = constraint_matrix.sort_index() column_ids = np.asarray(constraint_matrix.columns) row_ids = np.asarray(constraint_matrix.index) constraint_matrix_np = np.asarray(constraint_matrix) # if len(constraint_matrix.columns) != max(decision_variables['variable_id']) + 1: # raise check.ModelBuildError("Not all variables used in constraint matrix") rhs = dict(zip(rhs_and_type['constraint_id'], rhs_and_type['rhs'])) enq_type = dict(zip(rhs_and_type['constraint_id'], rhs_and_type['type'])) #var_list = np.asarray([lp_variables[k] for k in sorted(list(lp_variables))]) #t0 = time() for row, id in zip(constraint_matrix_np, row_ids): new_constraint = make_constraint(lp_variables, row, rhs[id], column_ids, enq_type[id]) prob.add_constr(new_constraint, name=str(id)) #print(time() - t0) # for row_index in sos_constraints: # sos_set = get_sos(var_list, constraint_matrix_np[row_index], column_ids) # prob.add_sos(list(zip(sos_set, [0 for var in sos_set])), 1) # 4. Solve the problem k = prob.add_var(var_type=BINARY, obj=1.0) #tc = 0 #t0 = time() status = prob.optimize() #tc += time() - t0 #print(tc) if status != OptimizationStatus.OPTIMAL: raise ValueError('Linear program infeasible') # 5. Retrieve optimal values of each variable #t0 = time() decision_variables = decision_variables.droplevel(1) decision_variables['lp_variables'] = [ lp_variables[i] for i in decision_variables['variable_id'] ] decision_variables['value'] = decision_variables['lp_variables'].apply( lambda x: x.x) decision_variables = decision_variables.drop('lp_variables', axis=1) split_decision_variables = {} for variable_group in decision_variables.index.unique(): split_decision_variables[variable_group] = \ decision_variables[decision_variables.index == variable_group].reset_index(drop=True) #print('get values {}'.format(time() - t0)) # 6. Retrieve the shadow costs of market constraints start_obj = prob.objective.x #initial_solution = [(v, v.x) for v in list(sos_variables['vars']) if v.x > 0.01] #print(initial_solution) #prob.start = initial_solution #prob.validate_mip_start() for constraint_group in market_rhs_and_type.keys(): cg = constraint_group market_rhs_and_type[cg]['price'] = 0.0 for id in list(market_rhs_and_type[cg]['constraint_id']): constraint = prob.constr_by_name(str(id)) constraint.rhs += 1.0 #t0 = time() prob.optimize() #tc += time() - t0 marginal_cost = prob.objective.x - start_obj market_rhs_and_type[cg].loc[ market_rhs_and_type[cg]['constraint_id'] == id, 'price'] = marginal_cost constraint.rhs -= 1.0 # market_rhs_and_type[constraint_group]['price'] = \ # market_rhs_and_type[constraint_group].apply(lambda x: get_price(x['constraint_id'], prob), axis=1) #print(tc) return split_decision_variables, market_rhs_and_type