├── README.md ├── genetic_algo_demo.py ├── libs ├── __pycache__ │ ├── node.cpython-36.pyc │ ├── optimizer.cpython-36.pyc │ ├── placement.cpython-36.pyc │ ├── placement_controller.cpython-36.pyc │ └── renderer.cpython-36.pyc ├── node.py ├── optimizer.py ├── placement.py ├── placement_controller.py └── renderer.py └── tests └── placement_controller_test.py /README.md: -------------------------------------------------------------------------------- 1 | # PyQt Genetic Algo 2 | Simple implementation and visualisation of genetic algorithm in work using Python & Qt. 3 | 4 | ### Backround 5 | Genetic algorithm is special technique for solving optimization problems, 6 | specialy in places where there are a lot of combinations and finding optimal solotion by simply brute-forcing is impossible. 7 | 8 | ### The task to solve 9 | The Aim of this project is to achieve optimized cell placement to complete efficient routing. 10 | Optimization should meet following criterias. 11 | * Each cell (when moved) should preserve it's connectivity ( parent & child connections ) 12 | * Cell position can be adjusted within same colum ( can be moved vertically, but not horizontally ) 13 | * Intersections/crossings beetween connections should be minimal. 14 | 15 | #### Self explanatory images : ) 16 | *Left:* Initial placmenet                                                                                  *Right:* optimized placement 17 | 18 |            19 | 20 | 21 | ### Implementation 22 | I've used MVC pattern to abstract the data from renderer. 23 | Theortically should be possible to add any view that can will use get_data function returning 2D array of nodes used and renderer them. 24 | 25 | 26 | ### More complicated cases 27 | 28 | Placement before ( 51 crossings ) 29 | 30 | 31 | 32 | Optimized ( 1 crossing ) 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /genetic_algo_demo.py: -------------------------------------------------------------------------------- 1 | from libs.placement import Placement 2 | from libs.placement_controller import PlacementController 3 | from libs.optimizer import Optimizer 4 | from libs.renderer import Renderer 5 | 6 | #initial_settings = { 7 | # 'num_layers' : 15, 8 | # 'max_nodes_in_layer' : 7, 9 | # 'min_node_connection' : 2, 10 | # 'max_node_connection' : 3, 11 | #} 12 | 13 | if __name__ == '__main__': 14 | #create placement controller 15 | #placement_controller = PlacementController() 16 | 17 | #create placement 18 | #placement = Placement(**initial_settings) 19 | 20 | #set 21 | #placement_controller.set_placement(placement) 22 | 23 | #optimize placement, using placement controller 24 | #optimizer = Optimizer(placement,placement_controller) 25 | 26 | #placement_controller.set_data(optimizer.get_best_placement()) 27 | 28 | #draw 29 | r = Renderer() 30 | r.render() 31 | 32 | -------------------------------------------------------------------------------- /libs/__pycache__/node.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levibyte/pyqt_genetic_algo/af3ec1d8614b5bfe18fed995f59bffbb1d4d68bd/libs/__pycache__/node.cpython-36.pyc -------------------------------------------------------------------------------- /libs/__pycache__/optimizer.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levibyte/pyqt_genetic_algo/af3ec1d8614b5bfe18fed995f59bffbb1d4d68bd/libs/__pycache__/optimizer.cpython-36.pyc -------------------------------------------------------------------------------- /libs/__pycache__/placement.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levibyte/pyqt_genetic_algo/af3ec1d8614b5bfe18fed995f59bffbb1d4d68bd/libs/__pycache__/placement.cpython-36.pyc -------------------------------------------------------------------------------- /libs/__pycache__/placement_controller.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levibyte/pyqt_genetic_algo/af3ec1d8614b5bfe18fed995f59bffbb1d4d68bd/libs/__pycache__/placement_controller.cpython-36.pyc -------------------------------------------------------------------------------- /libs/__pycache__/renderer.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levibyte/pyqt_genetic_algo/af3ec1d8614b5bfe18fed995f59bffbb1d4d68bd/libs/__pycache__/renderer.cpython-36.pyc -------------------------------------------------------------------------------- /libs/node.py: -------------------------------------------------------------------------------- 1 | class Color: 2 | def __init__(self,r,g,b): 3 | self.red = r 4 | self.green = g 5 | self.blue = b 6 | 7 | def get(self): 8 | return self.red,self.green,self.blue 9 | 10 | class Node: 11 | def __init__(self, name, color): 12 | self.name = name 13 | self.color = color 14 | self.parents = [] 15 | self.connected_nodes = [] 16 | 17 | def add_connection(self,Node): 18 | self.connected_nodes.append(Node) 19 | 20 | def get_connected(self): 21 | return self.connected_nodes 22 | 23 | def get_name(self): 24 | return self.name 25 | 26 | def get_color(self): 27 | return self.color.get() 28 | 29 | def colorize(self): 30 | for node in self.connected_nodes: 31 | node.set_color(self.color) 32 | 33 | for p in self.parents: 34 | p.set_color(self.color) 35 | p.colorize() 36 | 37 | def set_color(self,color): 38 | self.color = color 39 | 40 | def add_parent(self,node): 41 | self.parents.append(node) 42 | 43 | def get_parents(self): 44 | return self.parents 45 | 46 | -------------------------------------------------------------------------------- /libs/optimizer.py: -------------------------------------------------------------------------------- 1 | from libs.placement import Placement 2 | from libs.placement_controller import PlacementController 3 | 4 | import random 5 | import copy 6 | 7 | class Optimizer: 8 | def __init__(self, placement, controller): 9 | self.data = placement.get_data() 10 | self.controller = controller 11 | self.controller.set_data(self.data) 12 | #self.controller.set_placement(placement) 13 | #self.generations = [] 14 | self.best_data = [] 15 | self.initial_fitness = self.controller.calc_intersections() 16 | self.current_fitness = self.initial_fitness 17 | print("BEGIN: {}".format(self.controller.calc_intersections())) 18 | self.optimize2() 19 | 20 | def rand_optimize(self): 21 | #print("before optimization: {}".format(self.current_fitness)) 22 | for i in range(30000): 23 | self.controller.add_change() 24 | #fitness = self.controller.calc_intersections() 25 | #print ("iteration: {} , fitness = {}".format(i,fitness)) 26 | if self.controller.calc_intersections() is 3: 27 | #if fitness < self.current_fitness: 28 | #fitness = self.current_fitness 29 | self.best_data = copy.deepcopy(self.controller.get_placement()) 30 | 31 | self.controller.set_data(copy.deepcopy(self.best_data)) 32 | break 33 | 34 | #print("after optimization: {}".format(self.current_fitness)) 35 | #self.best_data = self.controller.get_placement() 36 | #self.controller.set_data(self.best_data) 37 | print("END: {}".format(self.controller.calc_intersections())) 38 | 39 | def optimize2(self): 40 | gens_max=15 41 | survivors_factor=3 42 | crossover_factor=2 43 | max_iterations=1000 44 | 45 | gens = {self.initial_fitness:self.data} 46 | 47 | #first generation 48 | for i in range(gens_max): 49 | fitness = self.controller.calc_intersections() 50 | gen = copy.deepcopy(self.controller.get_placement()) 51 | gens[fitness] = gen 52 | self.controller.add_change() 53 | 54 | 55 | #each new generation 56 | for gen_num in range(max_iterations): 57 | print("**********GENERATION {}************ (inital is: {} )".format(gen_num,self.initial_fitness)) 58 | #selection 59 | winners=[] 60 | k=0 61 | max_winners=gens_max//survivors_factor 62 | for f in sorted(gens.keys()): 63 | if k < max_winners: 64 | print("-----winner {} fintess={}".format(k,f)) 65 | if f < self.current_fitness: 66 | self.current_fitness = f 67 | winners.append(gens[f]) 68 | else: 69 | del gens[f] 70 | #break 71 | k+=1 72 | 73 | if self.current_fitness is 0: 74 | break 75 | 76 | #crossover 77 | x = gens_max-crossover_factor*max_winners 78 | for g in range(x): 79 | import time 80 | random.seed(time.clock()) 81 | a = random.randint(0,len(winners)-1) 82 | random.seed(time.clock()) 83 | b = random.randint(0,len(winners)-1) 84 | #print("-----crossover winner{} winner{}".format(a,b)) 85 | gen1 = winners[a] 86 | gen2 = winners[b] 87 | gen = self.controller.merge(gen1,gen2) 88 | #gen = winners[random.randint(0,len(winners)-1)] 89 | self.controller.set_data(copy.deepcopy(gen)) 90 | #self.controller.add_change() 91 | fitness = self.controller.calc_intersections() 92 | gens[fitness] = copy.deepcopy(self.controller.get_placement()) 93 | #print("-->crossover {} {}".format(g,fitness)) 94 | 95 | #mutation 96 | x = crossover_factor*max_winners 97 | for g in range(x): 98 | gen = winners[random.randint(0,len(winners)-1)] 99 | self.controller.set_data(copy.deepcopy(gen)) 100 | self.controller.add_change() 101 | fitness = self.controller.calc_intersections() 102 | gens[fitness] = copy.deepcopy(self.controller.get_placement()) 103 | #print("-->crossover {} {}".format(g,fitness)) 104 | 105 | print() 106 | 107 | print("end.") 108 | 109 | #self.best_data = winners[0] 110 | self.best_data = copy.deepcopy(winners[0]) 111 | self.controller.set_data(copy.deepcopy(self.best_data)) 112 | 113 | #def get_best_placement(self): 114 | #return self.best_data 115 | -------------------------------------------------------------------------------- /libs/placement.py: -------------------------------------------------------------------------------- 1 | from libs.node import * 2 | from random import randint 3 | 4 | class Placement: 5 | def __init__(self,**kwargs): 6 | self.layers_max = kwargs['num_layers'] 7 | self.nodes_max = kwargs['max_nodes_in_layer'] 8 | self.connection_max = kwargs['max_node_connection'] 9 | self.connection_min = kwargs['min_node_connection'] 10 | 11 | self.create_random_placement() 12 | #self.layers = [] 13 | 14 | def create_random_placement(self): 15 | self.create_layers() 16 | self.create_connections() 17 | 18 | def create_rand_color(self): 19 | return Color(randint(0,255),randint(0,255),randint(0,255)) 20 | 21 | def create_layers(self): 22 | self.layers = [ [ Node("({},{})".format(i,j),self.create_rand_color()) for j in range(randint(2,self.nodes_max)) ] for i in range(self.layers_max) ] 23 | 24 | def create_connections(self): 25 | i = 0 26 | for layer in self.layers: 27 | j = 0 28 | for node in layer: 29 | print("Node {} {}".format(i,j)) 30 | if i is not self.layers_max-1: 31 | for k in range(randint(self.connection_min,self.connection_max)): 32 | x = len(self.layers[i+1])-1 33 | d = randint(0,x) 34 | print(" --> connecting to {} {}".format(i+1,d)) 35 | n = self.layers[i+1][d] 36 | #FIXME broken 37 | node.add_connection(n) 38 | n.add_parent(node) 39 | node.colorize() 40 | n.colorize() 41 | #col = node.get_color() 42 | #n.set_color(col) 43 | 44 | j+=1 45 | i+=1 46 | 47 | def get_data(self): 48 | return self.layers 49 | -------------------------------------------------------------------------------- /libs/placement_controller.py: -------------------------------------------------------------------------------- 1 | from libs.node import * 2 | import random 3 | from random import randint 4 | import copy 5 | 6 | class PlacementController: 7 | def __init__(self): 8 | self.layers = [] 9 | 10 | def get_placement(self): 11 | return self.layers 12 | 13 | #def set_placement(self,placement): 14 | #self.layers = placement.get_data() 15 | 16 | def set_data(self,data): 17 | #del self.layers 18 | #self.layers = [] 19 | #self.layers = copy.deepcopy(data) 20 | self.layers = data 21 | #self.layers = copy.copy(data) 22 | #print(" from set data:{}".format(self.calc_intersections())) 23 | 24 | def add_change(self): 25 | #return 0 26 | import time 27 | random.seed(time.clock()) 28 | 29 | x = randint(0,len(self.layers)-1) 30 | 31 | j = randint(0,len(self.layers[x])-1) 32 | y = randint(0,len(self.layers[x])-1) 33 | 34 | #print("SWAPPING {}{} to {}{}".format(x,j,x,y)) 35 | 36 | #del self.layers[x] 37 | self.layers[x][j],self.layers[x][y] = self.layers[x][y],self.layers[x][j] 38 | 39 | def calc_intersections(self): 40 | i = 0 41 | res = 0 42 | for layer in self.layers: 43 | res += self.calc_intersections_beetween_to_adjcent_layers(i) 44 | i += 1 45 | 46 | return res 47 | 48 | def calc_intersections_beetween_to_adjcent_layers(self,i): 49 | if i is len(self.layers)-1: 50 | return 0 51 | 52 | vec = [] 53 | for node in self.layers[i]: 54 | for n in node.get_connected(): 55 | i,j = self.find_ij(n) 56 | vec.append(j) 57 | #vec.append([j for i,j in node.get_connected()]) 58 | 59 | res = 0 60 | k = 0 61 | #print(vec) 62 | #exit(0) 63 | for v in vec: 64 | for i in range(0,k): 65 | if vec[i] > vec[k]: 66 | res += 1 67 | k += 1 68 | return res 69 | 70 | 71 | def find_ij(self,mynode): 72 | i = 0 73 | found = False 74 | 75 | for layer in self.layers: 76 | j = 0 77 | for node in layer: 78 | if node == mynode: 79 | found = True 80 | break 81 | j += 1 82 | 83 | if found is True: 84 | break 85 | i += 1 86 | 87 | return i,j 88 | 89 | 90 | def merge(self,nodes1,nodes2): 91 | res = [] 92 | for i in range(len(nodes1)): 93 | #print("////////MERGE: {} {}".format(len(nodes1),len(nodes2))) 94 | res.append(self.merge_columns(nodes1[i],nodes2[i])) 95 | 96 | return nodes2 97 | 98 | 99 | 100 | def merge_columns(self,col1,col2): 101 | in_first = set(col1[:len(col1)//2]) 102 | in_second = set(col2) 103 | in_second_but_not_in_first = in_second - in_first 104 | result = col1[:len(col1)//2] + list(in_second_but_not_in_first) 105 | #result = col1 + list(in_second_but_not_in_first) 106 | #return in_second 107 | return result 108 | 109 | 110 | -------------------------------------------------------------------------------- /libs/renderer.py: -------------------------------------------------------------------------------- 1 | from libs.node import * 2 | from libs.placement import Placement 3 | from libs.placement_controller import PlacementController 4 | from libs.optimizer import Optimizer 5 | 6 | from PyQt4.QtGui import * 7 | #from PyQt5.QtWidgets import * 8 | from PyQt4.QtCore import * 9 | 10 | initial_settings = { 11 | 'num_layers' : 13, 12 | 'max_nodes_in_layer' : 6, 13 | 'min_node_connection' : 1, 14 | 'max_node_connection' : 1, 15 | } 16 | 17 | 18 | class Canvas(QWidget): 19 | def __init__(self, controller, *args, **kwargs): 20 | super(Canvas, self).__init__(*args, **kwargs) 21 | self.controller = controller 22 | self.tx = 100 23 | self.ty = 100 24 | self.dx = 50 25 | self.dy = 50 26 | #create placement 27 | self.placement = Placement(**initial_settings) 28 | 29 | #set 30 | self.controller.set_data(self.placement.get_data()) 31 | self.nodes = self.controller.get_placement() 32 | print("Intersctions: {}".format(self.controller.calc_intersections())) 33 | print("Click on canvas to start optimization...") 34 | 35 | 36 | def keyPressEvent(self, event): 37 | self.placement = Placement(**initial_settings) 38 | self.controller.set_data(self.placement.get_data()) 39 | self.nodes = self.controller.get_placement() 40 | QWidget.repaint(self) 41 | 42 | 43 | def mouseReleaseEvent(self, event): 44 | #optimize placement, using placement controller 45 | optimizer = Optimizer(self.placement,self.controller) 46 | 47 | self.nodes = self.controller.get_placement() 48 | #print("Count: {}".format(self.controller.calc_intersections())) 49 | QWidget.repaint(self) 50 | #self.paintEvent(event) 51 | #self.draw() 52 | #ev = QResizeEvent(QSize(1000,1000),QSize(1000,1000)) 53 | #QMainWindow.resizeEvent(self, ev) 54 | 55 | def paintEvent(self, event): 56 | self.draw() 57 | 58 | #def dummy(self): 59 | 60 | def draw(self): 61 | i = 0 62 | for layer in self.nodes: 63 | j = 0 64 | for node in layer: 65 | #self.draw_node_and_its_connections(node) 66 | #self.draw_connections(i,j,node.get_connected()) 67 | #FIXME elimiate ghost nodes 68 | #if not node.get_parents() and not node.get_connected(): 69 | # print("") 70 | #else: 71 | self.draw_node(i,j,node) 72 | 73 | self.draw_connections(i,j,node.get_connected()) 74 | j += 1 75 | i += 1 76 | 77 | def draw_connections(self,i,j,nodes): 78 | for node in nodes: 79 | self.draw_connection(i,j,node) 80 | 81 | def draw_node(self,i,j,node): 82 | #print("drawing {} {}".format(i,j)) 83 | #i,j = sys.find_ij(node) 84 | r,g,b = node.get_color() 85 | color = QColor(r,g,b) 86 | p = QPainter(self) 87 | p.setRenderHint(QPainter.Antialiasing) 88 | 89 | r = QRect(self.tx*i+self.dx,self.ty*j+self.dy,20,20) 90 | outer, inner = Qt.black, color 91 | p.fillRect(r, QBrush(inner)) 92 | pen = QPen(outer) 93 | pen.setWidth(1) 94 | p.setPen(pen) 95 | p.drawRect(r) 96 | 97 | p.drawText(self.tx*i+self.dx,self.ty*j+self.dy,node.get_name()) 98 | 99 | def draw_connection(self,i,j,node): 100 | di,dj = self.controller.find_ij(node) 101 | #print("drawing connection from (){}{} to ({}){}{}".format(i,j,node.get_name(),di,dj)) 102 | 103 | p = QPainter(self) 104 | p.setRenderHint(QPainter.Antialiasing) 105 | #FIXME tuple 106 | r,g,b = node.get_color() 107 | color = QColor(r,g,b) 108 | pen = QPen(color, 3) 109 | p.setPen(pen) 110 | p.drawLine(10+self.tx*i+self.dx,10+self.ty*j+self.dy,10+self.tx*di+self.dx,10+self.ty*dj+self.dy) 111 | 112 | 113 | 114 | class MainWindow(QMainWindow): 115 | def __init__(self, controller, *args, **kwargs): 116 | super(MainWindow, self).__init__(*args, **kwargs) 117 | #nodes = sys.get_data() 118 | canvas = Canvas(controller) 119 | self.setCentralWidget(canvas) 120 | 121 | 122 | class Renderer(): 123 | #def __init__(self,p_c): 124 | # self.controller = p_c 125 | 126 | def __init__(self): 127 | i = 0 128 | 129 | def render(self): 130 | #create placement controller 131 | self.controller = PlacementController() 132 | 133 | #create placement 134 | #placement = Placement(**initial_settings) 135 | 136 | app = QApplication([]) 137 | r = MainWindow(self.controller) 138 | r.show() 139 | app.exec_() 140 | 141 | 142 | -------------------------------------------------------------------------------- /tests/placement_controller_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import copy 3 | 4 | from libs.placement_controller import PlacementController 5 | from libs.node import Node, Color 6 | 7 | @pytest.fixture 8 | def placement_data(): 9 | """ creates simple placement data 10 | Placement bellow: 11 | 12 | A D 13 | \ / 14 | B-\/-E 15 | /\ 16 | / \ 17 | C F 18 | 19 | Total there are 3 intersection within this placement. 20 | """ 21 | 22 | layers = [] 23 | layer1 = [] 24 | layer2 = [] 25 | 26 | dummy_color = Color(0,0,0) 27 | A = Node("A",dummy_color) 28 | B = Node("B",dummy_color) 29 | C = Node("C",dummy_color) 30 | D = Node("D",dummy_color) 31 | E = Node("E",dummy_color) 32 | F = Node("F",dummy_color) 33 | 34 | A.add_connection(F) 35 | B.add_connection(E) 36 | C.add_connection(D) 37 | 38 | 39 | layer1.append(A) 40 | layer1.append(B) 41 | layer1.append(C) 42 | 43 | layer2.append(D) 44 | layer2.append(E) 45 | layer2.append(F) 46 | 47 | 48 | layers.append(layer1) 49 | layers.append(layer2) 50 | 51 | return layers 52 | 53 | def test_count_intersections_raw(placement_data): 54 | pc = PlacementController(); 55 | pc.set_data(placement_data) 56 | assert 3 == pc.calc_intersections() 57 | 58 | def test_count_intersections_on_swap_colmns(placement_data): 59 | pc = PlacementController(); 60 | placement_data[0],placement_data[1] = placement_data[1],placement_data[0] 61 | pc.set_data(placement_data) 62 | assert 0 == pc.calc_intersections() 63 | 64 | def test_count_intersections_on_swap_inside_first_column(placement_data): 65 | pc = PlacementController(); 66 | placement_data[0][0],placement_data[0][2] = placement_data[0][2],placement_data[0][0] 67 | pc.set_data(placement_data) 68 | assert 0 == pc.calc_intersections() 69 | 70 | def test_count_intersections_on_swap_inside_second_column(placement_data): 71 | pc = PlacementController(); 72 | placement_data[1][0],placement_data[1][1] = placement_data[1][1],placement_data[1][0] 73 | pc.set_data(placement_data) 74 | assert 2 == pc.calc_intersections() 75 | 76 | def test_merge(placement_data): 77 | pc = PlacementController(); 78 | data2 = copy.deepcopy(placement_data) 79 | data2[0][0],data2[0][2] = data2[0][2],data2[0][0] 80 | #placement_data[1][0],placement_data[1][2] = placement_data[1][2],placement_data[1][0] 81 | pc.set_data(copy.deepcopy(pc.merge(placement_data,data2))) 82 | assert 0 == pc.calc_intersections() 83 | assert pc.get_placement()[0][0].get_connected()[0] == pc.get_placement()[1][0] 84 | 85 | 86 | 87 | 88 | --------------------------------------------------------------------------------