1use crate::solvers::SolversError as ExecutionError;
7use crate::solvers::{SolverConfig, SolverResult, SolverState};
8
9#[cfg(any(not(target_arch = "wasm32"), feature = "wasm-scientific"))]
13pub mod metaheuristics;
14
15#[repr(C)]
17pub struct NelderMeadSimplex {
18 pub vertices: [[f64; 4]; 5], pub values: [f64; 5],
22 pub iteration: u32,
24 pub best_point: [f64; 4],
26 pub best_value: f64,
27 pub config: SolverConfig,
29 pub solver_state: SolverState,
31}
32
33#[repr(C)]
35pub struct BoundedNewtonRaphson {
36 pub current_guess: f64,
38 pub previous_guess: f64,
40 pub current_value: f64,
42 pub current_derivative: f64,
44 pub lower_bound: f64,
46 pub upper_bound: f64,
47 pub config: SolverConfig,
49 pub solver_state: SolverState,
51}
52
53#[repr(C)]
55pub struct LevenbergMarquardtStack {
56 pub parameters: [f64; 4],
58 pub delta_parameters: [f64; 4],
60 pub jacobian: [[f64; 10]; 4],
62 pub residuals: [f64; 10],
64 pub lambda: f64,
66 pub chi_squared: f64,
68 pub config: SolverConfig,
70 pub solver_state: SolverState,
72}
73
74#[repr(C)]
76#[derive(Clone, Copy)]
77pub struct OptimizationState {
78 pub iteration: u32,
80 pub objective_value: f64,
82 pub converged: bool,
84 pub simplex_size: f64,
86}
87
88#[repr(C)]
90#[derive(Clone, Copy)]
91pub struct RootFindingState {
92 pub iteration: u32,
94 pub function_value: f64,
96 pub converged: bool,
98 pub step_size: f64,
100}
101
102#[repr(C)]
104#[derive(Clone, Copy)]
105pub struct CurveFitState {
106 pub iteration: u32,
108 pub chi_squared: f64,
110 pub converged: bool,
112 pub lambda: f64,
114}
115
116pub trait ObjectiveFunction {
118 fn evaluate(&self, params: &[f64; 4]) -> f64;
120
121 fn in_bounds(&self, _params: &[f64; 4]) -> bool {
123 true }
125}
126
127pub trait RootFunction {
129 fn evaluate(&self, x: f64) -> f64;
131
132 fn derivative(&self, x: f64) -> f64;
134}
135
136pub trait CurveFitFunction {
138 fn evaluate(&self, x: f64, params: &[f64; 4]) -> f64;
140
141 fn jacobian(&self, x: f64, params: &[f64; 4]) -> [f64; 4];
143}
144
145impl NelderMeadSimplex {
146 pub fn new(initial_point: [f64; 4], config: SolverConfig) -> Self {
148 Self {
149 vertices: Self::initialize_simplex(initial_point),
150 values: [0.0; 5],
151 iteration: 0,
152 best_point: initial_point,
153 best_value: f64::MAX,
154 config,
155 solver_state: SolverState::default(),
156 }
157 }
158
159 const fn initialize_simplex(initial_point: [f64; 4]) -> [[f64; 4]; 5] {
161 let mut simplex = [[0.0; 4]; 5];
162
163 simplex[0] = initial_point;
165
166 let mut i = 1;
168 while i < 5 {
169 let mut vertex = initial_point;
170 vertex[i - 1] += 0.1; simplex[i] = vertex;
172 i += 1;
173 }
174
175 simplex
176 }
177
178 pub fn optimize<F>(&mut self, f: &F) -> SolverResult<OptimizationState>
180 where
181 F: ObjectiveFunction,
182 {
183 for i in 0..5 {
185 self.values[i] = f.evaluate(&self.vertices[i]);
186 }
187
188 self.iteration = 0;
189 self.solver_state.converged = false;
190
191 while self.iteration < self.config.max_iterations {
192 self.sort_vertices();
194
195 self.best_point = self.vertices[0];
197 self.best_value = self.values[0];
198
199 if self.check_convergence() {
201 self.solver_state.converged = true;
202 break;
203 }
204
205 self.nelder_mead_step(f)?;
207
208 self.iteration += 1;
209 }
210
211 Ok(OptimizationState {
212 iteration: self.iteration,
213 objective_value: self.best_value,
214 converged: self.solver_state.converged,
215 simplex_size: self.calculate_simplex_size(),
216 })
217 }
218
219 fn sort_vertices(&mut self) {
221 for i in 0..4 {
223 for j in 0..4 - i {
224 if self.values[j] > self.values[j + 1] {
225 let temp_vertex = self.vertices[j];
227 self.vertices[j] = self.vertices[j + 1];
228 self.vertices[j + 1] = temp_vertex;
229
230 let temp_value = self.values[j];
232 self.values[j] = self.values[j + 1];
233 self.values[j + 1] = temp_value;
234 }
235 }
236 }
237 }
238
239 fn check_convergence(&self) -> bool {
241 let simplex_size = self.calculate_simplex_size();
242 simplex_size < self.config.tolerance
243 }
244
245 fn calculate_simplex_size(&self) -> f64 {
247 let mut size: f64 = 0.0;
248
249 for i in 1..5 {
250 let mut distance = 0.0;
251 for j in 0..4 {
252 distance += (self.vertices[i][j] - self.vertices[0][j]).powi(2);
253 }
254 size = size.max(distance.sqrt());
255 }
256
257 size
258 }
259
260 fn nelder_mead_step<F>(&mut self, f: &F) -> SolverResult<()>
262 where
263 F: ObjectiveFunction,
264 {
265 let centroid = self.calculate_centroid();
267
268 let reflected = self.reflect(¢roid);
270 let reflected_value = f.evaluate(&reflected);
271
272 if reflected_value < self.values[0] {
273 let expanded = self.expand(¢roid, &reflected);
275 let expanded_value = f.evaluate(&expanded);
276
277 if expanded_value < reflected_value {
278 self.vertices[4] = expanded;
279 self.values[4] = expanded_value;
280 } else {
281 self.vertices[4] = reflected;
282 self.values[4] = reflected_value;
283 }
284 } else if reflected_value < self.values[3] {
285 self.vertices[4] = reflected;
287 self.values[4] = reflected_value;
288 } else {
289 if reflected_value < self.values[4] {
291 let contracted = self.contract(¢roid, &reflected);
292 let contracted_value = f.evaluate(&contracted);
293
294 if contracted_value < reflected_value {
295 self.vertices[4] = contracted;
296 self.values[4] = contracted_value;
297 } else {
298 self.shrink(f)?;
299 }
300 } else {
301 let contracted = self.contract(¢roid, &self.vertices[4]);
302 let contracted_value = f.evaluate(&contracted);
303
304 if contracted_value < self.values[4] {
305 self.vertices[4] = contracted;
306 self.values[4] = contracted_value;
307 } else {
308 self.shrink(f)?;
309 }
310 }
311 }
312
313 Ok(())
314 }
315
316 fn calculate_centroid(&self) -> [f64; 4] {
318 let mut centroid = [0.0; 4];
319
320 for i in 0..4 {
321 for j in 0..4 {
322 centroid[j] += self.vertices[i][j];
323 }
324 }
325
326 for j in 0..4 {
327 centroid[j] /= 4.0;
328 }
329
330 centroid
331 }
332
333 fn reflect(&self, centroid: &[f64; 4]) -> [f64; 4] {
335 let mut reflected = [0.0; 4];
336 let alpha = 1.0; for i in 0..4 {
339 reflected[i] = centroid[i] + alpha * (centroid[i] - self.vertices[4][i]);
340 }
341
342 reflected
343 }
344
345 fn expand(&self, centroid: &[f64; 4], reflected: &[f64; 4]) -> [f64; 4] {
347 let mut expanded = [0.0; 4];
348 let gamma = 2.0; for i in 0..4 {
351 expanded[i] = centroid[i] + gamma * (reflected[i] - centroid[i]);
352 }
353
354 expanded
355 }
356
357 fn contract(&self, centroid: &[f64; 4], worst: &[f64; 4]) -> [f64; 4] {
359 let mut contracted = [0.0; 4];
360 let rho = 0.5; for i in 0..4 {
363 contracted[i] = centroid[i] + rho * (worst[i] - centroid[i]);
364 }
365
366 contracted
367 }
368
369 fn shrink<F>(&mut self, f: &F) -> SolverResult<()>
371 where
372 F: ObjectiveFunction,
373 {
374 let sigma = 0.5; for i in 1..5 {
377 for j in 0..4 {
378 self.vertices[i][j] =
379 self.vertices[0][j] + sigma * (self.vertices[i][j] - self.vertices[0][j]);
380 }
381 self.values[i] = f.evaluate(&self.vertices[i]);
382 }
383
384 Ok(())
385 }
386
387 pub fn get_best_solution(&self) -> ([f64; 4], f64) {
389 (self.best_point, self.best_value)
390 }
391}
392
393impl BoundedNewtonRaphson {
394 pub fn new(
396 initial_guess: f64,
397 lower_bound: f64,
398 upper_bound: f64,
399 config: SolverConfig,
400 ) -> Self {
401 Self {
402 current_guess: initial_guess,
403 previous_guess: initial_guess,
404 current_value: 0.0,
405 current_derivative: 0.0,
406 lower_bound,
407 upper_bound,
408 config,
409 solver_state: SolverState::default(),
410 }
411 }
412
413 pub fn find_root<F>(&mut self, f: &F) -> SolverResult<RootFindingState>
415 where
416 F: RootFunction,
417 {
418 self.solver_state.iteration = 0;
419 self.solver_state.converged = false;
420
421 while self.solver_state.iteration < self.config.max_iterations {
422 self.current_value = f.evaluate(self.current_guess);
424 self.current_derivative = f.derivative(self.current_guess);
425
426 if self.current_value.abs() < self.config.tolerance {
428 self.solver_state.converged = true;
429 break;
430 }
431
432 if self.current_derivative.abs() < 1e-10 {
434 return Err(ExecutionError::ConvergenceFailed);
435 }
436
437 let new_guess = self.current_guess - self.current_value / self.current_derivative;
439
440 let bounded_guess = new_guess.clamp(self.lower_bound, self.upper_bound);
442
443 if (bounded_guess - self.current_guess).abs() < self.config.tolerance {
445 self.solver_state.converged = true;
446 break;
447 }
448
449 self.previous_guess = self.current_guess;
450 self.current_guess = bounded_guess;
451 self.solver_state.iteration += 1;
452 }
453
454 Ok(RootFindingState {
455 iteration: self.solver_state.iteration,
456 function_value: self.current_value,
457 converged: self.solver_state.converged,
458 step_size: (self.current_guess - self.previous_guess).abs(),
459 })
460 }
461
462 pub fn get_root(&self) -> f64 {
464 self.current_guess
465 }
466}
467
468impl LevenbergMarquardtStack {
469 pub fn new(initial_parameters: [f64; 4], config: SolverConfig) -> Self {
471 Self {
472 parameters: initial_parameters,
473 delta_parameters: [0.0; 4],
474 jacobian: [[0.0; 10]; 4],
475 residuals: [0.0; 10],
476 lambda: 1e-3,
477 chi_squared: f64::MAX,
478 config,
479 solver_state: SolverState::default(),
480 }
481 }
482
483 pub fn fit_curve<F>(
485 &mut self,
486 f: &F,
487 x_data: &[f64; 10],
488 y_data: &[f64; 10],
489 ) -> SolverResult<CurveFitState>
490 where
491 F: CurveFitFunction,
492 {
493 self.solver_state.iteration = 0;
494 self.solver_state.converged = false;
495
496 self.evaluate_residuals(f, x_data, y_data)?;
498 self.chi_squared = self.calculate_chi_squared();
499
500 while self.solver_state.iteration < self.config.max_iterations {
501 self.calculate_jacobian(f, x_data)?;
503
504 self.solve_parameter_update()?;
506
507 let old_chi_squared = self.chi_squared;
509 let old_parameters = self.parameters;
510
511 for i in 0..4 {
513 self.parameters[i] += self.delta_parameters[i];
514 }
515
516 self.evaluate_residuals(f, x_data, y_data)?;
518 self.chi_squared = self.calculate_chi_squared();
519
520 if self.chi_squared < old_chi_squared {
522 self.lambda *= 0.1;
524 } else {
525 self.lambda *= 10.0;
527 self.parameters = old_parameters;
528 self.chi_squared = old_chi_squared;
529 }
530
531 if self.check_convergence(old_chi_squared) {
533 self.solver_state.converged = true;
534 break;
535 }
536
537 self.solver_state.iteration += 1;
538 }
539
540 Ok(CurveFitState {
541 iteration: self.solver_state.iteration,
542 chi_squared: self.chi_squared,
543 converged: self.solver_state.converged,
544 lambda: self.lambda,
545 })
546 }
547
548 fn evaluate_residuals<F>(
550 &mut self,
551 f: &F,
552 x_data: &[f64; 10],
553 y_data: &[f64; 10],
554 ) -> SolverResult<()>
555 where
556 F: CurveFitFunction,
557 {
558 for i in 0..10 {
559 let model_value = f.evaluate(x_data[i], &self.parameters);
560 self.residuals[i] = y_data[i] - model_value;
561 }
562
563 Ok(())
564 }
565
566 fn calculate_chi_squared(&self) -> f64 {
568 let mut chi_sq = 0.0;
569
570 for i in 0..10 {
571 chi_sq += self.residuals[i] * self.residuals[i];
572 }
573
574 chi_sq
575 }
576
577 fn calculate_jacobian<F>(&mut self, f: &F, x_data: &[f64; 10]) -> SolverResult<()>
579 where
580 F: CurveFitFunction,
581 {
582 for i in 0..10 {
583 let jacobian_row = f.jacobian(x_data[i], &self.parameters);
584 for j in 0..4 {
585 self.jacobian[j][i] = jacobian_row[j];
586 }
587 }
588
589 Ok(())
590 }
591
592 fn solve_parameter_update(&mut self) -> SolverResult<()> {
594 let mut jtj = [[0.0; 4]; 4];
596 for i in 0..4 {
597 for j in 0..4 {
598 let mut sum = 0.0;
599 for k in 0..10 {
600 sum += self.jacobian[i][k] * self.jacobian[j][k];
601 }
602 jtj[i][j] = sum;
603 }
604 jtj[i][i] += self.lambda;
606 }
607
608 let mut jtr = [0.0; 4];
610 for i in 0..4 {
611 let mut sum = 0.0;
612 for k in 0..10 {
613 sum += self.jacobian[i][k] * self.residuals[k];
614 }
615 jtr[i] = sum;
616 }
617
618 self.solve_4x4_system(&jtj, &jtr)
620 }
621
622 fn solve_4x4_system(&mut self, matrix: &[[f64; 4]; 4], rhs: &[f64; 4]) -> SolverResult<()> {
624 let mut a = *matrix;
626 let mut b = *rhs;
627 let mut pivot = [0; 4];
628
629 for i in 0..4 {
631 let mut max_row = i;
633 let mut max_val = a[i][i].abs();
634
635 for j in i + 1..4 {
636 if a[j][i].abs() > max_val {
637 max_val = a[j][i].abs();
638 max_row = j;
639 }
640 }
641
642 if max_val < 1e-10 {
643 return Err(ExecutionError::SingularMatrix);
644 }
645
646 if max_row != i {
648 for k in 0..4 {
649 let temp = a[i][k];
650 a[i][k] = a[max_row][k];
651 a[max_row][k] = temp;
652 }
653 let temp = b[i];
654 b[i] = b[max_row];
655 b[max_row] = temp;
656 }
657
658 pivot[i] = max_row;
659
660 for j in i + 1..4 {
662 let factor = a[j][i] / a[i][i];
663 a[j][i] = factor;
664
665 for k in i + 1..4 {
666 a[j][k] -= factor * a[i][k];
667 }
668 b[j] -= factor * b[i];
669 }
670 }
671
672 for i in (0..4).rev() {
674 let mut sum = b[i];
675 for j in i + 1..4 {
676 sum -= a[i][j] * self.delta_parameters[j];
677 }
678 self.delta_parameters[i] = sum / a[i][i];
679 }
680
681 Ok(())
682 }
683
684 fn check_convergence(&self, old_chi_squared: f64) -> bool {
686 let relative_change = (old_chi_squared - self.chi_squared).abs() / old_chi_squared;
687 relative_change < self.config.tolerance
688 }
689
690 pub fn get_parameters(&self) -> [f64; 4] {
692 self.parameters
693 }
694}
695
696impl Default for OptimizationState {
697 fn default() -> Self {
698 Self {
699 iteration: 0,
700 objective_value: f64::MAX,
701 converged: false,
702 simplex_size: f64::MAX,
703 }
704 }
705}
706
707impl Default for RootFindingState {
708 fn default() -> Self {
709 Self {
710 iteration: 0,
711 function_value: f64::MAX,
712 converged: false,
713 step_size: f64::MAX,
714 }
715 }
716}
717
718impl Default for CurveFitState {
719 fn default() -> Self {
720 Self {
721 iteration: 0,
722 chi_squared: f64::MAX,
723 converged: false,
724 lambda: 1e-3,
725 }
726 }
727}
728
729impl Default for NelderMeadSimplex {
730 fn default() -> Self {
731 Self::new([0.0; 4], SolverConfig::default())
732 }
733}
734
735impl Default for BoundedNewtonRaphson {
736 fn default() -> Self {
737 Self::new(0.0, -1e6, 1e6, SolverConfig::default())
738 }
739}
740
741impl Default for LevenbergMarquardtStack {
742 fn default() -> Self {
743 Self::new([0.0; 4], SolverConfig::default())
744 }
745}
746
747#[cfg(test)]
748mod tests {
749 use super::*;
750
751 struct QuadraticFunction;
753
754 impl ObjectiveFunction for QuadraticFunction {
755 fn evaluate(&self, params: &[f64; 4]) -> f64 {
756 (params[0] - 1.0).powi(2)
757 + (params[1] - 2.0).powi(2)
758 + (params[2] - 3.0).powi(2)
759 + (params[3] - 4.0).powi(2)
760 }
761 }
762
763 #[test]
764 fn test_nelder_mead_simplex() {
765 let mut optimizer = NelderMeadSimplex::new([0.0; 4], SolverConfig::default());
766 let func = QuadraticFunction;
767
768 let result = optimizer.optimize(&func);
769 assert!(result.is_ok());
770
771 let state = result.unwrap();
772 assert!(state.converged);
773
774 let (params, value) = optimizer.get_best_solution();
775 assert!((params[0] - 1.0).abs() < 0.1);
776 assert!((params[1] - 2.0).abs() < 0.1);
777 assert!((params[2] - 3.0).abs() < 0.1);
778 assert!((params[3] - 4.0).abs() < 0.1);
779 assert!(value < 0.1);
780 }
781
782 struct CubicFunction;
784
785 impl RootFunction for CubicFunction {
786 fn evaluate(&self, x: f64) -> f64 {
787 x.powi(3) - 2.0 * x - 5.0
788 }
789
790 fn derivative(&self, x: f64) -> f64 {
791 3.0 * x * x - 2.0
792 }
793 }
794
795 #[test]
796 fn test_bounded_newton_raphson() {
797 let mut solver = BoundedNewtonRaphson::new(2.0, -10.0, 10.0, SolverConfig::default());
798 let func = CubicFunction;
799
800 let result = solver.find_root(&func);
801 assert!(result.is_ok());
802
803 let state = result.unwrap();
804 assert!(state.converged);
805
806 let root = solver.get_root();
807 assert!((root - 2.094).abs() < 0.01); }
809
810 struct PolynomialFit;
812
813 impl CurveFitFunction for PolynomialFit {
814 fn evaluate(&self, x: f64, params: &[f64; 4]) -> f64 {
815 params[0] + params[1] * x + params[2] * x * x + params[3] * x * x * x
816 }
817
818 fn jacobian(&self, x: f64, _params: &[f64; 4]) -> [f64; 4] {
819 [1.0, x, x * x, x * x * x]
820 }
821 }
822
823 #[test]
824 fn test_levenberg_marquardt_stack() {
825 let mut optimizer =
826 LevenbergMarquardtStack::new([1.0, 1.0, 1.0, 1.0], SolverConfig::default());
827
828 let mut x_data = [0.0; 10];
830 let mut y_data = [0.0; 10];
831
832 for i in 0..10 {
833 x_data[i] = i as f64;
834 y_data[i] = 2.0
835 + 3.0 * x_data[i]
836 + 0.5 * x_data[i] * x_data[i]
837 + 0.1 * x_data[i] * x_data[i] * x_data[i];
838 }
839
840 let func = PolynomialFit;
841 let result = optimizer.fit_curve(&func, &x_data, &y_data);
842 assert!(result.is_ok());
843
844 let state = result.unwrap();
845 assert!(state.converged);
846
847 let params = optimizer.get_parameters();
848 assert!((params[0] - 2.0).abs() < 0.1);
849 assert!((params[1] - 3.0).abs() < 0.1);
850 }
851
852 #[test]
853 fn test_zero_allocation_guarantee() {
854 }
858}