145 lines
6.1 KiB
Python
145 lines
6.1 KiB
Python
import random
|
||
from typing import Callable, Sequence
|
||
|
||
from .operation import Operation
|
||
from .terminal import Terminal
|
||
|
||
type InitFunc = Callable[["Chromosome"], "Chromosome.Node"]
|
||
|
||
|
||
class Chromosome:
|
||
def __init__(
|
||
self,
|
||
operations: Sequence[Operation],
|
||
terminals: Sequence[Terminal],
|
||
init_func: InitFunc,
|
||
):
|
||
self.operations = operations
|
||
self.terminals = terminals
|
||
self.root = init_func(self)
|
||
|
||
def get_depth(self) -> int:
|
||
"""Вычисляет глубину дерева. Дерево из одного только корня имеет глубину 1."""
|
||
return self.root.get_depth() if self.root is not None else 0
|
||
|
||
def eval(self, values: list[float]) -> float:
|
||
"""Вычисляет значение хромосомы для заданных значений терминалов."""
|
||
if self.root is None:
|
||
raise ValueError("Chromosome is not initialized")
|
||
|
||
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:
|
||
"""Строковое представление древовидной структуры формулы."""
|
||
if self.root is None:
|
||
return ""
|
||
|
||
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 get_depth(self) -> int:
|
||
"""Вычисляет глубину поддерева."""
|
||
return (
|
||
max(child.get_depth() for child in self.children) + 1
|
||
if self.children
|
||
else 1
|
||
)
|
||
|
||
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])
|
||
|
||
|
||
def _random_terminal(terminals: Sequence[Terminal]) -> Terminal:
|
||
return random.choice(terminals)
|
||
|
||
|
||
def init_full(chromosome: Chromosome, max_depth: int) -> Chromosome.Node:
|
||
"""Полная инициализация.
|
||
|
||
В полном методе при генерации дерева, пока не достигнута максимальная глубина,
|
||
допускается выбор только функциональных символов, а на последнем уровне
|
||
(максимальной глубины) выбираются только терминальные символы.
|
||
"""
|
||
|
||
def build(level: int) -> Chromosome.Node:
|
||
# Если достигнута максимальная глубина — выбираем терминал
|
||
if level == max_depth:
|
||
return Chromosome.Node(_random_terminal(chromosome.terminals), [])
|
||
|
||
# Иначе выбираем операцию и создаём потомков
|
||
op = random.choice(chromosome.operations)
|
||
node = Chromosome.Node(op, [build(level + 1) for _ in range(op.arity)])
|
||
return node
|
||
|
||
return build(1)
|
||
|
||
|
||
def init_grow(
|
||
chromosome: Chromosome, max_depth: int, terminal_probability: float = 0.5
|
||
) -> Chromosome.Node:
|
||
"""Растущая инициализация.
|
||
|
||
В растущей инициализации генерируются нерегулярные деревья с различной глубиной
|
||
листьев вследствие случайного на каждом шаге выбора функционального
|
||
или терминального символа. Здесь при выборе терминального символа рост дерева
|
||
прекращается по текущей ветви и поэтому дерево имеет нерегулярную структуру.
|
||
"""
|
||
|
||
def build(level: int) -> Chromosome.Node:
|
||
# Если достигнута максимальная глубина, либо сыграла заданная вероятность
|
||
# — выбираем терминал
|
||
if level == max_depth or random.random() < terminal_probability:
|
||
return Chromosome.Node(_random_terminal(chromosome.terminals), [])
|
||
|
||
# Иначе выбираем случайную операцию и создаём потомков
|
||
op = random.choice(chromosome.operations)
|
||
children = [build(level + 1) for _ in range(op.arity)]
|
||
return Chromosome.Node(op, children)
|
||
|
||
return build(1)
|