1#[cfg(feature = "gym")]
2use crate::core::engines::reset_engine::{Reset, ResetEngine};
3use crate::core::engines::status_engine::{Status, StatusEngine};
4#[cfg(feature = "gym")]
5use crate::problems::gym::{GymRsEngine, GymRsQEngine};
6use crate::{core::engines::core_engine::HyperParameters, problems::iris::IrisEngine};
7use clap::{Parser, Subcommand, ValueEnum};
8use config::{Config, Environment, File};
9#[cfg(feature = "gym")]
10use gymnasia::envs::classical_control::{cartpole::CartPoleEnv, mountain_car::MountainCarEnv};
11use serde::{Deserialize, Serialize};
12
13use super::engines::core_engine::Core;
14use super::instruction::InstructionGeneratorParameters;
15use super::program::ProgramGeneratorParameters;
16#[cfg(feature = "gym")]
17use crate::extensions::q_learning::{QConsts, QProgramGeneratorParameters};
18
19#[derive(Debug, Clone, Copy, ValueEnum, Serialize, Deserialize)]
21#[serde(rename_all = "kebab-case")]
22pub enum EnvironmentType {
23 #[cfg(feature = "gym")]
25 CartPoleLgp,
26 #[cfg(feature = "gym")]
28 CartPoleQ,
29 #[cfg(feature = "gym")]
31 MountainCarLgp,
32 #[cfg(feature = "gym")]
34 MountainCarQ,
35 IrisLgp,
37}
38
39#[derive(Debug, Parser, Serialize, Deserialize)]
41pub struct ExperimentParams {
42 #[arg(value_enum)]
44 pub env: EnvironmentType,
45
46 #[arg(long, default_value = "100")]
49 pub population_size: usize,
50
51 #[arg(long, default_value = "100")]
53 pub n_generations: usize,
54
55 #[arg(long, default_value = "0.5")]
57 pub mutation_percent: f64,
58
59 #[arg(long, default_value = "0.5")]
61 pub crossover_percent: f64,
62
63 #[arg(long, default_value = "0.5")]
65 pub gap: f64,
66
67 #[arg(long, default_value = "100")]
69 pub n_trials: usize,
70
71 #[arg(long)]
73 pub seed: Option<u64>,
74
75 #[arg(long)]
77 pub n_threads: Option<usize>,
78
79 #[arg(long)]
81 pub default_fitness: Option<f64>,
82
83 #[arg(long, default_value = "12")]
86 pub max_instructions: usize,
87
88 #[arg(long, default_value = "1")]
90 pub n_extras: usize,
91
92 #[arg(long, default_value = "10.0")]
94 pub external_factor: f64,
95
96 #[arg(long, default_value = "0.1")]
99 pub alpha: f64,
100
101 #[arg(long, default_value = "0.9")]
103 pub gamma: f64,
104
105 #[arg(long, default_value = "0.05")]
107 pub epsilon: f64,
108
109 #[arg(long, default_value = "0.01")]
111 pub alpha_decay: f64,
112
113 #[arg(long, default_value = "0.001")]
115 pub epsilon_decay: f64,
116}
117
118#[derive(Parser)]
120#[command(
121 name = "lgp",
122 author,
123 version,
124 about = "Linear Genetic Programming Framework"
125)]
126pub struct Cli {
127 #[command(subcommand)]
128 pub command: Commands,
129}
130
131#[derive(Subcommand)]
133pub enum Commands {
134 Experiment(ExperimentParams),
136}
137
138macro_rules! run_experiment {
141 ($hyperparameters:ident) => {
142 for population in $hyperparameters
143 .build_engine()
144 .take($hyperparameters.n_generations)
145 {
146 println!("{}", StatusEngine::get_fitness(population.first().unwrap()));
147 }
148 println!("{}", serde_json::to_string(&$hyperparameters).unwrap());
149 };
150}
151
152impl ExperimentParams {
153 fn n_inputs(&self) -> usize {
155 match self.env {
156 #[cfg(feature = "gym")]
157 EnvironmentType::CartPoleLgp | EnvironmentType::CartPoleQ => 4,
158 #[cfg(feature = "gym")]
159 EnvironmentType::MountainCarLgp | EnvironmentType::MountainCarQ => 2,
160 EnvironmentType::IrisLgp => 4,
161 }
162 }
163
164 fn n_actions(&self) -> usize {
166 match self.env {
167 #[cfg(feature = "gym")]
168 EnvironmentType::CartPoleLgp | EnvironmentType::CartPoleQ => 2,
169 #[cfg(feature = "gym")]
170 EnvironmentType::MountainCarLgp | EnvironmentType::MountainCarQ => 3,
171 EnvironmentType::IrisLgp => 3,
172 }
173 }
174
175 fn env_default_fitness(&self) -> f64 {
177 match self.env {
178 #[cfg(feature = "gym")]
179 EnvironmentType::CartPoleLgp | EnvironmentType::CartPoleQ => 500.0,
180 #[cfg(feature = "gym")]
181 EnvironmentType::MountainCarLgp | EnvironmentType::MountainCarQ => -200.0,
182 EnvironmentType::IrisLgp => 0.0,
183 }
184 }
185
186 fn build_instruction_params(&self) -> InstructionGeneratorParameters {
188 InstructionGeneratorParameters {
189 n_extras: self.n_extras,
190 external_factor: self.external_factor,
191 n_actions: self.n_actions(),
192 n_inputs: self.n_inputs(),
193 }
194 }
195
196 fn build_program_params(&self) -> ProgramGeneratorParameters {
198 ProgramGeneratorParameters {
199 max_instructions: self.max_instructions,
200 instruction_generator_parameters: self.build_instruction_params(),
201 }
202 }
203
204 #[cfg(feature = "gym")]
206 fn build_q_consts(&self) -> QConsts {
207 QConsts::new(
208 self.alpha,
209 self.gamma,
210 self.epsilon,
211 self.alpha_decay,
212 self.epsilon_decay,
213 )
214 }
215
216 #[cfg(feature = "gym")]
218 fn build_q_program_params(&self) -> QProgramGeneratorParameters {
219 QProgramGeneratorParameters {
220 program_parameters: self.build_program_params(),
221 consts: self.build_q_consts(),
222 }
223 }
224
225 pub fn run(&self) {
227 let default_fitness = self
228 .default_fitness
229 .unwrap_or_else(|| self.env_default_fitness());
230
231 match self.env {
232 #[cfg(feature = "gym")]
233 EnvironmentType::CartPoleLgp => {
234 let hyperparameters: HyperParameters<GymRsEngine<CartPoleEnv>> = HyperParameters {
235 default_fitness,
236 population_size: self.population_size,
237 gap: self.gap,
238 mutation_percent: self.mutation_percent,
239 crossover_percent: self.crossover_percent,
240 n_generations: self.n_generations,
241 n_trials: self.n_trials,
242 seed: self.seed,
243 n_threads: self.n_threads,
244 program_parameters: self.build_program_params(),
245 };
246 run_experiment!(hyperparameters);
247 }
248 #[cfg(feature = "gym")]
249 EnvironmentType::CartPoleQ => {
250 let mut hyperparameters: HyperParameters<GymRsQEngine<CartPoleEnv>> =
251 HyperParameters {
252 default_fitness,
253 population_size: self.population_size,
254 gap: self.gap,
255 mutation_percent: self.mutation_percent,
256 crossover_percent: self.crossover_percent,
257 n_generations: self.n_generations,
258 n_trials: self.n_trials,
259 seed: self.seed,
260 n_threads: self.n_threads,
261 program_parameters: self.build_q_program_params(),
262 };
263 ResetEngine::reset(&mut hyperparameters.program_parameters.consts);
264 run_experiment!(hyperparameters);
265 }
266 #[cfg(feature = "gym")]
267 EnvironmentType::MountainCarLgp => {
268 let hyperparameters: HyperParameters<GymRsEngine<MountainCarEnv>> =
269 HyperParameters {
270 default_fitness,
271 population_size: self.population_size,
272 gap: self.gap,
273 mutation_percent: self.mutation_percent,
274 crossover_percent: self.crossover_percent,
275 n_generations: self.n_generations,
276 n_trials: self.n_trials,
277 seed: self.seed,
278 n_threads: self.n_threads,
279 program_parameters: self.build_program_params(),
280 };
281 run_experiment!(hyperparameters);
282 }
283 #[cfg(feature = "gym")]
284 EnvironmentType::MountainCarQ => {
285 let mut hyperparameters: HyperParameters<GymRsQEngine<MountainCarEnv>> =
286 HyperParameters {
287 default_fitness,
288 population_size: self.population_size,
289 gap: self.gap,
290 mutation_percent: self.mutation_percent,
291 crossover_percent: self.crossover_percent,
292 n_generations: self.n_generations,
293 n_trials: self.n_trials,
294 seed: self.seed,
295 n_threads: self.n_threads,
296 program_parameters: self.build_q_program_params(),
297 };
298 ResetEngine::reset(&mut hyperparameters.program_parameters.consts);
299 run_experiment!(hyperparameters);
300 }
301 EnvironmentType::IrisLgp => {
302 let hyperparameters: HyperParameters<IrisEngine> = HyperParameters {
303 default_fitness,
304 population_size: self.population_size,
305 gap: self.gap,
306 mutation_percent: self.mutation_percent,
307 crossover_percent: self.crossover_percent,
308 n_generations: self.n_generations,
309 n_trials: self.n_trials,
310 seed: self.seed,
311 n_threads: self.n_threads,
312 program_parameters: self.build_program_params(),
313 };
314 run_experiment!(hyperparameters);
315 }
316 }
317 }
318}
319
320pub fn load_hyper_parameters<C>(
321 filename: &str,
322) -> Result<HyperParameters<C>, Box<dyn std::error::Error>>
323where
324 C: Core,
325{
326 let settings = Config::builder()
327 .add_source(File::with_name(filename))
328 .add_source(Environment::default())
329 .build()?;
330
331 let parameters: HyperParameters<C> = settings.try_deserialize()?;
332 Ok(parameters)
333}