pub struct ServiceBuilder { /* private fields */ }Expand description
Builder for creating optimization services with registered type evaluators. Handles morphology registration and evaluator type erasure.
Implementations§
Source§impl ServiceBuilder
impl ServiceBuilder
Sourcepub async fn register<T, E>(self, evaluator: E) -> Result<Self, Error>
pub async fn register<T, E>(self, evaluator: E) -> Result<Self, Error>
Registers a problem type and its fitness evaluator with the optimization service.
This is the core method for connecting your domain-specific problems to the genetic algorithm engine. It establishes the relationship between your data structures, their genetic encoding, and the fitness evaluation logic.
§Core Concepts
§Type Registration Process
- Morphology Creation: Automatically creates and stores the search space definition
- Type Erasure: Converts your specific evaluator into a type-erased form for internal storage
- Hash Mapping: Associates your type’s hash with its evaluator for efficient lookup
- Database Integration: Ensures the type’s morphology exists in persistent storage
§Required Trait Implementations
Encodeable: Defines how your type maps to/from genetic representationEvaluator: Provides the fitness function that guides evolution
§Parameters
evaluator- Your fitness evaluator implementingEvaluator<T::Phenotype>
§Type Parameters
T: Encodeable- Your problem type that can be encoded as genesE: Evaluator<T::Phenotype>- Your fitness evaluator for decoded phenotypes
§Encodeable Implementation Requirements
Your type must implement Encodeable with:
NAME: Unique string identifier for your typePhenotype: The decoded form passed to evaluatorsmorphology(): Defines the search space bounds and structureencode(): Converts instances to genetic representationdecode(): Converts genes back to phenotypes for evaluation
§Evaluator Implementation Requirements
Your evaluator must implement Evaluator<T::Phenotype> with:
fitness(): Async function returning fitness scores (any finite f64 values)- Support for early termination via the
Terminatedtrait - Error handling for failed evaluations
§Common Registration Patterns
§Simple Numerical Optimization
use fx_durable_ga::models::*;
#[derive(Debug, Clone)]
struct Parameters {
learning_rate: f64,
batch_size: i64,
layers: i64,
}
impl Encodeable for Parameters {
const NAME: &'static str = "Parameters";
type Phenotype = Parameters;
fn morphology() -> Vec<GeneBounds> {
vec![
GeneBounds::decimal(0.001, 1.0, 1000, 3).unwrap(), // learning_rate
GeneBounds::integer(8, 512, 32).unwrap(), // batch_size
GeneBounds::integer(1, 10, 10).unwrap(), // layers
]
}
fn encode(&self) -> Vec<i64> {
let bounds = Self::morphology();
vec![
bounds[0].from_sample(self.learning_rate / 1.0),
bounds[1].from_sample((self.batch_size - 8) as f64 / (512 - 8) as f64),
bounds[2].from_sample((self.layers - 1) as f64 / 9.0),
]
}
fn decode(genes: &[i64]) -> Self::Phenotype {
let bounds = Self::morphology();
Parameters {
learning_rate: bounds[0].decode_f64(genes[0]),
batch_size: (bounds[1].decode_f64(genes[1]) * (512 - 8) as f64 + 8.0) as i64,
layers: (bounds[2].decode_f64(genes[2]) * 9.0 + 1.0) as i64,
}
}
}
struct ModelEvaluator;
impl Evaluator<Parameters> for ModelEvaluator {
fn fitness<'a>(
&self,
_genotype_id: uuid::Uuid,
params: Parameters,
_request: &Request,
_terminated: &'a Box<dyn Terminated>,
) -> BoxFuture<'a, Result<f64, anyhow::Error>> {
Box::pin(async move {
// Train model with parameters and return accuracy
let accuracy = train_model(params.learning_rate, params.batch_size, params.layers).await?;
Ok(accuracy)
})
}
}
let builder = builder.register::<Parameters, _>(ModelEvaluator).await?;§Geometric/Spatial Problems
use fx_durable_ga::models::*;
#[derive(Debug, Clone)]
struct Point3D {
x: f64,
y: f64,
z: f64,
}
impl Encodeable for Point3D {
const NAME: &'static str = "Point3D";
type Phenotype = Point3D;
fn morphology() -> Vec<GeneBounds> {
vec![
GeneBounds::decimal(-10.0, 10.0, 2000, 3).unwrap(), // x: -10.0 to 10.0
GeneBounds::decimal(-10.0, 10.0, 2000, 3).unwrap(), // y: -10.0 to 10.0
GeneBounds::decimal(0.0, 20.0, 2000, 3).unwrap(), // z: 0.0 to 20.0
]
}
fn encode(&self) -> Vec<i64> {
let bounds = Self::morphology();
vec![
bounds[0].from_sample((self.x + 10.0) / 20.0),
bounds[1].from_sample((self.y + 10.0) / 20.0),
bounds[2].from_sample(self.z / 20.0),
]
}
fn decode(genes: &[i64]) -> Self::Phenotype {
let bounds = Self::morphology();
Point3D {
x: bounds[0].decode_f64(genes[0]) * 20.0 - 10.0,
y: bounds[1].decode_f64(genes[1]) * 20.0 - 10.0,
z: bounds[2].decode_f64(genes[2]) * 20.0,
}
}
}
struct DistanceEvaluator {
target: Point3D,
}
impl Evaluator<Point3D> for DistanceEvaluator {
fn fitness<'a>(
&self,
_genotype_id: uuid::Uuid,
point: Point3D,
_request: &Request,
terminated: &'a Box<dyn Terminated>,
) -> BoxFuture<'a, Result<f64, anyhow::Error>> {
let target = self.target.clone();
Box::pin(async move {
// Check for early termination
if terminated.is_terminated().await {
return Err(anyhow::anyhow!("Evaluation terminated"));
}
let dx = point.x - target.x;
let dy = point.y - target.y;
let dz = point.z - target.z;
let distance = (dx*dx + dy*dy + dz*dz).sqrt();
// Convert distance to fitness (closer = higher fitness)
let fitness = 1.0 / (1.0 + distance);
Ok(fitness)
})
}
}
let evaluator = DistanceEvaluator {
target: Point3D { x: 5.0, y: -2.0, z: 10.0 }
};
let builder = builder.register::<Point3D, _>(evaluator).await?;§Combinatorial/Discrete Problems
use fx_durable_ga::models::*;
#[derive(Debug, Clone)]
struct Schedule {
assignments: Vec<i64>, // job assignments to workers
}
impl Encodeable for Schedule {
const NAME: &'static str = "Schedule";
type Phenotype = Schedule;
fn morphology() -> Vec<GeneBounds> {
// 10 jobs, each can be assigned to 5 workers (0-4)
(0..10)
.map(|_| GeneBounds::integer(0, 4, 5).unwrap())
.collect()
}
fn encode(&self) -> Vec<i64> {
self.assignments.clone()
}
fn decode(genes: &[i64]) -> Self::Phenotype {
Schedule {
assignments: genes.to_vec()
}
}
}
struct ScheduleEvaluator {
job_costs: Vec<Vec<f64>>, // job_costs[job][worker] = cost
}
impl Evaluator<Schedule> for ScheduleEvaluator {
fn fitness<'a>(
&self,
_genotype_id: uuid::Uuid,
schedule: Schedule,
_request: &Request,
_terminated: &'a Box<dyn Terminated>,
) -> BoxFuture<'a, Result<f64, anyhow::Error>> {
let costs = self.job_costs.clone();
Box::pin(async move {
let total_cost: f64 = schedule.assignments
.iter()
.enumerate()
.map(|(job, &worker)| costs[job][worker as usize])
.sum();
// Lower cost = higher fitness
let max_possible_cost = 1000.0;
let fitness = (max_possible_cost - total_cost) / max_possible_cost;
Ok(fitness.max(0.0))
})
}
}
let evaluator = ScheduleEvaluator {
job_costs: vec![vec![10.0, 20.0, 15.0, 25.0, 12.0]; 10]
};
let builder = builder.register::<Schedule, _>(evaluator).await?;§Best Practices
§Morphology Design
- Use appropriate bounds: Match your problem’s natural constraints
- Balance precision vs. search space: More precision = larger search space
- Consider gene interactions: Related parameters should be adjacent when possible
- Use meaningful step sizes: Align with your problem’s natural granularity
§Fitness Function Design
- Use natural value ranges: Return raw values like loss, accuracy, cost, profit
- Handle edge cases gracefully: Return appropriate default values for invalid solutions
- Check termination regularly: Use
terminated.is_terminated()in long evaluations - Avoid fitness cliffs: Gradual fitness landscapes converge better than binary ones
- Return errors for true failures: Let the system handle evaluation failures
§Performance Considerations
- Minimize evaluation cost: Fitness evaluation is the bottleneck
- Use caching when appropriate: Cache expensive computations
- Parallelize evaluations: The system handles concurrent fitness evaluations
- Profile your evaluator: Identify and optimize slow paths
§Registration Order
Multiple types can be registered with the same service:
let service = builder
.register::<TypeA, _>(EvaluatorA).await?
.register::<TypeB, _>(EvaluatorB).await?
.build();§Returns
Returns Ok(Self) if registration succeeds, allowing method chaining.
The morphology is created in the database if it doesn’t exist.
§Errors
- Database connection or transaction failures
- Morphology creation failures (invalid gene bounds)
- Type hash collisions (extremely rare with different type names)
§Example Usage
use fx_durable_ga::{bootstrap, models::*};
#[derive(Debug, Clone)]
struct OptimizationTarget {
value: f64,
}
impl Encodeable for OptimizationTarget {
const NAME: &'static str = "OptimizationTarget";
type Phenotype = OptimizationTarget;
fn morphology() -> Vec<GeneBounds> {
vec![GeneBounds::decimal(-100.0, 100.0, 10000, 4).unwrap()]
}
fn encode(&self) -> Vec<i64> {
let bounds = Self::morphology();
vec![bounds[0].from_sample((self.value + 100.0) / 200.0)]
}
fn decode(genes: &[i64]) -> Self::Phenotype {
let bounds = Self::morphology();
OptimizationTarget {
value: bounds[0].decode_f64(genes[0]) * 200.0 - 100.0
}
}
}
struct QuadraticEvaluator;
impl Evaluator<OptimizationTarget> for QuadraticEvaluator {
fn fitness<'a>(
&self,
_genotype_id: uuid::Uuid,
target: OptimizationTarget,
_request: &Request,
_terminated: &'a Box<dyn Terminated>,
) -> BoxFuture<'a, Result<f64, anyhow::Error>> {
Box::pin(async move {
// Maximize -(x-10)^2 + 100, so optimal x = 10
let fitness = -(target.value - 10.0).powi(2) + 100.0;
Ok(fitness) // Return raw fitness value
})
}
}
let service = bootstrap(pool)
.await?
.register::<OptimizationTarget, _>(QuadraticEvaluator)
.await?
.build();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 with_max_deduplication_attempts(self, attempts: i32) -> Self
pub fn with_max_deduplication_attempts(self, attempts: i32) -> Self
Sets the maximum number of deduplication attempts when breeding genotypes.
Sourcepub fn build(self) -> Service
pub fn build(self) -> Service
Builds the optimization service with all registered evaluators.
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}Auto Trait Implementations§
impl !RefUnwindSafe for ServiceBuilder
impl !UnwindSafe for ServiceBuilder
impl Freeze for ServiceBuilder
impl Send for ServiceBuilder
impl Sync for ServiceBuilder
impl Unpin for ServiceBuilder
impl UnsafeUnpin for ServiceBuilder
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
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