point_search/
point_search.rs1use anyhow::Result;
16use fx_durable_ga::{
17 bootstrap,
18 models::{
19 Crossover, Distribution, Encodeable, Evaluator, FitnessGoal, GeneBounds, Mutagen,
20 MutationRate, Request, Schedule, Selector, Temperature, Terminated,
21 },
22 register_event_handlers, register_job_handlers,
23};
24use fx_mq_jobs::FX_MQ_JOBS_SCHEMA_NAME;
25use fx_mq_jobs::Queries;
26use sqlx::postgres::PgPoolOptions;
27use std::time::Duration;
28use std::{env, sync::Arc};
29use uuid::Uuid;
30
31#[derive(Debug, Copy, Clone)]
36struct Point {
37 x: f64,
38 y: f64,
39 z: f64,
40}
41
42impl Encodeable for Point {
43 const NAME: &'static str = "point";
44
45 type Phenotype = Point;
46
47 fn morphology() -> Vec<GeneBounds> {
54 vec![
55 GeneBounds::decimal(0.5, 1.75, 1000, 3).unwrap(), GeneBounds::decimal(0.75, 2.00, 1000, 3).unwrap(), GeneBounds::decimal(2.00, 3.25, 1000, 3).unwrap(), ]
59 }
60
61 fn encode(&self) -> Vec<i64> {
65 let bounds = Self::morphology();
66 vec![
67 bounds[0]
68 .encode_f64(self.x)
69 .expect("x coordinate within bounds"),
70 bounds[1]
71 .encode_f64(self.y)
72 .expect("y coordinate within bounds"),
73 bounds[2]
74 .encode_f64(self.z)
75 .expect("z coordinate within bounds"),
76 ]
77 }
78
79 fn decode(genes: &[i64]) -> Self::Phenotype {
83 let bounds = Self::morphology();
84 Point {
85 x: bounds[0].decode_f64(genes[0]), y: bounds[1].decode_f64(genes[1]), z: bounds[2].decode_f64(genes[2]), }
89 }
90}
91
92struct PointDistanceEvaluator {
96 target_point: Point,
97}
98
99impl Evaluator<Point> for PointDistanceEvaluator {
100 fn fitness<'a>(
104 &self,
105 _genotype_id: Uuid,
106 phenotype: Point,
107 _: &Request,
108 terminated: &'a Box<dyn Terminated>,
109 ) -> futures::future::BoxFuture<'a, Result<f64, anyhow::Error>> {
110 let target = self.target_point;
111 Box::pin(async move {
112 if terminated.is_terminated().await {
113 return Ok(f64::MAX); }
115
116 let dx = phenotype.x - target.x;
118 let dy = phenotype.y - target.y;
119 let dz = phenotype.z - target.z;
120 let distance = (dx * dx + dy * dy + dz * dz).sqrt();
121
122 Ok(distance) })
124 }
125}
126
127const TIMEOUT_SECONDS: u64 = 900;
128
129#[tokio::main]
130async fn main() -> Result<()> {
131 dotenv::from_filename(".env.local").ok();
132
133 tracing_subscriber::fmt()
135 .with_max_level(tracing::Level::INFO)
136 .init();
137
138 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 let target_point = Point {
150 x: 1.0,
151 y: 1.5,
152 z: 2.5,
153 }; 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 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 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 for _ in 0..1 {
194 service
195 .new_optimization_request(
196 Point::NAME,
197 Point::HASH,
198 FitnessGoal::minimize(0.01)?, 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 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}