diff --git a/algo_dynam_results.pdf b/algo_dynam_results.pdf new file mode 100644 index 0000000..0057c19 Binary files /dev/null and b/algo_dynam_results.pdf differ diff --git a/comparaison_performance_partie1.txt b/comparaison_performance_partie1.txt new file mode 100644 index 0000000..ebfba83 --- /dev/null +++ b/comparaison_performance_partie1.txt @@ -0,0 +1,13 @@ +image: Algorithme naif: Algorithme optimisé: +0 0.0028851750130093655 0.004501615848153342 +1 0.0062464710737695234 0.010497580080201702 +2 1.059739164690073 4.061047410988084 +3 1.833371857650475 3.4331085488818265 +4 4.868523815337736 8.655220795702977 +5 1.459479728109199 4.091194763537741 +6 4.482694679179365 10.416106483583306 +7 6.101156954389975 10.042951255082741 +8 7.140349026362092 15.369357323512201 +9 40.6351284874879 179.92582705782087 +10 IndexError (?) 242.58593905887662 + diff --git a/constants.py b/constants.py new file mode 100644 index 0000000..8a13da9 --- /dev/null +++ b/constants.py @@ -0,0 +1,6 @@ +class const: + WHITE = 0 + BLACK = 1 + NOT_COLORED = 0.5 + LINES = 0 + COLUMNS = 1 diff --git a/mogpl_part1.py b/mogpl_part1.py new file mode 100644 index 0000000..47a0312 --- /dev/null +++ b/mogpl_part1.py @@ -0,0 +1,227 @@ +import numpy as np +import matplotlib.pyplot as plt +from constants import * + +""" +@param file_name: le nom d'un fichier contenant une instance du problème +@return sequences: une liste de deux listes. La première contient la description des lignes (une liste par ligne), + la deuxième la description des colonnes (une liste par colonne). + + Par exemple: pour l'exemple donné dans l'énoncé, la fonction renvoie + [[[3], [], [1, 1, 1], [3]], [[1, 1], [1], [1, 2], [1], [2]]] + + [3] - la première ligne contienne un bloc de la longueur 3 + [] - aucun bloc sur la deuxième ligne + + [1,1] - la première colonne contienne deux blocs, chacun de longueur 1 + + etc. +""" +def read_file(file_name): + sequences = [[], []] #première liste pour descriptions des lignes, deuxième piur description des colonnes + + indice = const.LINES # d'abord, on décrit les lignes - on va remplir sequences[0] + + with open(file_name) as file: + for line in file: #pour chaque ligne du fichier + if len(line) > 0 and line[0] == '#': # on passe de la déscription des lignes à celle des colonnes + indice = const.COLUMNS + else: #il s'agit d'une ligne décrivant une ligne/colonne + #on transforme la ligne des caractères en une liste des entiers, et on l'append à la fin de sequences[0]/sequences[1] + sequences[indice].append(list(map(int, line.split()))) + + return sequences + + +""" Réponse à la question Q4: + + Fonction qui permet de décider si il est possible de colorier la ligne avec sa séquence associée + @param line_length: le nombre de cases de la ligne + @param line_sequence: la séquence des blocs avec lesquels on veut colorier la ligne + @return : True si possible de colorier toute la ligne avec toutes les blocs, False sinon +""" +def color_line(line_length, line_sequence): + + #si la séquence est vide (aucun bloc), on renvoie directement True + if not line_sequence: + return True + + #sinon: + + T = np.zeros((line_length, len(line_sequence)+1)) #T[j][l] = True si possible de colorier j+1 premières cases avec l premiers blocs, False sinon + + #Quelques cas de base: + for j in range(line_length): + T[j][0] = True #il est toujours possible de colorier n'importe quel nombre de cases avec 0 blocs + T[j][1] = (j >= line_sequence[0]-1) #cas où on a un seul bloc - on peut colorier la ligne avec ssi sa longueur est <= nombre de cases considérées + + for l in range(2, len(line_sequence)+1): #cas où ou considère au moins 2 blocs + for j in range(line_length): + if j < sum(line_sequence[0:l]): #si la somme des longueurs des blocs est plus grand que le nombre de cases considérés, T[j][l] est fausse + T[j][l] = False + else: #sinon + # - soit le dernière bloc finit sur la case j+1. Dans un tel cas, T[j][l] est vraie ssi on peut faire rentrer les blocs précédents aux cases + # devant le début de ce dernière bloc (il ne faut pas oublié une espace blanche entre deux blocs!) + + # - soit la dernière case est blanche. Dans ce cas, T[j][l] ssi T[j-1][l] + T[j][l] = T[j - line_sequence[l-1] -1][l-1] or T[j-1][l] + + #On renvoie la "dernière" case du tableau indiquant si on peut colorier toute la ligne avec toute la séquence + return T[-1][-1] + + +""" Réponse à la question 6: + La fonction permet de décider si on peut colorier toute la ligne avec toute la séquence, sachant que certaines cases + sont déjà fixées à noire ou blanche. + + @param line: une liste de longueur M (M = nombre de colonnes) dont le i-ième élément est: + - const.BLACK si la i-ième case de la ligne est déjà noire + - const.WHITE si li i-ième case de la ligne est déjà blanche + - const.NOT_COLORED si la case n'était pas encore coloriée + @param line_sequence: la séquence des blocs avec lesquels on veut colorier la ligne + @return : True si possible de colorier toute la ligne avec toutes les blocs, False sinon +""" +def color_line_bis(line, line_sequence): + + #si la séquence est vide (aucun bloc), on renvoie directement True + if not line_sequence: + return (const.BLACK not in line) + + #sinon: + line_length = len(line) + T = np.zeros((line_length, len(line_sequence)+1)) #T[j][l] = True si possible de colorier j+1 premières cases avec l premiers blocs, False sinon + + #Cas où on considère 0 blocs + for j in range(line_length): + T[j][0] = True + + #Si on considère un seul bloc: + for j in range(line_length): + if j< line_sequence[0] -1: #si le nombre de cases est plus petit que la taille d'un bloc + T[j][1] = False + elif j == line_sequence[0]-1 : #nombre de cases est exactement la taille d'un bloc + #le bloc commence sur la première case et finit sur la dernière case considérée. La case suivante n'est pas noire + T[j][1] = ((const.WHITE not in line[j-line_sequence[0]+1:j+1]) and (line[j+1] != const.BLACK)) + elif j< line_length-1: + #si le nombre de cases est au moins la longueur du bloc, mais pas la ligne en entière: + # - soit le bloc finit sur la case j+1. Dans ce cas, toutes les cases entre son début et fin doivent être noires ou indéterminés. + # La case juste avant le début ne peut pas être noire, et la case après la fin ne peut pas être noire non plus. + # - soit le bloc se finit avant (partie droite de la disjonction) + T[j][1] = ((const.WHITE not in line[j-line_sequence[0]+1:j+1]) and (line[j-line_sequence[0]] != const.BLACK) and (line[j+1] != const.BLACK)) or (True in T[:,1][0:j]) + else: #on considère la ligne en entière - le même cas que précédamment, on n'a juste pas la case suivante + T[j][1] = ((const.WHITE not in line[j-line_sequence[0]+1:j+1]) and (line[j-line_sequence[0]] != const.BLACK)) or (True in T[:,1][0:j]) + + + #On considère enfin plus qu'un bloc: + for l in range(2, len(line_sequence)+1): + for j in range(line_length): + if j < sum(line_sequence[0:l]): #si la somme des longueurs des blocs est plus grand que le nombre de cases considérés, T[j][l] est fausse + T[j][l] = False + elif j< line_length-1: + # - soit le dernier bloc finit sur la case j+1. Dans ce cas là, aucune case entre le début et la fin du bloc ne peut pas être coloriée à blanche, + # et la case suivante et précédente ne peut pas être noire. + # - soit le dernier bloc finit avant, dans quel cas la case j+1 ne peut pas être noire et T[j-1][l] doit être vraie pour qu'on ait T[j][l] vraie + T[j][l] = (T[j - line_sequence[l-1] -1][l-1] and const.WHITE not in line[j-line_sequence[l-1]+1:j+1] and line[j-line_sequence[l-1]] != const.BLACK and line[j+1] != const.BLACK) or (T[j-1][l] and line[j] != const.BLACK) + + else: # j = line_lenght+1 + T[j][l] = (T[j - line_sequence[l-1] -1][l-1] and const.WHITE not in line[j-line_sequence[l-1] +1 :j+1] and line[j-line_sequence[l-1]] != const.BLACK) or (T[j-1][l] and line[j] != const.BLACK) + print(T) + return T[-1][-1] + +def coloration(A, sequences): + lines_to_see = [i for i in range(len(A))] + columns_to_see = [i for i in range(len(A[0]))] + + + + while lines_to_see or columns_to_see: + for i in lines_to_see: + new_to_see = [] + for j in range(len(A[i])): + can_colour = [False, False] + if A[i][j] == const.NOT_COLORED: + A[i][j] = const.WHITE + can_colour[const.WHITE] = colour_line_bis(A[i], sequences[const.LINES][i]) + A[i][j] = const.BLACK + can_colour[const.BLACK] = colour_line_bis(A[i], sequences[const.LINES][i]) + + if (can_colour[const.WHITE]) and (not can_colour[const.BLACK]): + A[i][j] = const.WHITE + new_to_see += [j] + elif (can_colour[const.BLACK]) and (not can_colour[const.WHITE]): + A[i][j] = const.BLACK + new_to_see += [j] + elif (can_colour[const.WHITE]) and (can_colour[const.BLACK]): + A[i][j] = const.NOT_COLORED + else: + return null #pas de solution + + + columns_to_see = list(set().union(columns_to_see, new_to_see)) + + lines_to_see = [] + + for j in columns_to_see: + new_to_see = [] + for i in range(len(np.transpose(A)[j])): + can_colour = [False, False] + if A[i][j] == const.NOT_COLORED: + A[i][j] = const.WHITE + can_colour[const.WHITE] = colour_line_bis(np.transpose(A)[j], sequences[const.COLUMNS][j]) + A[i][j] = const.BLACK + can_colour[const.BLACK] = colour_line_bis(np.transpose(A)[j], sequences[const.COLUMNS][j]) + if (can_colour[const.WHITE]) and (not can_colour[const.BLACK]): + A[i][j] = const.WHITE + new_to_see += [i] + elif (can_colour[const.BLACK]) and (not can_colour[const.WHITE]): + A[i][j] = const.BLACK + new_to_see += [i] + elif (can_colour[const.WHITE]) and (can_colour[const.BLACK]): + A[i][j] = const.NOT_COLORED + else: + return null #pas de solution + + + lines_to_see = list(set().union(lines_to_see, new_to_see)) + + columns_to_see = [] + + return A + + +#Quelques testes - TODO 29.1.2017: à nettoyer/organiser +sequences = read_file("0.txt") +print("Contenu du fichier: ",sequences) + +nb_lines = len(sequences[0]) +nb_columns = len(sequences[1]) + +T = color_line(nb_columns, sequences[0][2]) +print(T) + +#appel de la fonction coloration +A = np.full((nb_lines, nb_columns), const.NOT_COLORED) +coloration(A, sequences) + +#pour visualiser la coloriage + +plt.subplot(211) +plt.imshow(A, cmap = "Greys", interpolation = "nearest") +ax = plt.gca() + +# Major ticks +ax.set_xticks(np.arange(0, nb_columns, 1)); +ax.set_yticks(np.arange(0, nb_lines, 1)); + +# Minor ticks +ax.set_xticks(np.arange(-.5, nb_columns, 1), minor=True); +ax.set_yticks(np.arange(-.5, nb_lines, 1), minor=True); + +ax.grid(which = "minor", color='grey', linestyle='-', linewidth=2) +plt.show() + + + +line = [-1 for i in range(nb_columns)] +line[3] = const.BLACK +print(color_line_bis(line, sequences[0][2])) diff --git a/mogpl_part1_v2.py b/mogpl_part1_v2.py new file mode 100644 index 0000000..daa5d63 --- /dev/null +++ b/mogpl_part1_v2.py @@ -0,0 +1,271 @@ +# -*- coding: utf-8 -*- +import numpy as np +import matplotlib.pyplot as plt +from constants import * +import datetime +import timeit +import time + +""" +@param file_name: le nom d'un fichier contenant une instance du problème +@return sequences: une liste de deux listes. La première contient la description des lignes (une liste par ligne), + la deuxième la description des colonnes (une liste par colonne). + + Par exemple: pour l'exemple donné dans l'énoncé, la fonction renvoie + [[[3], [], [1, 1, 1], [3]], [[1, 1], [1], [1, 2], [1], [2]]] + + [3] - la première ligne contienne un bloc de la longueur 3 + [] - aucun bloc sur la deuxième ligne + + [1,1] - la première colonne contienne deux blocs, chacun de longueur 1 + + etc. +""" + + +def read_file(file_name): + sequences = [[], []] # première liste pour descriptions des lignes, deuxième pour description des colonnes + + indice = const.LINES # d'abord, on décrit les lignes - on va remplir sequences[0] + + with open(file_name) as file: + for line in file: # pour chaque ligne du fichier + if len(line) > 0 and line[0] == '#': # on passe de la déscription des lignes à celle des colonnes + indice = const.COLUMNS + else: # il s'agit d'une ligne décrivant une ligne/colonne + # on transforme la ligne des caractères en une liste des entiers, et on l'append à la fin de sequences[0]/sequences[1] + sequences[indice].append(list(map(int, line.split()))) + + return sequences + + +""" Réponse à la question Q4: + + Fonction qui permet de décider si il est possible de colorier la ligne avec sa séquence associée + @param line_length: le nombre de cases de la ligne + @param line_sequence: la séquence des blocs avec lesquels on veut colorier la ligne + @return : True si possible de colorier toute la ligne avec toutes les blocs, False sinon +""" + + +def colour_line(line_length, line_sequence): + # si la séquence est vide (aucun bloc), on renvoie directement True + if not line_sequence: + return True + + # sinon: + + T = np.zeros((line_length, len( + line_sequence) + 1)) # T[j][l] = True si possible de colorier j+1 premières cases avec l premiers blocs, False sinon + + # Quelques cas de base: + for j in range(line_length): + T[j][0] = True # il est toujours possible de colorier n'importe quel nombre de cases avec 0 blocs + T[j][1] = (j >= line_sequence[ + 0] - 1) # cas où on a un seul bloc - on peut colorier la ligne avec ssi sa longueur est <= nombre de cases considérées + + for l in range(2, len(line_sequence) + 1): # cas où ou considère au moins 2 blocs + for j in range(line_length): + if j < sum(line_sequence[ + 0:l]): # si la somme des longueurs des blocs est plus grand que le nombre de cases considérés, T[j][l] est fausse + T[j][l] = False + else: # sinon + # - soit le dernière bloc finit sur la case j+1. Dans un tel cas, T[j][l] est vraie ssi on peut faire rentrer les blocs précédents aux cases + # devant le début de ce dernière bloc (il ne faut pas oublié une espace blanche entre deux blocs!) + + # - soit la dernière case est blanche. Dans ce cas, T[j][l] ssi T[j-1][l] + T[j][l] = T[j - line_sequence[l - 1] - 1][l - 1] or T[j - 1][l] + + # On renvoie la "dernière" case du tableau indiquant si on peut colorier toute la ligne avec toute la séquence + return T[-1][-1] + + +""" Réponse à la question 6: + La fonction permet de décider si on peut colorier toute la ligne avec toute la séquence, sachant que certaines cases + sont déjà fixées à noire ou blanche. + + @param line: une liste de longueur M (M = nombre de colonnes) dont le i-ième élément est: + - const.BLACK si la i-ième case de la ligne est déjà noire + - const.WHITE si li i-ième case de la ligne est déjà blanche + - const.NOT_COLORED si la case n'était pas encore coloriée + @param line_sequence: la séquence des blocs avec lesquels on veut colorier la ligne + @return : True si possible de colorier toute la ligne avec toutes les blocs, False sinon +""" + + +def colour_line_bis(line, line_sequence): + # si la séquence est vide (aucun bloc), la réponse est TRUE ssi il n'y a pas de case déjà coloriée à noir. + if not line_sequence: + return (const.BLACK not in line) + + # si la ligne est entièrement colorée, il suffit de vérifier si le nombre de colonnes noires égale à la somme des longueure des blocs + # en effet, si cette fonction reçoit la ligne totalement colorée, cela signifie qu'on est en train de tester si la dernière case non colorée + # peut etre noire ou blanche, sqchqnt que les autres cases sont colorées de la bonne manière (UNIQUE); grace à cette unicité, la réponse est OUI + # si et seulement si la somme des cases noires est bonne + if (const.NOT_COLORED not in line): + return (list(line).count(const.BLACK) == sum(line_sequence)) + + # sinon: + line_length = len(line) + T = np.zeros((line_length, len( + line_sequence) + 1)) # T[j][l] = True si possible de colorier j+1 premières cases avec l premiers blocs, False sinon + + # Cas où on considère 0 blocs: + for j in range(line_length): + # on peut colorier j+1 premieres cases avec 0 blocs ssi il n'y a pas de case noire (qui imposerait une présence d'un bloc) + T[j][0] = const.BLACK not in line[0:j + 1] + + # Cas 2a + for l in range(len(line_sequence)): # l correspond à (l+1) blocs considérés + for j in range(0, sum(line_sequence[0:l + 1]) + l - 1): + # si j est < la somme des longueurs des (l+1) blocs considérés + l (si l+1 blocs, au moins l cases de plus pour les séparer), + # les blocs ne rentrent pas - T[j][l+1] est fausse + T[j][l + 1] = False + + # Cas 2b + # si on considère un seul bloc: T[j][1] = 1 si j+1 est la longueur du bloc et il n'y a pas de cases blanches + j = line_sequence[0] - 1 + T[j][1] = (const.WHITE not in line[0:j + 1]) and (line_length <= line_sequence[0] or line[j + 1] != const.BLACK) + + # sinon, si on considère plus qu'un bloc, la réponse sera toujours FAUX + # TODO 18.11.: je trouve que cette condition est un peu redondant si on a remplacé dans Cas 2a line_sequence[l]-1 par sum(line_sequence[0:l+1])+l-1, + # ce qui est d'ailleurs beuacoup plus efficace! A vérifier et éventuellement supprimer + for l in range(1, len(line_sequence)): + j = line_sequence[l] - 1 + T[j][l + 1] = False + + # Cas 2c - on considère au moins un bloc, et j >= sum(line_sequence[0:l+1])+l-1 + for l in range(len(line_sequence)): # pour chacun des blocs l correspond à l+1 blocs considérés + for j in range(sum(line_sequence[0:l + 1]) + l - 1, + line_length): # pour toutes les valeurs de j pas encore traitées + + # Soit le dernier bloc se termine sur la derniere case considerée : + + # 1) La derniere case ne peut pas etre blanc, ainsi que les cases precedentes qui forment le bloc: + last_black = const.WHITE not in (line[j - line_sequence[l] + 1:j + 1]) + # 2) La case avant soit n'existe pas, soit elle n'est pas coloriée au noir. + box_before = (j - line_sequence[l] < 0 or line[j - line_sequence[l]] != const.BLACK) + # 3) La case aprés soit n'existe pas, soit elle n'est pas coloriée au noir + box_after = (line_length == j + 1 or line[j + 1] != const.BLACK) + # 4) La relation de récursion est bien vérifiée - on peut remplire les cases avant le début de ce bloc (et avant la case "de séparation") + # avec l blocs précédentes + rec = (l == 0 or (j - line_sequence[l] - 1 >= 0 and T[j - line_sequence[l] - 1][l])) + + # Le dernier bloc peut finir sur la case j ssi + # - toutes les conditions 1-4 sont vérifiées + # - de plus, la somme des cases noires après la case j+1 (on finit sur j, il faut pas oublier la séparation) ne dépasse pas la somme des longueurs des blocs suivants + # - de plus, la somme des cases noires avant le début de ce dernier bloc ne dépasse pas la somme des longueurs des blocs précédents + ends_by_black = last_black and box_before and box_after and rec and ( + list(line[j + 2:]).count(const.BLACK) <= sum(line_sequence[l + 1:])) and ( + list(line[0:(j - line_sequence[l] + 1)]).count(const.BLACK) <= sum(line_sequence[0:l])) + + # Soit le dernier bloc se termine avant la dernière case considérée: + # la dernière case ne peut pas être noire, on doit être capable de rentrer les blocs dans les cases précedentes + # - et de plus, on calcule les cases noires avant/après de façon analogue qu'avant + ends_by_white = ((line[j] != const.BLACK) and T[j - 1][l + 1]) and ( + list(line[j + 1:]).count(const.BLACK) <= sum(line_sequence[l + 1:])) and ( + list(line[0:j]).count(const.BLACK) <= sum(line_sequence[0:l + 1])) + + # ENFIN, T[j][l+1] vrai si au moins une des deux confgurations ci-dessus possible + T[j][l + 1] = (ends_by_white or ends_by_black) + + return T[-1][-1] + + +def coloration(A, sequences): + lines_to_see = [i for i in range(len(A))] + columns_to_see = [i for i in range(len(A[0]))] + + while lines_to_see or columns_to_see: + for i in lines_to_see: + new_to_see = [] + for j in range(len(A[i])): + can_colour = [False, False] + if A[i][j] == const.NOT_COLORED: + A[i][j] = const.WHITE + can_colour[const.WHITE] = colour_line_bis(A[i], sequences[const.LINES][i]) + A[i][j] = const.BLACK + can_colour[const.BLACK] = colour_line_bis(A[i], sequences[const.LINES][i]) + + if (can_colour[const.WHITE]) and (not can_colour[const.BLACK]): + A[i][j] = const.WHITE + new_to_see += [j] + elif (can_colour[const.BLACK]) and (not can_colour[const.WHITE]): + A[i][j] = const.BLACK + new_to_see += [j] + elif (can_colour[const.WHITE]) and (can_colour[const.BLACK]): + A[i][j] = const.NOT_COLORED + else: + return None # pas de solution + + columns_to_see = list(set().union(columns_to_see, new_to_see)) + + lines_to_see = [] + + for j in columns_to_see: + new_to_see = [] + for i in range(len(np.transpose(A)[j])): + can_colour = [False, False] + if A[i][j] == const.NOT_COLORED: + A[i][j] = const.WHITE + can_colour[const.WHITE] = colour_line_bis(np.transpose(A)[j], sequences[const.COLUMNS][j]) + A[i][j] = const.BLACK + can_colour[const.BLACK] = colour_line_bis(np.transpose(A)[j], sequences[const.COLUMNS][j]) + if (can_colour[const.WHITE]) and (not can_colour[const.BLACK]): + A[i][j] = const.WHITE + new_to_see += [i] + elif (can_colour[const.BLACK]) and (not can_colour[const.WHITE]): + A[i][j] = const.BLACK + new_to_see += [i] + elif (can_colour[const.WHITE]) and (can_colour[const.BLACK]): + A[i][j] = const.NOT_COLORED + else: + return None # pas de solution + + lines_to_see = list(set().union(lines_to_see, new_to_see)) + + columns_to_see = [] + + return A + + +''' +num = 0 +sequences = read_file("instances/" + str(num)+".txt") +#print("Contenu du fichier: ",sequences) + +nb_lines = len(sequences[0]) +nb_columns = len(sequences[1]) +A = np.full((nb_lines, nb_columns), const.NOT_COLORED) + +start_time = timeit.default_timer() +coloration(A, sequences) +elapsed = timeit.default_timer() - start_time +print("Temps d'execution: ",elapsed) + +# Affichage de la figure +plt.imshow(A, cmap = "Greys", interpolation = "nearest") +#plt.rcParams["figure.figsize"] = (50,50) +ax = plt.gca() + +# Major ticks +ax.set_xticks(np.arange(0, nb_columns, 1)); +ax.set_yticks(np.arange(0, nb_lines, 1)); + +# Minor ticks +ax.set_xticks(np.arange(-.5, nb_columns, 1), minor=True); +ax.set_yticks(np.arange(-.5, nb_lines, 1), minor=True); + +ax.grid(which = "minor", color='grey', linestyle='-', linewidth=2) + +plt.show() +ts = time.time() +st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d-%H-%M-%S') +plt.savefig(str(st)+"image"+str(num)+".png") + + +line = [-1 for i in range(nb_columns)] +line[3] = const.BLACK +print(colour_line_bis(line, sequences[0][2])) + +''' \ No newline at end of file diff --git a/mogpl_part2.py b/mogpl_part2.py new file mode 100644 index 0000000..ed389b0 --- /dev/null +++ b/mogpl_part2.py @@ -0,0 +1,297 @@ +# -*- coding: utf-8 -*- + +from gurobipy import * +import numpy as np +import matplotlib.pyplot as plt +from constants import * +import datetime +import timeit +import time +import multiprocessing +from multiprocessing import Queue +import os + +from mogpl_part1_v2 import * +from mogpl_part3 import * + +# xij = 1 si (i,j) case noire, 0 si case blanche +# yijt = 1 si le bloc t de la ligne i commence en (i,j), 0 sinon +# zijt = 1 si le bloc t de la colonne j commence en (i,j), 0 sinon + + +# Q12 +# min z = sum(x[i, j] +# s.c. +# (pour tout i,j) +# +# x[i,j] € {0,1} +# +# sum( y[i, j, t] = 1 ) , t € [0..sli-1] +# TODO a completer + + +# Q13 +# pour t variant de 0 a nb_blocks dans la ligne/colonne : +# si sum(block[t]) + t < i : yijt = 0 +# si sum(block[t]) + t < j : zijt = 0 +# --> on peut retirer ces éléments du modèle + + + +""" +@param file_name: le nom d'un fichier contenant une instance du problème +@return sequences: une liste de deux listes. La première contient la description des lignes (une liste par ligne), + la deuxième la description des colonnes (une liste par colonne). + + Par exemple: pour l'exemple donné dans l'énoncé, la fonction renvoie + [[[3], [], [1, 1, 1], [3]], [[1, 1], [1], [1, 2], [1], [2]]] + + [3] - la première ligne contient un bloc de longueur 3 + [] - aucun bloc sur la deuxième ligne + + [1,1] - la première colonne contient deux blocs, chacun de longueur 1 + + etc. +""" + + +def read_file(file_name): + sequences = [[], []] # première liste pour descriptions des lignes, deuxième pour description des colonnes + + indice = const.LINES # d'abord, on décrit les lignes - on va remplir sequences[0] + + with open(file_name) as file: + for line in file: # pour chaque ligne du fichier + if len(line) > 0 and line[0] == '#': # on passe de la description des lignes à celle des colonnes + indice = const.COLUMNS + else: # il s'agit d'une ligne décrivant une ligne/colonne + # on transforme la ligne des caractères en une liste d'entiers, et on l'ajoute à la fin de sequences[0]/sequences[1] + sequences[indice].append(list(map(int, line.split()))) + + return sequences + + +def get_model(sequences): + m = Model("mogpl") + + # sequences des lignes et des colonnes + sl = sequences[const.LINES] + sc = sequences[const.COLUMNS] + nblines = len(sl) + nbcolumns = len(sc) + + # declaration des variables + x = [] + y = [] + z = [] + for i in range(nblines): + x.append([]) + y.append([]) + z.append([]) + for j in range(nbcolumns): + # xij + x[i].append(m.addVar(vtype=GRB.BINARY, lb=0, name="x(%d,%d)" % (i, j))) + y[i].append([]) + z[i].append([]) + + # parcours des sequences de la ligne i + for t in range(len(sl[i])): + # yijt + if sum(sl[i][:t]) + t <= j and sum(sl[i][t:]) + len(sl[i]) - (t + 1) <= nbcolumns - j: + y[i][j].append(m.addVar(vtype=GRB.BINARY, lb=0, name="y(%d,%d,%d)" % (i, j, t))) + else: + y[i][j].append(0) + + # parcours des sequences de la colonne j + for t in range(len(sc[j])): + # zijt + if sum(sc[j][:t]) + t <= i and sum(sc[j][t:]) + len(sc[j]) - (t + 1) <= nblines - i: + z[i][j].append(m.addVar(vtype=GRB.BINARY, lb=0, name="z(%d,%d,%d)" % (i, j, t))) + else: + z[i][j].append(0) + + # pour t variant de 0 a nb_blocks dans la ligne/colonne : + # si sum(block[t]) + t < i : yijt = 0 + # si sum(block[t]) + t < j : zijt = 0 + # --> on peut retirer ces éléments du modèle + + # maj du modele pour integrer les nouvelles variables + m.update() + + # obj = LinExpr(); + # obj = sum([x[i][j] for i in range(nblines) for j in range(nbcolumns)]) + + # definition de l'objectif + m.setObjective(0, GRB.MINIMIZE) + + # Definition des contraintes + for i in range(nblines): + + for t in range(len(sl[i])): + # un bloc ne doit apparaitre qu'une seule fois sur une ligne + m.addConstr(sum([y[i][j][t] for j in range(nbcolumns)]) == 1) + + # il doit y avoir le bon nombre de cases par ligne + m.addConstr(sum(x[i]) - sum(sl[i]) == 0) + + for j in range(nbcolumns): + + # parcours des blocs d'une ligne + for t in range(len(sl[i])): + + # le bloc t+1 doit se trouver apres le bloc t + if t + 1 < len(sl[i]): + m.addConstr(y[i][j][t] - sum([y[i][b][t + 1] for b in range(j + sl[i][t] + 1, nbcolumns)]) <= 0) + + # si le bloc t demarre a la position j, alors les cases qui suivent doivent etre noires sur la longueur du bloc + if nbcolumns >= j + sl[i][t]: + m.addConstr(y[i][j][t] - sum([x[i][j + k] for k in range(sl[i][t])]) / sl[i][t] <= 0) + # else: + # m.addConstr(y[i][j][t] == 0) # a enlever en retirant les variables + + # parcours des blocs d'une colonne + for t in range(len(sc[j])): + + # le bloc t+1 doit se trouver apres le bloc t + if t + 1 < len(sc[j]): + m.addConstr(z[i][j][t] - sum([z[b][j][t + 1] for b in range(i + sc[j][t] + 1, nblines)]) <= 0) + + # si le bloc t demarre a la position i, alors les cases qui suivent doivent etre noires sur la longueur du bloc + if nblines >= i + sc[j][t]: + m.addConstr(z[i][j][t] - sum([x[i + k][j] for k in range(sc[j][t])]) / sc[j][t] <= 0) + # else: + # m.addConstr(z[i][j][t] == 0) # a enlever en retirant les variables + + for j in range(nbcolumns): + # un bloc ne doit apparaitre qu'une seule fois sur une colonne + for t in range(len(sc[j])): + m.addConstr(sum([z[i][j][t] for i in range(nblines)]) == 1) + + # il doit y avoir le bon nombre de cases par ligne + m.addConstr(sum([x[i][j] for i in range(nblines)]) - sum(sc[j]) == 0) + + return m, x, y, z + + +# Appel rapide a la fonction de resolution en PLNE. +# Le out_q sert a renvoyer le resultat en cas d'appel avec timeout. +def algo_plne(sequences, out_q=Queue()): + m, x, y, z = (get_model(sequences)) + m.optimize() + nb_lines = len(x) + nb_columns = len(x[0]) + A = [[x[i][j].x for j in range(nb_columns)] for i in range(nb_lines)] + out_q.put(A) + return A + + +# Appel rapide a la fonction de resolution en programmation dynamique. +# Le out_q sert a renvoyer le resultat en cas d'appel avec timeout. +def algo_dynamique(sequences, out_q=Queue()): + A = np.full((len(sequences[0]), len(sequences[1])), const.NOT_COLORED) + coloration(A, sequences) + out_q.put(A) + return A + + +# affiche la figure correspondant a la solution +def printfigure(A): + # print(A) + + plt.imshow(A, cmap="Greys", interpolation="nearest") + ax = plt.gca() + + # Minor ticks + ax.set_xticks(np.arange(-.5, len(A[0]), 1), minor=True); + ax.set_yticks(np.arange(-.5, len(A), 1), minor=True); + + ax.grid(which="minor", color='grey', linestyle=':', linewidth=1) + + plt.show() + + +# test des differentes methodes (passees dans un tableau) +# path : le chemin du dossier des instances +# timeout : temps en secondes (0 = aucun) +# min : le numero de la premiere instance a traiter +# max : la derniere instance +# save : booleen pour sauvegarder les images calculees +# print_image : afficher les images calculees /!| ATTENTION met en pause l'execution ! +def testMethods(path, functions, timeout=120, min=0, max=16, save=False, print_image=False): + all_times = [[] for x in range(min, max + 1)] + out_q = Queue() + + for i in range(min, max + 1): + + sequences = read_file(os.path.join(path, str(i) + ".txt")) + for f in functions: + print("\nTest sur " + str(i) + ".txt : " + f.__name__ + " ...\n") + start_time = time.time() + execution_time = timeout * 1.1 # on rajoute 10% pour distinguer de ceux qui ont termine juste avant la fin + if __name__ == '__main__': + # executer le processus en parallele + p = multiprocessing.Process(target=f, args=(sequences, out_q)) + p.start() + p.join(timeout) + + # On stoppe p s'il est trop long + if p.is_alive(): + print("\nTemps dépassé, arret du processus de l'instance " + str(i) + " (" + f.__name__ + ")") + p.terminate() + p.join() + else: + execution_time = time.time() - start_time + print( + "\nTest terminé, temps d'execution de l'instance " + str(i) + " (" + f.__name__ + ") : " + str( + execution_time) + "\n") + A = out_q.get() + + # test de la validite du resultat + if any(A[l][c] == const.NOT_COLORED for l in range(len(A)) for c in range(len(A[0]))): + print("La solution n'est pas valide.\n") + execution_time = timeout * 1.1 + + # generation de la figure + plt.imshow(A, cmap="Greys", interpolation="nearest") + ax = plt.gca() + + # Minor ticks + ax.set_xticks(np.arange(-.5, len(A[0]), 1), minor=True); + ax.set_yticks(np.arange(-.5, len(A), 1), minor=True); + + ax.grid(which="minor", color='grey', linestyle=':', linewidth=1) + + st = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d-%H-%M-%S') + + if (save): plt.savefig("image" + str(i) + "_" + f.__name__ + "_" + str(st) + ".png") + + if (print_image): plt.show() + + all_times[i - min].append(execution_time) + + # generation et sauvegarde du graphe + plt.gcf().clear() + + lines = [[] for f in range(len(functions))] + for f in range(len(functions)): + lines[f], = plt.plot([all_times[i][f] for i in range(len(all_times))], label=functions[f].__name__) + plt.legend(handles=lines) + + st = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d-%H-%M-%S') + plt.savefig("benchmark_all" + "_" + str(st) + ".png") + + print("\nLe graphe a été enregistré dans le répertoire courant.") # affichage d'une seule instance + plt.show() + + +# Ancienne methode pour avoir une instance + +# m, x, y, z = (get_model(read_file("instances/14.txt"))) +# m.optimize() +# nb_lines = len(x) +# nb_columns = len(x[0]) +# A = [[x[i][j].x for j in range(nb_columns)] for i in range(nb_lines)] +# printfigure(x) + +# Test global de toutes les methodes avec toutes les instances +testMethods("instances", [algo_plne, algo_dynamique, algo_hybride], timeout=5, min=0, max=16, save=False, print_image=False) diff --git a/mogpl_part3.py b/mogpl_part3.py new file mode 100644 index 0000000..8158c8c --- /dev/null +++ b/mogpl_part3.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- + +from gurobipy import * +import numpy as np +import matplotlib.pyplot as plt +from constants import * +import time +import multiprocessing +from multiprocessing import Queue + +from mogpl_part1_v2 import coloration + +#TODO a terminer + +# Algorithme tentant d'abord la resolution par programmation dynamique, puis la resolution en PLNE avec la valeur de retour. +# Le out_q sert a renvoyer le resultat en cas d'appel avec timeout. +def algo_hybride(sequences, out_q=Queue()): + m = Model("mogpl") + + # sequences des lignes et des colonnes + sl = sequences[const.LINES] + sc = sequences[const.COLUMNS] + nblines = len(sl) + nbcolumns = len(sc) + + # declaration des variables + x = [] + y = [] + z = [] + for i in range(nblines): + x.append([]) + y.append([]) + z.append([]) + for j in range(nbcolumns): + # xij + x[i].append(m.addVar(vtype=GRB.BINARY, lb=0, name="x(%d,%d)" % (i, j))) + y[i].append([]) + z[i].append([]) + + # parcours des sequences de la ligne i + for t in range(len(sl[i])): + # yijt + if sum(sl[i][:t]) + t <= j and sum(sl[i][t:]) + len(sl[i]) - (t + 1) <= nbcolumns - j: + y[i][j].append(m.addVar(vtype=GRB.BINARY, lb=0, name="y(%d,%d,%d)" % (i, j, t))) + else: + y[i][j].append(0) + + # parcours des sequences de la colonne j + for t in range(len(sc[j])): + # zijt + if sum(sc[j][:t]) + t <= i and sum(sc[j][t:]) + len(sc[j]) - (t + 1) <= nblines - i: + z[i][j].append(m.addVar(vtype=GRB.BINARY, lb=0, name="z(%d,%d,%d)" % (i, j, t))) + else: + z[i][j].append(0) + + # pour t variant de 0 a nb_blocks dans la ligne/colonne : + # si sum(block[t]) + t < i : yijt = 0 + # si sum(block[t]) + t < j : zijt = 0 + # --> on peut retirer ces éléments du modèle + + # maj du modele pour integrer les nouvelles variables + m.update() + + # obj = LinExpr(); + # obj = sum([x[i][j] for i in range(nblines) for j in range(nbcolumns)]) + + # definition de l'objectif + m.setObjective(0, GRB.MINIMIZE) + + # Definition des contraintes + for i in range(nblines): + + for t in range(len(sl[i])): + # un bloc ne doit apparaitre qu'une seule fois sur une ligne + m.addConstr(sum([y[i][j][t] for j in range(nbcolumns)]) == 1) + + # il doit y avoir le bon nombre de cases par ligne + m.addConstr(sum(x[i]) - sum(sl[i]) == 0) + + for j in range(nbcolumns): + + # parcours des blocs d'une ligne + for t in range(len(sl[i])): + + # le bloc t+1 doit se trouver apres le bloc t + if t + 1 < len(sl[i]): + m.addConstr(y[i][j][t] - sum([y[i][b][t + 1] for b in range(j + sl[i][t] + 1, nbcolumns)]) <= 0) + + # si le bloc t demarre a la position j, alors les cases qui suivent doivent etre noires sur la longueur du bloc + if nbcolumns >= j + sl[i][t]: + m.addConstr(y[i][j][t] - sum([x[i][j + k] for k in range(sl[i][t])]) / sl[i][t] <= 0) + # else: + # m.addConstr(y[i][j][t] == 0) # a enlever en retirant les variables + + # parcours des blocs d'une colonne + for t in range(len(sc[j])): + + # le bloc t+1 doit se trouver apres le bloc t + if t + 1 < len(sc[j]): + m.addConstr(z[i][j][t] - sum([z[b][j][t + 1] for b in range(i + sc[j][t] + 1, nblines)]) <= 0) + + # si le bloc t demarre a la position i, alors les cases qui suivent doivent etre noires sur la longueur du bloc + if nblines >= i + sc[j][t]: + m.addConstr(z[i][j][t] - sum([x[i + k][j] for k in range(sc[j][t])]) / sc[j][t] <= 0) + # else: + # m.addConstr(z[i][j][t] == 0) # a enlever en retirant les variables + + for j in range(nbcolumns): + # un bloc ne doit apparaitre qu'une seule fois sur une colonne + for t in range(len(sc[j])): + m.addConstr(sum([z[i][j][t] for i in range(nblines)]) == 1) + + # il doit y avoir le bon nombre de cases par ligne + m.addConstr(sum([x[i][j] for i in range(nblines)]) - sum(sc[j]) == 0) + + #Pretraitement par l'algorithme dynamique: + pre = np.full((len(sequences[0]), len(sequences[1])), const.NOT_COLORED) + coloration(pre, sequences) + + #On fixe la valeur des cases déterminées: + for i in range(len(pre)): + for j in range(len(pre[0])): + #Si la case a été coloriée lors de pretraitement: + if pre[i][j] == const.BLACK: #pour la case noire + x[i][j].LB = const.BLACK #on fixe la borne inférieure de la variable correspondante à 1 + elif pre[i][j] == const.WHITE: #pour la case blanche + x[i][j].UB = const.WHITE #on fixe la borne supérieure de la variable correspondante à 0 + + m.optimize() + nb_lines = len(x) + nb_columns = len(x[0]) + A = [[x[i][j].x for j in range(nb_columns)] for i in range(nb_lines)] + out_q.put(A) + return A