From 83be98e923a133c52f2c893db1100238d5fe7c51 Mon Sep 17 00:00:00 2001 From: Arity-T Date: Tue, 21 Oct 2025 18:14:20 +0300 Subject: [PATCH] =?UTF-8?q?=D0=92=D1=8B=D0=BD=D0=B5=D1=81=20=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D0=BE=D0=B4=D1=8B=20=D0=B8=D0=BD=D0=B8=D1=86=D0=B8=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B8=D0=B7=20?= =?UTF-8?q?=D1=85=D1=80=D0=BE=D0=BC=D0=BE=D1=81=D0=BE=D0=BC=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab4/gp/chromosome.py | 134 ++++++++++++++++++++++-------------------- lab4/gp/population.py | 16 +++-- lab4/main.py | 7 ++- 3 files changed, 84 insertions(+), 73 deletions(-) diff --git a/lab4/gp/chromosome.py b/lab4/gp/chromosome.py index c52bcdf..74ed413 100644 --- a/lab4/gp/chromosome.py +++ b/lab4/gp/chromosome.py @@ -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) diff --git a/lab4/gp/population.py b/lab4/gp/population.py index 1906019..8ac5c97 100644 --- a/lab4/gp/population.py +++ b/lab4/gp/population.py @@ -1,7 +1,7 @@ import random from typing import Callable -from .chromosome import Chromosome, InitMethod +from .chromosome import Chromosome, InitFunc, init_full, init_grow type Population = list[Chromosome] @@ -9,7 +9,7 @@ type Population = list[Chromosome] def ramped_initialization( population_size: int, depths: list[int], - make_chromosome: Callable[[int, InitMethod], Chromosome], # (max_depth, method) + make_chromosome: Callable[[InitFunc], Chromosome], ) -> Population: """Комбинация методов grow и full инициализации хромосом для инициализации начальной популяции. @@ -25,14 +25,18 @@ def ramped_initialization( n_full = int(per_depth / 2) n_grow = int(per_depth / 2) - population.extend(make_chromosome(depth, "full") for _ in range(n_full)) - population.extend(make_chromosome(depth, "grow") for _ in range(n_grow)) + population.extend( + make_chromosome(lambda c: init_full(c, depth)) for _ in range(n_full) + ) + population.extend( + make_chromosome(lambda c: init_grow(c, depth)) for _ in range(n_grow) + ) # Из-за округления хромосом может оказаться меньше заданного количества, # поэтому дозаполняем остаток популяции случайными хромосомами while len(population) < population_size: depth = random.choice(depths) - method = "full" if random.random() < 0.5 else "grow" - population.append(make_chromosome(depth, method)) + init_func = init_full if random.random() < 0.5 else init_grow + population.append(make_chromosome(lambda c: init_func(c, depth))) return population diff --git a/lab4/main.py b/lab4/main.py index 44c2743..89bcf50 100644 --- a/lab4/main.py +++ b/lab4/main.py @@ -1,12 +1,13 @@ from gp import Chromosome, Terminal, ops +from gp.chromosome import init_grow from gp.population import ramped_initialization operations = ops.ALL terminals = [Terminal(f"x{i}") for i in range(1, 9)] -chrom = Chromosome(operations, terminals, 8, "grow") -print("Depth:", chrom.depth) +chrom = Chromosome(operations, terminals, init_func=lambda c: init_grow(c, 8)) +print("Depth:", chrom.get_depth()) print("Formula:", chrom) print("Tree:\n", chrom.str_tree()) @@ -16,7 +17,7 @@ print("Value for ", values, ":", chrom.eval(values)) population = ramped_initialization( 100, [3, 4, 5, 6, 7, 8], - lambda depth, method: Chromosome(operations, terminals, depth, method), + lambda init_func: Chromosome(operations, terminals, init_func), ) print("Population size:", len(population)) print("Population:")