From eea755b796f77ab5b9bb8aa917c974aa29f8f68e Mon Sep 17 00:00:00 2001 From: konog98 <konog98@mi.fu-berlin.de> Date: Thu, 20 Jun 2024 00:29:28 +0200 Subject: [PATCH] =?UTF-8?q?Letzte=20Optimierung,=20opt2=20version=20mit=20?= =?UTF-8?q?kombination=20der=20Grad=201=20nodes=20f=C3=BCr=20den=20warm=20?= =?UTF-8?q?start?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/diff.py | 48 +++ .../medium_test_set/{instances => }/10.gr | 0 .../medium_test_set/{solutions => }/10.sol | 0 .../medium_test_set/{instances => }/11.gr | 0 .../medium_test_set/{solutions => }/11.sol | 0 src/mytests/solutions/12.sol | 124 ++++++++ src/mytests/solutions/falsch12.sol | 3 + src/mytests/solutions/korrekt12.sol | 3 + ...t_besserer_zykel_betrachtung_und_cache.log | 14 + src/solver2.bat | 3 + src/solver_knoten_grad_1_2_try.py | 249 +++++++++++++++ src/solver_knoten_grad_1_einsortieren_opt.py | 63 ++-- src/solver_knoten_grad_1_einsortieren_opt2.py | 300 ++++++++++++++++++ src/solver_opt.py | 3 +- 14 files changed, 787 insertions(+), 23 deletions(-) create mode 100644 src/diff.py rename src/githubtests/medium_test_set/{instances => }/10.gr (100%) rename src/githubtests/medium_test_set/{solutions => }/10.sol (100%) rename src/githubtests/medium_test_set/{instances => }/11.gr (100%) rename src/githubtests/medium_test_set/{solutions => }/11.sol (100%) create mode 100644 src/mytests/solutions/12.sol create mode 100644 src/mytests/solutions/falsch12.sol create mode 100644 src/mytests/solutions/korrekt12.sol create mode 100644 src/optimiert_mit_besserer_zykel_betrachtung_und_cache.log create mode 100644 src/solver2.bat create mode 100644 src/solver_knoten_grad_1_2_try.py create mode 100644 src/solver_knoten_grad_1_einsortieren_opt2.py diff --git a/src/diff.py b/src/diff.py new file mode 100644 index 0000000..a87914c --- /dev/null +++ b/src/diff.py @@ -0,0 +1,48 @@ +def read_file(filename): + with open(filename, 'r') as file: + lines = [line.strip().strip('[]').split(',') for line in file.read().splitlines()] + return [[int(node.strip()) for node in line] for line in lines] + +def find_all_violations(sublist, combined_list): + combined_index = {node: i for i, node in enumerate(combined_list)} + violations = [] + for i in range(len(sublist) - 1): + if sublist[i] not in combined_index or sublist[i + 1] not in combined_index: + violations.append((sublist[i], sublist[i + 1], "Node not found in combined list")) + elif combined_index[sublist[i]] > combined_index[sublist[i + 1]]: + violations.append((sublist[i], sublist[i + 1], "Order violation")) + return violations + +def check_order_preservation(correct_lists): + if len(correct_lists) < 3: + print("The file does not contain three lists.") + return + + list1 = correct_lists[0] + list2 = correct_lists[1] + combined_list = correct_lists[2] + + list1_violations = find_all_violations(list1, combined_list) + list2_violations = find_all_violations(list2, combined_list) + + if not list1_violations and not list2_violations: + print("The order of nodes in the first two lists is preserved in the third list.") + else: + if list1_violations: + print("The order of nodes in the first list is not preserved in the third list:") + for node1, node2, reason in list1_violations: + print(f"Node {node1} should come before Node {node2}. Reason: {reason}") + if list2_violations: + print("The order of nodes in the second list is not preserved in the third list:") + for node1, node2, reason in list2_violations: + print(f"Node {node1} should come before Node {node2}. Reason: {reason}") + +def main(): + correct_file = 'mytests/solutions/korrekt12.sol' + + correct_lists = read_file(correct_file) + + check_order_preservation(correct_lists) + +if __name__ == "__main__": + main() diff --git a/src/githubtests/medium_test_set/instances/10.gr b/src/githubtests/medium_test_set/10.gr similarity index 100% rename from src/githubtests/medium_test_set/instances/10.gr rename to src/githubtests/medium_test_set/10.gr diff --git a/src/githubtests/medium_test_set/solutions/10.sol b/src/githubtests/medium_test_set/10.sol similarity index 100% rename from src/githubtests/medium_test_set/solutions/10.sol rename to src/githubtests/medium_test_set/10.sol diff --git a/src/githubtests/medium_test_set/instances/11.gr b/src/githubtests/medium_test_set/11.gr similarity index 100% rename from src/githubtests/medium_test_set/instances/11.gr rename to src/githubtests/medium_test_set/11.gr diff --git a/src/githubtests/medium_test_set/solutions/11.sol b/src/githubtests/medium_test_set/11.sol similarity index 100% rename from src/githubtests/medium_test_set/solutions/11.sol rename to src/githubtests/medium_test_set/11.sol diff --git a/src/mytests/solutions/12.sol b/src/mytests/solutions/12.sol new file mode 100644 index 0000000..515e761 --- /dev/null +++ b/src/mytests/solutions/12.sol @@ -0,0 +1,124 @@ +171 +147 +177 +126 +114 +198 +204 +151 +145 +167 +220 +219 +173 +158 +112 +228 +125 +191 +178 +143 +218 +196 +154 +117 +115 +166 +118 +183 +136 +188 +165 +201 +161 +123 +130 +200 +210 +203 +172 +144 +142 +187 +153 +192 +190 +113 +160 +127 +222 +163 +230 +133 +181 +157 +225 +212 +174 +169 +131 +120 +109 +207 +229 +179 +134 +175 +141 +155 +122 +231 +211 +186 +223 +189 +184 +221 +206 +193 +168 +110 +137 +209 +138 +124 +176 +217 +164 +156 +159 +150 +139 +197 +132 +111 +227 +180 +148 +119 +135 +152 +224 +194 +116 +140 +216 +185 +199 +214 +208 +149 +205 +129 +170 +146 +121 +195 +232 +213 +182 +215 +202 +226 +128 +162 diff --git a/src/mytests/solutions/falsch12.sol b/src/mytests/solutions/falsch12.sol new file mode 100644 index 0000000..c3737b2 --- /dev/null +++ b/src/mytests/solutions/falsch12.sol @@ -0,0 +1,3 @@ +[17, 18, 19, 20, 11, 12, 13, 14] +[16, 15] +[17, 18, 19, 20, 11, 12, 13, 14, 16, 15] diff --git a/src/mytests/solutions/korrekt12.sol b/src/mytests/solutions/korrekt12.sol new file mode 100644 index 0000000..28a603a --- /dev/null +++ b/src/mytests/solutions/korrekt12.sol @@ -0,0 +1,3 @@ +1: [147, 171, 126, 177, 114, 198, 151, 204, 145, 167, 112, 158, 173, 219, 220, 218, 115, 117, 154, 196, 118, 166, 210, 142, 144, 172, 203, 113, 190, 192, 127, 160, 163, 222, 225, 168, 193, 206, 221, 110, 137, 209, 138, 124, 119, 148, 180, 227, 135, 152, 185, 216, 149, 208, 214, 121, 146, 170, 213, 232, 182, 202, 215, 128, 162] +Rest: [171, 147, 177, 126, 114, 198, 204, 151, 145, 167, 220, 219, 173, 158, 112, 136, 125, 191, 143, 218, 196, 154, 117, 115, 166, 118, 228, 183, 188, 178, 165, 201, 161, 123, 130, 200, 210, 203, 172, 144, 142, 187, 181, 153, 192, 190, 113, 160, 127, 222, 163, 230, 133, 157, 225, 212, 174, 169, 131, 120, 109, 207, 229, 179, 141, 155, 134, 175, 122, 231, 211, 186, 223, 189, 184, 221, 206, 193, 168, 110, 137, 209, 138, 124, 176, 217, 164, 156, 159, 150, 139, 132, 111, 227, 180, 148, 119, 135, 152, 224, 194, 205, 226, 197, 195, 116, 140, 216, 185, 199, 214, 208, 149, 129, 170, 146, 121, 232, 213, 182, 215, 202, 128, 162] +Zusammen: [171, 147, 177, 126, 114, 198, 204, 151, 145, 167, 220, 219, 173, 158, 112, 136, 125, 191, 143, 218, 196, 154, 117, 115, 166, 118, 228, 183, 188, 178, 165, 201, 161, 123, 130, 200, 210, 203, 172, 144, 142, 187, 181, 153, 192, 190, 113, 160, 127, 222, 163, 230, 133, 157, 225, 212, 174, 169, 131, 120, 109, 207, 229, 179, 141, 155, 134, 175, 122, 231, 211, 186, 223, 189, 184, 221, 206, 193, 168, 110, 137, 209, 138, 124, 176, 217, 164, 156, 159, 150, 139, 132, 111, 227, 180, 148, 119, 135, 152, 224, 194, 205, 226, 197, 195, 116, 140, 216, 185, 199, 214, 208, 149, 129, 170, 146, 121, 232, 213, 182, 215, 202, 128, 162] diff --git a/src/optimiert_mit_besserer_zykel_betrachtung_und_cache.log b/src/optimiert_mit_besserer_zykel_betrachtung_und_cache.log new file mode 100644 index 0000000..4625f15 --- /dev/null +++ b/src/optimiert_mit_besserer_zykel_betrachtung_und_cache.log @@ -0,0 +1,14 @@ +2024-06-13 13:58:20,155 - Prozess f�r githubtests/tiny_test_set/instances/complete_4_5.gr gestartet +2024-06-13 13:58:20,155 - Die Ausgabedatei wird mytests/solutions\complete_4_5.sol sein +2024-06-13 13:58:20,160 - Gr��en der Partitionen: A=4, B=5 +2024-06-13 13:58:20,160 - 20 Kanten geladen. +2024-06-13 13:58:20,162 - x, y und c geladen. +2024-06-13 13:58:20,162 - Zielfunktion aufgestellt. +2024-06-13 13:58:20,163 - Crossing Constraints aufgestellt. +2024-06-13 13:58:20,190 - Status der L�sung: Optimal +2024-06-13 13:58:20,191 - Alle gefundenen Zyklen: 0 St�ck. +2024-06-13 13:58:20,200 - Optimale L�sung gefunden. Ergebnisse werden gespeichert. +2024-06-13 13:58:20,201 - Ergebnisse in mytests/solutions\complete_4_5.sol gespeichert +2024-06-13 13:58:20,201 - Crossings: 60, in mytests/crossings\complete_4_5.cros gespeichert +2024-06-13 13:58:20,201 - Verstrichene Zeit: 0:00:00.046 (h:m:s.ms) + diff --git a/src/solver2.bat b/src/solver2.bat new file mode 100644 index 0000000..58afd78 --- /dev/null +++ b/src/solver2.bat @@ -0,0 +1,3 @@ +@echo off +python solver_knoten_grad_1_einsortieren_opt2.py %* + diff --git a/src/solver_knoten_grad_1_2_try.py b/src/solver_knoten_grad_1_2_try.py new file mode 100644 index 0000000..2c1df8d --- /dev/null +++ b/src/solver_knoten_grad_1_2_try.py @@ -0,0 +1,249 @@ +import math +import sys +import logging +from pulp import * +from collections import defaultdict, deque + +def count_crossings_via_variables(c_vars): + crossings = 0 + for c_var in c_vars.values(): + if c_var.varValue == 1: + crossings += 1 + return crossings + +def prioritize_cycles(cycles): + cycles.sort(key=len, reverse=False) + return cycles + +def detect_cycle(graph, node, visited, rec_stack, path, cycles): + visited[node] = True + rec_stack[node] = True + path.append(node) + + for neighbor in graph[node]: + if not visited[neighbor]: + detect_cycle(graph, neighbor, visited, rec_stack, path, cycles) + elif rec_stack[neighbor]: + cycle_start = path.index(neighbor) + cycles.append(path[cycle_start:].copy()) + + rec_stack[node] = False + path.pop() + +def find_all_cycles(graph, nodes): + visited = {node: False for node in nodes} + rec_stack = {node: False for node in nodes} + path = [] + cycles = [] + + for node in nodes: + if not visited[node]: + detect_cycle(graph, node, visited, rec_stack, path, cycles) + + unique_cycles = [] + seen = set() + for cycle in cycles: + cycle_tuple = tuple(sorted(cycle)) + if cycle_tuple not in seen: + seen.add(cycle_tuple) + unique_cycles.append(cycle) + + return unique_cycles + +def add_cycle_constraints(prob, y, cycles, added_constraints): + for cycle in cycles: + cycle = list(dict.fromkeys(cycle)) + for i in range(len(cycle)): + 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: + prob += y[(cycle[i], cycle[j])] + y[(cycle[j], cycle[i])] == 1 + added_constraints.add(constraint_1) + + for k in range(j + 1, len(cycle)): + 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: + prob += 0 <= y[(cycle[i], cycle[j])] + y[(cycle[j], cycle[k])] <= 1 + y[(cycle[i], cycle[k])] + added_constraints.add(constraint_2) + +def calculate_crossings(nodes, neighbors_a): + pos = {node: idx for idx, node in enumerate(nodes)} + crossings = 0 + for i, u in enumerate(nodes): + for j in range(i + 1, len(nodes)): + v = nodes[j] + for a in neighbors_a[u]: + for b in neighbors_a[v]: + if (a < b and pos[u] > pos[v]) or (a > b and pos[u] < pos[v]): + crossings += 1 + return crossings + +def insert_node_with_min_crossings(sorted_nodes, degree_one_node, neighbors_a, start_index): + min_crossings = float('inf') + min_index = start_index + for i in range(start_index, len(sorted_nodes) + 1): + new_nodes = sorted_nodes[:i] + [degree_one_node] + sorted_nodes[i:] + new_crossings = calculate_crossings(new_nodes, neighbors_a) + #print(f"{i} = {new_crossings} crossings") + if new_crossings < min_crossings: + min_crossings = new_crossings + min_index = i + return sorted_nodes[:min_index] + [degree_one_node] + sorted_nodes[min_index:], min_index + + +def solve_bipartite_minimization(input_lines): + edges = [] + degrees_b = defaultdict(int) + neighbors_a = defaultdict(list) + + for line in input_lines: + if line.startswith('c'): + continue + elif line.startswith('p'): + parts = line.split() + n0 = int(parts[2]) + n1 = int(parts[3]) + else: + x, y = map(int, line.split()) + edges.append((x, y)) + degrees_b[y] += 1 + neighbors_a[y].append(x) + #print(edges) + #print(neighbors_a) + + # Identify and sort nodes in partition B with degree 1 + degree_one_nodes = [node for node, degree in degrees_b.items() if degree == 1] + #print(degree_one_nodes) + degree_one_nodes.sort(key=lambda b: neighbors_a[b][0]) + #print(degree_one_nodes) + + degree_two_nodes = [node for node, degree in degrees_b.items() if degree == 2] + + # Remove degree one nodes from nodes list + all_nodes = set(range(n0 + 1, n0 + n1 + 1)) + remaining_nodes = [node for node in all_nodes if node not in degree_one_nodes] + if len(remaining_nodes)==0: + return degree_one_nodes + + remaining_nodes = [node for node in all_nodes if node not in degree_one_nodes and node not in degree_two_nodes] + + # Remove edges that involve degree one nodes + #filtered_edges = [(i, j) for (i, j) in edges if j not in degree_one_nodes] + filtered_edges = [(i, j) for (i, j) in edges if j not in degree_one_nodes and j not in degree_two_nodes] + + # Handle degree two nodes + for node in degree_two_nodes: + neighbors = neighbors_a[node] + if len(neighbors) == 2: + a, b = neighbors + if (a, b) not in filtered_edges and (b, a) not in filtered_edges: + filtered_edges.append((a, b)) + + # ILP for remaining nodes + prob = LpProblem("Minimize_Crossings", LpMinimize) + y = {(i, j): LpVariable(f"y_{i}_{j}", 0, 1, cat='Binary') for i in remaining_nodes for j in remaining_nodes if i != j} + c = {(i, j, k, l): LpVariable(f"c_{i}_{j}_{k}_{l}", 0, 1, cat='Binary') for (i, j) in filtered_edges for (k, l) in filtered_edges} + + prob += lpSum(c.values()) + + for (i, j) in filtered_edges: + for (k, l) in filtered_edges: + if k > i: + if j > l and (l, j) in y: + prob += c[(i, j, k, l)] == y[(l, j)] + elif l > j and (j, l) in y: + prob += c[(i, j, k, l)] == 1 - y[(j, l)] + + added_constraints = set() + max_cycle_length = 9 + + while True: + prob.solve(PULP_CBC_CMD(msg=0)) + + if prob.status != LpStatusOptimal: + break + + graph = defaultdict(list) + in_degree = defaultdict(int) + for i in remaining_nodes: + for j in remaining_nodes: + if i != j: + y_ij = y.get((i, j)) + if y_ij is not None: + if y_ij.varValue == 1: + graph[i].append(j) + in_degree[j] += 1 + elif y_ij.varValue == 0: + graph[j].append(i) + in_degree[i] += 1 + + all_cycles = find_all_cycles(graph, remaining_nodes) + + if not all_cycles: + break + + prioritized_cycles = prioritize_cycles(all_cycles) + + 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) + cycles_to_process = prioritized_cycles[:k] + add_cycle_constraints(prob, y, cycles_to_process, added_constraints) + + # Collect the topologically sorted order of the remaining nodes + if prob.status == LpStatusOptimal: + zero_in_degree_queue = deque([i for i in remaining_nodes if in_degree[i] == 0]) + sorted_remaining_nodes = [] + while zero_in_degree_queue: + node = zero_in_degree_queue.popleft() + sorted_remaining_nodes.append(node) + for neighbor in graph[node]: + in_degree[neighbor] -= 1 + if in_degree[neighbor] == 0: + zero_in_degree_queue.append(neighbor) + #print(neighbors_a) + #print("degree_one: ", degree_one_nodes) + #print("remaining: ", sorted_remaining_nodes) + + # Insert degree one nodes into sorted_remaining_nodes with minimal crossings + # Handle degree two nodes + for node in degree_two_nodes: + neighbors = neighbors_a[node] + if len(neighbors) == 2: + a, b = neighbors + if (a, b) not in filtered_edges and (b, a) not in filtered_edges: + filtered_edges.append((a, b)) + + start_index = 0 + + for degree_one_node in degree_one_nodes: + sorted_remaining_nodes, start_index = insert_node_with_min_crossings(sorted_remaining_nodes, degree_one_node, neighbors_a, start_index) + + # Insert degree two nodes back into the final order + for degree_two_node in degree_two_nodes: + neighbors = neighbors_a[degree_two_node] + if len(neighbors) == 2: + a, b = neighbors + if a in sorted_remaining_nodes and b in sorted_remaining_nodes: + pos_a = sorted_remaining_nodes.index(a) + pos_b = sorted_remaining_nodes.index(b) + insert_pos = (pos_a + pos_b) // 2 + sorted_remaining_nodes = sorted_remaining_nodes[:insert_pos] + [degree_two_node] + sorted_remaining_nodes[insert_pos:] + + return sorted_remaining_nodes + else: + return None + +def main(): + input_lines = sys.stdin.read().strip().split('\n') + result = solve_bipartite_minimization(input_lines) + + if result: + print("\n".join(map(str, result))) + else: + print("No result") + +if __name__ == "__main__": + main() diff --git a/src/solver_knoten_grad_1_einsortieren_opt.py b/src/solver_knoten_grad_1_einsortieren_opt.py index d674171..962c0e8 100644 --- a/src/solver_knoten_grad_1_einsortieren_opt.py +++ b/src/solver_knoten_grad_1_einsortieren_opt.py @@ -85,13 +85,15 @@ def insert_node_with_min_crossings(sorted_nodes, degree_one_node, neighbors_a, s for i in range(start_index, len(sorted_nodes) + 1): new_nodes = sorted_nodes[:i] + [degree_one_node] + sorted_nodes[i:] new_crossings = calculate_crossings(new_nodes, neighbors_a) + #print(f"{i} = {new_crossings} crossings") if new_crossings < min_crossings: min_crossings = new_crossings min_index = i return sorted_nodes[:min_index] + [degree_one_node] + sorted_nodes[min_index:], min_index - def solve_bipartite_minimization(input_lines): + base_name = os.path.basename('korrekt12.sol') + output_file = os.path.join('mytests/solutions', base_name) edges = [] degrees_b = defaultdict(int) neighbors_a = defaultdict(list) @@ -108,18 +110,16 @@ def solve_bipartite_minimization(input_lines): edges.append((x, y)) degrees_b[y] += 1 neighbors_a[y].append(x) - #print(edges) - #print(neighbors_a) + # Identify and sort nodes in partition B with degree 1 degree_one_nodes = [node for node, degree in degrees_b.items() if degree == 1] - #print(degree_one_nodes) degree_one_nodes.sort(key=lambda b: neighbors_a[b][0]) - #print(degree_one_nodes) + # Remove degree one nodes from nodes list all_nodes = set(range(n0 + 1, n0 + n1 + 1)) remaining_nodes = [node for node in all_nodes if node not in degree_one_nodes] - if len(remaining_nodes)==0: + if len(remaining_nodes) == 0: return degree_one_nodes # Remove edges that involve degree one nodes @@ -133,16 +133,16 @@ def solve_bipartite_minimization(input_lines): prob += lpSum(c.values()) for (i, j) in filtered_edges: - for (k, l) in filtered_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)] == 1 - y[(j, l)] + for (k, l) in filtered_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)] == 1 - y[(j, l)] added_constraints = set() max_cycle_length = 9 - + iteration = 1 while True: prob.solve(PULP_CBC_CMD(msg=0)) @@ -164,18 +164,19 @@ def solve_bipartite_minimization(input_lines): in_degree[i] += 1 all_cycles = find_all_cycles(graph, remaining_nodes) - + #print("Zykel_a",iteration," ",len(all_cycles)) if not all_cycles: break prioritized_cycles = prioritize_cycles(all_cycles) if len(prioritized_cycles[-1]) > max_cycle_length: - k = min(int(math.floor(len(prioritized_cycles) / 4)), len(prioritized_cycles)) + k = min(int(math.floor(len(prioritized_cycles) / 2)), len(prioritized_cycles)) else: k = len(prioritized_cycles) cycles_to_process = prioritized_cycles[:k] add_cycle_constraints(prob, y, cycles_to_process, added_constraints) + iteration += 1 # Collect the topologically sorted order of the remaining nodes if prob.status == LpStatusOptimal: @@ -188,23 +189,43 @@ def solve_bipartite_minimization(input_lines): in_degree[neighbor] -= 1 if in_degree[neighbor] == 0: zero_in_degree_queue.append(neighbor) - #print(neighbors_a) - #print("degree_one: ", degree_one_nodes) - #print("remaining: ", sorted_remaining_nodes) - + + # Combine all nodes + combined_nodes = sorted_remaining_nodes + degree_one_nodes + # Insert degree one nodes into sorted_remaining_nodes with minimal crossings start_index = 0 for degree_one_node in degree_one_nodes: sorted_remaining_nodes, start_index = insert_node_with_min_crossings(sorted_remaining_nodes, degree_one_node, neighbors_a, start_index) - return sorted_remaining_nodes + os.makedirs(os.path.dirname(output_file), exist_ok=True) + with open(output_file, 'w') as f: + f.write(f"1: {degree_one_nodes}\n") + f.write(f"Rest: {sorted_remaining_nodes}\n") + f.write(f"Zusammen: {sorted_remaining_nodes}\n") + + # Optimize the order of remaining nodes using heuristic method + final_sorted_nodes = optimize_remaining_nodes(sorted_remaining_nodes, neighbors_a) + + return final_sorted_nodes else: return None +def optimize_remaining_nodes(sorted_nodes, neighbors_a): + improvement = True + while improvement: + improvement = False + for i in range(len(sorted_nodes) - 1): + for j in range(i + 1, len(sorted_nodes)): + new_nodes = sorted_nodes[:i] + [sorted_nodes[j]] + sorted_nodes[i+1:j] + [sorted_nodes[i]] + sorted_nodes[j+1:] + if calculate_crossings(new_nodes, neighbors_a) < calculate_crossings(sorted_nodes, neighbors_a): + sorted_nodes = new_nodes + improvement = True + return sorted_nodes + def main(): input_lines = sys.stdin.read().strip().split('\n') result = solve_bipartite_minimization(input_lines) - if result: print("\n".join(map(str, result))) else: diff --git a/src/solver_knoten_grad_1_einsortieren_opt2.py b/src/solver_knoten_grad_1_einsortieren_opt2.py new file mode 100644 index 0000000..4de13a9 --- /dev/null +++ b/src/solver_knoten_grad_1_einsortieren_opt2.py @@ -0,0 +1,300 @@ +import math +import sys +import logging +from pulp import * +from collections import defaultdict, deque + +def count_crossings_via_variables(c_vars): + crossings = 0 + for c_var in c_vars.values(): + if c_var.varValue == 1: + crossings += 1 + return crossings + +def prioritize_cycles(cycles): + cycles.sort(key=len, reverse=False) + return cycles + +def detect_cycle(graph, node, visited, rec_stack, path, cycles): + visited[node] = True + rec_stack[node] = True + path.append(node) + + for neighbor in graph[node]: + if not visited[neighbor]: + detect_cycle(graph, neighbor, visited, rec_stack, path, cycles) + elif rec_stack[neighbor]: + cycle_start = path.index(neighbor) + cycles.append(path[cycle_start:].copy()) + + rec_stack[node] = False + path.pop() + +def find_all_cycles(graph, nodes): + visited = {node: False for node in nodes} + rec_stack = {node: False for node in nodes} + path = [] + cycles = [] + + for node in nodes: + if not visited[node]: + detect_cycle(graph, node, visited, rec_stack, path, cycles) + + unique_cycles = [] + seen = set() + for cycle in cycles: + cycle_tuple = tuple(sorted(cycle)) + if cycle_tuple not in seen: + seen.add(cycle_tuple) + unique_cycles.append(cycle) + + return unique_cycles + +def add_cycle_constraints(prob, y, cycles, added_constraints): + for cycle in cycles: + cycle = list(dict.fromkeys(cycle)) + for i in range(len(cycle)): + 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: + prob += y[(cycle[i], cycle[j])] + y[(cycle[j], cycle[i])] == 1 + added_constraints.add(constraint_1) + + for k in range(j + 1, len(cycle)): + 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: + prob += 0 <= y[(cycle[i], cycle[j])] + y[(cycle[j], cycle[k])] <= 1 + y[(cycle[i], cycle[k])] + added_constraints.add(constraint_2) + +def calculate_crossings(nodes, neighbors_a): + pos = {node: idx for idx, node in enumerate(nodes)} + crossings = 0 + for i, u in enumerate(nodes): + for j in range(i + 1, len(nodes)): + v = nodes[j] + for a in neighbors_a[u]: + for b in neighbors_a[v]: + if (a < b and pos[u] > pos[v]) or (a > b and pos[u] < pos[v]): + crossings += 1 + return crossings + +def insert_node_with_min_crossings(sorted_nodes, degree_one_node, neighbors_a, start_index): + min_crossings = float('inf') + min_index = start_index + for i in range(start_index, len(sorted_nodes) + 1): + new_nodes = sorted_nodes[:i] + [degree_one_node] + sorted_nodes[i:] + new_crossings = calculate_crossings(new_nodes, neighbors_a) + #print(f"{i} = {new_crossings} crossings") + if new_crossings < min_crossings: + min_crossings = new_crossings + min_index = i + return sorted_nodes[:min_index] + [degree_one_node] + sorted_nodes[min_index:], min_index + + +def solve_bipartite_minimization(input_lines): + base_name = os.path.basename('korrekt12.sol') + output_file = os.path.join('mytests/solutions', base_name) + edges = [] + degrees_b = defaultdict(int) + neighbors_a = defaultdict(list) + + for line in input_lines: + if line.startswith('c'): + continue + elif line.startswith('p'): + parts = line.split() + n0 = int(parts[2]) + n1 = int(parts[3]) + else: + x, y = map(int, line.split()) + edges.append((x, y)) + degrees_b[y] += 1 + neighbors_a[y].append(x) + #print(edges) + #print(neighbors_a) + # Identify and sort nodes in partition B with degree 1 + degree_one_nodes = [node for node, degree in degrees_b.items() if degree == 1] + #print(degree_one_nodes) + degree_one_nodes.sort(key=lambda b: neighbors_a[b][0]) + #print(degree_one_nodes) + # Remove degree one nodes from nodes list + all_nodes = set(range(n0 + 1, n0 + n1 + 1)) + remaining_nodes = [node for node in all_nodes if node not in degree_one_nodes] + #print(remaining_nodes) + if len(remaining_nodes)==0: + return degree_one_nodes + + # Remove edges that involve degree one nodes + filtered_edges = [(i, j) for (i, j) in edges if j not in degree_one_nodes] + + # ILP for remaining nodes + prob = LpProblem("Minimize_Crossings", LpMinimize) + y = {(i, j): LpVariable(f"y_{i}_{j}", 0, 1, cat='Binary') for i in remaining_nodes for j in remaining_nodes if i != j} + c = {(i, j, k, l): LpVariable(f"c_{i}_{j}_{k}_{l}", 0, 1, cat='Binary') for (i, j) in filtered_edges for (k, l) in filtered_edges} + + prob += lpSum(c.values()) + + for (i, j) in filtered_edges: + for (k, l) in filtered_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)] == 1 - y[(j, l)] + + added_constraints = set() + max_cycle_length = 9 + iteration = 1 + while True: + prob.solve(PULP_CBC_CMD(msg=0)) + + if prob.status != LpStatusOptimal: + break + + graph = defaultdict(list) + in_degree = defaultdict(int) + for i in remaining_nodes: + for j in remaining_nodes: + if i != j: + y_ij = y.get((i, j)) + if y_ij is not None: + if y_ij.varValue == 1: + graph[i].append(j) + in_degree[j] += 1 + elif y_ij.varValue == 0: + graph[j].append(i) + in_degree[i] += 1 + + all_cycles = find_all_cycles(graph, remaining_nodes) + #print("Zykel_a",iteration," ",len(all_cycles)) + if not all_cycles: + break + + prioritized_cycles = prioritize_cycles(all_cycles) + + if len(prioritized_cycles[-1]) > max_cycle_length: + k = min(int(math.floor(len(prioritized_cycles) / 2)), len(prioritized_cycles)) + else: + k = len(prioritized_cycles) + cycles_to_process = prioritized_cycles[:k] + add_cycle_constraints(prob, y, cycles_to_process, added_constraints) + iteration += 1 + + # Collect the topologically sorted order of the remaining nodes + if prob.status == LpStatusOptimal: + zero_in_degree_queue = deque([i for i in remaining_nodes if in_degree[i] == 0]) + sorted_remaining_nodes = [] + while zero_in_degree_queue: + node = zero_in_degree_queue.popleft() + sorted_remaining_nodes.append(node) + for neighbor in graph[node]: + in_degree[neighbor] -= 1 + if in_degree[neighbor] == 0: + zero_in_degree_queue.append(neighbor) + #print(neighbors_a) + #print("degree_one: ", degree_one_nodes) + #print("remaining: ", sorted_remaining_nodes) + # Combine all nodes + combined_nodes = sorted_remaining_nodes + degree_one_nodes + # Insert degree one nodes into sorted_remaining_nodes with minimal crossings + # Use the result as a warm start for a new ILP + final_prob = LpProblem("Minimize_Final_Crossings", LpMinimize) + final_y = {(i, j): LpVariable(f"final_y_{i}_{j}", 0, 1, cat='Binary') for i in combined_nodes for j in combined_nodes if i != j} + final_c = {(i, j, k, l): LpVariable(f"final_c_{i}_{j}_{k}_{l}", 0, 1, cat='Binary') for (i, j) in edges for (k, l) in edges} + + final_prob += lpSum(final_c.values()) + + for (i, j) in edges: + for (k, l) in edges: + if k > i: + if j > l: + final_prob += final_c[(i, j, k, l)] == final_y[(l, j)] + elif l > j: + final_prob += final_c[(i, j, k, l)] == 1 - final_y[(j, l)] + """ + # Warm start using the sorted_remaining_nodes + for i in range(len(sorted_remaining_nodes) - 1): + for j in range(i + 1, len(sorted_remaining_nodes) - 1): + final_y[(sorted_remaining_nodes[i], sorted_remaining_nodes[j])].setInitialValue(1) + final_y[(sorted_remaining_nodes[j], sorted_remaining_nodes[i])].setInitialValue(0) + """ + for i in range(len(sorted_remaining_nodes) - 1): + final_y[(sorted_remaining_nodes[i], sorted_remaining_nodes[i + 1])].setInitialValue(1) + for i in range(len(degree_one_nodes) - 1): + final_y[(degree_one_nodes[i], degree_one_nodes[i + 1])].setInitialValue(1) + + added_constraints = set() + max_cycle_length = 9 + iteration = 1 + while True: + final_prob.solve(PULP_CBC_CMD(msg=0, warmStart=True)) + + if final_prob.status != LpStatusOptimal: + break + + graph = defaultdict(list) + in_degree = defaultdict(int) + for i in all_nodes: + for j in all_nodes: + if i != j: + y_ij = final_y.get((i, j)) + if y_ij is not None: + if y_ij.varValue == 1: + graph[i].append(j) + in_degree[j] += 1 + elif y_ij.varValue == 0: + graph[j].append(i) + in_degree[i] += 1 + + all_cycles = find_all_cycles(graph, all_nodes) + #print("Zykel_b",iteration," ",len(all_cycles)) + if not all_cycles: + break + + prioritized_cycles = prioritize_cycles(all_cycles) + + 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) + cycles_to_process = prioritized_cycles[:k] + add_cycle_constraints(final_prob, final_y, cycles_to_process, added_constraints) + iteration += 1 + + # Collect the topologically sorted order of the remaining nodes + if final_prob.status == LpStatusOptimal: + + zero_in_degree_queue = deque([i for i in all_nodes if in_degree[i] == 0]) + final_sorted_nodes = [] + while zero_in_degree_queue: + node = zero_in_degree_queue.popleft() + final_sorted_nodes.append(node) + for neighbor in graph[node]: + in_degree[neighbor] -= 1 + if in_degree[neighbor] == 0: + zero_in_degree_queue.append(neighbor) + + os.makedirs(os.path.dirname(output_file), exist_ok=True) + with open(output_file, 'w') as f: + f.write(f"{degree_one_nodes}\n") + f.write(f"{sorted_remaining_nodes}\n") + f.write(f"{final_sorted_nodes}\n") + + return final_sorted_nodes + + else: + return None + else: + return None + +def main(): + input_lines = sys.stdin.read().strip().split('\n') + result = solve_bipartite_minimization(input_lines) + if result: + print("\n".join(map(str, result))) + else: + print("No result") + +if __name__ == "__main__": + main() diff --git a/src/solver_opt.py b/src/solver_opt.py index c858917..8d745f2 100644 --- a/src/solver_opt.py +++ b/src/solver_opt.py @@ -96,7 +96,7 @@ def solve_bipartite_minimization(input_lines): 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 @@ -153,7 +153,6 @@ 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 -- GitLab