├── 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 |
--------------------------------------------------------------------------------