Skip to main content

Distribution

Enum Distribution 

Source
pub enum Distribution {
    LatinHypercube {
        population_size: u32,
    },
    Random {
        population_size: u32,
    },
}
Expand description

Strategy for generating initial populations in genetic algorithms.

The distribution strategy determines how the initial population is spread across the search space. Different strategies have distinct characteristics that can significantly impact optimization performance, especially during early generations.

§Strategy Comparison

Latin Hypercube Sampling: Provides superior space coverage by ensuring each parameter dimension is evenly sampled. This typically leads to better initial diversity and faster convergence.

Random Sampling: Simple uniform random distribution across the search space. May result in clustered solutions or gaps in coverage, but requires no additional computation.

§When to Choose Each Strategy

  • Latin Hypercube: Recommended for most optimization problems, especially when:

    • You have limited population budget (small population sizes)
    • The search space is large or complex
    • You need consistent, reproducible space coverage
    • You want faster initial convergence
  • Random: Consider when:

    • You have very large population sizes (>1000)
    • Computational efficiency is critical
    • You’re comparing against baseline random methods
    • The search space has unusual constraints that Latin Hypercube might not handle well

§Performance Impact

Latin Hypercube sampling typically provides 10-30% better initial population quality (measured by best fitness found) compared to random sampling, with the advantage being most pronounced for smaller populations (20-200 individuals).

§Examples

use fx_durable_ga::models::Distribution;

// Recommended: Latin Hypercube for better space coverage
let lhc_dist = Distribution::latin_hypercube(50);

// Simple random distribution
let random_dist = Distribution::random(50);

// Large population where random is acceptable
let large_random = Distribution::random(1000);

// Small population where Latin Hypercube shines
let small_lhc = Distribution::latin_hypercube(20);

Variants§

§

LatinHypercube

Latin Hypercube sampling ensures even coverage across all parameter dimensions.

This method divides each parameter’s range into equal intervals and ensures exactly one sample falls in each interval. The intervals are then randomly shuffled across dimensions to decorrelate parameters while maintaining uniform marginal distributions.

Advantages:

  • Superior space coverage with fewer samples
  • More consistent results across runs
  • Better representation of parameter interactions
  • Typically finds better initial solutions

Best for: Small to medium population sizes (10-500), complex search spaces

Fields

§population_size: u32

Number of individuals in the initial population. Each individual will be positioned to maximize overall space coverage.

§

Random

Pure random sampling generates individuals uniformly at random across the search space.

Each parameter is independently sampled from its allowed range without coordination between dimensions or individuals. This can result in uneven coverage with potential clusters and gaps.

Advantages:

  • Simple and fast generation
  • No assumptions about search space structure
  • Familiar and well-understood behavior

Best for: Very large populations (>1000), simple search spaces, baseline comparisons

Fields

§population_size: u32

Number of individuals to generate randomly. Larger populations help compensate for potential clustering and coverage gaps.

Implementations§

Source§

impl Distribution

Source

pub fn latin_hypercube(population_size: u32) -> Self

Creates a Latin Hypercube distribution strategy for superior space coverage.

Latin Hypercube sampling provides better initial population diversity by ensuring that each parameter dimension is sampled evenly across its range. This typically results in better optimization performance, especially for smaller populations.

§Parameters
  • population_size: Number of individuals to generate. For best results, use 10-500. Very small populations (<10) may not fully benefit from the structured sampling, while very large populations (>1000) may not justify the additional computation.
§When to Use
  • Recommended default for most optimization problems
  • Complex, multi-dimensional search spaces
  • When population budget is limited
  • When you need consistent, reproducible coverage
§Examples
use fx_durable_ga::models::Distribution;

// Standard population size with excellent coverage
let dist = Distribution::latin_hypercube(50);

// Small population where coverage is critical  
let small_dist = Distribution::latin_hypercube(20);

// Medium population for complex problems
let medium_dist = Distribution::latin_hypercube(200);
Examples found in repository?
examples/regression_model.rs (line 249)
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 203)
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 431)
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 random(population_size: u32) -> Self

Creates a random distribution strategy for simple uniform sampling.

Random sampling generates each individual independently by sampling each parameter uniformly from its allowed range. While simple, this can result in uneven coverage with potential clustering or gaps in the search space.

§Parameters
  • population_size: Number of individuals to generate. Larger populations (>100) help compensate for potential clustering effects.
§When to Use
  • Very large populations (>1000) where coverage gaps are less significant
  • Computational efficiency is critical
  • Comparing against baseline random methods
  • Simple, low-dimensional search spaces
  • When search space has constraints that might interfere with structured sampling
§Examples
use fx_durable_ga::models::Distribution;

// Large population where random sampling is acceptable
let large_dist = Distribution::random(1000);

// Baseline comparison distribution
let baseline_dist = Distribution::random(50);

// High-speed generation for simple problems
let fast_dist = Distribution::random(100);

Trait Implementations§

Source§

impl Clone for Distribution

Source§

fn clone(&self) -> Distribution

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 Distribution

Source§

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

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

impl<'de> Deserialize<'de> for Distribution

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 Distribution

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