Skip to main content

MutationRate

Struct MutationRate 

Source
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

Source

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 chance
Examples found in repository?
examples/regression_model.rs (line 247)
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
Hide additional examples
examples/point_search.rs (line 201)
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}
examples/feature_engineering.rs (line 429)
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}
Source

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 diversity
  • lower: Ending mutation rate (0.0-1.0) for solution preservation
  • multiplier: 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)?;
Source

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 diversity
  • lower: Ending mutation rate (0.0-1.0) for final stability
  • multiplier: 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

Source§

fn clone(&self) -> MutationRate

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for MutationRate

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for MutationRate

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Serialize for MutationRate

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more