"""Genetic operators: selection, crossover, mutation.""" from __future__ import annotations import random from factor_engine.gp.operators import ( AbsOp, Neg, RankCS, TsDecayLinear, TsEMA, TsMean, TsRank, TsSlope, TsStd, TsZScore, ZScoreCS, choose_binary_operator, generate_random_tree, make_binary, make_unary, ) def tree_too_large(tree, max_depth, max_nodes): return tree.get_depth() > max_depth or tree.get_size() > max_nodes def tournament_selection(population, fitnesses, k=5): idxs = random.sample(range(len(population)), k) best_idx = max(idxs, key=lambda i: fitnesses[i]) return population[best_idx].clone() def replace_random_subtree(tree, new_subtree): tree = tree.clone() nodes = tree.get_nodes() if len(nodes) <= 1: return new_subtree.clone() target = random.choice(nodes) if target is tree: return new_subtree.clone() for node in nodes: if hasattr(node, "children"): for i, child in enumerate(node.children): if child is target: node.children[i] = new_subtree.clone() return tree return tree def mutate(tree, max_init_depth): if random.random() < 0.55: new_subtree = generate_random_tree(1, max_depth=random.randint(2, max(2, max_init_depth - 1))) return replace_random_subtree(tree, new_subtree) if random.random() < 0.85: child = tree.clone() op = random.choice([AbsOp, Neg, RankCS, ZScoreCS, TsMean, TsStd, TsZScore, TsRank, TsDecayLinear, TsEMA, TsSlope]) return make_unary(op, child) return make_binary(choose_binary_operator(), tree.clone(), generate_random_tree(1, max_depth=3)) def crossover(parent1, parent2): p1 = parent1.clone() donor = random.choice(parent2.clone().get_nodes()).clone() return replace_random_subtree(p1, donor)