# constraint : leave each city only once for i in V: model += xsum(x[i][j] for j in V - {i}) == 1 # constraint : enter each city only once for i in V: model += xsum(x[j][i] for j in V - {i}) == 1 # subtour elimination for (i, j) in product(V - {0}, V - {0}): if i != j: model += y[i] - (n + 1) * x[i][j] >= y[j] - n # optimizing model.threads = 4 model.optimize(max_nodes=75) # checking if a solution was found if model.num_solutions: out.write("route with total distance %g found: %s" % (model.objective_value, 0)) nc = 0 while True: nc = [i for i in V if x[nc][i].x >= 0.99][0] out.write(" -> %s" % nc) if nc == 0: break out.write("\n") # sanity tests
def solve(self, max_runtime = 120): n, V = len(self.adj_mat), set(range(len(self.adj_mat))) H = set(range(len(self.house_names))) model = Model() model.threads = -1 model.max_mip_gap = 0.05 """ Define Parameters d: {0, 1} whether or not an edge is selected y: for connectivity constraint s: whether or not a node is visited """ d = {} for i in V: for j in V: #if self.adj_mat[i][j] > 0: if self.full_mat[i][j] > 0: d[(i, j)] = model.add_var(var_type=BINARY) z = [model.add_var() for i in V] y = [model.add_var(var_type=BINARY) for i in V] # i is node, j is house/pedestrian c = [] s = [] for i in V: c.append(self.closest_nodes(10, self.node_names[i])) temp_row = [] for j in H: temp_row.append(model.add_var(var_type=BINARY)) s.append(temp_row) """ Define optimization function """ def cost_func(): total = 0 for i in V: for j in V: if self.full_mat[i][j] > 0: total += self.full_mat[i][j] * d[(i,j)] * 2.0 / 3.0 for i in V: for j in H: total += c[i][j] * s[i][j] return total model.objective = minimize(cost_func()) """ Add constraints """ model += y[self.node_names.index(self.start)] == 1 # Will need if skimp on distance calulation with closest_nodes #for i in H: # model += xsum(s[j][i] * c[j][i] for j in V) >= 0.2 for i in H: model += xsum(s[j][i] for j in V) == 1 for i in V: for j in H: model += s[i][j] <= y[i] for i in V: model += xsum(d[(i,j)] for j in V -{i} if self.full_mat[i][j] > 0) == 1 * y[i] for i in V: model+= xsum(d[(j,i)] for j in V - {i} if self.full_mat[i][j] > 0) == 1 * y[i] for (i, j) in product(V - {0}, V - {0}): if i != j and self.full_mat[i][j] > 0: model += z[i] - (n+1)*d[(i,j)] >= z[j] - n status = model.optimize(max_seconds = max_runtime) n = 0 for i in V: for j in V: if i != j and d[(i, j)].x > 0.99: n += 1 print('oofus doofus', n) if model.num_solutions: nc = self.node_names.index(self.start) solution = [] # visited is to make sure we don't terminate the path too quickly visited = {} for _ in range(500): solution.append(self.node_names[nc]) visited[nc] = len(visited) all_visited = True #nc_ls = [] print('abcdefgh') for i in V: if (nc, i) in d and d[(nc, i)].x > 0.99: nc = i if nc not in visited: print('BROKE') all_visited = False break #nc_ls.append(i) #break if all_visited: first_visit = len(visited) for k in visited: if visited[k] < first_visit: first_visit = visited[k] nc = k if nc == self.node_names.index(self.start): solution.append(self.start) break solution = self.make_valid_path(solution) print(solution) return solution, status, model.gap else: return None, status, model.gap
def solve_integer_programing( reactant_species: List[str], product_species: List[str], reactant_bonds: List[Bond], product_bonds: List[Bond], msg: bool = True, threads: Optional[int] = None, ) -> Tuple[int, List[Union[int, None]], List[Union[int, None]]]: """ Solve an integer programming problem to get atom mapping between reactants and products. This is a utility function for `get_reaction_atom_mapping()`. Args: reactant_species: species string of reactant atoms product_species: species string of product atoms reactant_bonds: bonds in reactant product_bonds: bonds in product msg: whether to show the solver running message to stdout. threads: number of threads for the solver. `None` to use default. Returns: objective: minimized objective value. This corresponds to the number of changed bonds (both broken and formed) in the reaction. r2p_mapping: mapping of reactant atom to product atom, e.g. r2p_mapping[0] giving 3 means that reactant atom 0 maps to product atom 3. A value of `None` means a mapping cannot be found for the reactant atom. p2r_mapping: mapping of product atom to reactant atom, e.g. p2r_mapping[3] giving 0 means that product atom 3 maps to reactant atom 0. A value of `None` means a mapping cannot be found for the product atom. Reference: `Stereochemically Consistent Reaction Mapping and Identification of Multiple Reaction Mechanisms through Integer Linear Optimization`, J. Chem. Inf. Model. 2012, 52, 84–92, https://doi.org/10.1021/ci200351b """ atoms = list(range(len(reactant_species))) # init model and variables model = Model(name="Reaction_Atom_Mapping", sense=MINIMIZE, solver_name=CBC) model.emphasis = 1 if threads is not None: model.threads = threads if msg: model.verbose = 1 else: model.verbose = 0 y_vars = {(i, k): model.add_var(var_type=BINARY, name=f"y_{i}_{k}") for i in atoms for k in atoms} alpha_vars = {(i, j, k, l): model.add_var(var_type=BINARY, name=f"alpha_{i}_{j}_{k}_{l}") for (i, j) in reactant_bonds for (k, l) in product_bonds} # add constraints # constraint 2: each atom in the reactants maps to exactly one atom in the products # constraint 3: each atom in the products maps to exactly one atom in the reactants for i in atoms: model += xsum([y_vars[(i, k)] for k in atoms]) == 1 for k in atoms: model += xsum([y_vars[(i, k)] for i in atoms]) == 1 # constraint 4: allows only atoms of the same type to map to one another for i in atoms: for k in atoms: if reactant_species[i] != product_species[k]: model += y_vars[(i, k)] == 0 # constraints 5 and 6: define each alpha_ijkl variable, permitting it to take the # value of one only if the reactant bond (i,j) maps to the product bond (k,l) for (i, j) in reactant_bonds: for (k, l) in product_bonds: model += alpha_vars[(i, j, k, l)] <= y_vars[(i, k)] + y_vars[(i, l)] model += alpha_vars[(i, j, k, l)] <= y_vars[(j, k)] + y_vars[(j, l)] # create objective obj1 = xsum(1 - xsum(alpha_vars[(i, j, k, l)] for (k, l) in product_bonds) for (i, j) in reactant_bonds) obj2 = xsum(1 - xsum(alpha_vars[(i, j, k, l)] for (i, j) in reactant_bonds) for (k, l) in product_bonds) obj = obj1 + obj2 # solve the problem try: model.objective = obj model.optimize() except Exception: raise ReactionMappingError("Failed solving integer programming.") if not model.num_solutions: raise ReactionMappingError("Failed solving integer programming.") # get atom mapping between reactant and product r2p_mapping = [None for _ in atoms] # type: List[Union[int, None]] p2r_mapping = [None for _ in atoms] # type: List[Union[int, None]] for (i, k), v in y_vars.items(): if v.x == 1: r2p_mapping[i] = k p2r_mapping[k] = i objective = model.objective_value # type: int return objective, r2p_mapping, p2r_mapping
def solve(G: nx.Graph, max_c, max_k, timeout, existing_solution, target_distance): """ Args: G: networkx.Graph Returns: c: list of cities to remove k: list of edges to remove """ model = Model() model.threads = int(os.getenv("THREAD_COUNT", "24")) model.max_mip_gap = 1e-12 skipped_nodes = [model.add_var(var_type=BINARY) for node in G] flow_over_edge = [[model.add_var(var_type=CONTINUOUS) for j in G] for i in G] # no flow over nonexistent edges for i in G: for j in G: if not G.has_edge(i, j): model += flow_over_edge[i][j] == 0 model += xsum(flow_over_edge[0][other_node] for other_node in G) == (len(G) - 1) - xsum(skipped_nodes) for other_node in G: model += flow_over_edge[other_node][0] == 0 distance_to_node = [model.add_var(var_type=CONTINUOUS) for node in G] model += distance_to_node[0] == 0 # in any connected subset of G, there will always be a shortest path w/ length <= sum of all edges # so a fake_infinity will never be better than any other option fake_infinity = sum([weight for _, _, weight in G.edges().data("weight")]) skipped_edges = [] skipped_edge_map = {} model += skipped_nodes[0] == 0 model += skipped_nodes[len(G) - 1] == 0 for node_a, node_b, weight in G.edges().data("weight"): edge_skip_var = model.add_var(var_type=BINARY) skipped_edges.append((edge_skip_var, (node_a, node_b))) model += edge_skip_var >= skipped_nodes[node_a] model += edge_skip_var >= skipped_nodes[node_b] model += distance_to_node[node_a] <= distance_to_node[node_b] + weight + edge_skip_var * fake_infinity model += distance_to_node[node_b] <= distance_to_node[node_a] + weight + edge_skip_var * fake_infinity # no flow if edge or node removed model += flow_over_edge[node_a][node_b] <= (len(G) - 1) - edge_skip_var * (len(G) - 1) model += flow_over_edge[node_b][node_a] <= (len(G) - 1) - edge_skip_var * (len(G) - 1) # results in binary variable leakage for node in G: # immediately force the distance to infinity if we skip the node model += distance_to_node[node] >= skipped_nodes[node] * fake_infinity model += distance_to_node[node] <= fake_infinity for node in G: if node != 0: flow_into_node = xsum(flow_over_edge[other_node][node] for other_node in G) flow_out_of_node = xsum(flow_over_edge[node][other_node] for other_node in G) model += flow_into_node - flow_out_of_node == 1 - skipped_nodes[node] model += xsum([var for var, _ in skipped_edges]) - xsum([skipped_nodes[i] * len(G[i]) for i in G]) <= max_k model += xsum(skipped_nodes) <= max_c if target_distance: model += distance_to_node[len(G) - 1] >= target_distance model += distance_to_node[len(G) - 1] <= target_distance model.objective = maximize(distance_to_node[len(G) - 1]) # these cuts are used more often but aggressively using them doesn't seem to help # model.solver.set_int_param("FlowCoverCuts", 2) # model.solver.set_int_param("MIRCuts", 2) if existing_solution: solution_variables = [] for node in G: solution_variables.append((skipped_nodes[node], 1.0 if node in existing_solution[0] else 0.0)) for edge_var, edge in skipped_edges: is_skipped = (edge in existing_solution[1]) or ((edge[1], edge[0]) in existing_solution[1]) or \ (edge[0] in existing_solution[0]) or (edge[1] in existing_solution[0]) solution_variables.append((edge_var, 1.0 if is_skipped else 0.0)) model.start = solution_variables status = model.optimize(max_seconds=timeout) if model.num_solutions > 0: c = [] for node in G: if skipped_nodes[node].x > 0.99: c.append(node) k = [] for skip_var, edge in skipped_edges: if skip_var.x > 0.99 and not ((edge[0] in c) or (edge[1] in c)): k.append(edge) return c, k, status == OptimizationStatus.OPTIMAL, model.gap else: return None
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 solve(list_of_locations, list_of_homes, starting_car_location, adjacency_matrix, params=[]): """ Write your algorithm here. Input: list_of_locations: A list of locations such that node i of the graph corresponds to name at index i of the list list_of_homes: A list of homes starting_car_location: The name of the starting location for the car adjacency_matrix: The adjacency matrix from the input file Output: A list of locations representing the car path A dictionary mapping drop-off location to a list of homes of TAs that got off at that particular location NOTE: both outputs should be in terms of indices not the names of the locations themselves """ location_name_to_index = {} for i in range(0, len(list_of_locations)): location_name_to_index[list_of_locations[i]] = i tas = range(0, len(list_of_homes)) nTas = len(tas) home_indices = list(map(lambda home: location_name_to_index[home], list_of_homes)) starting_car_index = location_name_to_index[starting_car_location] (G, message) = adjacency_matrix_to_graph(adjacency_matrix) shortest_paths = nx.floyd_warshall(G) #shortest_paths_uw = nx.floyd_warshall(G, weight=None) L = range(0, len(list_of_locations)) nL = len(L) model = Model() model.threads = 8 edge_taken = [[model.add_var(var_type=BINARY) for j in L] for i in L] drop_ta_at_stop = [[model.add_var(var_type=BINARY) for stop in L] for ta in tas] driving_cost = (2 / 3) * xsum( G.edges[i, j]["weight"] * edge_taken[i][j] for i in L for j in L if G.has_edge(i, j) ) walking_cost = xsum( shortest_paths[home_indices[ta]][stop] * drop_ta_at_stop[ta][stop] for stop in L for ta in tas ) model.objective = minimize(driving_cost + walking_cost) # only take edges that exist for i in L: for j in L: if not G.has_edge(i, j): model += edge_taken[i][j] == 0 # enter city same number of times as we exist the city for i in L: model += xsum(edge_taken[incoming][i] for incoming in L) == xsum(edge_taken[i][to] for to in L) # every TA is dropped off at exactly one stop for ta in tas: model += xsum(drop_ta_at_stop[ta][stop] for stop in L) == 1 if False: # MCF formulation ta_over_edge = [[[model.add_var(var_type=BINARY) for ta in tas] for j in L] for i in L] # each TA gets dropped off at their stop for node in (set(L) - {starting_car_index}): for ta in tas: ta_entering_node = xsum( ta_over_edge[prev][node][ta] for prev in L ) ta_leaving_node = xsum( ta_over_edge[node][nxt][ta] for nxt in L ) ta_dropped_at_stop = drop_ta_at_stop[ta][node] model += ta_entering_node == ta_leaving_node + ta_dropped_at_stop # each TA must be dropped off somewhere along the route for ta in tas: leaving_start = xsum(ta_over_edge[starting_car_index][nxt][ta] for nxt in L) returning_start = xsum(ta_over_edge[prev][starting_car_index][ta] for prev in L) model += leaving_start == 1 - drop_ta_at_stop[ta][starting_car_index] # drop TA off right before we leave model += returning_start == 0 # if a TA goes over an edge, we must take it as well for i in L: for j in L: for ta in tas: model += edge_taken[i][j] >= ta_over_edge[i][j][ta] else: # SCF formulation flow_over_edge = [[model.add_var(var_type=INTEGER) for j in L] for i in L] # flow decreases only when TAs are dropped off for node in (set(L) - {starting_car_index}): tas_entering_node = xsum( flow_over_edge[prev][node] for prev in L ) tas_leaving_node = xsum( flow_over_edge[node][nxt] for nxt in L ) tas_dropped_at_stop = xsum(drop_ta_at_stop[ta][node] for ta in tas) model += tas_entering_node == tas_leaving_node + tas_dropped_at_stop # each TA must be dropped off somewhere along the route leaving_start = xsum(flow_over_edge[starting_car_index][nxt] for nxt in L) returning_start = xsum(flow_over_edge[prev][starting_car_index] for prev in L) model += leaving_start == nTas - xsum(drop_ta_at_stop[ta][starting_car_index] for ta in tas) model += returning_start == 0 # if flow goes over an edge, we must take it as well for i in L: for j in L: shortest_paths = nx.all_simple_paths(G,starting_car_index,i) r_vals = [] for path in shortest_paths: r_val = 0 for node in path: if node in home_indices: r_val+=1 r_vals.append(r_val) model += edge_taken[i][j] * (nTas - min(r_vals)) >= flow_over_edge[i][j] print(model.constrs) status = model.optimize(max_seconds=60*15) if model.num_solutions > 0: edge_graph = nx.DiGraph() print("Edges taken:") count = 0 for i in range(0, nL): for j in range(0, nL): if (edge_taken[i][j].x >= 0.99): edge_graph.add_edge(i, j) print(i, j, G.edges[i, j]["weight"]) count += 1 if count == 0: print("Path is empty") path = [starting_car_index] else: print("Path:") path = [u for u, v in nx.eulerian_circuit(edge_graph, source=starting_car_index)] + [starting_car_index] for node in path: print(node) dropoffs = {} for ta in tas: drop_off_stop = [i for i in L if drop_ta_at_stop[ta][i].x >= 0.99][0] if not drop_off_stop in dropoffs: dropoffs[drop_off_stop] = [] dropoffs[drop_off_stop].append(home_indices[ta]) return (path, dropoffs, status == OptimizationStatus.OPTIMAL) return None
def fl_mip_solver(input_data): # Modify this code to run your optimization algorithm # parse the input lines = input_data.split('\n') parts = lines[0].split() facility_count = int(parts[0]) customer_count = int(parts[1]) facilities = [] for i in range(1, facility_count+1): parts = lines[i].split() facilities.append(Facility(i-1, float(parts[0]), int(parts[1]), Point(float(parts[2]), float(parts[3])) )) customers = [] for i in range(facility_count+1, facility_count+1+customer_count): parts = lines[i].split() customers.append(Customer(i-1-facility_count, int(parts[0]), Point(float(parts[1]), float(parts[2])))) print('F count:',facility_count) print('C count:',customer_count) # instantiates setup cost vector set_cost = [f.setup_cost for f in facilities] # instantiates proportional capacity matrix such that Cap[i,j] represents the demand of # customer 'j' as a fraction of the total capacity of facility 'i' Cap = [[c.demand/f.capacity for c in customers] for f in facilities] # instantiates distance matrix in such a way that "sum_{j \in M} D[i,j]*at[i,j]" is the total # distance cost for facility 'i' D = [[length(c.location, f.location) for c in customers] for f in facilities] # declares MIP model #m = Model(solver_name=CBC) 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 open facilities variables op = [m.add_var(name="op{}".format(i),var_type='B') for i in range(facility_count)] # instantiates matrix of atribution variables # (line 'i' is an atribution vector for facility 'i') # e.g.: At[3][4] returns whether customer 4 is assigned to facility 3 At = [[m.add_var(name="At{},{}".format(i,j) ,var_type='B') for j in range(customer_count)] for i in range(facility_count)] print('-Variables declared!',datetime.datetime.now()) # instantiates objective function # m.objective = minimize("setup costs" + "distance costs") # Form example: # m.objective = minimize( # xsum(dist[i, j] * x[i, j] for (i, j) in product(F, C)) + xsum(y[i] for i in F) ) m.objective = minimize( xsum(set_cost[i]*op[i] for i in range(facility_count)) + xsum(sum(D[i][j]*At[i][j] for j in range(customer_count)) for i in range(facility_count)) ) print('-Objective declared!',datetime.datetime.now()) # instatiates capacity constraints # -can be expressed as "sum_{j \in M} Cap[i,j]*At[i,j] <= op[i]" for all facilities 'i' # -if a facility is closed (op[i]=0), its effective capacity is 0 for i in range(facility_count): m.add_lazy_constr( xsum(Cap[i][j]*At[i][j] for j in range(customer_count)) <= op[i] ) # instantiates assignment constraints (maximum of 1 facility per customer) # -can be expressed as: "sum of elements over of each column of 'At' == 1" for i in range(customer_count): m += xsum(At[j][i] for j in range(facility_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) used=[i for i in range(facility_count) if m.vars[i].x==1] solution=[None] * customer_count for i in range(facility_count): for j in range(customer_count): if round(m.vars[i*customer_count + j + facility_count].x) == 1: solution[j]=i # parse output varibles and convert to conventional solution output format # calculate the cost of the solution obj = sum([facilities[f].setup_cost for f in used]) for customer in customers: obj += length(customer.location, facilities[solution[customer.index]].location) if status == OptimizationStatus.OPTIMAL: # prepare the solution in the specified output format output_data = '%.2f' % obj + ' ' + str(1) + '\n' output_data += ' '.join(map(str, solution)) elif status == OptimizationStatus.FEASIBLE: output_data = '%.2f' % obj + ' ' + str(0) + '\n' output_data += ' '.join(map(str, solution)) return output_data
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