Предложенный алгоритм - это очень ранний прототип рабочей версии. Суть публикации познакомить всех желающих с возможностями генетических алгоритмов в различных сферах бизнеса.
Постановка задачи
По данным от Заказчика выбрать оптимальный вариант количества и планировок квартир для этажа многоквартирного дома.
Исходные данные
База планировок
Для тестовой модели мы подготовили базу планировок в Notion. В дальнейшем к ней легко подключиться по API из Google Colab.
Опишем квартиру (FLAT_DNA) как ДНК, состоящую из двух генов:
Тип квартиры
Торцевая (E)
Стандарт (S)
Тип планировки
Студия (S)
1 комнатная (1k)
2 комнатная (2k)
3 комнатная (3k)
ДНК этажа (FLOOR) будет иметь такую структуру:
Первый ген = ДНК квартиры с зафиксированным геном “Тип квартиры” = (E)
Со второго до предпоследнего все ДНК квартиры случайные
Последний ген = ДНК квартиры с зафиксированным геном “Тип квартиры” = (E)
Первое поколение (BuildNewGeneration)
По умолчанию для первого поколения мы создаем (GENETIC_FLOOR.NEWBORN) планировок этажей и выбираем размер этажа = 10 квартирам (FLOOR_SIZE)
Заполняем полученное поколение планировок конкретными квартирами из Базы Данных с помощью функции FillFloor
В функции заложено правило, что первая и последняя квартира являются строго типа E, т.е. торцевыми
Определяем лучшую планировку этажа в поколении (FindBestFloor)
Для каждой планировки в поколении определяем 3 дельты:
Отклонение от заданной процентовки квартир (DeltaP)
Отклонение от заданной ширины этажа (DeltaS)
Сводное отклонение (Delta)
Выбираем вариант планировки с наименьшим значением сводного отклонения
Меняем размер этажа на кол-во квартир в лучшем варианте (GENETIC_FLOOR.FLOOR_SIZE)
Передаем ДНК лучшего варианта в первом поколении в следующее поколение
Последующие поколения
На основе полученных вариантов из предыдущего поколения создаем GENETIC_FLOOR.MUTATED вариантов с измененными ДНК квартир
Вероятность мутации 1/20
Мутация затрагивает только тип планировки
Добавляем новые планировки, но с уже измененным количеством квартир на этажа (GENETIC_FLOOR.FLOOR_SIZE)
Заполняем полученное поколение планировок конкретными квартирами из Базы Данных с помощью функции FillFloor
Определяем лучшую планировку этажа в поколении (FindBestFloor)
Передаем ДНК лучшего варианта в первом поколении в следующее поколение
Исходный код на Python
import random import copy import pprint from PIL import Image as IMG, ImageDraw, ImageFilter import requests class FLAT_DNA(): Gens = { # Тип квартиры # E - Торцевая квартира, S - Стандартная квартира 'Type': [['E', 'S'], 0.5], # Планировка # S - Студия, 1k - 1 комнатная, 2k - 2 комнатная, 3k - 3 комнатная 'Layout': [['S', '1k', '2k', '3k'], 0.5], } def __init__(self, DNA = None): self.DNA = { 'Type' : random.choice(FLAT_DNA.Gens['Type'][0]), 'Layout' : random.choice(FLAT_DNA.Gens['Layout'][0]) } if DNA is not None: for i in DNA.keys(): self.DNA[i] = DNA[i] def __repr__(self): return str(self.DNA) class FLOOR(): def __init__(self, N): self.N = N self.Plan = [] for i in range(N): self.Plan.append([FLAT_DNA({'Type' : 'S'}), []]) self.Plan[0][0].DNA['Type'] = 'E' self.Plan[-1][0].DNA['Type'] = 'E' # Функция наполнения ДНК этажа конкретными вариантами кварти из БД (случайным перебором) def FillFloor(self): for i in range(len(self.Plan)): if self.Plan[i][0].DNA['Type'] == 'E': flat = random.choice(DB_FlatType_Dict['Торцевая']) self.Plan[i][1].append(flat) #print(flat) if self.Plan[i][0].DNA['Type'] == 'S': flat = random.choice(DB_FlatType_Dict['Рядовая']) self.Plan[i][1].append(flat) #print(flat) # Функция определения фактической геометрии этажа и площади def FloorSquare(self): self.Width = 0 self.Deep = 0 for i in range(len(self.Plan)): self.Width += self.Plan[i][1][0]['properties']['Ширина']['number'] self.Deep += self.Plan[i][1][0]['properties']['Глубина']['number'] self.Square = self.Width * self.Deep # Функция генерации словаря с процентовкой квартир на этаже def FloorPercent(self): self.FloorPercent_Dict = {} for g in FLAT_DNA.Gens['Layout'][0]: if g not in self.FloorPercent_Dict.keys(): self.FloorPercent_Dict[g] = 0 for i in range(len(self.Plan)): if self.Plan[i][0].DNA['Layout'] == g: self.FloorPercent_Dict[g] += 1 for i in self.FloorPercent_Dict.keys(): self.FloorPercent_Dict[i] = self.FloorPercent_Dict[i]*100/self.N def GetPNG(self): floor_img = IMG.new('RGB', (6000, 3000), color = 'white') W = 0 for i in range(len(self.Plan)): url = self.Plan[i][1][0]['properties']['Foto']['files'][0]['file']['url'] im = IMG.open(requests.get(url, stream=True).raw) floor_img.paste(im, (int(W/10), 0)) W += self.Plan[i][1][0]['properties']['Ширина']['number'] floor_img.save(f'./floor_img.png') class GENETIC_FLOOR(): FLOOR_SIZE = 10 NEWBORN = 20 MUTATED = 100 SURVIVERS = 1 # [Floors, Angle, Floor_width, Floor_deep, Room_S, Room_1k, Room_2k, Room_3k] def __init__(self, data): self.data = data self.Generation = {} def RunGenerations(self, N): for i in range(N): #print(f'--- Поколение №{(i+1)} ---') if hasattr(self, 'BestFloor'): # Создаем поколение и передаем в него лучший вариант из предыдущего поколения self.BuildNewGeneration([self.BestFloor[0]]) else: # Создаем первое поколение self.BuildNewGeneration() # Определяем лучшый вариант в текущем поколении self.FindBestFloor(GENETIC_FLOOR.SURVIVERS) #print(f'Первый вариант в поколении: {self.Generation[0].Width}') #print(f'Ко-во вариантов в поколении: {len(self.Generation)}') print(f'Итоговое распределение квартир: {self.BestFloor[0].FloorPercent_Dict}, отклонения: {self.BestDeltaP}/{self.BestDeltaS}/{self.BestDelta}, ширина: {self.BestFloor[0].Width}') #print(f'Квартир на этаже: {self.BestFloor[0].N}') def BuildNewGeneration(self, Survivers_array=[]): NewGeneration = {} self.Generation = {} index = 0 # Добавляем лучшие этажи из предыдущего поколения к новому поколению for i in Survivers_array: NewGeneration[index] = copy.deepcopy(i) index += 1 # Меняем планировки в лучших вариантах из предыдущего поколения for i in Survivers_array: for k in range(GENETIC_FLOOR.MUTATED): for p in range(len(i.Plan)): if i.Plan[p][0].DNA['Type'] == 'E': if random.randint(1, 20) == 5: flat = random.choice(DB_FlatType_Dict['Торцевая']) i.Plan[p][1][0] = flat #print(f'Мутация торцевой квартиры') if i.Plan[p][0].DNA['Type'] == 'S': if random.randint(1, 20) == 5: flat = random.choice(DB_FlatType_Dict['Рядовая']) i.Plan[p][1][0] = flat #print(f'Мутация радовой квартиры') NewGeneration[index] = i index += 1 # Добавляем новых вариантов к новому поколению for i in range(GENETIC_FLOOR.NEWBORN): N_new = GENETIC_FLOOR.FLOOR_SIZE + random.randint(-3, 4) if N_new<=0: N_new = 1 floor1 = FLOOR(N_new) floor1.FillFloor() NewGeneration[index] = floor1 index += 1 self.Generation = copy.deepcopy(NewGeneration) def FindBestFloor(self, N=1): DeltaP = 1000000 # Отклонение по процентам DeltaS = 1000000 # Отклонение по площади Delta = 1000000 # Отклонение сводное #Delta_array = [] BestFloor = [] for i in self.Generation.keys(): g = self.Generation[i] # Определили процентовку g.FloorPercent() # Определили геометрию g.FloorSquare() # Считаем отклонения от заданной процентовки DeltaP_tmp = self.CampareFloorPercent({'S': self.data[4], '1k': self.data[5], '2k': self.data[6], '3k': self.data[7]}, g.FloorPercent_Dict) # Считаем отклонение от Ширины этажа DeltaS_tmp = abs(g.Width - self.data[2]) # Выводим сводное отклонение Delta_tmp = DeltaP_tmp + DeltaS_tmp / 1500 if Delta_tmp < Delta: BestFloor.insert(0, g) DeltaP = DeltaP_tmp DeltaS = DeltaS_tmp Delta = Delta_tmp #Delta_array.append(Delta_tmp) self.BestFloor = BestFloor self.BestDelta = Delta self.BestDeltaP = DeltaP self.BestDeltaS = DeltaS GENETIC_FLOOR.FLOOR_SIZE = self.BestFloor[0].N def CampareFloorPercent(self, d1, d2): Delta = 0 for i in d1.keys(): Delta += abs(d1[i] - d2[i]) return Delta