#pragma once /* * Copyright (C) 2024 Brett Terpstra * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef BLT_GP_EXAMPLE_SYMBOLIC_REGRESSION_H #define BLT_GP_EXAMPLE_SYMBOLIC_REGRESSION_H #include #include #include #include #include namespace blt::gp::example { class symbolic_regression_t { public: struct context { float x, y; }; private: bool fitness_function(const tree_t& current_tree, fitness_t& fitness, size_t) const { constexpr static double value_cutoff = 1.e15; for (auto& fitness_case : training_cases) { const auto diff = std::abs(fitness_case.y - current_tree.get_evaluation_value(fitness_case)); if (diff < value_cutoff) { fitness.raw_fitness += diff; if (diff <= 0.01) fitness.hits++; } else fitness.raw_fitness += value_cutoff; } fitness.standardized_fitness = fitness.raw_fitness; fitness.adjusted_fitness = (1.0 / (1.0 + fitness.standardized_fitness)); return static_cast(fitness.hits) == training_cases.size(); } static float example_function(const float x) { return x * x * x * x + x * x * x + x * x + x; } public: symbolic_regression_t(const prog_config_t& config, const size_t seed): program{seed, config} { BLT_INFO("Starting BLT-GP Symbolic Regression Example"); BLT_DEBUG("Setup Fitness cases"); for (auto& fitness_case : training_cases) { constexpr float range = 10; constexpr float half_range = range / 2.0; const auto x = program.get_random().get_float(-half_range, half_range); const auto y = example_function(x); fitness_case = {x, y}; } } template auto make_operations(operator_builder& builder) { static operation_t add{[](const float a, const float b) { return a + b; }, "add"}; static operation_t sub([](const float a, const float b) { return a - b; }, "sub"); static operation_t mul([](const float a, const float b) { return a * b; }, "mul"); static operation_t pro_div([](const float a, const float b) { return b == 0.0f ? 1.0f : a / b; }, "div"); static operation_t op_sin([](const float a) { return std::sin(a); }, "sin"); static operation_t op_cos([](const float a) { return std::cos(a); }, "cos"); static operation_t op_exp([](const float a) { return std::exp(a); }, "exp"); static operation_t op_log([](const float a) { return a == 0.0f ? 0.0f : std::log(a); }, "log"); static auto lit = operation_t([this]() { return program.get_random().get_float(-1.0f, 1.0f); }, "lit").set_ephemeral(); static operation_t op_x([](const context& context) { return context.x; }, "x"); return builder.build(add, sub, mul, pro_div, op_sin, op_cos, op_exp, op_log, lit, op_x); } void execute() { BLT_DEBUG("Setup Types and Operators"); operator_builder builder{}; program.set_operations(make_operations(builder)); BLT_DEBUG("Generate Initial Population"); auto sel = select_tournament_t{}; auto fitness = [this](const tree_t& c, fitness_t& f, const size_t i) { return fitness_function(c, f, i); }; program.generate_population(program.get_typesystem().get_type().id(), fitness, sel, sel, sel); BLT_DEBUG("Begin Generation Loop"); while (!program.should_terminate()) { auto cross = crossover_calls.start_measurement(); auto mut = mutation_calls.start_measurement(); auto repo = reproduction_calls.start_measurement(); BLT_TRACE("------------{Begin Generation %ld}------------", program.get_current_generation()); BLT_TRACE("Creating next generation"); program.create_next_generation(); BLT_TRACE("Move to next generation"); program.next_generation(); BLT_TRACE("Evaluate Fitness"); program.evaluate_fitness(); const auto& stats = program.get_population_stats(); BLT_TRACE("Avg Fit: %lf, Best Fit: %lf, Worst Fit: %lf, Overall Fit: %lf", stats.average_fitness.load(std::memory_order_relaxed), stats.best_fitness.load(std::memory_order_relaxed), stats.worst_fitness.load(std::memory_order_relaxed), stats.overall_fitness.load(std::memory_order_relaxed)); crossover_calls.stop_measurement(cross); mutation_calls.stop_measurement(mut); reproduction_calls.stop_measurement(repo); const auto total = (cross.get_call_difference() * 2) + mut.get_call_difference() + repo.get_call_difference(); BLT_TRACE("Calls Crossover: %ld, Mutation %ld, Reproduction %ld; %ld", cross.get_call_difference(), mut.get_call_difference(), repo.get_call_difference(), total); BLT_TRACE("Value Crossover: %ld, Mutation %ld, Reproduction %ld; %ld", cross.get_value_difference(), mut.get_value_difference(), repo.get_value_difference(), (cross.get_value_difference() * 2 + mut.get_value_difference() + repo.get_value_difference()) - total); BLT_TRACE("----------------------------------------------"); std::cout << std::endl; } const auto best = program.get_best_individuals<3>(); BLT_INFO("Best approximations:"); for (auto& i_ref : best) { auto& i = i_ref.get(); BLT_DEBUG("Fitness: %lf, stand: %lf, raw: %lf", i.fitness.adjusted_fitness, i.fitness.standardized_fitness, i.fitness.raw_fitness); i.tree.print(program, std::cout); std::cout << "\n"; } const auto& stats = program.get_population_stats(); BLT_INFO("Stats:"); BLT_INFO("Average fitness: %lf", stats.average_fitness.load()); BLT_INFO("Best fitness: %lf", stats.best_fitness.load()); BLT_INFO("Worst fitness: %lf", stats.worst_fitness.load()); BLT_INFO("Overall fitness: %lf", stats.overall_fitness.load()); // TODO: make stats helper } private: gp_program program; mutation_t mut; std::array training_cases{}; }; } #endif //BLT_GP_EXAMPLE_SYMBOLIC_REGRESSION_H