another save

This commit is contained in:
2025-11-05 20:07:35 +03:00
parent 8e8e0abd0d
commit 26bd6da1b4
16 changed files with 328 additions and 512 deletions

View File

@@ -1,159 +1,113 @@
import random
from typing import Callable, Sequence
from typing import Sequence
from .operation import Operation
from .terminal import Terminal
type InitFunc = Callable[["Chromosome"], "Chromosome.Node"]
from .node import Node
from .primitive import Primitive
class Chromosome:
def __init__(
self,
operations: Sequence[Operation],
terminals: Sequence[Terminal],
init_func: InitFunc,
terminals: Sequence[Primitive],
operations: Sequence[Primitive],
root: Node,
):
self.operations = operations
self.terminals = terminals
self.root = init_func(self)
self.operations = operations
self.root = root
def get_depth(self) -> int:
"""Вычисляет глубину дерева. Дерево из одного только корня имеет глубину 1."""
return self.root.get_depth() if self.root is not None else 0
def copy(self) -> Chromosome:
return Chromosome(self.terminals, self.operations, self.root.copy_subtree())
def clone(self) -> "Chromosome":
"""Создает копию хромосомы."""
return Chromosome(
self.operations,
self.terminals,
lambda _: self.root.clone(),
)
def prune(self, max_depth: int) -> None:
self.root.prune(self.terminals, max_depth)
def eval(self, values: list[float]) -> float:
"""Вычисляет значение хромосомы для заданных значений терминалов."""
if self.root is None:
raise ValueError("Chromosome is not initialized")
def shrink_mutation(self) -> None:
"""Усекающая мутация. Заменяет случайно выбранную операцию на случайный терминал."""
operation_nodes = [n for n in self.root.list_nodes() if n.value.arity > 0]
# Мне это не нравится, но, возможно, это будет работать
for terminal, value in zip(self.terminals, values):
terminal._value = value
if not operation_nodes:
return
return self.root._eval()
target_node = random.choice(operation_nodes)
target_node.prune(self.terminals, max_depth=1)
def grow_mutation(self, max_depth: int) -> None:
"""Растущая мутация. Заменяет случайно выбранный узел на случайное поддерево."""
target_node = random.choice(self.root.list_nodes())
max_subtree_depth = max_depth - target_node.get_level() + 1
subtree = Chromosome.grow_init(
self.terminals, self.operations, max_subtree_depth
).root
if target_node.parent:
target_node.parent.replace_child(target_node, subtree)
else:
self.root = subtree
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
@classmethod
def full_init(
cls,
terminals: Sequence[Primitive],
operations: Sequence[Primitive],
max_depth: int,
) -> Chromosome:
"""Полная инициализация.
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)
def build(level: int) -> Node:
# Если достигнута максимальная глубина — выбираем терминал
if level == max_depth:
return Node(random.choice(terminals))
class Node:
def __init__(
self, value: Operation | Terminal, children: list["Chromosome.Node"]
):
self.value = value
self.children = children
# Иначе выбираем операцию и создаём потомков
op = random.choice(operations)
node = Node(op)
for _ in range(op.arity):
node.add_child(build(level + 1))
return node
def clone(self) -> "Chromosome.Node":
"""Создает копию поддерева."""
return Chromosome.Node(
self.value, [child.clone() for child in self.children]
)
return cls(terminals, operations, build(1))
def get_depth(self) -> int:
"""Вычисляет глубину поддерева."""
return (
max(child.get_depth() for child in self.children) + 1
if self.children
else 1
)
@classmethod
def grow_init(
cls,
terminals: Sequence[Primitive],
operations: Sequence[Primitive],
max_depth: int,
# min_depth: int, # ???
terminal_probability: float = 0.5,
) -> Chromosome:
"""Растущая инициализация.
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]})"
def build(level: int) -> Node:
# Если достигнута максимальная глубина, либо сыграла заданная вероятность
# — выбираем терминал
if level == max_depth or random.random() < terminal_probability:
return Node(random.choice(terminals))
return (
f"{self.value.name}({', '.join(str(child) for child in self.children)})"
)
# Иначе выбираем случайную операцию и создаём потомков
op = random.choice(operations)
node = Node(op)
for _ in range(op.arity):
node.add_child(build(level + 1))
return node
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)
return cls(terminals, operations, build(1))