another save
This commit is contained in:
@@ -1,192 +1,28 @@
|
||||
import random
|
||||
from typing import Optional
|
||||
|
||||
from .chromosome import Chromosome
|
||||
from .operation import Operation
|
||||
from .terminal import Terminal
|
||||
from .node import swap_subtrees
|
||||
|
||||
|
||||
def crossover_subtree(
|
||||
p1: Chromosome, p2: Chromosome, max_depth: int | None = None
|
||||
parent1: Chromosome, parent2: Chromosome, max_depth: int
|
||||
) -> tuple[Chromosome, Chromosome]:
|
||||
"""Кроссовер поддеревьев.
|
||||
|
||||
Выбираются случайные узлы в каждом родителе, затем соответствующие им поддеревья
|
||||
меняются местами. Если глубина результирующих хромосом превышает max_depth,
|
||||
то их деревья обрезаются до max_depth.
|
||||
"""
|
||||
Кроссовер поддеревьев: выбираются случайные узлы в каждом родителе,
|
||||
затем соответствующие поддеревья обмениваются местами. Возвращаются два новых потомка.
|
||||
child1 = parent1.copy()
|
||||
child2 = parent2.copy()
|
||||
|
||||
Аргументы:
|
||||
p1 : первый родитель (не изменяется).
|
||||
p2 : второй родитель (не изменяется).
|
||||
max_depth : максимальная допустимая глубина потомков. Если None, ограничение не применяется.
|
||||
# Выбираем случайные узлы, не включая корень
|
||||
cut1 = random.choice(child1.root.list_nodes()[1:])
|
||||
cut2 = random.choice(child2.root.list_nodes()[1:])
|
||||
|
||||
Примечания:
|
||||
- Для «совместимости» узлов сперва пытаемся подобрать пары одного класса
|
||||
(оба Terminal или оба Operation). Если подходящей пары не нашлось за
|
||||
разумное число попыток — допускаем любой обмен.
|
||||
- Если задан max_depth, проверяется глубина результирующих потомков,
|
||||
и при превышении лимита выбор узлов повторяется.
|
||||
- Обмен выполняется на КЛОНАХ родителей, чтобы не портить входные деревья.
|
||||
"""
|
||||
swap_subtrees(cut1, cut2)
|
||||
|
||||
# -------- Вспомогательные функции --------
|
||||
child1.prune(max_depth)
|
||||
child2.prune(max_depth)
|
||||
|
||||
def enumerate_nodes_with_meta(
|
||||
root: Chromosome.Node,
|
||||
) -> list[
|
||||
tuple[Chromosome.Node, Optional[Chromosome.Node], Optional[int], list[int], int]
|
||||
]:
|
||||
"""
|
||||
Возвращает список кортежей: (node, parent, index_in_parent, path_from_root, depth_from_root)
|
||||
path_from_root — список индексов детей от корня до узла.
|
||||
depth_from_root: 1 для корня, 2 для детей корня и т.д.
|
||||
"""
|
||||
out: list[
|
||||
tuple[
|
||||
Chromosome.Node,
|
||||
Optional[Chromosome.Node],
|
||||
Optional[int],
|
||||
list[int],
|
||||
int,
|
||||
]
|
||||
] = []
|
||||
|
||||
def dfs(
|
||||
node: Chromosome.Node,
|
||||
parent: Optional[Chromosome.Node],
|
||||
idx: Optional[int],
|
||||
path: list[int],
|
||||
depth: int,
|
||||
) -> None:
|
||||
out.append((node, parent, idx, path, depth))
|
||||
for i, ch in enumerate(node.children):
|
||||
dfs(ch, node, i, path + [i], depth + 1)
|
||||
|
||||
dfs(root, None, None, [], 1)
|
||||
return out
|
||||
|
||||
def get_parent_and_index_by_path(
|
||||
root: Chromosome.Node, path: list[int]
|
||||
) -> tuple[Optional[Chromosome.Node], Optional[int], Chromosome.Node]:
|
||||
"""
|
||||
По пути возвращает (parent, index_in_parent, node).
|
||||
Для корня parent/index равны None.
|
||||
"""
|
||||
if not path:
|
||||
return None, None, root
|
||||
|
||||
parent = root
|
||||
for i in path[:-1]:
|
||||
parent = parent.children[i]
|
||||
idx = path[-1]
|
||||
return parent, idx, parent.children[idx]
|
||||
|
||||
def is_op(node: Chromosome.Node) -> bool:
|
||||
return isinstance(node.value, Operation)
|
||||
|
||||
def is_term(node: Chromosome.Node) -> bool:
|
||||
return isinstance(node.value, Terminal)
|
||||
|
||||
def check_depth_after_swap(
|
||||
node_depth: int, new_subtree: Chromosome.Node, max_d: int
|
||||
) -> bool:
|
||||
"""
|
||||
Проверяет, не превысит ли глубина дерева max_d после замены узла на глубине node_depth
|
||||
на поддерево new_subtree.
|
||||
|
||||
node_depth: глубина узла, который заменяем (1 для корня)
|
||||
new_subtree: поддерево, которое вставляем
|
||||
max_d: максимальная допустимая глубина
|
||||
|
||||
Возвращает True, если глубина будет в пределах нормы.
|
||||
"""
|
||||
# Глубина нового поддерева
|
||||
subtree_depth = new_subtree.get_depth()
|
||||
# Итоговая глубина = глубина узла + глубина поддерева - 1
|
||||
# (т.к. node_depth уже включает этот узел)
|
||||
resulting_depth = node_depth + subtree_depth - 1
|
||||
return resulting_depth <= max_d
|
||||
|
||||
# -------- Выбор доминантного/рецессивного родителя (просто случайно) --------
|
||||
dom, rec = (p1, p2) if random.random() < 0.5 else (p2, p1)
|
||||
|
||||
# Собираем все узлы с метаданными
|
||||
dom_nodes = enumerate_nodes_with_meta(dom.root)
|
||||
rec_nodes = enumerate_nodes_with_meta(rec.root)
|
||||
|
||||
# Пытаемся выбрать совместимые узлы: оба термины ИЛИ оба операции.
|
||||
# Дадим несколько попыток, затем, если не повезло — возьмём любые.
|
||||
MAX_TRIES = 64
|
||||
chosen_dom = None
|
||||
chosen_rec = None
|
||||
|
||||
for _ in range(MAX_TRIES):
|
||||
nd = random.choice(dom_nodes)
|
||||
# Предпочтём узел того же «класса»
|
||||
if is_term(nd[0]):
|
||||
same_type_pool = [nr for nr in rec_nodes if is_term(nr[0])]
|
||||
elif is_op(nd[0]):
|
||||
same_type_pool = [nr for nr in rec_nodes if is_op(nr[0])]
|
||||
else:
|
||||
same_type_pool = rec_nodes # на всякий
|
||||
|
||||
if same_type_pool:
|
||||
nr = random.choice(same_type_pool)
|
||||
|
||||
# Если задан max_depth, проверяем, что обмен не приведёт к превышению глубины
|
||||
if max_depth is not None:
|
||||
nd_node, _, _, nd_path, nd_depth = nd
|
||||
nr_node, _, _, nr_path, nr_depth = nr
|
||||
|
||||
# Проверяем обе возможные замены
|
||||
dom_ok = check_depth_after_swap(nd_depth, nr_node, max_depth)
|
||||
rec_ok = check_depth_after_swap(nr_depth, nd_node, max_depth)
|
||||
|
||||
if dom_ok and rec_ok:
|
||||
chosen_dom, chosen_rec = nd, nr
|
||||
break
|
||||
# Иначе пробуем другую пару
|
||||
else:
|
||||
# Если ограничения нет, принимаем первую подходящую пару
|
||||
chosen_dom, chosen_rec = nd, nr
|
||||
break
|
||||
|
||||
# Если подобрать подходящую пару не удалось
|
||||
if chosen_dom is None or chosen_rec is None:
|
||||
# Возвращаем клоны родителей без изменений
|
||||
return (p1.clone(), p2.clone())
|
||||
|
||||
_, _, _, dom_path, _ = chosen_dom
|
||||
_, _, _, rec_path, _ = chosen_rec
|
||||
|
||||
# -------- Создаём клоны родителей --------
|
||||
c_dom = dom.clone()
|
||||
c_rec = rec.clone()
|
||||
|
||||
# Выцепляем соответствующие позиции на клонах по тем же путям
|
||||
c_dom_parent, c_dom_idx, c_dom_node = get_parent_and_index_by_path(
|
||||
c_dom.root, dom_path
|
||||
)
|
||||
c_rec_parent, c_rec_idx, c_rec_node = get_parent_and_index_by_path(
|
||||
c_rec.root, rec_path
|
||||
)
|
||||
|
||||
# Клонируем поддеревья, чтобы не смешивать ссылки между хромосомами
|
||||
subtree_dom = c_dom_node.clone()
|
||||
subtree_rec = c_rec_node.clone()
|
||||
|
||||
# Меняем местами
|
||||
if c_dom_parent is None:
|
||||
# Меняем корень
|
||||
c_dom.root = subtree_rec
|
||||
else:
|
||||
c_dom_parent.children[c_dom_idx] = subtree_rec # type: ignore[index]
|
||||
|
||||
if c_rec_parent is None:
|
||||
c_rec.root = subtree_dom
|
||||
else:
|
||||
c_rec_parent.children[c_rec_idx] = subtree_dom # type: ignore[index]
|
||||
|
||||
# Возвращаем потомков в том же порядке, что и вход (p1 -> first, p2 -> second)
|
||||
if dom is p1:
|
||||
return (c_dom, c_rec)
|
||||
else:
|
||||
return (c_rec, c_dom)
|
||||
return child1, child2
|
||||
|
||||
Reference in New Issue
Block a user