From 1bb4174b684cc9d2c99272b38f65019e44c5f4c6 Mon Sep 17 00:00:00 2001 From: konog98 <konog98@mi.fu-berlin.de> Date: Mon, 10 Jun 2024 00:03:43 +0200 Subject: [PATCH] Weitere kleinere Anpassungen. --- README.md | 8 +++- src/README.md | 17 ------- .../medium_test_set/instances}/11.gr | 0 .../medium_test_set/solutions}/11.sol | 0 src/solver.py | 45 ++++++++++++------- 5 files changed, 35 insertions(+), 35 deletions(-) delete mode 100644 src/README.md rename src/{ => githubtests/medium_test_set/instances}/11.gr (100%) rename src/{ => githubtests/medium_test_set/solutions}/11.sol (100%) diff --git a/README.md b/README.md index dd0739b..4b0f9f3 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,10 @@ This project is a STUDENT SUBMISSION and contains a solver for the PACE 2024 challenge. The solver aims to minimize crossings in bipartite graphs using linear programming and various optimization techniques. +Our approach first attempts to minimize crossings by assigning a truth value for each crossing. Due to this simplistic restriction, cycles can occur in the B partition (y). We then systematically try to reduce these cycles by adding constraints. Attempting to add all constraints initially sets too many constraints for the ILP solver, making it infeasible. However, the current solution is memory and runtime intensive. + +From the medium_test_sets, only the following graphs run within the allotted time: 8.gr, 9.gr, 10.gr, 11.gr, 14.gr, 40.gr, 48.gr, 49.gr, 52.gr. + ## Setup 1. **Install Python**: go to [python.org](https://wiki.python.org/moin/BeginnersGuide/Download) @@ -53,13 +57,13 @@ This project requires the following external libraries: To run the solver with an input file, use the following command: ```bash - Get-Content path/to/input_file.gr | python solver_opt.py + Get-Content path/to/input_file.gr | python solver.py ``` Example: ```bash - Get-Content githubtests/tiny_test_set/instances/complete_4_5.gr | python solver_opt.py + Get-Content githubtests/tiny_test_set/instances/complete_4_5.gr | python solver.py ``` ## Verifier and Tester in PowerShell diff --git a/src/README.md b/src/README.md deleted file mode 100644 index 66911e8..0000000 --- a/src/README.md +++ /dev/null @@ -1,17 +0,0 @@ -Entscheidungsvariablen: - x ij zur Angabe der Reihenfolge zwischen i und j in Menge A. - y ij zur Angabe der Reihenfolge zwischen i und j in Menge B. - c ijkl, um anzugeben, ob es eine Kreuzung zwischen den Kanten (i, j) und (k, l) gibt. - -xij = 1 bedeutet, dass π1(i) < π1(j) — Knoten i kommt vor Knoten j in der ersten Permutation. -yij = 1 bedeutet, dass π2(i) < π2(j) — Knoten i kommt vor Knoten j in der zweiten Permutation. - -main: logged nur das wesentliche - -backupmain: aktuelles backup - -foretesting: gleiches wie main, nur mit konsolen output, was nur für kleine graphen etwas bringt. - -solver.py: Funktion die 2 Argumente übernimmt, Inputgraph und Outputgraph. Wird für den pace2024tester genutzt. - -solver.bat: Script für windwos, welches die solver.py mit den beiden Konsolen Inputs ausführt. \ No newline at end of file diff --git a/src/11.gr b/src/githubtests/medium_test_set/instances/11.gr similarity index 100% rename from src/11.gr rename to src/githubtests/medium_test_set/instances/11.gr diff --git a/src/11.sol b/src/githubtests/medium_test_set/solutions/11.sol similarity index 100% rename from src/11.sol rename to src/githubtests/medium_test_set/solutions/11.sol diff --git a/src/solver.py b/src/solver.py index c858917..e757788 100644 --- a/src/solver.py +++ b/src/solver.py @@ -3,6 +3,7 @@ import sys from pulp import * from collections import defaultdict, deque +# Count the number of crossings from the crossing variables def count_crossings_via_variables(c_vars): crossings = 0 for c_var in c_vars.values(): @@ -10,11 +11,12 @@ def count_crossings_via_variables(c_vars): crossings += 1 return crossings +# Prioritize cycles based on their length (example: shorter cycles first) def prioritize_cycles(cycles): - # Beispiel: Zyklen nach Länge priorisieren cycles.sort(key=len, reverse=False) return cycles +# Detect cycles in the graph using depth-first search (DFS) def detect_cycle(graph, node, visited, rec_stack, path, cycles): visited[node] = True rec_stack[node] = True @@ -30,6 +32,7 @@ def detect_cycle(graph, node, visited, rec_stack, path, cycles): rec_stack[node] = False path.pop() +# Find all unique cycles in the graph def find_all_cycles(graph, nodes): visited = {node: False for node in nodes} rec_stack = {node: False for node in nodes} @@ -40,7 +43,7 @@ def find_all_cycles(graph, nodes): if not visited[node]: detect_cycle(graph, node, visited, rec_stack, path, cycles) - # Entferne doppelte Zyklen + # Remove duplicate cycles unique_cycles = [] seen = set() for cycle in cycles: @@ -51,6 +54,7 @@ def find_all_cycles(graph, nodes): return unique_cycles +# Add constraints to the linear programming problem based on the detected cycles def add_cycle_constraints(prob, y, cycles, added_constraints): for cycle in cycles: cycle = list(dict.fromkeys(cycle)) @@ -58,7 +62,7 @@ def add_cycle_constraints(prob, y, cycles, added_constraints): for j in range(i + 1, len(cycle)): constraint_1 = ((cycle[i], cycle[j]), (cycle[j], cycle[i])) if constraint_1 not in added_constraints: - # einer von beiden ist wahr + # One of the two must be true prob += y[(cycle[i], cycle[j])] + y[(cycle[j], cycle[i])] == 1 added_constraints.add(constraint_1) @@ -66,10 +70,11 @@ def add_cycle_constraints(prob, y, cycles, added_constraints): if (cycle[i], cycle[j]) in y and (cycle[j], cycle[k]) in y and (cycle[i], cycle[k]) in y: constraint_2 = ((cycle[i], cycle[j]), (cycle[j], cycle[k]), (cycle[i], cycle[k])) if constraint_2 not in added_constraints: - #i < j und j < l -> i< k reihenfolgen constraint transitivität + # i < j and j < k -> i < k (transitivity constraint) prob += 0 <= y[(cycle[i], cycle[j])] + y[(cycle[j], cycle[k])] <= 1 + y[(cycle[i], cycle[k])] added_constraints.add(constraint_2) +# Solve the bipartite minimization problem def solve_bipartite_minimization(input_lines): edges = [] for line in input_lines: @@ -82,33 +87,39 @@ def solve_bipartite_minimization(input_lines): else: x, y = map(int, line.split()) edges.append((x, y)) - logging.info(f"{len(edges)} Kanten geladen.") + logging.info(f"{len(edges)} edges loaded.") + # Define the linear programming problem prob = LpProblem("Minimize_Crossings", LpMinimize) + + # Define binary variables for the edges y = {(i, j): LpVariable(f"y_{i}_{j}", 0, 1, cat='Binary') for i in range(n0 + 1, n0 + n1 + 1) for j in range(n0 + 1, n0 + n1 + 1) if i != j} + + # Define binary variables for crossings c = {(i, j, k, l): LpVariable(f"c_{i}_{j}_{k}_{l}", 0, 1, cat='Binary') for (i, j) in edges for (k, l) in edges} + # Objective function: minimize the sum of crossings prob += lpSum(c.values()) + # Add constraints to the problem for (i, j) in edges: for (k, l) in edges: if k > i: if j > l: prob += c[(i, j, k, l)] == y[(l, j)] elif l > j: - prob += c[(i, j, k, l)] == y[(l, j)] + prob += c[(i, j, k, l)] == 1 - y[(j, l)] added_constraints = set() - iteration = 0 + # The maximum cycle length at which the elimination of all cycles begins max_cycle_length = 9 while True: - iteration += 1 prob.solve(PULP_CBC_CMD(msg=0)) - if prob.status != LpStatusOptimal: break + # Build the graph based on the current solution graph = defaultdict(list) in_degree = defaultdict(int) nodes = range(n0 + 1, n0 + n1 + 1) @@ -124,25 +135,27 @@ def solve_bipartite_minimization(input_lines): graph[j].append(i) in_degree[i] += 1 + # Detect all cycles in the graph all_cycles = find_all_cycles(graph, nodes) if not all_cycles: break + # Prioritize the cycles by their length prioritized_cycles = prioritize_cycles(all_cycles) - # Berechne k basierend auf dem aktuellen Prozentsatz - # Überprüfe die Länge des längsten Zyklus und passe k entsprechend an + # Check the length of the longest cycle and adjust k accordingly if len(prioritized_cycles[-1]) > max_cycle_length: k = min(int(math.floor(len(prioritized_cycles)/4)), len(prioritized_cycles)) else: k = len(prioritized_cycles) - # Nehme nur die ersten k Zyklen aus der priorisierten Liste + + # Take only the first k cycles from the prioritized list cycles_to_process = prioritized_cycles[:k] add_cycle_constraints(prob, y, cycles_to_process, added_constraints) if prob.status == LpStatusOptimal: - + # Topologically sort the nodes based on the in-degree zero_in_degree_queue = deque([i for i in nodes if in_degree[i] == 0]) sorted_b = [] while zero_in_degree_queue: @@ -153,11 +166,11 @@ def solve_bipartite_minimization(input_lines): if in_degree[neighbor] == 0: zero_in_degree_queue.append(neighbor) - a = count_crossings_via_variables(c) return sorted_b else: - return - + return None + +# Main function to read input and solve the problem def main(): input_lines = sys.stdin.read().strip().split('\n') result = solve_bipartite_minimization(input_lines) -- GitLab