pub struct MutationRate { /* private fields */ }Expand description
Controls the probability that each gene will be mutated during reproduction.
Mutation rate determines how frequently genes are changed when creating offspring. Higher rates provide more genetic diversity but may disrupt good solutions, while lower rates preserve good solutions but may limit exploration.
§When to Use Different Values
- High (0.7-1.0): Dense problems, escaping local optima, early exploration
- Medium (0.3-0.7): Balanced search, most general-purpose applications
- Low (0.0-0.3): Preserving good solutions, fine-tuning, convergence phase
§Interaction with Population Size
- Small populations: Use higher mutation rates to maintain diversity
- Large populations: Can use lower mutation rates as diversity emerges naturally
§Examples
use fx_durable_ga::models::MutationRate;
// Conservative mutation for stable problems
let rate = MutationRate::constant(0.1)?;
// Start diverse, converge to precision
let rate = MutationRate::linear(0.6, 0.05, 1.0)?;
// Rapid diversity reduction
let rate = MutationRate::exponential(0.8, 0.02, 1.5, 3)?;Implementations§
Source§impl MutationRate
impl MutationRate
Sourcepub fn constant(value: f64) -> Result<Self, MutationRateError>
pub fn constant(value: f64) -> Result<Self, MutationRateError>
Creates a constant mutation rate that remains unchanged throughout optimization.
§Parameters
value: Mutation probability per gene in [0.0, 1.0]. Higher values increase genetic diversity.
§When to Use
- Simple problems with stable optima
- Baseline testing and comparison
- When optimal mutation rate is already known
- Problems requiring consistent exploration level
§Examples
use fx_durable_ga::models::MutationRate;
let conservative = MutationRate::constant(0.05)?; // 5% mutation chance
let balanced = MutationRate::constant(0.3)?; // 30% mutation chance
let aggressive = MutationRate::constant(0.7)?; // 70% mutation chanceExamples found in repository?
195async fn main() -> Result<()> {
196 dotenv::from_filename(".env.local").ok();
197 tracing_subscriber::fmt()
198 .pretty()
199 .with_thread_ids(true)
200 .with_max_level(Level::INFO)
201 .init();
202
203 let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
204 let pool = PgPoolOptions::new()
205 .max_connections(10)
206 .connect(&database_url)
207 .await?;
208
209 // Run all default migrations
210 fx_durable_ga::migrations::run_default_migrations(&pool).await?;
211
212 let service = Arc::new(
213 bootstrap(pool.clone())
214 .await?
215 .register::<NeuralArchitecture, _>(ArchitectureEvaluator)
216 .await?
217 .build(),
218 );
219
220 let mut registry = fx_event_bus::EventHandlerRegistry::new();
221 register_event_handlers(
222 Arc::new(Queries::new(FX_MQ_JOBS_SCHEMA_NAME)),
223 service.clone(),
224 &mut registry,
225 );
226 let mut listener = fx_event_bus::Listener::new(pool.clone(), registry);
227 tokio::spawn(async move { listener.listen(None).await });
228
229 let host_id = Uuid::parse_str("00000000-0000-0000-0000-123456789abc")?;
230 let mut jobs_listener = fx_mq_jobs::Listener::new(
231 pool.clone(),
232 register_job_handlers(&service, fx_mq_jobs::RegistryBuilder::new()),
233 WORKERS,
234 host_id,
235 Duration::from_secs(600),
236 )
237 .await?;
238 tokio::spawn(async move { jobs_listener.listen().await });
239
240 service
241 .new_optimization_request(
242 NeuralArchitecture::NAME,
243 NeuralArchitecture::HASH,
244 FitnessGoal::minimize(FITNESS_TARGET)?,
245 Schedule::generational(10, 10),
246 Selector::tournament(5, 15)?,
247 Mutagen::new(Temperature::constant(0.8)?, MutationRate::constant(0.4)?),
248 Crossover::uniform(0.5)?,
249 Distribution::latin_hypercube(15),
250 None::<()>,
251 )
252 .await?;
253
254 tokio::time::sleep(Duration::from_secs(3600)).await;
255 Ok(())
256}More examples
130async fn main() -> Result<()> {
131 dotenv::from_filename(".env.local").ok();
132
133 // Initialize logging to see optimization progress
134 tracing_subscriber::fmt()
135 .with_max_level(tracing::Level::INFO)
136 .init();
137
138 // Database setup - genetic algorithms need persistent storage for populations
139 let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
140 let pool = PgPoolOptions::new()
141 .max_connections(50)
142 .connect(&database_url)
143 .await?;
144
145 fx_event_bus::run_migrations(&pool).await?;
146 fx_mq_jobs::run_migrations(&pool, fx_mq_jobs::FX_MQ_JOBS_SCHEMA_NAME).await?;
147
148 // Bootstrap the optimization service and register our problem type
149 let target_point = Point {
150 x: 1.0,
151 y: 1.5,
152 z: 2.5,
153 }; // Target point to find
154 let evaluator = PointDistanceEvaluator { target_point };
155 let service = Arc::new(
156 bootstrap(pool.clone())
157 .await?
158 .register::<Point, _>(evaluator)
159 .await?
160 .build(),
161 );
162
163 // setup event handling and spawn an event handling agent
164 let mut registry = fx_event_bus::EventHandlerRegistry::new();
165 register_event_handlers(
166 Arc::new(Queries::new(FX_MQ_JOBS_SCHEMA_NAME)),
167 service.clone(),
168 &mut registry,
169 );
170 let mut listener = fx_event_bus::Listener::new(pool.clone(), registry);
171 let _events_handle = tokio::spawn(async move {
172 listener.listen(None).await?;
173 Ok::<(), sqlx::Error>(())
174 });
175
176 // setup job handling and initiate workers
177 let host_id = Uuid::parse_str("ba3a4752-c4ce-4129-aa1d-55a0b2107e68").expect("valid uuid");
178 let hold_for = Duration::from_secs(TIMEOUT_SECONDS);
179 let mut jobs_listener = fx_mq_jobs::Listener::new(
180 pool.clone(),
181 register_job_handlers(&service, fx_mq_jobs::RegistryBuilder::new()),
182 8,
183 host_id,
184 hold_for,
185 )
186 .await?;
187 let _jobs_handle = tokio::spawn(async move {
188 jobs_listener.listen().await?;
189 Ok::<(), anyhow::Error>(())
190 });
191
192 // Create multiple optimization requests with different genetic algorithm parameters
193 for _ in 0..1 {
194 service
195 .new_optimization_request(
196 Point::NAME,
197 Point::HASH,
198 FitnessGoal::minimize(0.01)?, // Stop when distance ≤ 0.01
199 Schedule::generational(200, 30),
200 Selector::tournament(7, 100)?,
201 Mutagen::new(Temperature::constant(0.7)?, MutationRate::constant(0.3)?),
202 Crossover::uniform(0.5)?,
203 Distribution::latin_hypercube(1000),
204 None::<()>,
205 )
206 .await?;
207 }
208
209 let timeout_duration = Duration::from_secs(TIMEOUT_SECONDS);
210 let start_time = std::time::Instant::now();
211
212 loop {
213 tokio::time::sleep(Duration::from_secs(1)).await;
214
215 // Check if we've exceeded the timeout
216 if start_time.elapsed() >= timeout_duration {
217 println!(
218 "Timeout reached after {} seconds. Stopping optimization.",
219 timeout_duration.as_secs()
220 );
221 break;
222 }
223 }
224
225 Ok(())
226}377async fn main() -> Result<()> {
378 dotenv::from_filename(".env.local").ok();
379 tracing_subscriber::fmt()
380 .pretty()
381 .with_thread_ids(true)
382 .with_max_level(Level::INFO)
383 .init();
384
385 let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
386 let pool = PgPoolOptions::new()
387 .max_connections(10)
388 .connect(&database_url)
389 .await?;
390
391 // Run all default migrations
392 fx_durable_ga::migrations::run_default_migrations(&pool).await?;
393
394 let service = Arc::new(
395 bootstrap(pool.clone())
396 .await?
397 .register::<FeatureConfig, _>(FeatureEvaluator)
398 .await?
399 .build(),
400 );
401
402 let mut registry = fx_event_bus::EventHandlerRegistry::new();
403 register_event_handlers(
404 Arc::new(Queries::new(FX_MQ_JOBS_SCHEMA_NAME)),
405 service.clone(),
406 &mut registry,
407 );
408 let mut listener = fx_event_bus::Listener::new(pool.clone(), registry);
409 tokio::spawn(async move { listener.listen(None).await });
410
411 let host_id = Uuid::parse_str("00000000-0000-0000-0000-123456789abc")?;
412 let mut jobs_listener = fx_mq_jobs::Listener::new(
413 pool.clone(),
414 register_job_handlers(&service, fx_mq_jobs::RegistryBuilder::new()),
415 WORKERS,
416 host_id,
417 Duration::from_secs(600),
418 )
419 .await?;
420 tokio::spawn(async move { jobs_listener.listen().await });
421
422 let request_id = service
423 .new_optimization_request(
424 FeatureConfig::NAME,
425 FeatureConfig::HASH,
426 FitnessGoal::minimize(FITNESS_TARGET)?,
427 Schedule::generational(40, 10),
428 Selector::tournament(5, 45)?,
429 Mutagen::new(Temperature::constant(0.7)?, MutationRate::constant(0.35)?),
430 Crossover::uniform(0.5)?,
431 Distribution::latin_hypercube(40),
432 None::<()>,
433 )
434 .await?;
435
436 // Poll for completion every 15 seconds (timeout after 1 hour)
437 let timeout = Duration::from_secs(7200);
438 let poll_interval = Duration::from_secs(15);
439 let start = std::time::Instant::now();
440
441 loop {
442 if service.is_request_concluded(request_id).await? {
443 println!("\nOptimization completed!");
444 break;
445 }
446
447 if start.elapsed() > timeout {
448 println!("\nOptimization timed out after 1 hour.");
449 break;
450 }
451
452 tokio::time::sleep(poll_interval).await;
453 }
454
455 // Get and print the best configuration
456 if let Some((genotype, fitness)) = service.get_best_genotype(request_id).await? {
457 let config = FeatureConfig::decode(&genotype.genome());
458 println!("\n=== Best Configuration ===");
459 println!("Fitness (MSE): {:.6}", fitness);
460 println!("RMSE: {:.6}°C", fitness.sqrt());
461 println!("\nHyperparameters:");
462 println!(" Hidden Size: {}", config.hidden_size);
463 println!(" Learning Rate: {}", config.learning_rate);
464 println!(" Sequence Length: {}", config.sequence_length);
465 println!("\nFeatures:");
466 for (i, feature) in config.features.iter().enumerate() {
467 let pipeline = feature
468 .transforms
469 .iter()
470 .map(|t| t.to_string())
471 .collect::<Vec<_>>()
472 .join(" → ");
473 if pipeline.is_empty() {
474 println!(" feat_{}: {}", i, feature.source);
475 } else {
476 println!(" feat_{}: {} → {}", i, feature.source, pipeline);
477 }
478 }
479 } else {
480 println!("No genotypes were evaluated.");
481 }
482
483 Ok(())
484}Sourcepub fn linear(
upper: f64,
lower: f64,
multiplier: f64,
) -> Result<Self, MutationRateError>
pub fn linear( upper: f64, lower: f64, multiplier: f64, ) -> Result<Self, MutationRateError>
Creates a mutation rate that decays linearly to reduce disruption over time.
Linear decay starts with high genetic diversity and gradually reduces mutations to preserve promising solutions as they emerge.
§Parameters
upper: Starting mutation rate (0.0-1.0) for early diversitylower: Ending mutation rate (0.0-1.0) for solution preservationmultiplier: Decay speed (1.0 = normal, >1.0 = faster, <1.0 = slower)
§When to Use
- Most optimization problems benefit from this approach
- When good solutions emerge gradually over time
- Problems where early diversity and later stability both matter
- Balanced exploration-exploitation scenarios
§Examples
use fx_durable_ga::models::MutationRate;
// Standard diversity-to-stability transition
let rate = MutationRate::linear(0.5, 0.1, 1.0)?;
// Slower transition for complex problems
let rate = MutationRate::linear(0.6, 0.2, 0.7)?;
// Rapid stabilization
let rate = MutationRate::linear(0.8, 0.05, 1.4)?;Sourcepub fn exponential(
upper: f64,
lower: f64,
multiplier: f64,
exponent: i32,
) -> Result<Self, MutationRateError>
pub fn exponential( upper: f64, lower: f64, multiplier: f64, exponent: i32, ) -> Result<Self, MutationRateError>
Creates a mutation rate that decays exponentially for rapid convergence.
Exponential decay maintains high genetic diversity initially, then rapidly reduces mutations to lock in promising solutions. This provides an intense early search followed by strong solution preservation.
§Parameters
upper: Starting mutation rate (0.0-1.0) for initial diversitylower: Ending mutation rate (0.0-1.0) for final stabilitymultiplier: Controls timing of decay (higher = earlier/faster reduction)exponent: Controls decay aggressiveness (higher = more dramatic curve)
§When to Use
- Time-constrained optimization requiring fast convergence
- Problems with clear early vs. late phases
- When disruption of good solutions is particularly costly
- Search spaces where early exploration is critical
§Examples
use fx_durable_ga::models::MutationRate;
// Rapid diversity reduction
let rate = MutationRate::exponential(0.7, 0.05, 1.0, 3)?;
// Extreme early diversity, quick stabilization
let rate = MutationRate::exponential(0.9, 0.01, 1.3, 4)?;
// Gentler exponential curve
let rate = MutationRate::exponential(0.5, 0.1, 0.8, 2)?;Trait Implementations§
Source§impl Clone for MutationRate
impl Clone for MutationRate
Source§fn clone(&self) -> MutationRate
fn clone(&self) -> MutationRate
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for MutationRate
impl Debug for MutationRate
Source§impl<'de> Deserialize<'de> for MutationRate
impl<'de> Deserialize<'de> for MutationRate
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Auto Trait Implementations§
impl Freeze for MutationRate
impl RefUnwindSafe for MutationRate
impl Send for MutationRate
impl Sync for MutationRate
impl Unpin for MutationRate
impl UnsafeUnpin for MutationRate
impl UnwindSafe for MutationRate
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
impl<T> ErasedDestructor for Twhere
T: 'static,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more