Advertisement
Guest User

Untitled

a guest
Mar 18th, 2021
44
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 11.86 KB | None | 0 0
  1. import random
  2. import copy
  3. import time
  4. from PIL import Image
  5. import tkinter as tk
  6. import matplotlib.pyplot as plt
  7. from shapely.geometry import LineString
  8.  
  9.  
  10. def read_data(filename: str) -> (list, list):
  11.     with open('./data/' + filename) as f:
  12.         size = list(map(int, f.readline().split(';')))
  13.         coordinates = [list(map(int, i.split(';'))) for i in f.readlines()]
  14.         return size, coordinates
  15.  
  16.  
  17. def random_hex_color() -> str:
  18.     return '#%06X' % random.randint(0, 256 ** 3 - 1)
  19.  
  20.  
  21. def line_intersect(x1, y1, x2, y2, x3, y3, x4, y4) -> bool:
  22.     return LineString([(x1, y1), (x2, y2)]).intersects(LineString([(x3, y3), (x4, y4)]))
  23.  
  24.  
  25. # Constans
  26. SAVE_IMAGES = False
  27. POPULATION_SIZE = 15
  28. CROSSOVER_CHANCE = 0.9
  29. MUTATION_CHANCE = 0.18
  30. MUTATION_SEGMENT_DISTANCE = 1
  31. MUTATION_SEGMENT_PIVOT = 2
  32. EPOCHES = 1000
  33. LINE_DIS = 40
  34. MAX_SEGMENTS = 4
  35. MAX_SEGMENT_DIS = 4
  36. COST_FUNCTION_WEIGHTS = [22, 3, 8, 10, 18]
  37.  
  38. # Dynamic variables
  39. (field_width, field_height), coordinates = read_data('zad1.txt')
  40.  
  41.  
  42. class Individual:
  43.     def __init__(self):
  44.         self.paths = []
  45.  
  46.     def __str__(self):
  47.         return '\n'.join(str(i) for i in self.paths)
  48.  
  49.     def fitness_score(self):
  50.         summary_distance = 0
  51.         summary_segments = 0
  52.         crossouts = 0
  53.         summary_distance_outside = 0
  54.         summary_segments_outside = 0
  55.        
  56.         lines = []
  57.         for path in self.paths:
  58.             summary_segments += len(path.segments)
  59.  
  60.             x, y = path.xs, path.ys
  61.             for seg in path.segments:
  62.                 summary_distance += seg.dis
  63.  
  64.                 xe, ye = x, y
  65.                 if seg.dir == 'U': ye -= seg.dis
  66.                 if seg.dir == 'D': ye += seg.dis
  67.                 if seg.dir == 'L': xe -= seg.dis
  68.                 if seg.dir == 'R': xe += seg.dis
  69.                 lines.append([x, y, xe, ye, path])
  70.  
  71.                 for i in range(seg.dis):
  72.                     if x <= 0 or y <= 0 or x > field_width or y > field_height: summary_distance_outside += 1
  73.                     if seg.dir == 'U': y -= 1
  74.                     if seg.dir == 'D': y += 1
  75.                     if seg.dir == 'L': x -= 1
  76.                     if seg.dir == 'R': x += 1
  77.                
  78.                 if x <= 0 or x > field_width or y <= 0 or y >= field_height: summary_segments_outside += 1
  79.                 else:
  80.                     if seg.dir == 'D' and y - seg.dis <= 0: summary_segments_outside += 1
  81.                     if seg.dir == 'U' and y + seg.dis > field_height: summary_segments_outside += 1
  82.                     if seg.dir == 'R' and x - seg.dis <= 0: summary_segments_outside += 1
  83.                     if seg.dir == 'L' and x + seg.dis > field_width: summary_segments_outside += 1
  84.  
  85.         for i in range(len(lines) - 1):
  86.             for j in range(i + 1, len(lines)):
  87.                 if line_intersect(*lines[i][:-1], *lines[j][:-1]):
  88.                     crossouts += 1
  89.  
  90.         summary_distance_outside += 1 if summary_distance_outside != 0 else 0
  91.         return sum([a * b for a, b in zip([crossouts, summary_distance, summary_segments, summary_segments_outside, summary_distance_outside], COST_FUNCTION_WEIGHTS)])
  92.  
  93.  
  94. class Path:
  95.     def __init__(self, xs=0, ys=0, xe=0, ye=0):
  96.         self.xs = xs
  97.         self.ys = ys
  98.         self.xe = xe
  99.         self.ye = ye
  100.         self.segments = []
  101.         self.color = random_hex_color()
  102.  
  103.     def __str__(self):
  104.         return '(%s, %s); (%s, %s);\n' % (self.xs, self.ys, self.xe, self.ye) + '\t'.join(str(i) for i in self.segments)
  105.  
  106.  
  107. class Segment:
  108.     def __init__(self, dir='', dis=0):
  109.         self.dir = dir
  110.         self.dis = dis
  111.  
  112.     def __str__(self):
  113.         return '[%s, %s]' % (self.dir, self.dis)
  114.  
  115.  
  116. def generate_random_segments(path: Path) -> list:
  117.     segments = []
  118.     x, y = path.xs, path.ys
  119.     previous_dir = None
  120.  
  121.     for i in range(random.randint(0, MAX_SEGMENTS)):
  122.         if x == path.xe and y == path.ye: return segments
  123.  
  124.         dirs = [*'UDLR']
  125.         if previous_dir: dirs.remove(previous_dir)
  126.         if previous_dir == 'U': dirs.remove('D')
  127.         if previous_dir == 'D': dirs.remove('U')
  128.         if previous_dir == 'L': dirs.remove('R')
  129.         if previous_dir == 'R': dirs.remove('L')
  130.  
  131.         dr = random.choice(dirs)
  132.         ds = random.randint(1, MAX_SEGMENT_DIS)
  133.         segment = Segment(dr, ds)
  134.         segments.append(segment)
  135.  
  136.         if dr == 'U': y -= ds
  137.         if dr == 'D': y += ds
  138.         if dr == 'L': x -= ds
  139.         if dr == 'R': x += ds
  140.         previous_dir = dr
  141.  
  142.     segments += connect_random_segments(Path(x, y, path.xe, path.ye))
  143.     return segments
  144.  
  145.  
  146. def connect_random_segments(path) -> list:
  147.     # If they are on the same line
  148.     if path.xs == path.xe:
  149.         if path.ys < path.ye: return [Segment('D', path.ye - path.ys)]
  150.         elif path.ys > path.ye: return [Segment('U', path.ys - path.ye)]
  151.         else: return []
  152.  
  153.     if path.ys == path.ye:
  154.         if path.xs > path.xe: return [Segment('L', path.xs - path.xe)]
  155.         elif path.xs < path.xe: return [Segment('R', path.xe - path.xs)]
  156.         else: return []
  157.  
  158.     def horizontal(segment) -> None:
  159.         segment.dir = 'R' if path.xs < path.xe else 'L'
  160.         segment.dis = abs(path.xs - path.xe)
  161.  
  162.     def vertical(segment) -> None:
  163.         segment.dir = 'D' if path.ys < path.ye else 'U'
  164.         segment.dis = abs(path.ys - path.ye)
  165.  
  166.     segments = []
  167.     previous_dir = None
  168.     for _ in range(2):
  169.         segment = Segment()
  170.         if str(previous_dir) in 'UD': horizontal(segment)
  171.         elif str(previous_dir) in 'LR': vertical(segment)
  172.         else:
  173.             if random.random() > 0.5: horizontal(segment)
  174.             else: vertical(segment)
  175.         previous_dir = segment.dir
  176.         segments.append(segment)
  177.  
  178.     return segments
  179.  
  180.  
  181. def initial_population() -> list:
  182.     population = []
  183.     for _ in range(POPULATION_SIZE):
  184.         individual = Individual()
  185.         for [x, y, xe, ye] in coordinates:
  186.             path = Path(x, y, xe, ye)
  187.             path.segments = generate_random_segments(path)
  188.             individual.paths.append(path)
  189.         population.append(individual)
  190.     return population
  191.  
  192.  
  193.  
  194. def fitness_proportionate_selection(array: list, k=1) -> Individual:
  195.     total_fintess_score = sum([ind.fitness_score() for ind in array])
  196.     return min(random.choices(array, [ind.fitness_score() / total_fintess_score for ind in array], k=k), key=lambda x: x.fitness_score())
  197.  
  198.  
  199. def tournament_selection(array: list, k=1) -> Individual:
  200.     return min(random.sample(array, k=k), key=lambda ind: ind.fitness_score())
  201.  
  202.  
  203. # TODO: Add uniform crossover
  204. def single_point_crossover(parent1: Individual, parent2: Individual) -> (Individual, Individual):
  205.     # we need to replace entire path
  206.     # parents always has the same number of paths    
  207.     if random.random() > CROSSOVER_CHANCE:  return copy.deepcopy(parent1), copy.deepcopy(parent2)
  208.  
  209.     # TODO: use zip function for uniform crossover
  210.     pivot = random.randint(1, len(parent1.paths))
  211.  
  212.     i1 = Individual()
  213.     i2 = Individual()
  214.  
  215.     p1 = list(copy.deepcopy(path) for path in parent1.paths)
  216.     p2 = list(copy.deepcopy(path) for path in parent2.paths)
  217.    
  218.     i1.paths = p1[:pivot] + p2[pivot:]
  219.     i2.paths = p2[:pivot] + p1[pivot:]
  220.     return i1, i2
  221.  
  222.    
  223. def mutate(ind: Individual) -> None:
  224.     if random.random() > MUTATION_CHANCE: return
  225.  
  226.     for path in ind.paths:
  227.         segment = random.choice(path.segments)
  228.         idx = path.segments.index(segment)
  229.  
  230.         # Path B
  231.         if random.random() > 0.5:
  232.             if segment.dis > MUTATION_SEGMENT_PIVOT:
  233.                 pivot = random.randint(1, segment.dis)
  234.                 segment.dis -= pivot
  235.                 path.segments.insert(idx + 1, Segment(segment.dir, pivot))
  236.                 idx += 1
  237.                 segment = path.segments[idx]
  238.  
  239.         # Path A
  240.         if segment.dir in 'UD':
  241.             _dir = 'L' if random.random() >= 0.5 else 'R'
  242.             _again_dir = 'R' if _dir == 'L' else 'L'
  243.             _dirs = 'UD'
  244.         else:
  245.             _dir = 'D' if random.random() >= 0.5 else 'U'
  246.             _again_dir = 'U' if _dir == 'D' else 'D'
  247.             _dirs = 'LR'
  248.  
  249.         if idx > 0 and path.segments[idx - 1].dir not in _dirs:
  250.             path.segments[idx - 1].dis += MUTATION_SEGMENT_DISTANCE * (-1 if path.segments[idx - 1].dir == _again_dir else 1)
  251.             if path.segments[idx - 1].dis == 0:
  252.                 path.segments.pop(idx - 1)
  253.                 idx -= 1
  254.         else:
  255.             path.segments.insert(idx, Segment(_dir, MUTATION_SEGMENT_DISTANCE))
  256.             idx += 1
  257.            
  258.         if idx < len(path.segments) - 1 and path.segments[idx + 1].dir not in _dirs:
  259.             path.segments[idx + 1].dis += MUTATION_SEGMENT_DISTANCE * (-1 if path.segments[idx + 1].dir == _dir else 1)
  260.             if path.segments[idx + 1].dis == 0:
  261.                 path.segments.pop(idx + 1)
  262.         else: path.segments.insert(idx + 1, Segment(_again_dir, MUTATION_SEGMENT_DISTANCE))
  263.  
  264. population = initial_population()
  265.  
  266. scores = []
  267. for j in range(EPOCHES):
  268.     for i in range(0, len(population), 2):
  269.         a = fitness_proportionate_selection(population, k=POPULATION_SIZE // 2)
  270.         b = tournament_selection(population, k=POPULATION_SIZE // 2)
  271.        
  272.         nA, nB = single_point_crossover(a, b)
  273.  
  274.         mutate(nA)
  275.         mutate(nB)
  276.  
  277.         population.pop(0)
  278.         population.pop(0)
  279.  
  280.         population.append(nA)
  281.         population.append(nB)
  282.  
  283.     average_score = sum([x.fitness_score() for x in population]) / len(population)
  284.     scores.append(average_score)
  285.  
  286.     if j % (EPOCHES // 10) == 0:
  287.         print('Epoch: ', j)
  288.  
  289.     if average_score <= 3:
  290.         break
  291.  
  292. plt.plot(scores)
  293.  
  294.  
  295. C_WIDTH = field_width * LINE_DIS
  296. C_HEIGHT = field_height * LINE_DIS
  297.  
  298. root = tk.Tk()
  299. root.title('Genetic Algorithm')
  300. root.resizable(False, False)
  301.  
  302. w = tk.Canvas(root, width=C_WIDTH, height=C_HEIGHT, bg="#505050")
  303. w.pack()
  304. root.update()
  305.  
  306.  
  307. def draw_individual(ind: Individual) -> None:
  308.     # Draw individual by drawing all segments for ecah path
  309.     offset = LINE_DIS / 2
  310.     for path in ind.paths:
  311.         x, y = path.xs * LINE_DIS, path.ys * LINE_DIS
  312.         for segment in path.segments:
  313.             x_gap, y_gap = 0, 0
  314.             if segment.dir in ('U', 'D'): y_gap = LINE_DIS
  315.             if segment.dir in ('L', 'R'): x_gap = LINE_DIS
  316.            
  317.             x_sign, y_sign = 1, 1
  318.             if segment.dir == 'U': y_sign = -1
  319.             if segment.dir == 'L': x_sign = -1
  320.  
  321.             for i in range(segment.dis):
  322.                 w.create_line(x - offset, y - offset, x + x_gap * x_sign - offset, y - offset + y_sign * y_gap, fill=path.color) # fill='#aaa'
  323.                 if segment.dir == 'U': y -= LINE_DIS
  324.                 if segment.dir == 'D': y += LINE_DIS
  325.                 if segment.dir == 'L': x -= LINE_DIS
  326.                 if segment.dir == 'R': x += LINE_DIS
  327.  
  328.  
  329. def draw_field() -> None:
  330.     # Draw background dots
  331.     for x in range(0, C_WIDTH, LINE_DIS):
  332.         for y in range(0, C_HEIGHT, LINE_DIS):
  333.             draw_rect(w, x, y, x + LINE_DIS, y + LINE_DIS, _offset=2)
  334.  
  335.  
  336. def draw_rect(w, x1, y1, x2, y2, _offset=2, color='#bbb') -> None:
  337.     # Helper function to draw a circle
  338.     offset = LINE_DIS / _offset
  339.     w.create_oval(x1 + offset, y1 + offset, x2 - offset, y2 - offset, fill=color, outline='')
  340.  
  341.  
  342. def draw_coords() -> None:
  343.     # Draw points by their coordinates
  344.     for x in coordinates:
  345.         color = random_hex_color()
  346.         x = list(map(lambda e: (e - 1) * LINE_DIS, x))
  347.         draw_rect(w, x[0], x[1], x[0] + LINE_DIS, x[1] + LINE_DIS, _offset=3, color=color)
  348.         draw_rect(w, x[2], x[3], x[2] + LINE_DIS, x[3] + LINE_DIS, _offset=3, color=color)
  349.  
  350.  
  351. ind = min(population, lambda x: x.fitness_score())
  352.  
  353. draw_field()
  354. draw_individual(ind)
  355. draw_coords()
  356.  
  357. with open('model.txt', 'w') as f:
  358.     f.write(str(ind))
  359.  
  360. root.mainloop()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement