1use crate::{Optimizer, OptimizerError, OptimizerResult};
21use parking_lot::RwLock as PLRwLock;
22use scirs2_core::RngExt;
23use std::collections::HashMap;
24use std::sync::{Arc, RwLock};
25use torsh_core::{device::CpuDevice, DType};
26use torsh_tensor::Tensor;
27
28pub trait ObjectiveFunction: Send + Sync {
30 fn evaluate(&self, parameters: &[f32]) -> OptimizerResult<f32>;
33
34 fn dimension(&self) -> usize;
36
37 fn bounds(&self) -> Option<(Vec<f32>, Vec<f32>)> {
39 None
40 }
41
42 fn name(&self) -> &str {
44 "Unknown"
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct GradientFreeConfig {
51 pub max_evaluations: usize,
53 pub tolerance: f32,
55 pub max_stagnation: usize,
57 pub device: Arc<CpuDevice>,
59 pub seed: Option<u64>,
61 pub verbose: bool,
63}
64
65impl Default for GradientFreeConfig {
66 fn default() -> Self {
67 Self {
68 max_evaluations: 10000,
69 tolerance: 1e-6,
70 max_stagnation: 100,
71 device: Arc::new(CpuDevice::new()),
72 seed: None,
73 verbose: false,
74 }
75 }
76}
77
78#[derive(Debug, Clone)]
80pub struct OptimizationResult {
81 pub best_parameters: Vec<f32>,
83 pub best_value: f32,
85 pub evaluations: usize,
87 pub iterations: usize,
89 pub converged: bool,
91 pub convergence_reason: String,
93 pub history: Vec<(Vec<f32>, f32)>,
95}
96
97#[derive(Debug)]
99pub struct NelderMead {
100 config: GradientFreeConfig,
101 alpha: f32,
103 gamma: f32,
105 rho: f32,
107 sigma: f32,
109}
110
111impl NelderMead {
112 pub fn new(config: GradientFreeConfig) -> Self {
113 Self {
114 config,
115 alpha: 1.0, gamma: 2.0, rho: 0.5, sigma: 0.5, }
120 }
121
122 pub fn with_coefficients(mut self, alpha: f32, gamma: f32, rho: f32, sigma: f32) -> Self {
123 self.alpha = alpha;
124 self.gamma = gamma;
125 self.rho = rho;
126 self.sigma = sigma;
127 self
128 }
129
130 pub fn optimize<F: ObjectiveFunction>(
131 &self,
132 objective: &F,
133 initial_point: &[f32],
134 ) -> OptimizerResult<OptimizationResult> {
135 let n = objective.dimension();
136 if initial_point.len() != n {
137 return Err(OptimizerError::InvalidInput(format!(
138 "Initial point dimension {} doesn't match objective dimension {}",
139 initial_point.len(),
140 n
141 )));
142 }
143
144 let mut simplex = self.initialize_simplex(initial_point)?;
146 let mut values = Vec::with_capacity(n + 1);
147 let mut evaluations = 0;
148 let mut iterations = 0;
149 let mut history = Vec::new();
150 let mut best_value = f32::INFINITY;
151 let mut best_params = initial_point.to_vec();
152 let mut stagnation_count = 0;
153
154 for vertex in &simplex {
156 let value = objective.evaluate(vertex)?;
157 values.push(value);
158 evaluations += 1;
159
160 if value < best_value {
161 best_value = value;
162 best_params = vertex.clone();
163 stagnation_count = 0;
164 }
165
166 history.push((vertex.clone(), value));
167 }
168
169 while evaluations < self.config.max_evaluations
170 && stagnation_count < self.config.max_stagnation
171 {
172 let mut indices: Vec<usize> = (0..simplex.len()).collect();
174 indices.sort_by(|&a, &b| {
175 values[a]
176 .partial_cmp(&values[b])
177 .unwrap_or(std::cmp::Ordering::Equal)
178 });
179
180 let best_idx = indices[0];
181 let worst_idx = indices[n];
182 let second_worst_idx = indices[n - 1];
183
184 let range = values[worst_idx] - values[best_idx];
186 if range < self.config.tolerance {
187 return Ok(OptimizationResult {
188 best_parameters: best_params,
189 best_value,
190 evaluations,
191 iterations,
192 converged: true,
193 convergence_reason: "Tolerance reached".to_string(),
194 history,
195 });
196 }
197
198 let centroid = self.compute_centroid(&simplex, &indices[..n])?;
200
201 let reflected = self.reflect(&simplex[worst_idx], ¢roid)?;
203 let reflected_value = objective.evaluate(&reflected)?;
204 evaluations += 1;
205 history.push((reflected.clone(), reflected_value));
206
207 if reflected_value < best_value {
208 best_value = reflected_value;
209 best_params = reflected.clone();
210 stagnation_count = 0;
211 } else {
212 stagnation_count += 1;
213 }
214
215 if values[best_idx] <= reflected_value && reflected_value < values[second_worst_idx] {
216 simplex[worst_idx] = reflected;
218 values[worst_idx] = reflected_value;
219 } else if reflected_value < values[best_idx] {
220 let expanded = self.expand(&reflected, ¢roid)?;
222 let expanded_value = objective.evaluate(&expanded)?;
223 evaluations += 1;
224 history.push((expanded.clone(), expanded_value));
225
226 if expanded_value < reflected_value {
227 simplex[worst_idx] = expanded.clone();
228 values[worst_idx] = expanded_value;
229
230 if expanded_value < best_value {
231 best_value = expanded_value;
232 best_params = expanded;
233 stagnation_count = 0;
234 }
235 } else {
236 simplex[worst_idx] = reflected;
237 values[worst_idx] = reflected_value;
238 }
239 } else {
240 let contracted = if reflected_value < values[worst_idx] {
242 self.contract_outside(&reflected, ¢roid)?
244 } else {
245 self.contract_inside(&simplex[worst_idx], ¢roid)?
247 };
248
249 let contracted_value = objective.evaluate(&contracted)?;
250 evaluations += 1;
251 history.push((contracted.clone(), contracted_value));
252
253 if contracted_value < values[worst_idx].min(reflected_value) {
254 simplex[worst_idx] = contracted.clone();
255 values[worst_idx] = contracted_value;
256
257 if contracted_value < best_value {
258 best_value = contracted_value;
259 best_params = contracted;
260 stagnation_count = 0;
261 }
262 } else {
263 for i in 1..=n {
265 let vertex_idx = indices[i];
266 simplex[vertex_idx] =
267 self.shrink(&simplex[vertex_idx], &simplex[best_idx])?;
268 values[vertex_idx] = objective.evaluate(&simplex[vertex_idx])?;
269 evaluations += 1;
270 history.push((simplex[vertex_idx].clone(), values[vertex_idx]));
271
272 if values[vertex_idx] < best_value {
273 best_value = values[vertex_idx];
274 best_params = simplex[vertex_idx].clone();
275 stagnation_count = 0;
276 }
277 }
278 }
279 }
280
281 iterations += 1;
282
283 if self.config.verbose && iterations % 100 == 0 {
284 println!("Iteration {}: Best value = {:.6e}", iterations, best_value);
285 }
286 }
287
288 let converged = stagnation_count < self.config.max_stagnation;
289 let reason = if converged {
290 "Maximum evaluations reached".to_string()
291 } else {
292 "Stagnation limit reached".to_string()
293 };
294
295 Ok(OptimizationResult {
296 best_parameters: best_params,
297 best_value,
298 evaluations,
299 iterations,
300 converged,
301 convergence_reason: reason,
302 history,
303 })
304 }
305
306 fn initialize_simplex(&self, initial_point: &[f32]) -> OptimizerResult<Vec<Vec<f32>>> {
307 let n = initial_point.len();
308 let mut simplex = Vec::with_capacity(n + 1);
309
310 simplex.push(initial_point.to_vec());
312
313 for i in 0..n {
315 let mut vertex = initial_point.to_vec();
316 let step = if initial_point[i].abs() > 1e-6 {
317 initial_point[i] * 0.05 } else {
319 0.00025 };
321 vertex[i] += step;
322 simplex.push(vertex);
323 }
324
325 Ok(simplex)
326 }
327
328 fn compute_centroid(
329 &self,
330 simplex: &[Vec<f32>],
331 indices: &[usize],
332 ) -> OptimizerResult<Vec<f32>> {
333 let n = simplex[0].len();
334 let mut centroid = vec![0.0; n];
335
336 for &idx in indices {
337 for i in 0..n {
338 centroid[i] += simplex[idx][i];
339 }
340 }
341
342 for i in 0..n {
343 centroid[i] /= indices.len() as f32;
344 }
345
346 Ok(centroid)
347 }
348
349 fn reflect(&self, worst: &[f32], centroid: &[f32]) -> OptimizerResult<Vec<f32>> {
350 let mut reflected = Vec::with_capacity(worst.len());
351 for i in 0..worst.len() {
352 reflected.push(centroid[i] + self.alpha * (centroid[i] - worst[i]));
353 }
354 Ok(reflected)
355 }
356
357 fn expand(&self, reflected: &[f32], centroid: &[f32]) -> OptimizerResult<Vec<f32>> {
358 let mut expanded = Vec::with_capacity(reflected.len());
359 for i in 0..reflected.len() {
360 expanded.push(centroid[i] + self.gamma * (reflected[i] - centroid[i]));
361 }
362 Ok(expanded)
363 }
364
365 fn contract_outside(&self, reflected: &[f32], centroid: &[f32]) -> OptimizerResult<Vec<f32>> {
366 let mut contracted = Vec::with_capacity(reflected.len());
367 for i in 0..reflected.len() {
368 contracted.push(centroid[i] + self.rho * (reflected[i] - centroid[i]));
369 }
370 Ok(contracted)
371 }
372
373 fn contract_inside(&self, worst: &[f32], centroid: &[f32]) -> OptimizerResult<Vec<f32>> {
374 let mut contracted = Vec::with_capacity(worst.len());
375 for i in 0..worst.len() {
376 contracted.push(centroid[i] + self.rho * (worst[i] - centroid[i]));
377 }
378 Ok(contracted)
379 }
380
381 fn shrink(&self, vertex: &[f32], best: &[f32]) -> OptimizerResult<Vec<f32>> {
382 let mut shrunk = Vec::with_capacity(vertex.len());
383 for i in 0..vertex.len() {
384 shrunk.push(best[i] + self.sigma * (vertex[i] - best[i]));
385 }
386 Ok(shrunk)
387 }
388}
389
390#[derive(Debug)]
392pub struct ParticleSwarmOptimizer {
393 config: GradientFreeConfig,
394 num_particles: usize,
396 inertia: f32,
398 c1: f32,
400 c2: f32,
402 max_velocity: f32,
404}
405
406impl ParticleSwarmOptimizer {
407 pub fn new(config: GradientFreeConfig, num_particles: usize) -> Self {
408 Self {
409 config,
410 num_particles,
411 inertia: 0.9,
412 c1: 2.0,
413 c2: 2.0,
414 max_velocity: 1.0,
415 }
416 }
417
418 pub fn with_parameters(mut self, inertia: f32, c1: f32, c2: f32, max_velocity: f32) -> Self {
419 self.inertia = inertia;
420 self.c1 = c1;
421 self.c2 = c2;
422 self.max_velocity = max_velocity;
423 self
424 }
425
426 pub fn optimize<F: ObjectiveFunction>(
427 &self,
428 objective: &F,
429 initial_bounds: &[(f32, f32)],
430 ) -> OptimizerResult<OptimizationResult> {
431 let dimension = objective.dimension();
432 if initial_bounds.len() != dimension {
433 return Err(OptimizerError::InvalidInput(
434 "Bounds dimension doesn't match objective dimension".to_string(),
435 ));
436 }
437
438 let mut positions = Vec::with_capacity(self.num_particles);
440 let mut velocities = Vec::with_capacity(self.num_particles);
441 let mut personal_best_positions = Vec::with_capacity(self.num_particles);
442 let mut personal_best_values = Vec::with_capacity(self.num_particles);
443 let mut current_values = Vec::with_capacity(self.num_particles);
444
445 use scirs2_core::random::{Random, Rng, SeedableRng};
447
448 let mut rng = if let Some(seed) = self.config.seed {
449 Random::seed(seed)
450 } else {
451 Random::seed(0)
452 };
453
454 for _ in 0..self.num_particles {
456 let mut position = Vec::with_capacity(dimension);
457 let mut velocity = Vec::with_capacity(dimension);
458
459 for i in 0..dimension {
460 let (min_bound, max_bound) = initial_bounds[i];
461 position.push(rng.random::<f32>() * (max_bound - min_bound) + min_bound);
462 velocity.push(rng.random::<f32>() * self.max_velocity * 2.0 - self.max_velocity);
463 }
464
465 positions.push(position);
466 velocities.push(velocity);
467 }
468
469 let mut evaluations = 0;
471 let mut global_best_position = vec![0.0; dimension];
472 let mut global_best_value = f32::INFINITY;
473 let mut history = Vec::new();
474
475 for i in 0..self.num_particles {
476 let value = objective.evaluate(&positions[i])?;
477 current_values.push(value);
478 personal_best_positions.push(positions[i].clone());
479 personal_best_values.push(value);
480 evaluations += 1;
481 history.push((positions[i].clone(), value));
482
483 if value < global_best_value {
484 global_best_value = value;
485 global_best_position = positions[i].clone();
486 }
487 }
488
489 let mut iterations = 0;
490 let mut stagnation_count = 0;
491
492 while evaluations < self.config.max_evaluations
493 && stagnation_count < self.config.max_stagnation
494 {
495 let old_global_best = global_best_value;
496
497 for i in 0..self.num_particles {
498 for j in 0..dimension {
500 let r1 = rng.random::<f32>();
501 let r2 = rng.random::<f32>();
502
503 velocities[i][j] = self.inertia * velocities[i][j]
504 + self.c1 * r1 * (personal_best_positions[i][j] - positions[i][j])
505 + self.c2 * r2 * (global_best_position[j] - positions[i][j]);
506
507 velocities[i][j] = velocities[i][j]
509 .max(-self.max_velocity)
510 .min(self.max_velocity);
511 }
512
513 for j in 0..dimension {
515 positions[i][j] += velocities[i][j];
516
517 let (min_bound, max_bound) = initial_bounds[j];
519 positions[i][j] = positions[i][j].max(min_bound).min(max_bound);
520 }
521
522 let value = objective.evaluate(&positions[i])?;
524 current_values[i] = value;
525 evaluations += 1;
526 history.push((positions[i].clone(), value));
527
528 if value < personal_best_values[i] {
530 personal_best_values[i] = value;
531 personal_best_positions[i] = positions[i].clone();
532
533 if value < global_best_value {
535 global_best_value = value;
536 global_best_position = positions[i].clone();
537 }
538 }
539 }
540
541 if (global_best_value - old_global_best).abs() < self.config.tolerance {
543 stagnation_count += 1;
544 } else {
545 stagnation_count = 0;
546 }
547
548 iterations += 1;
549
550 if self.config.verbose && iterations % 10 == 0 {
551 println!(
552 "Iteration {}: Best value = {:.6e}",
553 iterations, global_best_value
554 );
555 }
556 }
557
558 let converged = stagnation_count < self.config.max_stagnation;
559 let reason = if converged {
560 "Maximum evaluations reached".to_string()
561 } else {
562 "Stagnation limit reached".to_string()
563 };
564
565 Ok(OptimizationResult {
566 best_parameters: global_best_position,
567 best_value: global_best_value,
568 evaluations,
569 iterations,
570 converged,
571 convergence_reason: reason,
572 history,
573 })
574 }
575}
576
577#[derive(Debug)]
579pub struct RandomSearch {
580 config: GradientFreeConfig,
581}
582
583impl RandomSearch {
584 pub fn new(config: GradientFreeConfig) -> Self {
585 Self { config }
586 }
587
588 pub fn optimize<F: ObjectiveFunction>(
589 &self,
590 objective: &F,
591 bounds: &[(f32, f32)],
592 ) -> OptimizerResult<OptimizationResult> {
593 let dimension = objective.dimension();
594 if bounds.len() != dimension {
595 return Err(OptimizerError::InvalidInput(
596 "Bounds dimension doesn't match objective dimension".to_string(),
597 ));
598 }
599
600 use scirs2_core::random::{Random, Rng, SeedableRng};
602
603 let mut rng = if let Some(seed) = self.config.seed {
604 Random::seed(seed)
605 } else {
606 Random::seed(0)
607 };
608
609 let mut best_parameters = Vec::with_capacity(dimension);
610 let mut best_value = f32::INFINITY;
611 let mut evaluations = 0;
612 let mut history = Vec::new();
613
614 while evaluations < self.config.max_evaluations {
615 let mut point = Vec::with_capacity(dimension);
617 for i in 0..dimension {
618 let (min_bound, max_bound) = bounds[i];
619 point.push(rng.random::<f32>() * (max_bound - min_bound) + min_bound);
620 }
621
622 let value = objective.evaluate(&point)?;
624 evaluations += 1;
625 history.push((point.clone(), value));
626
627 if value < best_value {
629 best_value = value;
630 best_parameters = point;
631 }
632
633 if self.config.verbose && evaluations % 1000 == 0 {
634 println!(
635 "Evaluation {}: Best value = {:.6e}",
636 evaluations, best_value
637 );
638 }
639 }
640
641 Ok(OptimizationResult {
642 best_parameters,
643 best_value,
644 evaluations,
645 iterations: evaluations,
646 converged: false,
647 convergence_reason: "Maximum evaluations reached".to_string(),
648 history,
649 })
650 }
651}
652
653pub mod test_functions {
655 use super::*;
656
657 pub struct Sphere {
659 pub dimension: usize,
660 }
661
662 impl ObjectiveFunction for Sphere {
663 fn evaluate(&self, parameters: &[f32]) -> OptimizerResult<f32> {
664 Ok(parameters.iter().map(|&x| x * x).sum())
665 }
666
667 fn dimension(&self) -> usize {
668 self.dimension
669 }
670
671 fn bounds(&self) -> Option<(Vec<f32>, Vec<f32>)> {
672 Some((vec![-5.0; self.dimension], vec![5.0; self.dimension]))
673 }
674
675 fn name(&self) -> &str {
676 "Sphere"
677 }
678 }
679
680 pub struct Rosenbrock {
682 pub dimension: usize,
683 }
684
685 impl ObjectiveFunction for Rosenbrock {
686 fn evaluate(&self, parameters: &[f32]) -> OptimizerResult<f32> {
687 let mut sum = 0.0;
688 for i in 0..parameters.len() - 1 {
689 let term1 = parameters[i + 1] - parameters[i] * parameters[i];
690 let term2 = 1.0 - parameters[i];
691 sum += 100.0 * term1 * term1 + term2 * term2;
692 }
693 Ok(sum)
694 }
695
696 fn dimension(&self) -> usize {
697 self.dimension
698 }
699
700 fn bounds(&self) -> Option<(Vec<f32>, Vec<f32>)> {
701 Some((vec![-2.0; self.dimension], vec![2.0; self.dimension]))
702 }
703
704 fn name(&self) -> &str {
705 "Rosenbrock"
706 }
707 }
708
709 pub struct Rastrigin {
711 pub dimension: usize,
712 pub a: f32,
713 }
714
715 impl Rastrigin {
716 pub fn new(dimension: usize) -> Self {
717 Self { dimension, a: 10.0 }
718 }
719 }
720
721 impl ObjectiveFunction for Rastrigin {
722 fn evaluate(&self, parameters: &[f32]) -> OptimizerResult<f32> {
723 let n = parameters.len() as f32;
724 let mut sum = self.a * n;
725 for &x in parameters {
726 sum += x * x - self.a * (2.0 * std::f32::consts::PI * x).cos();
727 }
728 Ok(sum)
729 }
730
731 fn dimension(&self) -> usize {
732 self.dimension
733 }
734
735 fn bounds(&self) -> Option<(Vec<f32>, Vec<f32>)> {
736 Some((vec![-5.12; self.dimension], vec![5.12; self.dimension]))
737 }
738
739 fn name(&self) -> &str {
740 "Rastrigin"
741 }
742 }
743}
744
745#[cfg(test)]
746mod tests {
747 use super::test_functions::*;
748 use super::*;
749
750 #[test]
751 fn test_nelder_mead_sphere() {
752 let config = GradientFreeConfig {
753 max_evaluations: 1000,
754 tolerance: 1e-6,
755 ..Default::default()
756 };
757
758 let optimizer = NelderMead::new(config);
759 let objective = Sphere { dimension: 2 };
760 let initial_point = vec![1.0, 1.0];
761
762 let result = optimizer.optimize(&objective, &initial_point).unwrap();
763
764 assert!(result.best_value < 1e-5);
765 assert!(result.best_parameters.iter().all(|&x| x.abs() < 0.1));
766 }
767
768 #[test]
769 fn test_pso_sphere() {
770 let config = GradientFreeConfig {
771 max_evaluations: 2000,
772 tolerance: 1e-6,
773 seed: Some(42),
774 ..Default::default()
775 };
776
777 let optimizer = ParticleSwarmOptimizer::new(config, 20);
778 let objective = Sphere { dimension: 2 };
779 let bounds = vec![(-5.0, 5.0), (-5.0, 5.0)];
780
781 let result = optimizer.optimize(&objective, &bounds).unwrap();
782
783 assert!(result.best_value < 1e-3);
784 }
785
786 #[test]
787 fn test_random_search() {
788 let config = GradientFreeConfig {
789 max_evaluations: 5000,
790 seed: Some(42),
791 ..Default::default()
792 };
793
794 let optimizer = RandomSearch::new(config);
795 let objective = Sphere { dimension: 2 };
796 let bounds = vec![(-5.0, 5.0), (-5.0, 5.0)];
797
798 let result = optimizer.optimize(&objective, &bounds).unwrap();
799
800 assert!(result.best_value < 1.0);
802 assert_eq!(result.evaluations, 5000);
803 }
804}