Files
genetic-algorithms/lab4/gp/chromosome.py

139 lines
6.1 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 Literal, Sequence
from .operation import Operation
from .terminal import Terminal
type InitMethod = Literal["full", "grow"]
class Chromosome:
def __init__(
self,
operations: Sequence[Operation],
terminals: Sequence[Terminal],
max_depth: int,
init_method: InitMethod,
terminal_probability: float = 0.5,
):
self.operations = operations
self.terminals = terminals
self.root = (
self._init_full(max_depth)
if init_method == "full"
else self._init_grow(max_depth, terminal_probability)
)
self.depth = self._compute_depth(self.root)
def _compute_depth(self, node: "Chromosome.Node") -> int:
"""Вычисляет глубину дерева. Дерево из одного только корня имеет глубину 1."""
return (
max(self._compute_depth(child) for child in node.children) + 1
if node.children
else 1
)
def _random_terminal(self) -> Terminal:
return random.choice(self.terminals)
def _init_full(self, max_depth: int) -> "Chromosome.Node":
"""Полная инициализация.
В полном методе при генерации дерева, пока не достигнута максимальная глубина,
допускается выбор только функциональных символов, а на последнем уровне
(максимальной глубины) выбираются только терминальные символы.
"""
def build(level: int) -> Chromosome.Node:
# Если достигнута максимальная глубина — выбираем терминал
if level == max_depth:
return Chromosome.Node(self._random_terminal(), [])
# Иначе выбираем операцию и создаём потомков
op = random.choice(self.operations)
node = Chromosome.Node(op, [build(level + 1) for _ in range(op.arity)])
return node
return build(1)
def _init_grow(
self, max_depth: int, terminal_probability: float
) -> "Chromosome.Node":
"""Растущая инициализация.
В растущей инициализации генерируются нерегулярные деревья с различной глубиной
листьев вследствие случайного на каждом шаге выбора функционального
или терминального символа. Здесь при выборе терминального символа рост дерева
прекращается по текущей ветви и поэтому дерево имеет нерегулярную структуру.
"""
def build(level: int) -> Chromosome.Node:
# Если достигнута максимальная глубина, либо сыграла заданная вероятность
# — выбираем терминал
if level == max_depth or random.random() < terminal_probability:
return Chromosome.Node(self._random_terminal(), [])
# Иначе выбираем случайную операцию и создаём потомков
op = random.choice(self.operations)
children = [build(level + 1) for _ in range(op.arity)]
return Chromosome.Node(op, children)
return build(1)
def eval(self, values: list[float]) -> float:
"""Вычисляет значение хромосомы для заданных значений терминалов."""
for terminal, value in zip(self.terminals, values):
terminal._value = value
return self.root._eval()
def __str__(self) -> str:
"""Строковое представление хромосомы в виде формулы в инфиксной форме."""
return str(self.root)
def _tree_lines(
self, node: "Chromosome.Node", prefix: str = "", is_last: bool = True
) -> list[str]:
connector = "└── " if is_last else "├── "
lines = [prefix + connector + node.value.name]
child_prefix = prefix + (" " if is_last else "")
for i, child in enumerate(node.children):
last = i == len(node.children) - 1
lines.extend(self._tree_lines(child, child_prefix, last))
return lines
def str_tree(self) -> str:
"""Строковое представление древовидной структуры формулы."""
lines = [self.root.value.name]
for i, child in enumerate(self.root.children):
last = i == len(self.root.children) - 1
lines.extend(self._tree_lines(child, "", last))
return "\n".join(lines)
class Node:
def __init__(
self, value: Operation | Terminal, children: list["Chromosome.Node"]
):
self.value = value
self.children = children
def __str__(self) -> str:
"""Рекурсивный перевод древовидного вида формулы в строку в инфиксной форме."""
if isinstance(self.value, Terminal):
return self.value.name
if self.value.arity == 2:
return f"({self.children[0]} {self.value.name} {self.children[1]})"
return (
f"{self.value.name}({', '.join(str(child) for child in self.children)})"
)
def _eval(self) -> float:
"""Рекурсивно вычисляет значение поддерева. Значения терминалов должны быть
заданы предварительно."""
if isinstance(self.value, Terminal):
return self.value._value # type: ignore
return self.value._eval([child._eval() for child in self.children])