Files
genetic-algorithms/lab4/gp/mutations.py
2025-11-04 15:02:02 +03:00

144 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import random
from typing import Optional
from .chromosome import Chromosome
from .operation import Operation
from .terminal import Terminal
def mutate_grow(parent: Chromosome, max_depth: int, max_tries: int = 64) -> Chromosome:
"""
Растущая мутация (subtree-growing mutation).
Аргументы:
parent : исходная хромосома (не изменяется).
max_depth : верхняя граница глубины мутанта.
max_tries : ограничение попыток подбора узла/поддерева.
Возвращает:
Новый экземпляр Chromosome (мутант).
"""
# ---------- Вспомогательные ----------
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)
depth_from_root: 1 для корня, 2 для детей корня и т.д.
"""
out: list[
tuple[
Chromosome.Node,
Optional[Chromosome.Node],
Optional[int],
list[int],
int,
]
] = []
def dfs(
n: Chromosome.Node,
p: Optional[Chromosome.Node],
idx: Optional[int],
path: list[int],
depth: int,
) -> None:
out.append((n, p, idx, path, depth))
for i, ch in enumerate(n.children):
dfs(ch, n, 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]:
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 build_depth_limited_subtree(
chromo: Chromosome, max_depth_limit: int, current_depth: int = 1
) -> Chromosome.Node:
"""
Строит случайное поддерево с ограничением по глубине.
current_depth: текущая глубина узла (1 для корня поддерева).
max_depth_limit: максимальная допустимая глубина поддерева.
"""
# Если достигли максимальной глубины — обязательно терминал
if current_depth >= max_depth_limit:
term = random.choice(chromo.terminals)
return Chromosome.Node(term, [])
# Иначе случайно выбираем между операцией и терминалом
# С большей вероятностью выбираем операцию, если глубина позволяет
if random.random() < 0.7: # 70% вероятность операции
op = random.choice(chromo.operations)
children = [
build_depth_limited_subtree(chromo, max_depth_limit, current_depth + 1)
for _ in range(op.arity)
]
return Chromosome.Node(op, children)
else:
term = random.choice(chromo.terminals)
return Chromosome.Node(term, [])
# ---------- Подготовка ----------
# Если в дереве только терминал — мутация невозможна (нужен нетерминал)
if isinstance(parent.root.value, Terminal):
return parent.clone()
# Работаем на клоне
child = parent.clone()
# Список нетерминальных узлов с путями и глубинами
nodes = enumerate_nodes_with_meta(child.root)
internal = [
(n, p, i, path, depth)
for (n, p, i, path, depth) in nodes
if isinstance(n.value, Operation)
]
if not internal:
# На всякий случай: если всё терминалы
return child
# ---------- Основной цикл подбора позиции ----------
for _ in range(max_tries):
node, _, _, path, node_depth = random.choice(internal)
# Вычисляем максимальную допустимую глубину для нового поддерева
# max_depth - node_depth + 1 (так как node_depth начинается с 1)
allowed_subtree_depth = max_depth - node_depth + 1
if allowed_subtree_depth < 1:
# Этот узел слишком глубоко — попробуем другой
continue
# Строим новое поддерево с ограничением по глубине
new_sub = build_depth_limited_subtree(child, allowed_subtree_depth)
# Вставляем его на место узла мутации
parent_node, idx, _ = get_parent_and_index_by_path(child.root, path)
if parent_node is None:
child.root = new_sub
else:
parent_node.children[idx] = new_sub # type: ignore[index]
# Проверяем, что не превысили максимальную глубину
if child.get_depth() <= max_depth:
return child
else:
# Откат: пересоздаём клон
child = parent.clone()
# Если не удалось подобрать подходящее место/поддерево — вернём немутированного клона
return parent.clone()