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
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
Implementations§
Source§impl Distribution
impl Distribution
Sourcepub fn latin_hypercube(population_size: u32) -> Self
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?
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 random(population_size: u32) -> Self
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
impl Clone for Distribution
Source§fn clone(&self) -> Distribution
fn clone(&self) -> Distribution
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 Distribution
impl Debug for Distribution
Source§impl<'de> Deserialize<'de> for Distribution
impl<'de> Deserialize<'de> for Distribution
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 Distribution
impl RefUnwindSafe for Distribution
impl Send for Distribution
impl Sync for Distribution
impl Unpin for Distribution
impl UnsafeUnpin for Distribution
impl UnwindSafe for Distribution
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