Вынес методы инициализации из хромосомы

This commit is contained in:
2025-10-21 18:14:20 +03:00
parent afd7a700ca
commit 83be98e923
3 changed files with 84 additions and 73 deletions

View File

@@ -1,10 +1,10 @@
import random
from typing import Literal, Sequence
from typing import Callable, Sequence
from .operation import Operation
from .terminal import Terminal
type InitMethod = Literal["full", "grow"]
type InitFunc = Callable[["Chromosome"], "Chromosome.Node"]
class Chromosome:
@@ -12,76 +12,21 @@ class Chromosome:
self,
operations: Sequence[Operation],
terminals: Sequence[Terminal],
max_depth: int,
init_method: InitMethod,
terminal_probability: float = 0.5,
init_func: InitFunc,
):
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)
self.root = init_func(self)
def _compute_depth(self, node: "Chromosome.Node") -> int:
def get_depth(self) -> 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)
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
@@ -104,6 +49,9 @@ class 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
@@ -117,6 +65,14 @@ class Chromosome:
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):
@@ -136,3 +92,53 @@ class Chromosome:
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)