diff --git a/src/branchcut.py b/src/branchcut.py index ac5261486b06dcbc6f5d456ee489dbd9b6d492e8..95fed22333e04e428c09b35b74ede6ffcfdc7f9a 100644 --- a/src/branchcut.py +++ b/src/branchcut.py @@ -2,6 +2,12 @@ import pulp from typing import List, Tuple, Dict import numpy as np import abc +import mip +import logging +import timeit +import argparse + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') class LPSolver(abc.ABC): @@ -29,6 +35,41 @@ class LPSolver(abc.ABC): def _set_costs(self, edges): pass +class MipLPSolver(LPSolver): + def __init__(self, edges, num_verts_a, num_verts_b): + self.model = mip.Model(sense=mip.MINIMIZE, solver_name=mip.CBC) + self.variables = self._make_variables(num_verts_a, num_verts_b) + self._make_constraints(num_verts_a, num_verts_b) + self._set_costs(edges) + + def solve(self): + return self.model.optimize() + + def _make_variables(self, num_verts_a, num_verts_b): + return { + (i, j): self.model.add_var(name=f"y_{i}_{j}", var_type=mip.BINARY) + for i in range(num_verts_a + 1, num_verts_a + num_verts_b + 1) + for j in range(num_verts_a + 1, num_verts_a + num_verts_b + 1) if i < j + } + + def _set_costs(self, edges): + self.model.objective = mip.minimize( + mip.xsum( + self.variables[(l, j)] if k > i and j > l + else 1 - self.variables[(j, l)] if l > j + else 0 + for (i, j) in edges + for (k, l) in edges + ) + ) + + def _make_constraints(self, n0, n1): + for i in range(n0 + 1, n0 + n1 - 1): + for j in range(i + 1, n0 + n1): + for k in range(j + 1, n0 + n1 + 1): + self.model.add_constr(self.variables[(i, k)] >= self.variables[(i, j)] + self.variables[(j, k)] - 1) + + class PulpLPSolver(LPSolver): def __init__(self, edges, constraints, num_verts_a, num_verts_b): @@ -94,68 +135,33 @@ class PulpLPSolver(LPSolver): self.problem += costs[(i, j, k, l)] == self.variables[(l, j)] elif l > j: self.problem += costs[(i, j, k, l)] == 1 - self.variables[(j, l)] - - -def _cut(constraints: Dict[Tuple], constraints_pool: Dict[Tuple]) -> Dict[Tuple]: - """ - Adds cutting planes: Adds constraints from constraints_pool to constraints. - """ - new_constraints = constraints.copy() - return new_constraints - -def _branch(solved_problem: pulp.LpProblem, variables: Dict[Tuple]): - """ - Branches on a binary variable: Sets a relaxed variable to either 0 or 1. - """ - pass - -def _parse_graph_file() -> Tuple[List[Tuple]]: - pass -def solve(edges: List[Tuple]): - """ - Solves an ILP using Branch-and-Cut. - """ - edges, constraints, num_verts_a, num_verts_b = _parse_graph_file() - problem = PulpLPSolver(edges, constraints, num_verts_a, num_verts_b) - problems = [problem] - - x_star = None - v_star = -np.Inf - while len(problems) > 0: - constraints_violated = True - current_problem = problems.pop(0) - # this while loop is necessary for step 6 - while constraints_violated: - current_problem.solve() - # problem infeasible, go to next problem in queue - if current_problem.is_problem_infeasible(): - break - - if current_problem.is_solution_optimal(): - # not an improvement, go to next problem in queue - if pulp.value(current_problem) >= v_star: - break - # current solution improves the value of the solution, continue evaluation of the solution - else: - # step 5 - # check if solution is integer: if yes, set current solution as best solution - integer_variables, fractional_variables = current_problem.get_integer_and_fractional_variables_of_solution() - if len(fractional_variables) == 0: - v_star = pulp.value(current_problem) - x_star = current_problem - continue - - # step 6 - # get constraints that are fulfilled by the integer vars but violated by the fractional ones - violated_constraints = _get_violated_constraints(integer_variables, fractional_variables) - - # add the violated constraints back to the LP and go back to the inner while loop - if len(violated_constraints) > 0: - current_problem += violated_constraints - else: - constraints_violated = False - +def _parse_graph_file(graph_file) -> Tuple[List[Tuple]]: + edges = [] + with open(graph_file, "r") as file: + for line in file: + if line.startswith('c'): + continue + elif line.startswith('p'): + parts = line.split() + n0 = int(parts[2]) + n1 = int(parts[3]) + logging.info(f"Größen der Partitionen: A={n0}, B={n1}") + else: + x, y = map(int, line.split()) + edges.append((x, y)) + return edges, n0, n1 +def solve(filepath): + mipsolver = MipLPSolver(*_parse_graph_file(filepath)) + print(timeit.timeit(mipsolver.solve, number=1)) + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("file") + args = parser.parse_args() + solve(args.file) + + \ No newline at end of file