def step(g, loops, feas_elims, ub, stats): # Solve relaxation with the cycle matrix of loops. This solution # MUST BE rigorous. relax_elims, lb = solve_relaxation(g, loops, stats) assert lb <= ub if lb == ub: log('*** Optimal solution found ***') return None, None, feas_elims, ub # g_ruins = g.copy() for e in relax_elims: g_ruins.remove_edge(*e) # if nx.is_directed_acyclic_graph(g_ruins): log('*** Relaxation became feasible ***') return None, None, relax_elims, lb, # log() log('Remaining graph') info(g_ruins, log=log) # # Get the missed edges and the ruins of the relaxation: We need new # candidate loops. The missed_edges does not have to be rigorous. We may # also improve the currently best feasible solution. missed = missed_edges(g_ruins) assert missed new_feas_elims = relax_elims + missed new_ub = sum(g[u][v]['weight'] for u, v in new_feas_elims) assert new_ub > lb if new_ub < ub: log('Improved UB: {} -> {}'.format(ub, new_ub)) ub, feas_elims = new_ub, new_feas_elims double_check(g, ub, feas_elims, is_labeled=True, log=log) return missed, g_ruins, feas_elims, ub
def solve_triangle_inequalities(g, stats, feasible_sol): iteratively_remove_runs_and_bypasses(g) # The above simplification removes edges, but the d['orig_edges'] still # refers to these edges. As a result, the double_check calls in this module # and in mfes will fail in the cost calculation, when trying to access # those not present edges to figure out their edge weights. So we have to # replace d['orig_edges'] appropriately and undo this at the end of this # function. Additionally, as runs are removed, new edges are introduced # that are not in the original graph. It is much simpler to just relabel # the graph and then undo it on the elimination order. origedges_map = get_orig_edges_map(g) # model, y = build_lp(g, feasible_sol) start = time() success = solve_ilp(model, stats) end = time() print('Overall solution time: {0:0.1f} s'.format(end - start)) assert success, 'Solver failures are not handled at the moment...' cost = int(round(model.getObjective().getValue())) elim_order = recover_order(model, y, len(g)) elims = get_torn_edges(g, elim_order) # Done. Now undo the d['orig_edges'] mess. final_elims = [] for edge in elims: final_elims.extend(origedges_map[edge]) double_check(g, cost, elims, is_labeled=True) return final_elims, cost
def simplify(g): print('Nodes:', g.number_of_nodes()) print('Edges:', g.number_of_edges()) cycles = list(nx.simple_cycles(g)) # <- print('Loops:', len(cycles)) # <- orig_input = deepcopy(g) sccs = split_to_nontrivial_sccs(g) # Clean-up the new smaller SCCs without splitting them for sc in sccs: iteratively_remove_runs_and_bypasses(sc) # running_cost, elims = 0, [ ] info_after_cleanup(sccs, running_cost) # clean_sccs = [ ] for scc in sccs: plot(scc, prog='sfdp') new_sccs, running_cost = try_neighborhood(scc, running_cost, elims) clean_sccs.extend( new_sccs ) # check what remains if clean_sccs: info_after_cleanup(clean_sccs, running_cost) print(sorted(n for n in clean_sccs[0])) dbg_dump_as_edgelist(clean_sccs[0]) else: print('The simplifier eliminated the whole input at cost', running_cost) double_check(orig_input, running_cost, elims) print('Chosen:', elims)
def solve_with_pcm(g, stats, feasible_sol): g_orig = g.copy() iteratively_remove_runs_and_bypasses(g) # d['orig_edges'] mess, origedges_map = get_orig_edges_map(g) # see explanation in grb_pcm.py # # FIXME It assumes that we only have a single SCC elims, ub = feasible_solution(g) if feasible_sol is None else feasible_sol # A shortest path around each edge, see grb_pcm.py for possible improvements loops = initial_loop_set(g) print('Initial cycle matrix size:', len(loops)) # Build the model, set the elims as initial solution m, vrs = build_ilp(g, loops, elims) # Put into the model dict everything we need in the callback m._vrs, m._g, m._loops, m._elims, m._ub = vrs, g, loops, elims, ub # solve(m, stats) # loops, elims, ub = m._loops, m._elims, m._ub print('Final cycle matrix size:', len(loops)) #simplify(g, loops, elims, ub) #run_IIS(g, loops, elims, ub) # Done. Now undo the d['orig_edges'] mess. final_elims = [] for edge in elims: final_elims.extend(origedges_map[edge]) double_check(g_orig, ub, final_elims, is_labeled=True, log=print) return final_elims, ub, loops
def rigorous_mfes(subgraph, cutoff): # It is just a convenience wrapper around solve_cm. Returns: # (error msg, elims, obj, ncyc). The error message is empty iff successful. success, edges_per_cycle = get_all_cycles(subgraph, cutoff) if not success: return 'Too many simple cycles in relaxed SCC', None, None, None elims, objective = solve_cm(subgraph, edges_per_cycle) if elims is None: return 'Solver failure, giving up', None, None, None assert objective > 0 # in an SCC at least... double_check(subgraph, objective, elims) return '', elims, objective, len(edges_per_cycle)
def solve_problem(g_orig, stats=None): 'Returns: [torn edges], cost.' elims, cost = [], 0 for sc in noncopy_split_to_nontrivial_sccs( g_orig.copy()): # <- Copy passed! partial_elims, partial_cost = solve_with_pcm(sc, stats) elims.extend(partial_elims) cost += partial_cost double_check(g_orig, cost, elims, is_labeled=True, log=log) log('Input graph') info(g_orig, log=log) return elims, cost
def solve_problem(g_orig, stats=None, feasible_sol=None): 'Returns: [torn edges], cost.' elims, cost = [], 0 for sc in noncopy_split_to_nontrivial_sccs( g_orig.copy()): # <- Copy passed! partial_elims, partial_cost = \ solve_triangle_inequalities(sc, stats, feasible_sol) elims.extend(partial_elims) cost += partial_cost double_check(g_orig, cost, elims, is_labeled=True) print('Input graph') info_short(g_orig) return elims, cost
def solve_problem(g_orig, stats, feasible_sol=None): 'Returns: [torn edges], cost.' elims, cost = [], 0 g2 = g_orig.copy() # Remove self-loops for u, v, d in g_orig.selfloop_edges(data=True): g2.remove_edge(u, v) elims.append((u, v)) cost += d['weight'] cycle_matrix = [] # Make each SCC acyclic for sc in noncopy_split_to_nontrivial_sccs(g2): partial_elims, partial_cost, loops = solve_with_pcm( sc, stats, feasible_sol) elims.extend(partial_elims) cost += partial_cost cycle_matrix.extend(loops) double_check(g_orig, cost, elims, is_labeled=True, log=log) log('Input graph') info(g_orig, log=log) return elims, cost, cycle_matrix
def run_mfes_heuristic(g_input, try_one_cut=False, is_labeled=False): # Set the edge attributes 'weight' and 'orig_edges' if necessary g, copy_g = label_edges(g_input, is_labeled) # greedy_choice = no_lookahead if try_one_cut else with_lookahead # running_cost, elims = 0, [] sccs, running_cost = iterate_cleanup(g, running_cost, elims, copy_g) info_after_cleanup(sccs, running_cost) while True: sccs_to_process = [] for sc in sccs: log('-----------------------------------------------------------') #distributions(sc) new_state = greedy_choice(sc, running_cost, elims) new_sccs, elims, running_cost = new_state sccs_to_process.extend(new_sccs) sccs = sccs_to_process if len(sccs) == 0: # Make sure that what we return is at least consistent double_check(g_input, running_cost, elims, is_labeled, log=log) return running_cost, elims
def extend_cm(m): g, ub, ub2 = m._g, m._ub, int(round(m.cbGet(GRB.Callback.MIPSOL_OBJ))) #if ub2 >= ub: # return log('Callback with obj: ', ub2) # relax_elims, new_feas_elims, missed_loops = get_solution(m) if not missed_loops: if ub2 < ub: log('Relaxation became feasible and improved UB {} -> {}'.format( ub, ub2)) m._elims, m._ub = relax_elims, ub2 double_check(g, ub2, relax_elims, is_labeled=True, log=log) return # new_ub = sum(g[u][v]['weight'] for u, v in new_feas_elims) if new_ub < ub: log('Improved UB: {} -> {}'.format(ub, new_ub)) double_check(g, new_ub, new_feas_elims, is_labeled=True, log=log) m._ub, m._elims = new_ub, new_feas_elims # extend_the_cycle_matrix(m, missed_loops)