#[cfg(feature = "gym")]
use crate::core::engines::reset_engine::{Reset, ResetEngine};
use crate::core::engines::status_engine::{Status, StatusEngine};
#[cfg(feature = "gym")]
use crate::problems::gym::{GymRsEngine, GymRsQEngine};
use crate::{core::engines::core_engine::HyperParameters, problems::iris::IrisEngine};
use clap::{Parser, Subcommand, ValueEnum};
use config::{Config, Environment, File};
#[cfg(feature = "gym")]
use gymnasia::envs::classical_control::{cartpole::CartPoleEnv, mountain_car::MountainCarEnv};
use serde::{Deserialize, Serialize};
use super::engines::core_engine::Core;
use super::instruction::InstructionGeneratorParameters;
use super::program::ProgramGeneratorParameters;
#[cfg(feature = "gym")]
use crate::extensions::q_learning::{QConsts, QProgramGeneratorParameters};
#[derive(Debug, Clone, Copy, ValueEnum, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum EnvironmentType {
#[cfg(feature = "gym")]
CartPoleLgp,
#[cfg(feature = "gym")]
CartPoleQ,
#[cfg(feature = "gym")]
MountainCarLgp,
#[cfg(feature = "gym")]
MountainCarQ,
IrisLgp,
}
#[derive(Debug, Parser, Serialize, Deserialize)]
pub struct ExperimentParams {
#[arg(value_enum)]
pub env: EnvironmentType,
#[arg(long, default_value = "100")]
pub population_size: usize,
#[arg(long, default_value = "100")]
pub n_generations: usize,
#[arg(long, default_value = "0.5")]
pub mutation_percent: f64,
#[arg(long, default_value = "0.5")]
pub crossover_percent: f64,
#[arg(long, default_value = "0.5")]
pub gap: f64,
#[arg(long, default_value = "100")]
pub n_trials: usize,
#[arg(long)]
pub seed: Option<u64>,
#[arg(long)]
pub n_threads: Option<usize>,
#[arg(long)]
pub default_fitness: Option<f64>,
#[arg(long, default_value = "12")]
pub max_instructions: usize,
#[arg(long, default_value = "1")]
pub n_extras: usize,
#[arg(long, default_value = "10.0")]
pub external_factor: f64,
#[arg(long, default_value = "0.1")]
pub alpha: f64,
#[arg(long, default_value = "0.9")]
pub gamma: f64,
#[arg(long, default_value = "0.05")]
pub epsilon: f64,
#[arg(long, default_value = "0.01")]
pub alpha_decay: f64,
#[arg(long, default_value = "0.001")]
pub epsilon_decay: f64,
}
#[derive(Parser)]
#[command(
name = "lgp",
author,
version,
about = "Linear Genetic Programming Framework"
)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
Experiment(ExperimentParams),
}
macro_rules! run_experiment {
($hyperparameters:ident) => {
for population in $hyperparameters
.build_engine()
.take($hyperparameters.n_generations)
{
println!("{}", StatusEngine::get_fitness(population.first().unwrap()));
}
println!("{}", serde_json::to_string(&$hyperparameters).unwrap());
};
}
impl ExperimentParams {
fn n_inputs(&self) -> usize {
match self.env {
#[cfg(feature = "gym")]
EnvironmentType::CartPoleLgp | EnvironmentType::CartPoleQ => 4,
#[cfg(feature = "gym")]
EnvironmentType::MountainCarLgp | EnvironmentType::MountainCarQ => 2,
EnvironmentType::IrisLgp => 4,
}
}
fn n_actions(&self) -> usize {
match self.env {
#[cfg(feature = "gym")]
EnvironmentType::CartPoleLgp | EnvironmentType::CartPoleQ => 2,
#[cfg(feature = "gym")]
EnvironmentType::MountainCarLgp | EnvironmentType::MountainCarQ => 3,
EnvironmentType::IrisLgp => 3,
}
}
fn env_default_fitness(&self) -> f64 {
match self.env {
#[cfg(feature = "gym")]
EnvironmentType::CartPoleLgp | EnvironmentType::CartPoleQ => 500.0,
#[cfg(feature = "gym")]
EnvironmentType::MountainCarLgp | EnvironmentType::MountainCarQ => -200.0,
EnvironmentType::IrisLgp => 0.0,
}
}
fn build_instruction_params(&self) -> InstructionGeneratorParameters {
InstructionGeneratorParameters {
n_extras: self.n_extras,
external_factor: self.external_factor,
n_actions: self.n_actions(),
n_inputs: self.n_inputs(),
}
}
fn build_program_params(&self) -> ProgramGeneratorParameters {
ProgramGeneratorParameters {
max_instructions: self.max_instructions,
instruction_generator_parameters: self.build_instruction_params(),
}
}
#[cfg(feature = "gym")]
fn build_q_consts(&self) -> QConsts {
QConsts::new(
self.alpha,
self.gamma,
self.epsilon,
self.alpha_decay,
self.epsilon_decay,
)
}
#[cfg(feature = "gym")]
fn build_q_program_params(&self) -> QProgramGeneratorParameters {
QProgramGeneratorParameters {
program_parameters: self.build_program_params(),
consts: self.build_q_consts(),
}
}
pub fn run(&self) {
let default_fitness = self
.default_fitness
.unwrap_or_else(|| self.env_default_fitness());
match self.env {
#[cfg(feature = "gym")]
EnvironmentType::CartPoleLgp => {
let hyperparameters: HyperParameters<GymRsEngine<CartPoleEnv>> = HyperParameters {
default_fitness,
population_size: self.population_size,
gap: self.gap,
mutation_percent: self.mutation_percent,
crossover_percent: self.crossover_percent,
n_generations: self.n_generations,
n_trials: self.n_trials,
seed: self.seed,
n_threads: self.n_threads,
program_parameters: self.build_program_params(),
};
run_experiment!(hyperparameters);
}
#[cfg(feature = "gym")]
EnvironmentType::CartPoleQ => {
let mut hyperparameters: HyperParameters<GymRsQEngine<CartPoleEnv>> =
HyperParameters {
default_fitness,
population_size: self.population_size,
gap: self.gap,
mutation_percent: self.mutation_percent,
crossover_percent: self.crossover_percent,
n_generations: self.n_generations,
n_trials: self.n_trials,
seed: self.seed,
n_threads: self.n_threads,
program_parameters: self.build_q_program_params(),
};
ResetEngine::reset(&mut hyperparameters.program_parameters.consts);
run_experiment!(hyperparameters);
}
#[cfg(feature = "gym")]
EnvironmentType::MountainCarLgp => {
let hyperparameters: HyperParameters<GymRsEngine<MountainCarEnv>> =
HyperParameters {
default_fitness,
population_size: self.population_size,
gap: self.gap,
mutation_percent: self.mutation_percent,
crossover_percent: self.crossover_percent,
n_generations: self.n_generations,
n_trials: self.n_trials,
seed: self.seed,
n_threads: self.n_threads,
program_parameters: self.build_program_params(),
};
run_experiment!(hyperparameters);
}
#[cfg(feature = "gym")]
EnvironmentType::MountainCarQ => {
let mut hyperparameters: HyperParameters<GymRsQEngine<MountainCarEnv>> =
HyperParameters {
default_fitness,
population_size: self.population_size,
gap: self.gap,
mutation_percent: self.mutation_percent,
crossover_percent: self.crossover_percent,
n_generations: self.n_generations,
n_trials: self.n_trials,
seed: self.seed,
n_threads: self.n_threads,
program_parameters: self.build_q_program_params(),
};
ResetEngine::reset(&mut hyperparameters.program_parameters.consts);
run_experiment!(hyperparameters);
}
EnvironmentType::IrisLgp => {
let hyperparameters: HyperParameters<IrisEngine> = HyperParameters {
default_fitness,
population_size: self.population_size,
gap: self.gap,
mutation_percent: self.mutation_percent,
crossover_percent: self.crossover_percent,
n_generations: self.n_generations,
n_trials: self.n_trials,
seed: self.seed,
n_threads: self.n_threads,
program_parameters: self.build_program_params(),
};
run_experiment!(hyperparameters);
}
}
}
}
pub fn load_hyper_parameters<C>(
filename: &str,
) -> Result<HyperParameters<C>, Box<dyn std::error::Error>>
where
C: Core,
{
let settings = Config::builder()
.add_source(File::with_name(filename))
.add_source(Environment::default())
.build()?;
let parameters: HyperParameters<C> = settings.try_deserialize()?;
Ok(parameters)
}