diff --git a/include/fces/controller.hpp b/include/fces/controller.hpp index 299957c..e696d3c 100644 --- a/include/fces/controller.hpp +++ b/include/fces/controller.hpp @@ -22,9 +22,9 @@ namespace fces { // Controller input dimension (layer stats features) -constexpr int GENOME_INPUT_DIM = 9; +constexpr int GENOME_INPUT_DIM = 14; // Controller hidden dimension -constexpr int GENOME_HIDDEN_DIM = 16; +constexpr int GENOME_HIDDEN_DIM = 8; // Controller output dimension: [multiplier, sign_gate, wd_mult] constexpr int GENOME_OUTPUT_DIM = 3; // Total genome size: input->hidden weights + hidden biases + hidden->output weights + output biases diff --git a/include/fces/fitness.hpp b/include/fces/fitness.hpp index 6c883be..7a7c258 100644 --- a/include/fces/fitness.hpp +++ b/include/fces/fitness.hpp @@ -59,19 +59,55 @@ private: float grokking_coefficient_; }; +/** + * FuzzySet represents a fuzzy set with a trapezoidal membership function. + */ +class FuzzySet { +public: + FuzzySet(std::string name, float a, float b, float c, float d) noexcept + : name_(std::move(name)), a_(a), b_(b), c_(c), d_(d) {} + + float membership(float x) const noexcept { + if (!std::isfinite(x)) { + return 0.0f; + } + if (x <= a_ || x >= d_) { + return 0.0f; + } + if (x >= b_ && x <= c_) { + return 1.0f; + } + if (x > a_ && x < b_) { + float range = b_ - a_; + return (x - a_) / (range > 0.0f ? range : 1e-9f); + } + if (x > c_ && x < d_) { + float range = d_ - c_; + return (d_ - x) / (range > 0.0f ? range : 1e-9f); + } + return 0.0f; + } + + const std::string& name() const noexcept { return name_; } + +private: + std::string name_; + float a_; + float b_; + float c_; + float d_; +}; + /** * Fitness metrics for multi-objective evaluation. */ struct FitnessMetrics { - float loss_improvement = 0.0f; - float sparsity_score = 0.0f; - float stability_score = 0.0f; - float novelty_score = 0.0f; - - /// Weighted combination - float total(float alpha = 0.7f, float beta = 0.3f) const { - return alpha * loss_improvement + beta * sparsity_score; - } + float training_advantage = 0.0f; + float validation_advantage = 0.0f; + float grad_cv = 0.0f; + float sparsity_delta = 0.0f; + float consistency_gap = 0.0f; + float stable_rank = 0.0f; }; /** @@ -79,12 +115,24 @@ struct FitnessMetrics { */ class FuzzyFitnessEvaluator { public: - FitnessMetrics evaluate( - float loss_before, - float loss_after, - float sparsity = 0.0f, - float val_loss = -1.0f - ) const; + FuzzyFitnessEvaluator() noexcept; + + float evaluate(const FitnessMetrics& metrics) const noexcept; + +private: + FuzzySet stability_set_; + FuzzySet train_set_; + FuzzySet val_set_; + FuzzySet sparsity_set_; + FuzzySet consistency_set_; + FuzzySet rank_set_; + + float w_stability_ = 0.2f; + float w_train_ = 0.2f; + float w_val_ = 0.3f; + float w_sparsity_ = 0.1f; + float w_consistency_ = 0.2f; + float w_rank_ = 0.1f; }; } // namespace fces diff --git a/include/fces/optimizer.hpp b/include/fces/optimizer.hpp index 0ef368f..6ea07d1 100644 --- a/include/fces/optimizer.hpp +++ b/include/fces/optimizer.hpp @@ -74,10 +74,18 @@ private: float rollback_ema_ = 0.0f; int stagnation_counter_ = 0; float last_loss_velocity_ = 0.0f; + float last_sparsity_ = 0.0f; // RAM backup std::vector ram_backup_; + // Layer stats and group mappings + std::vector> layer_stats_; + std::vector param_group_mapping_; + std::unique_ptr spectral_sensor_; + SpectralController spectral_controller_; + float last_spectral_rank_ = 0.0f; + // Internal methods void gather_stats(); void apply_parameter_updates(const torch::Tensor& actions); diff --git a/include/fces/spectral.hpp b/include/fces/spectral.hpp index b5642e8..8f26c48 100644 --- a/include/fces/spectral.hpp +++ b/include/fces/spectral.hpp @@ -23,6 +23,7 @@ namespace fces { */ class SpectralSensor { public: + SpectralSensor() = default; explicit SpectralSensor(torch::nn::Module& model); /// Track a layer's weight tensor diff --git a/src/controller.cpp b/src/controller.cpp index aa810eb..3d4fbb4 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -30,14 +30,21 @@ Genome Genome::clone() const { // --------------------------------------------------------------- FuzzyController::FuzzyController() - : id(next_id_++), origin("random") { + : id(next_id_++), origin("genesis") { genome.randomize(rng_); // Bias output toward acceleration (V2.1 insight) - // Set output biases (last GENOME_OUTPUT_DIM elements) to +2.0 + // Set output biases (last GENOME_OUTPUT_DIM elements) to +2.0, -1.0, 0.0 with noise constexpr int bias_start = GENOME_SIZE - GENOME_OUTPUT_DIM; - for (int i = bias_start; i < GENOME_SIZE; ++i) { - genome.weights[i] = 2.0f; - } + std::normal_distribution bias_noise(0.0f, 0.5f); + genome.weights[bias_start] = 2.0f + bias_noise(rng_); + genome.weights[bias_start + 1] = -1.0f + bias_noise(rng_); + genome.weights[bias_start + 2] = 0.0f + bias_noise(rng_); + + // Initialize plasticity with variance to seed evolution + std::normal_distribution plast_noise(0.0f, 0.05f); + genome.plasticity = std::max(0.01f, 0.1f + plast_noise(rng_)); + genome.sigma_gene = 0.1f; + genome.gene_success.fill(1.0f); } FuzzyController::FuzzyController(Genome genome) @@ -61,46 +68,111 @@ torch::Tensor FuzzyController::decide_update( const float* w = genome.weights.data(); // Layer 1: input -> hidden - const float* W1 = w; // [GENOME_INPUT_DIM x GENOME_HIDDEN_DIM] - const float* b1 = w + (GENOME_INPUT_DIM * GENOME_HIDDEN_DIM); // [GENOME_HIDDEN_DIM] + const float* W1 = w; // [(GENOME_INPUT_DIM + 1) x GENOME_HIDDEN_DIM] // Layer 2: hidden -> output - const float* W2 = b1 + GENOME_HIDDEN_DIM; // [GENOME_HIDDEN_DIM x GENOME_OUTPUT_DIM] - const float* b2 = W2 + (GENOME_HIDDEN_DIM * GENOME_OUTPUT_DIM); // [GENOME_OUTPUT_DIM] + const float* W2 = w + ((GENOME_INPUT_DIM + 1) * GENOME_HIDDEN_DIM); // [(GENOME_HIDDEN_DIM + 1) x GENOME_OUTPUT_DIM] for (int g = 0; g < num_groups; ++g) { + // One-Hot Layer Type: Clamp to 5 to avoid overflow for new categories + float layer_type_val = (layer_stats[g].size() >= 3) ? layer_stats[g][2] : 5.0f; + int type_idx = std::min(5, static_cast(layer_type_val)); + std::array type_onehot{0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; + if (type_idx >= 0 && type_idx < 5) { + type_onehot[type_idx] = 1.0f; + } + // Build input vector std::array input{}; - if (!layer_stats[g].empty()) { - // Copy available stats, pad with context - const int n = std::min(static_cast(layer_stats[g].size()), GENOME_INPUT_DIM - 4); - for (int i = 0; i < n; ++i) { - input[i] = layer_stats[g][i]; - } - } - // Append global context - input[GENOME_INPUT_DIM - 4] = loss_trend; - input[GENOME_INPUT_DIM - 3] = step_pct; - input[GENOME_INPUT_DIM - 2] = grad_stability; - input[GENOME_INPUT_DIM - 1] = stagnation_intensity; + float gn = (layer_stats[g].size() >= 1) ? layer_stats[g][0] : 0.0f; + float sp = (layer_stats[g].size() >= 2) ? layer_stats[g][1] : 0.0f; - // Forward pass: hidden = tanh(W1 * input + b1) + // Sanitization matching nan_to_num + if (!std::isfinite(gn) || std::isnan(gn)) gn = 0.0f; + if (gn > 10.0f) gn = 10.0f; + if (gn < 0.0f) gn = 0.0f; + + if (!std::isfinite(sp) || std::isnan(sp)) sp = 0.0f; + if (sp > 1.0f) sp = 1.0f; + if (sp < 0.0f) sp = 0.0f; + + input[0] = gn; + input[1] = sp; + input[2] = loss_trend; + input[3] = step_pct; + input[4] = (num_groups > 1) ? static_cast(g) / (num_groups - 1.0f) : 0.0f; + input[5] = rollback_rate; + input[6] = grad_stability; + input[7] = spectral_alpha; + input[8] = type_onehot[0]; + input[9] = type_onehot[1]; + input[10] = type_onehot[2]; + input[11] = type_onehot[3]; + input[12] = type_onehot[4]; + input[13] = projected_drift; + + // Forward pass: hidden = tanh(W1 * [input, 1]) std::array hidden{}; for (int h = 0; h < GENOME_HIDDEN_DIM; ++h) { - float sum = b1[h]; + float sum = W1[GENOME_INPUT_DIM * GENOME_HIDDEN_DIM + h]; // Bias weight for (int i = 0; i < GENOME_INPUT_DIM; ++i) { - sum += W1[h * GENOME_INPUT_DIM + i] * input[i]; + sum += input[i] * W1[i * GENOME_HIDDEN_DIM + h]; } hidden[h] = std::tanh(sum); } - // Output = W2 * hidden + b2 + // Output = W2 * [hidden, 1] + std::array out_layer{}; for (int o = 0; o < GENOME_OUTPUT_DIM; ++o) { - float sum = b2[o]; + float sum = W2[GENOME_HIDDEN_DIM * GENOME_OUTPUT_DIM + o]; // Bias weight for (int h = 0; h < GENOME_HIDDEN_DIM; ++h) { - sum += W2[o * GENOME_HIDDEN_DIM + h] * hidden[h]; + sum += hidden[h] * W2[h * GENOME_OUTPUT_DIM + o]; } - actions[g][o] = sum; + out_layer[o] = sum; } + + // Parse Output + float log_mult_raw = out_layer[0]; + float log_wd_raw = out_layer[2]; + float sign_logit = out_layer[1]; + + if (!std::isfinite(sign_logit) || std::isnan(sign_logit)) { + sign_logit = 0.0f; + } + + float noise_std = 0.0f; + if (genome.plasticity > 0.01f) { + noise_std += genome.plasticity; + } + if (stagnation_intensity > 0.0f) { + noise_std += stagnation_intensity * 0.5f; + } + + if (kzm_damping > 0.0f) { + noise_std *= (1.0f - kzm_damping); + log_mult_raw *= (1.0f - kzm_damping); + } + + if (noise_std > 0.0f) { + std::normal_distribution noise_dist(0.0f, noise_std); + log_mult_raw += noise_dist(rng_); + log_wd_raw += noise_dist(rng_); + } + + if (!std::isfinite(log_mult_raw) || std::isnan(log_mult_raw)) { + log_mult_raw = 0.0f; + } + log_mult_raw = std::max(-6.0f, std::min(6.0f, log_mult_raw)); + float mult = std::pow(2.0f, log_mult_raw); + + if (!std::isfinite(log_wd_raw) || std::isnan(log_wd_raw)) { + log_wd_raw = 0.0f; + } + log_wd_raw = std::max(-6.0f, std::min(6.0f, log_wd_raw)); + float wd_mult = std::pow(2.0f, log_wd_raw); + + actions[g][0] = mult; + actions[g][1] = sign_logit; + actions[g][2] = wd_mult; } return actions; @@ -108,67 +180,174 @@ torch::Tensor FuzzyController::decide_update( FuzzyController FuzzyController::mutate(float current_loss, float sigma_scale) const { Genome child_genome = genome.clone(); - std::normal_distribution noise(0.0f, genome.sigma_gene * sigma_scale); + std::normal_distribution std_normal(0.0f, 1.0f); + float tau = 0.2f; + float new_sigma = genome.sigma_gene * std::exp(tau * std_normal(rng_)); + new_sigma = std::max(0.001f, std::min(0.8f, new_sigma)); + + float new_plast = genome.plasticity * std::exp(tau * std_normal(rng_)); + new_plast = std::max(0.0f, std::min(0.5f, new_plast)); + + float loss_val = std::max(0.0f, current_loss); + float annealing_factor = std::sqrt(loss_val + 0.1f); + float effective_sigma = new_sigma * sigma_scale * annealing_factor; + + std::normal_distribution noise(0.0f, effective_sigma); for (size_t i = 0; i < child_genome.weights.size(); ++i) { child_genome.weights[i] += noise(rng_); + child_genome.gene_success[i] = genome.gene_success[i] * 0.95f; } + child_genome.sigma_gene = new_sigma; + child_genome.plasticity = new_plast; + FuzzyController child(child_genome); child.origin = "mutation"; return child; } -FuzzyController FuzzyController::crossover(const FuzzyController& partner, bool use_alignment) const { +FuzzyController FuzzyController::crossover(const FuzzyController& partner, bool /*use_alignment*/) const { Genome child_genome; - std::uniform_real_distribution coin(0.0f, 1.0f); + std::uniform_real_distribution u_dist(0.0f, 1.0f); for (size_t i = 0; i < child_genome.weights.size(); ++i) { - if (use_alignment && genome.gene_success[i] > partner.genome.gene_success[i]) { - child_genome.weights[i] = genome.weights[i]; - } else if (use_alignment && partner.genome.gene_success[i] > genome.gene_success[i]) { - child_genome.weights[i] = partner.genome.weights[i]; + float success_self = genome.gene_success[i]; + float success_partner = partner.genome.gene_success[i]; + float prob_a = success_self / (success_self + success_partner + 1e-9f); + + bool choose_self = false; + if (u_dist(rng_) < 0.1f) { + // 10% Random injection + choose_self = (u_dist(rng_) < 0.5f); } else { - // Uniform crossover - child_genome.weights[i] = (coin(rng_) < 0.5f) - ? genome.weights[i] - : partner.genome.weights[i]; + // 90% Meritocratic + choose_self = (u_dist(rng_) < prob_a); + } + + if (choose_self) { + child_genome.weights[i] = genome.weights[i]; + child_genome.gene_success[i] = success_self; + } else { + child_genome.weights[i] = partner.genome.weights[i]; + child_genome.gene_success[i] = success_partner; } - child_genome.gene_success[i] = 0.0f; } + child_genome.sigma_gene = (genome.sigma_gene + partner.genome.sigma_gene) * 0.5f; + child_genome.plasticity = (genome.plasticity + partner.genome.plasticity) * 0.5f; + FuzzyController child(child_genome); child.origin = "crossover"; return child; } FuzzyController FuzzyController::create_orthogonal_child(float intensity) const { - Genome child_genome = genome.clone(); - // Negate a random subset of weights - std::uniform_real_distribution coin(0.0f, 1.0f); - for (size_t i = 0; i < child_genome.weights.size(); ++i) { - if (coin(rng_) < 0.3f * intensity) { - child_genome.weights[i] = -child_genome.weights[i]; + Genome child_genome; + std::normal_distribution norm_dist(0.0f, 1.0f); + + float norm_elite = 0.0f; + for (float w : genome.weights) { + norm_elite += w * w; + } + norm_elite = std::sqrt(norm_elite) + 1e-9f; + + std::array random_vec{}; + float dot_product = 0.0f; + for (size_t i = 0; i < GENOME_SIZE; ++i) { + random_vec[i] = norm_dist(rng_); + dot_product += random_vec[i] * genome.weights[i]; + } + + std::array orthogonal_vec{}; + float norm_ortho = 0.0f; + for (size_t i = 0; i < GENOME_SIZE; ++i) { + float projection = (dot_product / (norm_elite * norm_elite)) * genome.weights[i]; + orthogonal_vec[i] = random_vec[i] - projection; + norm_ortho += orthogonal_vec[i] * orthogonal_vec[i]; + } + norm_ortho = std::sqrt(norm_ortho) + 1e-9f; + + std::array scaled_vec{}; + float final_norm = 0.0f; + for (size_t i = 0; i < GENOME_SIZE; ++i) { + scaled_vec[i] = orthogonal_vec[i] * (norm_elite / norm_ortho) * intensity; + final_norm += scaled_vec[i] * scaled_vec[i]; + } + final_norm = std::sqrt(final_norm); + + float max_allowed = std::max(norm_elite, 10.0f); + if (final_norm > max_allowed) { + float scale = max_allowed / (final_norm + 1e-9f); + for (size_t i = 0; i < GENOME_SIZE; ++i) { + scaled_vec[i] *= scale; } } + child_genome.weights = scaled_vec; + child_genome.gene_success.fill(1.0f); + child_genome.sigma_gene = 0.2f; + child_genome.plasticity = 0.2f; + FuzzyController child(child_genome); child.origin = "phoenix_rebirth"; return child; } std::pair FuzzyController::banach_tarski_fission(float intensity) const { - Genome plus_genome = genome.clone(); - Genome minus_genome = genome.clone(); - std::normal_distribution noise(0.0f, 0.1f * intensity); + Genome plus_genome; + Genome minus_genome; - for (size_t i = 0; i < genome.weights.size(); ++i) { - float delta = noise(rng_); - plus_genome.weights[i] += delta; - minus_genome.weights[i] -= delta; + float norm_parent = 0.0f; + float max_success = 0.0f; + for (size_t i = 0; i < GENOME_SIZE; ++i) { + norm_parent += genome.weights[i] * genome.weights[i]; + if (genome.gene_success[i] > max_success) { + max_success = genome.gene_success[i]; + } + } + norm_parent = std::sqrt(norm_parent) + 1e-9f; + max_success += 1e-9f; + + std::normal_distribution norm_dist(0.0f, 1.0f); + std::array noise{}; + float dot_product = 0.0f; + for (size_t i = 0; i < GENOME_SIZE; ++i) { + float saliency = genome.gene_success[i] / max_success; + noise[i] = norm_dist(rng_) * saliency; + dot_product += noise[i] * genome.weights[i]; } - return {FuzzyController(plus_genome), FuzzyController(minus_genome)}; + std::array fission_vec{}; + float norm_fission = 0.0f; + for (size_t i = 0; i < GENOME_SIZE; ++i) { + fission_vec[i] = noise[i] - (dot_product / (norm_parent * norm_parent)) * genome.weights[i]; + norm_fission += fission_vec[i] * fission_vec[i]; + } + norm_fission = std::sqrt(norm_fission) + 1e-9f; + + for (size_t i = 0; i < GENOME_SIZE; ++i) { + float scaled_fission = fission_vec[i] * (norm_parent / norm_fission) * 0.1f * intensity; + plus_genome.weights[i] = genome.weights[i] + scaled_fission; + minus_genome.weights[i] = genome.weights[i] - scaled_fission; + + plus_genome.gene_success[i] = 1.0f; + minus_genome.gene_success[i] = 1.0f; + } + + plus_genome.sigma_gene = genome.sigma_gene * 0.9f; + minus_genome.sigma_gene = genome.sigma_gene * 0.9f; + + plus_genome.plasticity = genome.plasticity; + minus_genome.plasticity = genome.plasticity; + + FuzzyController child_plus(plus_genome); + child_plus.origin = "fission_plus"; + + FuzzyController child_minus(minus_genome); + child_minus.origin = "fission_minus"; + + return {child_plus, child_minus}; } } // namespace fces diff --git a/src/evolution.cpp b/src/evolution.cpp index 94f6d2d..d5d67bc 100644 --- a/src/evolution.cpp +++ b/src/evolution.cpp @@ -18,7 +18,35 @@ FuzzyController& EvolutionManager::get_active_controller() { void EvolutionManager::update_population_dynamics( float loss_velocity, float ema_loss, int step_counter, int total_steps ) { - // TODO: Port full dynamics (auto-sizing, lockdown, evolution triggers) + float progress = static_cast(step_counter) / std::max(1, total_steps); + + if (step_counter % 20 == 0) { + population_.evolve( + std::abs(loss_velocity), + loss_velocity, + progress + ); + } + + if (!auto_population_ || step_counter % 50 != 0) { + return; + } + + int current_pop = population_.size(); + float adaptive_threshold = 0.05f * (1.0f + ema_loss); + adaptive_threshold = std::min(0.5f, adaptive_threshold); + + if (std::abs(loss_velocity) < adaptive_threshold) { + int target_pop = 200; + if (target_pop > current_pop) { + population_.resize(target_pop, progress); + } + } else { + int target_pop = 40; + if (target_pop < current_pop) { + population_.resize(target_pop, progress); + } + } } } // namespace fces diff --git a/src/fitness.cpp b/src/fitness.cpp index 2b0bb9a..29861b8 100644 --- a/src/fitness.cpp +++ b/src/fitness.cpp @@ -59,13 +59,38 @@ float FitnessEngine::compute_kzm_damping(float spectral_alpha) const { // FuzzyFitnessEvaluator // --------------------------------------------------------------- -FitnessMetrics FuzzyFitnessEvaluator::evaluate( - float loss_before, float loss_after, float sparsity, float val_loss -) const { - FitnessMetrics metrics; - metrics.loss_improvement = loss_before - loss_after; - metrics.sparsity_score = sparsity * sparsity; // Quadratic reward - return metrics; +FuzzyFitnessEvaluator::FuzzyFitnessEvaluator() noexcept + : stability_set_("Stable", -1.0f, 0.0f, 0.1f, 0.5f), + train_set_("Effective", 0.0f, 0.05f, 1.0f, 10.0f), + val_set_("Generalizing", 0.0f, 0.05f, 1.0f, 10.0f), + sparsity_set_("Sparse", 0.0f, 0.001f, 1.0f, 1.0f), + consistency_set_("Consistent", -1.0f, 0.0f, 0.02f, 0.1f), + rank_set_("LowRank", -1.0f, 0.0f, 5.0f, 20.0f) {} + +float FuzzyFitnessEvaluator::evaluate(const FitnessMetrics& metrics) const noexcept { + float m_stability = stability_set_.membership(metrics.grad_cv); + float m_train = train_set_.membership(metrics.training_advantage); + float m_val = val_set_.membership(metrics.validation_advantage); + float m_sparsity = sparsity_set_.membership(metrics.sparsity_delta); + float m_consistency = consistency_set_.membership(metrics.consistency_gap); + float m_rank = rank_set_.membership(metrics.stable_rank); + + float weighted_score = + m_stability * w_stability_ + + m_train * w_train_ + + m_val * w_val_ + + m_sparsity * w_sparsity_ + + m_consistency * w_consistency_ + + m_rank * w_rank_; + + float total_weight = w_stability_ + w_train_ + w_val_ + w_sparsity_ + w_consistency_ + w_rank_; + if (total_weight > 0.0f) { + weighted_score /= total_weight; + } + + // V153: Generalization-Aware Gate (Non-Linear) + float gate_efficiency = 0.5f + 0.5f * m_consistency; + return weighted_score * gate_efficiency; } } // namespace fces diff --git a/src/population.cpp b/src/population.cpp index d74df38..f84f547 100644 --- a/src/population.cpp +++ b/src/population.cpp @@ -29,7 +29,6 @@ Population::Population( } FuzzyController& Population::get_active_controller() { - // TODO: Implement sticky selection with interval if (active_controller_ == nullptr || steps_active_ >= selection_interval_) { active_controller_ = &select_weighted(); steps_active_ = 0; @@ -39,10 +38,73 @@ FuzzyController& Population::get_active_controller() { } FuzzyController& Population::select_weighted() { - // TODO: Implement tournament selection with age weighting static thread_local std::mt19937 rng{std::random_device{}()}; + if (gladiators_.empty()) { + throw std::runtime_error("Empty gladiators population"); + } + + float sum_fit = 0.0f; + for (const auto& g : gladiators_) { + sum_fit += std::max(0.0f, g.fitness); + } + if (sum_fit == 0.0f) { + std::uniform_int_distribution dist(0, static_cast(gladiators_.size()) - 1); + return gladiators_[dist(rng)]; + } + + // Select 3 random candidates for tournament std::uniform_int_distribution dist(0, static_cast(gladiators_.size()) - 1); - return gladiators_[dist(rng)]; + int idx1 = dist(rng); + int idx2 = dist(rng); + int idx3 = dist(rng); + + auto get_score = [this](const FuzzyController& c) { + float base_score = c.fitness + (0.01f * static_cast(c.age)); + // Add novelty score if archive has enough entries + if (behavioral_archive_.size() >= 5) { + float novelty = 0.0f; + // Get behavioral vector: first 20 weights + std::vector behavior(c.genome.weights.begin(), c.genome.weights.begin() + std::min(20, static_cast(c.genome.weights.size()))); + std::vector distances; + distances.reserve(behavioral_archive_.size()); + for (const auto& archived : behavioral_archive_) { + float dist_sum = 0.0f; + for (size_t i = 0; i < behavior.size() && i < archived.size(); ++i) { + float diff = behavior[i] - archived[i]; + dist_sum += diff * diff; + } + distances.push_back(std::sqrt(dist_sum)); + } + std::sort(distances.begin(), distances.end()); + int k = std::min(5, static_cast(distances.size())); + float avg_dist = 0.0f; + for (int i = 0; i < k; ++i) { + avg_dist += distances[i]; + } + if (k > 0) avg_dist /= static_cast(k); + base_score += NOVELTY_WEIGHT * avg_dist; + } + return base_score; + }; + + FuzzyController* best = &gladiators_[idx1]; + float best_score = get_score(*best); + + FuzzyController* cand2 = &gladiators_[idx2]; + float score2 = get_score(*cand2); + if (score2 > best_score) { + best = cand2; + best_score = score2; + } + + FuzzyController* cand3 = &gladiators_[idx3]; + float score3 = get_score(*cand3); + if (score3 > best_score) { + best = cand3; + best_score = score3; + } + + return *best; } FuzzyController& Population::get_best_active() { @@ -53,14 +115,42 @@ FuzzyController& Population::get_best_active() { } FuzzyController& Population::get_worst_active() { - return *std::min_element(gladiators_.begin(), gladiators_.end(), - [](const FuzzyController& a, const FuzzyController& b) { - return a.fitness < b.fitness; + auto elites = get_elites(); + std::vector non_elites; + for (auto& g : gladiators_) { + bool is_elite = false; + for (auto* e : elites) { + if (e->id == g.id) { + is_elite = true; + break; + } + } + if (!is_elite) { + non_elites.push_back(&g); + } + } + + if (non_elites.empty()) { + return *std::min_element(gladiators_.begin(), gladiators_.end(), + [](const FuzzyController& a, const FuzzyController& b) { + return a.fitness < b.fitness; + }); + } + + return **std::min_element(non_elites.begin(), non_elites.end(), + [](const FuzzyController* a, const FuzzyController* b) { + return a->fitness < b->fitness; }); } void Population::kill(FuzzyController& controller) { - // TODO: Elite protection + auto elites = get_elites(); + for (auto* e : elites) { + if (e->id == controller.id) { + return; // Elite protection + } + } + auto it = std::find_if(gladiators_.begin(), gladiators_.end(), [&](const FuzzyController& c) { return c.id == controller.id; }); if (it != gladiators_.end()) { @@ -72,14 +162,31 @@ void Population::kill(FuzzyController& controller) { } void Population::update_controller_fitness(FuzzyController& controller, float reward, bool increment_eval) { - controller.fitness += reward; + if (increment_eval) { + controller.age++; + controller.evaluation_count++; + } controller.lifetime_fitness += reward; - if (increment_eval) controller.evaluation_count++; - controller.age++; + + // Track in rolling history + constexpr size_t RECENT_WINDOW = 20; + controller.fitness_history.push_back(reward); + if (controller.fitness_history.size() > RECENT_WINDOW) { + controller.fitness_history.erase(controller.fitness_history.begin()); + } + + if (elite_strategy_ == EliteStrategy::EMA) { + constexpr float EMA_ALPHA = 0.1f; + controller.ema_fitness = (1.0f - EMA_ALPHA) * controller.ema_fitness + EMA_ALPHA * reward; + controller.fitness = reward; + } else if (elite_strategy_ == EliteStrategy::Rolling) { + controller.fitness = reward; + } else { + controller.fitness = reward; + } } void Population::mark_violated(FuzzyController& controller) { - // Deduplicate auto it = std::find_if(violated_controllers_.begin(), violated_controllers_.end(), [&](const FuzzyController& c) { return c.id == controller.id; }); if (it == violated_controllers_.end()) { @@ -88,16 +195,292 @@ void Population::mark_violated(FuzzyController& controller) { } float Population::get_effective_fitness(const FuzzyController& controller, float training_progress) const { - // TODO: Implement recency-weighted effective fitness (V42.5) - return controller.fitness; + float recent_avg = 0.0f; + if (!controller.fitness_history.empty()) { + float sum = 0.0f; + for (float f : controller.fitness_history) sum += f; + recent_avg = sum / controller.fitness_history.size(); + } + + float lifetime_avg = 0.0f; + if (controller.evaluation_count > 0) { + lifetime_avg = controller.lifetime_fitness / static_cast(controller.evaluation_count); + } + + float alpha = 0.2f + 0.6f * training_progress; + return alpha * recent_avg + (1.0f - alpha) * lifetime_avg; } void Population::evolve(float current_loss, float velocity, float training_progress) { - // TODO: Port full evolution logic from Python + static thread_local std::mt19937 rng{std::random_device{}()}; + std::uniform_real_distribution coin(0.0f, 1.0f); + + if (gladiators_.empty()) return; + + FuzzyController& worst = get_worst_active(); + FuzzyController& best_active = get_best_active(); + auto elites = get_elites(); + + // Update behavioral archive for novelty search + if (best_active.fitness > -999.0f) { + std::vector behavior(best_active.genome.weights.begin(), best_active.genome.weights.begin() + std::min(20, static_cast(best_active.genome.weights.size()))); + behavioral_archive_.push_back(behavior); + if (behavioral_archive_.size() > BEHAVIORAL_ARCHIVE_SIZE) { + behavioral_archive_.erase(behavioral_archive_.begin()); + } + } + + // Phase-dependent scheduling + float phase_sigma_mult = 1.0f; + float phase_phoenix_intensity = 1.0f; + if (training_progress < 0.1f) { + phase_sigma_mult = 2.0f; + phase_phoenix_intensity = 1.5f; + } else if (training_progress > 0.7f) { + phase_sigma_mult = 0.5f; + phase_phoenix_intensity = 0.5f; + } + + // Loss-linked mutation rate + float mutation_rate = 0.5f; + if (link_mutation_) { + mutation_rate = std::max(0.05f, std::min(1.0f, current_loss / 5.0f)); + } + mutation_rate *= phase_sigma_mult; + mutation_rate = std::min(1.0f, mutation_rate); + + // Pairing probabilities + float elite_prob = 0.3f; + if (link_elite_) { + elite_prob = std::max(0.2f, std::min(0.8f, 1.0f - current_loss / 5.0f)); + } + float violator_prob = 0.1f; + if (link_violator_) { + violator_prob = std::max(0.0f, std::min(0.5f, (current_loss - 1.0f) / 4.0f)); + } + + // Select parent + FuzzyController* parent = &best_active; + std::vector partner_pool; + + float roll = coin(rng); + if (roll < elite_prob && !elites.empty()) { + std::uniform_int_distribution elite_dist(0, static_cast(elites.size()) - 1); + parent = elites[elite_dist(rng)]; + partner_pool = elites; + } else if (roll < elite_prob + violator_prob && !violated_controllers_.empty()) { + parent = &best_active; + // Filter living violators + for (auto& v : violated_controllers_) { + for (auto& g : gladiators_) { + if (g.id == v.id) { + partner_pool.push_back(&g); + break; + } + } + } + if (partner_pool.empty()) { + // Fallback + for (size_t i = 0; i < std::min(static_cast(10), gladiators_.size()); ++i) { + partner_pool.push_back(&gladiators_[i]); + } + } + } else { + parent = &best_active; + for (size_t i = 0; i < std::min(static_cast(10), gladiators_.size()); ++i) { + partner_pool.push_back(&gladiators_[i]); + } + } + + // Crossover or mutation + FuzzyController child; + if (coin(rng) < 0.7f && partner_pool.size() > 1) { + std::uniform_int_distribution pool_dist(0, static_cast(partner_pool.size()) - 1); + FuzzyController* partner = partner_pool[pool_dist(rng)]; + if (partner->id == parent->id) { + // Pick another if possible + for (auto* p : partner_pool) { + if (p->id != parent->id) { + partner = p; + break; + } + } + } + child = parent->crossover(*partner, false); + } else { + float sigma_mod = global_sigma_modifier_ * mutation_rate; + if (velocity < -0.05f) { + sigma_mod *= 1.0f / (1.0f + std::abs(velocity) * 10.0f); + } + // Epigenetic lock + if (parent->fitness > 0.5f) { + sigma_mod *= 0.1f; + } + child = parent->mutate(current_loss, sigma_mod); + } + + // Recover temperature + global_sigma_modifier_ = std::min(1.0f, global_sigma_modifier_ * 1.01f); + + // Fuzzy Pacer + if (use_fuzzy_pacer_) { + fitness_history_.push_back(best_active.fitness); + if (fitness_history_.size() > 20) { + fitness_history_.erase(fitness_history_.begin()); + } + if (fitness_history_.size() >= 10) { + float trend = 0.0f; + for (size_t i = 1; i < fitness_history_.size(); ++i) { + trend += (fitness_history_[i] - fitness_history_[i - 1]); + } + trend /= (fitness_history_.size() - 1); + float diversity = get_diversity_index(); + if (trend < 0.001f && diversity < 0.2f) { + global_sigma_modifier_ = std::min(5.0f, global_sigma_modifier_ * 1.2f); + } else if (trend > 0.01f) { + global_sigma_modifier_ = std::max(0.1f, global_sigma_modifier_ * 0.95f); + } + } + } + + // Banach-Tarski Fission + if (use_banach_fission_ && coin(rng) < 0.2f && !elites.empty()) { + auto* prime_elite = elites[0]; + auto fission_pair = prime_elite->banach_tarski_fission(phase_phoenix_intensity); + + // Find second worst + FuzzyController* second_worst = nullptr; + for (auto& g : gladiators_) { + if (g.id != worst.id) { + if (second_worst == nullptr || g.fitness < second_worst->fitness) { + second_worst = &g; + } + } + } + + // Replace worst and second_worst with plus and minus child + if (second_worst) { + uint64_t sw_id = second_worst->id; + auto it = std::find_if(gladiators_.begin(), gladiators_.end(), [&](const FuzzyController& c) { return c.id == sw_id; }); + if (it != gladiators_.end()) { + gladiators_.erase(it); + } + gladiators_.push_back(fission_pair.first); + } + + uint64_t w_id = worst.id; + auto it = std::find_if(gladiators_.begin(), gladiators_.end(), [&](const FuzzyController& c) { return c.id == w_id; }); + if (it != gladiators_.end()) { + gladiators_.erase(it); + } + gladiators_.push_back(fission_pair.second); + } else { + // Phoenix Rebirth or Standard replacement + uint64_t w_id = worst.id; + auto it = std::find_if(gladiators_.begin(), gladiators_.end(), [&](const FuzzyController& c) { return c.id == w_id; }); + if (it != gladiators_.end()) { + gladiators_.erase(it); + } + + if (coin(rng) < 0.1f && !elites.empty()) { + auto* prime_elite = elites[0]; + gladiators_.push_back(prime_elite->create_orthogonal_child(phase_phoenix_intensity)); + } else { + gladiators_.push_back(child); + } + } + + // Periodic Reset + if (elite_strategy_ == EliteStrategy::Reset) { + reset_step_counter_++; + if (reset_step_counter_ >= 500) { + reset_step_counter_ = 0; + for (auto& g : gladiators_) { + g.fitness = 0.0f; + g.ema_fitness = 0.0f; + g.fitness_history.clear(); + } + } + } + + // Archive best + if (best_active.fitness > -999.0f) { + add_to_repository(best_active); + } } void Population::resize(int target_size, float training_progress) { - // TODO: Port resize logic + int current_size = static_cast(gladiators_.size()); + if (current_size == target_size) return; + + static thread_local std::mt19937 rng{std::random_device{}()}; + + if (current_size < target_size) { + int needed = target_size - current_size; + bool has_eval = false; + for (const auto& g : gladiators_) { + if (g.evaluation_count > 0) { + has_eval = true; + break; + } + } + if (has_eval) { + std::vector> candidates; + for (auto& g : gladiators_) { + candidates.push_back({get_effective_fitness(g, training_progress), &g}); + } + std::sort(candidates.begin(), candidates.end(), + [](const std::pair& a, const std::pair& b) { + return a.first > b.first; + }); + + int limit = std::min(10, static_cast(candidates.size())); + std::uniform_int_distribution cand_dist(0, limit - 1); + for (int i = 0; i < needed; ++i) { + FuzzyController* parent = candidates[cand_dist(rng)].second; + float mutation_str = 0.1f; + auto child = parent->mutate(mutation_str, 1.0f); + + float stability = 1.0f - std::min(1.0f, mutation_str); + std::uniform_real_distribution noise_dist(-0.1f, 0.1f); + float noise = noise_dist(rng) * std::abs(parent->fitness); + child.fitness = parent->fitness * stability + noise; + + gladiators_.push_back(child); + } + } else { + for (int i = 0; i < needed; ++i) { + gladiators_.emplace_back(); + } + } + } else { + std::vector evaluated; + std::vector unevaluated; + for (auto& g : gladiators_) { + if (g.evaluation_count > 0) { + evaluated.push_back(&g); + } else { + unevaluated.push_back(&g); + } + } + + std::sort(evaluated.begin(), evaluated.end(), + [this, training_progress](const FuzzyController* a, const FuzzyController* b) { + return get_effective_fitness(*a, training_progress) > get_effective_fitness(*b, training_progress); + }); + + std::vector new_pop; + new_pop.reserve(target_size); + for (int i = 0; i < std::min(target_size, static_cast(evaluated.size())); ++i) { + new_pop.push_back(*evaluated[i]); + } + int remaining = target_size - static_cast(new_pop.size()); + for (int i = 0; i < std::min(remaining, static_cast(unevaluated.size())); ++i) { + new_pop.push_back(*unevaluated[i]); + } + + gladiators_ = std::move(new_pop); + } } void Population::calm_down() { @@ -106,17 +489,78 @@ void Population::calm_down() { } float Population::get_diversity_index() const { - // TODO: Compute behavioral spread - return 0.5f; + if (gladiators_.size() < 2) return 0.0f; + float sum_dist = 0.0f; + int count = 0; + for (size_t i = 0; i < gladiators_.size(); ++i) { + for (size_t j = i + 1; j < gladiators_.size(); ++j) { + float dist_sq = 0.0f; + for (size_t w = 0; w < GENOME_SIZE; ++w) { + float diff = gladiators_[i].genome.weights[w] - gladiators_[j].genome.weights[w]; + dist_sq += diff * diff; + } + sum_dist += std::sqrt(dist_sq); + count++; + } + } + return sum_dist / static_cast(count); } std::vector Population::get_elites() { - // TODO: Implement strategy-aware elite selection - return {}; + if (gladiators_.size() <= static_cast(ELITE_COUNT)) { + std::vector ptrs; + ptrs.reserve(gladiators_.size()); + for (auto& g : gladiators_) { + ptrs.push_back(&g); + } + return ptrs; + } + + std::vector> candidates; + candidates.reserve(gladiators_.size()); + for (auto& g : gladiators_) { + float effective_fitness = 0.0f; + if (elite_strategy_ == EliteStrategy::AgePenalty) { + effective_fitness = g.fitness / std::log(static_cast(g.age) + 2.0f); + } else if (elite_strategy_ == EliteStrategy::EMA) { + effective_fitness = g.ema_fitness; + } else if (elite_strategy_ == EliteStrategy::Rolling) { + if (!g.fitness_history.empty()) { + float sum = 0.0f; + for (float f : g.fitness_history) sum += f; + effective_fitness = sum / g.fitness_history.size(); + } else { + effective_fitness = g.fitness; + } + } else { + effective_fitness = g.fitness; + } + candidates.push_back({effective_fitness, &g}); + } + + std::sort(candidates.begin(), candidates.end(), + [](const std::pair& a, const std::pair& b) { + return a.first > b.first; + }); + + std::vector elites; + elites.reserve(ELITE_COUNT); + for (int i = 0; i < ELITE_COUNT; ++i) { + elites.push_back(candidates[i].second); + } + return elites; } void Population::add_to_repository(const FuzzyController& controller) { - // TODO: Maintain sorted repository + auto it = std::lower_bound(repository_.begin(), repository_.end(), controller, + [](const FuzzyController& a, const FuzzyController& b) { + return a.fitness > b.fitness; + }); + repository_.insert(it, controller); + + if (repository_.size() > 1000) { + repository_.resize(1000); + } } } // namespace fces