import random import math from typing import List, Dict, Any, Tuple from itertools import product class SmartSearch: def __init__(self, ea_config): self.ea_config = ea_config self.total_combinations = ea_config.get_total_combinations() def select_strategy(self) -> str: if self.ea_config.search_strategy != 'auto': return self.ea_config.search_strategy if self.total_combinations <= 10000: return 'grid' elif self.total_combinations <= 500000: return 'latin' else: return 'genetic' def generate_combinations(self, max_samples: int = 2000) -> List[Dict]: strategy = self.select_strategy() print('Selected strategy: ' + strategy + ' for ' + str(self.total_combinations) + ' combinations') if strategy == 'grid': return self._grid_search() elif strategy == 'random': return self._random_search(max_samples) elif strategy == 'latin': return self._latin_hypercube(max_samples) elif strategy == 'genetic': return self._genetic_search(max_samples) return [] def _grid_search(self) -> List[Dict]: param_names = list(self.ea_config.parameters.keys()) param_values = [p.expand_values() for p in self.ea_config.parameters.values()] combinations = list(product(*param_values)) return [dict(zip(param_names, combo)) for combo in combinations] def _random_search(self, max_samples: int) -> List[Dict]: all_values = {name: p.expand_values() for name, p in self.ea_config.parameters.items()} samples = [] for _ in range(min(max_samples, self.total_combinations)): sample = {name: random.choice(values) for name, values in all_values.items()} if sample not in samples: samples.append(sample) return samples def _latin_hypercube(self, max_samples: int) -> List[Dict]: all_values = {name: p.expand_values() for name, p in self.ea_config.parameters.items()} n_params = len(all_values) samples = [] for i in range(min(max_samples, self.total_combinations)): sample = {} for j, (name, values) in enumerate(all_values.items()): idx = int((i / max_samples) * len(values)) % len(values) sample[name] = values[idx] if sample not in samples: samples.append(sample) return samples def _genetic_search(self, max_samples: int) -> List[Dict]: pop_size = min(50, max_samples) n_generations = max_samples // pop_size all_values = {name: p.expand_values() for name, p in self.ea_config.parameters.items()} population = [] for _ in range(pop_size): individual = {name: random.choice(values) for name, values in all_values.items()} population.append(individual) for gen in range(n_generations): population = self._evolve(population, all_values) return population[:max_samples] def _evolve(self, population: List[Dict], all_values: Dict) -> List[Dict]: crossover_rate = 0.8 mutation_rate = 0.15 offspring = [] for _ in range(len(population)): parent1, parent2 = random.sample(population, 2) if random.random() < crossover_rate: child = self._crossover(parent1, parent2) else: child = parent1.copy() if random.random() < mutation_rate: child = self._mutate(child, all_values) offspring.append(child) return population[:5] + offspring[:len(population)-5] def _crossover(self, parent1: Dict, parent2: Dict) -> Dict: child = {} for key in parent1.keys(): if random.random() < 0.5: child[key] = parent1[key] else: child[key] = parent2[key] return child def _mutate(self, individual: Dict, all_values: Dict) -> Dict: key = random.choice(list(all_values.keys())) individual[key] = random.choice(all_values[key]) return individual class GeneticOptimizer: def __init__(self, ea_config, population_size: int = 50, generations: int = 30): self.ea_config = ea_config self.population_size = population_size self.generations = generations self.all_values = {name: p.expand_values() for name, p in ea_config.parameters.items()} def create_individual(self) -> Dict: return {name: random.choice(values) for name, values in self.all_values.items()} def evaluate(self, individual: Dict) -> float: return random.random() * 10 def tournament_select(self, population: List[Dict], k: int = 3) -> Dict: tournament = random.sample(population, k) return max(tournament, key=self.evaluate) def crossover(self, parent1: Dict, parent2: Dict) -> Tuple[Dict, Dict]: child1, child2 = {}, {} for key in parent1.keys(): if random.random() < 0.5: child1[key] = parent1[key] child2[key] = parent2[key] else: child1[key] = parent2[key] child2[key] = parent1[key] return child1, child2 def mutate(self, individual: Dict, rate: float = 0.15) -> Dict: for key in individual.keys(): if random.random() < rate: individual[key] = random.choice(self.all_values[key]) return individual def run(self) -> List[Dict]: population = [self.create_individual() for _ in range(self.population_size)] best_individuals = [] for gen in range(self.generations): fitness_scores = [(ind, self.evaluate(ind)) for ind in population] fitness_scores.sort(key=lambda x: x[1], reverse=True) elite = [ind for ind, _ in fitness_scores[:5]] best_individuals.extend(elite) new_population = elite.copy() while len(new_population) < self.population_size: parent1 = self.tournament_select(population) parent2 = self.tournament_select(population) child1, child2 = self.crossover(parent1, parent2) child1 = self.mutate(child1) child2 = self.mutate(child2) new_population.extend([child1, child2]) population = new_population[:self.population_size] return best_individuals