From f017d9454d25d94660bd29a16065dae69ae79405 Mon Sep 17 00:00:00 2001 From: voic00 <voic00@mi.fu-berlin.de> Date: Wed, 5 Jun 2024 18:41:28 +0200 Subject: [PATCH] =?UTF-8?q?teile=20y=20auf=20je=20nachdem=20ob=20dazugeh?= =?UTF-8?q?=C3=B6riger=20Knoten=20von=20Grad=201=20oder=20nicht?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main_iterative_opt.py | 630 +++++++++++++++++++------------------- 1 file changed, 317 insertions(+), 313 deletions(-) diff --git a/src/main_iterative_opt.py b/src/main_iterative_opt.py index c8937de..268f6c4 100644 --- a/src/main_iterative_opt.py +++ b/src/main_iterative_opt.py @@ -1,313 +1,317 @@ -import math -import os -import logging -import time -from datetime import datetime, timedelta -from pulp import * -from collections import defaultdict, deque - -# Erstelle einen FileHandler und einen StreamHandler -# betrachtet alle möglichen zykel, geht diese aber schrittweise ab und betrachtet nicht alle auf einmal, mit überprüfung für existenz der constraints -file_handler = logging.FileHandler('optimiert_mit_besserer_zykel_betrachtung_und_cache.log') -console_handler = logging.StreamHandler() -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s', handlers=[file_handler, console_handler]) - -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): - # Beispiel: Zyklen nach Länge priorisieren - 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) - - # Entferne doppelte Zyklen - 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: - # einer von beiden ist wahr - 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: - #i < j und j < l -> i< k reihenfolgen constraint transitivität - prob += 0 <= y[(cycle[i], cycle[j])] + y[(cycle[j], cycle[k])] <= 1 + y[(cycle[i], cycle[k])] - added_constraints.add(constraint_2) - logging.info(f"Constraint hinzugefügt für: {cycle}") - logging.info(f"Constraint hinzugefügt für alle bestehenden cycles.") - -def add_edge_to_node_dict(dict : dict[int, list[int]], edge : tuple[int, int], minBPartitionValue : int) -> None: - # Find out which Value belongs to B - x, y = edge - bNode, aNode = 0, 0 - # If the graph is truly bi-partite we can only check one value the other HAS TO be in the other partition - if x >= minBPartitionValue: - bNode, aNode = x, y - else: - bNode, aNode = y, x - - # Add node to dict if not already there - if not bNode in dict: - dict[bNode] = [] - - # Append edge to dictionary of edges - dict[bNode].append(aNode) - -def parse_graph_file(graph_file : str) -> tuple[int, int, list[tuple[int ,int]]]: - edges = [] - number_of_nodes_in_A = 0 - number_of_nodes_in_B = 0 - with open(graph_file, "r") as file: - for line in file: - if line.startswith('c'): - continue - elif line.startswith('p'): - parts = line.split() - number_of_nodes_in_A = int(parts[2]) - number_of_nodes_in_B = int(parts[3]) - else: - x, y = map(int, line.split()) - edges.append((x, y)) - - return number_of_nodes_in_A, number_of_nodes_in_B, edges - - -def solve_bipartite_minimization(graph_file): - start_time = time() - logging.info(f"Prozess für {graph_file} gestartet") - graph_name = os.path.basename(graph_file) - solution_name = graph_name.replace('.gr', '.sol') - crossing_name = graph_name.replace('.gr', '.cros') - solution_path = os.path.join('mytests/solutions', solution_name) - crossing_path = os.path.join('mytests/crossings', crossing_name) - logging.info(f"Die Ausgabedatei wird {solution_path} sein") - - number_of_nodes_in_A, number_of_nodes_in_B, edges = parse_graph_file(graph_file) - logging.info(f"Größen der Partitionen: A={number_of_nodes_in_A}, B={number_of_nodes_in_B}") - logging.info(f"{len(edges)} Kanten geladen.") - - # Suche nach allen Knoten B, die nur eine Kante haben - # Als erstes: Erstelle Dict mit Keys = Knoten in B, Value = Liste aller Kanten - B_nodes_with_associated_A_nodes : dict[int, list[int]]= {} - for edge in edges: - add_edge_to_node_dict(B_nodes_with_associated_A_nodes, edge, number_of_nodes_in_A + 1) - - # Erstelle Liste aller Nodes in B mit einem Grad größer 1 - nodes_with_degree_higher_one = dict(filter(lambda keyValuePair: len(keyValuePair[1]) > 1, B_nodes_with_associated_A_nodes.items())) - - # Erstelle Liste aller Nodes in B mit einem Grad = 1 und - nodes_with_degree_equals_one = {bNode: aNodes[0] for (bNode, aNodes) in B_nodes_with_associated_A_nodes.items() if len(aNodes) == 1} - # Sortiere sie nach ihrem korrospondierenden Knoten Wert von A, dann sind alle Knoten in B mit Grad 1 so sortiert, dass ihre Kanten kreuzungsfrei sind - nodes_with_degree_equals_one.sort(key= lambda node: node[1]) - # Erstelle Liste mit nur den Grad 1 Knoten aus B, erleichtert nachher das Filtern der Edges - nodes_with_degree_equals_one_bNode_only = [bNode for bNode, _ in nodes_with_degree_equals_one] - logging.info(f"{len(B_nodes_with_associated_A_nodes)} viele Knoten, davon mit Grad 1: {len(nodes_with_degree_equals_one)} und Knoten Grads > 1: {len(nodes_with_degree_higher_one)}.") - - # Erstelle die Listen der Kanten, sortiert danach, ob sie in B mit einem Knoten mit Grad 1 verbunden sind, oder höhergradig sind - edges_of_degree_one_nodes = [(x, y) for x, y in edges if x in nodes_with_degree_equals_one_bNode_only or y in nodes_with_degree_equals_one_bNode_only] - edges_for_nodes_with_higher_degree = [(x, y) for x, y in edges if x in nodes_with_degree_higher_one or y in nodes_with_degree_higher_one] - logging.info(f"{len(edges)} viele Kanten, davon: Für Grad 1 : {len(edges_of_degree_one_nodes)} Für Grad > 1: {len(edges_for_nodes_with_higher_degree)}") - - # Nutze nur die Knoten mit mehr als einer Kante - # Löse mit den Knoten, die mehr als 1 Kante haben - # Löse Zyklen auf - # Füge Knoten mit 1 Kante zu den Variablen hinzu - # Löse neues ILP - # Löse Zyklen auf - # Ende - - - - - prob = LpProblem("Minimize_Crossings", LpMinimize) - - # Erstelle Konstanten für die Sortierung - # positionalVariablesForDegreeOne = {(nodes_with_degree_equals_one[i], nodes_with_degree_equals_one[i+1]) : LpVariable(f"y_{nodes_with_degree_equals_one[i]}_{nodes_with_degree_equals_one[i+1]}", 0, 1, cat='Binary') for i in range(len(nodes_with_degree_equals_one)-1)} - # crossingVariablesForDegreeOne = {(i, j, k, l): LpVariable(f"c_{i}_{j}_{k}_{l}", 0, 1, cat='Binary') for (i, j) in edges_of_degree_one_nodes for (k, l) in edges_of_degree_one_nodes} - - # positionalVariablesForMultiDegree = {(nodes_with_degree_higher_one[i], nod)} - # crossingVariablesForMultiDegree = {} - - # crossingVariablesBetweenMultiAndOne = {} - - # Variable y(i, j) : Liegt i links von j ? Wenn ja 1, sonst 0. - positional_vars_without_degree_one_nodes = {} - for nodeX in range(number_of_nodes_in_A + 1, number_of_nodes_in_A + number_of_nodes_in_B + 1): - if not nodeX in nodes_with_degree_equals_one: - continue - for nodeY in range(number_of_nodes_in_A + 1, number_of_nodes_in_A + number_of_nodes_in_B + 1): - if not nodeY in nodes_with_degree_equals_one: - continue - if i == j: - continue - positional_vars_without_degree_one_nodes[(nodeX, nodeY)] = LpVariable(f"y_{nodeX}_{nodeY}", 0, 1, cat='Binary') - positional_vars_without_degree_one_nodes[(nodeX, nodeY)].setInitialValue(0) - - # Setze die Variablen, der Knoten, die Grad 1 haben so, dass die Ordnung bereits vorgegeben ist (Hoffnung hier ist, dass damit der Solver schon mit einer besseren Lösung anfangen kann, weil er dieses triviale Probem nicht zusätzlich lösen muss) - for i in range(len(nodes_with_degree_equals_one)-1): - positional_vars_without_degree_one_nodes[(nodes_with_degree_equals_one[i][0], nodes_with_degree_equals_one[i+1][0])].setInitialValue(1) - - # Erstelle die Crossing constraints für die Kanten, die an Knoten liegen, die Grad = 1 haben - for (nodeB, nodeA) in nodes_with_degree_equals_one: - for (k, l) in edges: - - positionalVariables = {(i, j): LpVariable(f"y_{i}_{j}", 0, 1, cat='Binary') for i in range(number_of_nodes_in_A + 1, number_of_nodes_in_A + number_of_nodes_in_B + 1) for j in range(number_of_nodes_in_A + 1, number_of_nodes_in_A + number_of_nodes_in_B + 1) if i != j} - # Variable c(i,j,k,l) : Kreuzt die Kante zwischen i-j die Kante zwischen k-l - crossingVariables = {(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} - logging.info("y und c geladen.") - - prob += lpSum(crossingVariables.values()) - logging.info("Zielfunktion aufgestellt.") - - for (i, j) in edges: - for (k, l) in edges: - if k > i: - if j > l: - prob += crossingVariables[(i, j, k, l)] == positionalVariables[(l, j)] - elif l > j: - prob += crossingVariables[(i, j, k, l)] == 1 - positionalVariables[(j, l)] - logging.info("Crossing Constraints aufgestellt.") - - added_constraints = set() - iteration = 0 - max_cycle_length = 9 - - while True: - iteration += 1 - prob.solve() - logging.info(f"Status der Lösung: {LpStatus[prob.status]}") - if prob.status != LpStatusOptimal: - logging.warning("Keine optimale Lösung gefunden.") - break - - graph = defaultdict(list) - in_degree = defaultdict(int) - nodes = range(number_of_nodes_in_A + 1, number_of_nodes_in_A + number_of_nodes_in_B + 1) - for i in nodes: - for j in nodes: - if i != j: - y_ij = positionalVariables.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, nodes) - logging.info(f"Alle gefundenen Zyklen: {len(all_cycles)} Stück.") - - if not all_cycles: - break - - prioritized_cycles = prioritize_cycles(all_cycles) - logging.info(f"Priorisierte Zyklen aufgestellt.") - - # Überprüfe die Länge des längsten Zyklus und passe k entsprechend an - 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] - logging.info(f"Zyklen zur Verarbeitung in dieser Iteration: {cycles_to_process}") - logging.info(f"Anzahl der Zyklen zur Verarbeitung in dieser Iteration: {len(cycles_to_process)}") - add_cycle_constraints(prob, positionalVariables, cycles_to_process, added_constraints) - - if prob.status == LpStatusOptimal: - logging.info("Optimale Lösung gefunden. Ergebnisse werden gespeichert.") - - zero_in_degree_queue = deque([i for i in nodes if in_degree[i] == 0]) - sorted_b = [] - while zero_in_degree_queue: - node = zero_in_degree_queue.popleft() - sorted_b.append(node) - for neighbor in graph[node]: - in_degree[neighbor] -= 1 - if in_degree[neighbor] == 0: - zero_in_degree_queue.append(neighbor) - - a = count_crossings_via_variables(crossingVariables) - os.makedirs(os.path.dirname(solution_path), exist_ok=True) - with open(solution_path, 'w') as f: - for b in sorted_b: - f.write(f"{b}\n") - print(f"{b}") - print("Crossings: ", a) - logging.info(f"Ergebnisse in {solution_path} gespeichert") - - a = count_crossings_via_variables(crossingVariables) - os.makedirs(os.path.dirname(crossing_path), exist_ok=True) - with open(crossing_path, 'w') as f: - f.write(f"Crossings: {a}\n") - logging.info(f"Crossings: {a}, in {crossing_path} gespeichert") - else: - logging.warning("Keine optimale Lösung gefunden.") - - end_time = time() - elapsed_time = end_time - start_time - elapsed_time_td = timedelta(seconds=elapsed_time) - formatted_time = str(elapsed_time_td) - formatted_time = formatted_time[:-3] - logging.info(f"Verstrichene Zeit: {formatted_time} (h:m:s.ms) \n") - -def process_directory(directory_path): - for filename in os.listdir(directory_path): - if filename.endswith('.gr'): - file_path = os.path.join(directory_path, filename) - solve_bipartite_minimization(file_path) - logging.info(f"Verarbeitung abgeschlossen für {file_path}") - -# directory_path = 'githubtests/tiny_test_set/instances/' -# directory_path = 'mytests/instances/' -# process_directory(directory_path) - -# test_file = 'githubtests/tiny_test_set/instances/complete_4_5.gr' -test_file = 'githubtests/medium_test_set/instances/10.gr' -#test_file = 'mytests/instances/80.gr' -solve_bipartite_minimization(test_file) +import math +import os +import logging +import time +from datetime import datetime, timedelta +from pulp import * +from collections import defaultdict, deque + +# Erstelle einen FileHandler und einen StreamHandler +# betrachtet alle möglichen zykel, geht diese aber schrittweise ab und betrachtet nicht alle auf einmal, mit überprüfung für existenz der constraints +file_handler = logging.FileHandler('optimiert_mit_besserer_zykel_betrachtung_und_cache.log') +console_handler = logging.StreamHandler() +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s', handlers=[file_handler, console_handler]) + +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): + # Beispiel: Zyklen nach Länge priorisieren + 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) + + # Entferne doppelte Zyklen + 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: + # einer von beiden ist wahr + 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: + #i < j und j < l -> i< k reihenfolgen constraint transitivität + prob += 0 <= y[(cycle[i], cycle[j])] + y[(cycle[j], cycle[k])] <= 1 + y[(cycle[i], cycle[k])] + added_constraints.add(constraint_2) + logging.info(f"Constraint hinzugefügt für: {cycle}") + logging.info(f"Constraint hinzugefügt für alle bestehenden cycles.") + +def add_edge_to_node_dict(dict : dict[int, list[int]], edge : tuple[int, int], minBPartitionValue : int) -> None: + # Find out which Value belongs to B + x, y = edge + bNode, aNode = 0, 0 + # If the graph is truly bi-partite we can only check one value the other HAS TO be in the other partition + if x >= minBPartitionValue: + bNode, aNode = x, y + else: + bNode, aNode = y, x + + # Add node to dict if not already there + if not bNode in dict: + dict[bNode] = [] + + # Append edge to dictionary of edges + dict[bNode].append(aNode) + +def parse_graph_file(graph_file : str) -> tuple[int, int, list[tuple[int ,int]]]: + edges = [] + number_of_nodes_in_A = 0 + number_of_nodes_in_B = 0 + with open(graph_file, "r") as file: + for line in file: + if line.startswith('c'): + continue + elif line.startswith('p'): + parts = line.split() + number_of_nodes_in_A = int(parts[2]) + number_of_nodes_in_B = int(parts[3]) + else: + x, y = map(int, line.split()) + edges.append((x, y)) + + return number_of_nodes_in_A, number_of_nodes_in_B, edges + + +def solve_bipartite_minimization(graph_file): + start_time = time() + logging.info(f"Prozess für {graph_file} gestartet") + graph_name = os.path.basename(graph_file) + solution_name = graph_name.replace('.gr', '.sol') + crossing_name = graph_name.replace('.gr', '.cros') + solution_path = os.path.join('mytests/solutions', solution_name) + crossing_path = os.path.join('mytests/crossings', crossing_name) + logging.info(f"Die Ausgabedatei wird {solution_path} sein") + + number_of_nodes_in_A, number_of_nodes_in_B, edges = parse_graph_file(graph_file) + logging.info(f"Größen der Partitionen: A={number_of_nodes_in_A}, B={number_of_nodes_in_B}") + logging.info(f"{len(edges)} Kanten geladen.") + + # Suche nach allen Knoten B, die nur eine Kante haben + # Als erstes: Erstelle Dict mit Keys = Knoten in B, Value = Liste aller Kanten + B_nodes_with_associated_A_nodes : dict[int, list[int]]= {} + for edge in edges: + add_edge_to_node_dict(B_nodes_with_associated_A_nodes, edge, number_of_nodes_in_A + 1) + + # Erstelle Liste aller Nodes in B mit einem Grad größer 1 + nodes_with_degree_higher_one = dict(filter(lambda keyValuePair: len(keyValuePair[1]) > 1, B_nodes_with_associated_A_nodes.items())) + + # Erstelle Liste aller Nodes in B mit einem Grad = 1 und + nodes_with_degree_equals_one = [(bNode, aNodes[0]) for (bNode, aNodes) in B_nodes_with_associated_A_nodes.items() if len(aNodes) == 1] + # Sortiere sie nach ihrem korrospondierenden Knoten Wert von A, dann sind alle Knoten in B mit Grad 1 so sortiert, dass ihre Kanten kreuzungsfrei sind + nodes_with_degree_equals_one.sort(key= lambda node: node[1]) + # Erstelle Liste mit nur den Grad 1 Knoten aus B, erleichtert nachher das Filtern der Edges + nodes_with_degree_equals_one_bNode_only = [bNode for bNode, _ in nodes_with_degree_equals_one] + logging.info(f"{len(B_nodes_with_associated_A_nodes)} viele Knoten, davon mit Grad 1: {len(nodes_with_degree_equals_one)} und Knoten Grads > 1: {len(nodes_with_degree_higher_one)}.") + + # Erstelle die Listen der Kanten, sortiert danach, ob sie in B mit einem Knoten mit Grad 1 verbunden sind, oder höhergradig sind + edges_of_degree_one_nodes = [(x, y) for x, y in edges if x in nodes_with_degree_equals_one_bNode_only or y in nodes_with_degree_equals_one_bNode_only] + edges_for_nodes_with_higher_degree = [(x, y) for x, y in edges if x in nodes_with_degree_higher_one or y in nodes_with_degree_higher_one] + logging.info(f"{len(edges)} viele Kanten, davon: Für Grad 1 : {len(edges_of_degree_one_nodes)} Für Grad > 1: {len(edges_for_nodes_with_higher_degree)}") + + # Nutze nur die Knoten mit mehr als einer Kante + # Löse mit den Knoten, die mehr als 1 Kante haben + # Löse Zyklen auf + # Füge Knoten mit 1 Kante zu den Variablen hinzu + # Löse neues ILP + # Löse Zyklen auf + # Ende + + prob = LpProblem("Minimize_Crossings", LpMinimize) + + # Erstelle Konstanten für die Sortierung + # Variable y(i, j) : Liegt i links von j ? Wenn ja 1, sonst 0. + # positionalVariablesForDegreeOne = {(nodes_with_degree_equals_one[i], nodes_with_degree_equals_one[i+1]) : LpVariable(f"y_{nodes_with_degree_equals_one[i]}_{nodes_with_degree_equals_one[i+1]}", 0, 1, cat='Binary') for i in range(len(nodes_with_degree_equals_one)-1)} + # crossingVariablesForDegreeOne = {(i, j, k, l): LpVariable(f"c_{i}_{j}_{k}_{l}", 0, 1, cat='Binary') for (i, j) in edges_of_degree_one_nodes for (k, l) in edges_of_degree_one_nodes} + + # positionalVariablesForMultiDegree = {(nodes_with_degree_higher_one[i], nod)} + # crossingVariablesForMultiDegree = {} + + # crossingVariablesBetweenMultiAndOne = {} + + # Für alle Knoten {B | Knoten Grad == 1} x {B | Knoten Grad == 1}: erstelle Positions variable + positional_vars_degree_one_nodes_only = {} + for nodeX in range(number_of_nodes_in_A + 1, number_of_nodes_in_A + number_of_nodes_in_B + 1): + if nodeX in nodes_with_degree_higher_one: + continue + for nodeY in range(number_of_nodes_in_A + 1, number_of_nodes_in_A + number_of_nodes_in_B + 1): + if nodeY in nodes_with_degree_higher_one: + continue + if nodeX == nodeY: + continue + positional_vars_degree_one_nodes_only[(nodeX, nodeY)] = LpVariable(f"y_{nodeX}_{nodeY}", 0, 1, cat='Binary') + positional_vars_degree_one_nodes_only[(nodeX, nodeY)].setInitialValue(0) + + # Für alle Kanten (a ~ b) : Grad(b) > 1 + + + positional_vars_with_degree_one_nodes = {} + # Setze die Variablen, der Knoten, die Grad 1 haben so, dass die Ordnung bereits vorgegeben ist (Hoffnung hier ist, dass damit der Solver schon mit einer besseren Lösung anfangen kann, weil er dieses triviale Probem nicht zusätzlich lösen muss) + # for i in range(len(nodes_with_degree_equals_one)-1): + # positional_vars_with_degree_one_nodes[(nodes_with_degree_equals_one[i][0], nodes_with_degree_equals_one[i+1][0])].setInitialValue(1) + + # Erstelle die Crossing constraints für die Kanten, die an Knoten liegen, die Grad = 1 haben + # for (nodeB, nodeA) in nodes_with_degree_equals_one: + # for (k, l) in edges: + + + positionalVariables = {(i, j): LpVariable(f"y_{i}_{j}", 0, 1, cat='Binary') for i in range(number_of_nodes_in_A + 1, number_of_nodes_in_A + number_of_nodes_in_B + 1) for j in range(number_of_nodes_in_A + 1, number_of_nodes_in_A + number_of_nodes_in_B + 1) if i != j and not (i, j) in positional_vars_degree_one_nodes_only} + # Variable c(i,j,k,l) : Kreuzt die Kante zwischen i-j die Kante zwischen k-l + crossingVariables = {(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} + logging.info("y und c geladen.") + logging.info(f"Positions Variablen y: gesamt erwartet {number_of_nodes_in_B * (number_of_nodes_in_B - 1)}, davon wirklich gesamt {len(positional_vars_degree_one_nodes_only) + len(positionalVariables)}, für Knoten mit Grad 1: {len(positional_vars_degree_one_nodes_only)}, für Knoten mit Grad > 1: {len(positionalVariables)}") + + prob += lpSum(crossingVariables.values()) + logging.info("Zielfunktion aufgestellt.") + + for (i, j) in edges: + for (k, l) in edges: + if k > i: + if j > l: + prob += crossingVariables[(i, j, k, l)] == positionalVariables[(l, j)] + elif l > j: + prob += crossingVariables[(i, j, k, l)] == 1 - positionalVariables[(j, l)] + logging.info("Crossing Constraints aufgestellt.") + + added_constraints = set() + iteration = 0 + max_cycle_length = 9 + + while True: + iteration += 1 + prob.solve() + logging.info(f"Status der Lösung: {LpStatus[prob.status]}") + if prob.status != LpStatusOptimal: + logging.warning("Keine optimale Lösung gefunden.") + break + + graph = defaultdict(list) + in_degree = defaultdict(int) + nodes = range(number_of_nodes_in_A + 1, number_of_nodes_in_A + number_of_nodes_in_B + 1) + for i in nodes: + for j in nodes: + if i != j: + y_ij = positionalVariables.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, nodes) + logging.info(f"Alle gefundenen Zyklen: {len(all_cycles)} Stück.") + + if not all_cycles: + break + + prioritized_cycles = prioritize_cycles(all_cycles) + logging.info(f"Priorisierte Zyklen aufgestellt.") + + # Überprüfe die Länge des längsten Zyklus und passe k entsprechend an + 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] + logging.info(f"Zyklen zur Verarbeitung in dieser Iteration: {cycles_to_process}") + logging.info(f"Anzahl der Zyklen zur Verarbeitung in dieser Iteration: {len(cycles_to_process)}") + add_cycle_constraints(prob, positionalVariables, cycles_to_process, added_constraints) + + if prob.status == LpStatusOptimal: + logging.info("Optimale Lösung gefunden. Ergebnisse werden gespeichert.") + + zero_in_degree_queue = deque([i for i in nodes if in_degree[i] == 0]) + sorted_b = [] + while zero_in_degree_queue: + node = zero_in_degree_queue.popleft() + sorted_b.append(node) + for neighbor in graph[node]: + in_degree[neighbor] -= 1 + if in_degree[neighbor] == 0: + zero_in_degree_queue.append(neighbor) + + a = count_crossings_via_variables(crossingVariables) + os.makedirs(os.path.dirname(solution_path), exist_ok=True) + with open(solution_path, 'w') as f: + for b in sorted_b: + f.write(f"{b}\n") + print(f"{b}") + print("Crossings: ", a) + logging.info(f"Ergebnisse in {solution_path} gespeichert") + + a = count_crossings_via_variables(crossingVariables) + os.makedirs(os.path.dirname(crossing_path), exist_ok=True) + with open(crossing_path, 'w') as f: + f.write(f"Crossings: {a}\n") + logging.info(f"Crossings: {a}, in {crossing_path} gespeichert") + else: + logging.warning("Keine optimale Lösung gefunden.") + + end_time = time() + elapsed_time = end_time - start_time + elapsed_time_td = timedelta(seconds=elapsed_time) + formatted_time = str(elapsed_time_td) + formatted_time = formatted_time[:-3] + logging.info(f"Verstrichene Zeit: {formatted_time} (h:m:s.ms) \n") + +def process_directory(directory_path): + for filename in os.listdir(directory_path): + if filename.endswith('.gr'): + file_path = os.path.join(directory_path, filename) + solve_bipartite_minimization(file_path) + logging.info(f"Verarbeitung abgeschlossen für {file_path}") + +# directory_path = 'githubtests/tiny_test_set/instances/' +# directory_path = 'mytests/instances/' +# process_directory(directory_path) + +# test_file = 'githubtests/tiny_test_set/instances/complete_4_5.gr' +test_file = 'githubtests/medium_test_set/instances/10.gr' +#test_file = 'mytests/instances/80.gr' +solve_bipartite_minimization(test_file) -- GitLab