Skip to main content

qualia_core_db/solvers/optimization/
mod.rs

1//! Optimization & Root Finding - Zero-Allocation Implementation
2//!
3//! This module provides fixed-size stack-based optimization algorithms and
4//! root finding methods suitable for the #![no_std] environment of Qualia-DB.
5
6use crate::solvers::SolversError as ExecutionError;
7use crate::solvers::{SolverConfig, SolverResult, SolverState};
8
9/// General-dimension metaheuristic optimizers (hill-climbing / simulated annealing /
10/// Artificial Bee Colony) — global search beyond the fixed-`[f64;4]` solvers below.
11/// Heap-using batch-analytics layer; the engine ontology alignment consumes it.
12#[cfg(any(not(target_arch = "wasm32"), feature = "wasm-scientific"))]
13pub mod metaheuristics;
14
15/// Nelder-Mead simplex optimizer for unconstrained optimization
16#[repr(C)]
17pub struct NelderMeadSimplex {
18    /// Simplex vertices (n+1 points in n dimensions)
19    pub vertices: [[f64; 4]; 5], // 4D simplex
20    /// Function values at vertices
21    pub values: [f64; 5],
22    /// Current iteration
23    pub iteration: u32,
24    /// Best point found
25    pub best_point: [f64; 4],
26    pub best_value: f64,
27    /// Solver configuration
28    pub config: SolverConfig,
29    /// Solver state
30    pub solver_state: SolverState,
31}
32
33/// Bounded Newton-Raphson root finder
34#[repr(C)]
35pub struct BoundedNewtonRaphson {
36    /// Current guess
37    pub current_guess: f64,
38    /// Previous guess for convergence checking
39    pub previous_guess: f64,
40    /// Function value at current guess
41    pub current_value: f64,
42    /// Derivative at current guess
43    pub current_derivative: f64,
44    /// Search bounds
45    pub lower_bound: f64,
46    pub upper_bound: f64,
47    /// Solver configuration
48    pub config: SolverConfig,
49    /// Solver state
50    pub solver_state: SolverState,
51}
52
53/// Levenberg-Marquardt curve fitting optimizer
54#[repr(C)]
55pub struct LevenbergMarquardtStack {
56    /// Current parameters
57    pub parameters: [f64; 4],
58    /// Parameter updates
59    pub delta_parameters: [f64; 4],
60    /// Jacobian matrix (4xN observations)
61    pub jacobian: [[f64; 10]; 4],
62    /// Residual vector
63    pub residuals: [f64; 10],
64    /// Damping parameter
65    pub lambda: f64,
66    /// Chi-squared value
67    pub chi_squared: f64,
68    /// Solver configuration
69    pub config: SolverConfig,
70    /// Solver state
71    pub solver_state: SolverState,
72}
73
74/// Optimization state tracking
75#[repr(C)]
76#[derive(Clone, Copy)]
77pub struct OptimizationState {
78    /// Current iteration
79    pub iteration: u32,
80    /// Current objective value
81    pub objective_value: f64,
82    /// Converged flag
83    pub converged: bool,
84    /// Simplex size (for Nelder-Mead)
85    pub simplex_size: f64,
86}
87
88/// Root finding state tracking
89#[repr(C)]
90#[derive(Clone, Copy)]
91pub struct RootFindingState {
92    /// Current iteration
93    pub iteration: u32,
94    /// Current function value
95    pub function_value: f64,
96    /// Converged flag
97    pub converged: bool,
98    /// Step size
99    pub step_size: f64,
100}
101
102/// Curve fitting state tracking
103#[repr(C)]
104#[derive(Clone, Copy)]
105pub struct CurveFitState {
106    /// Current iteration
107    pub iteration: u32,
108    /// Current chi-squared
109    pub chi_squared: f64,
110    /// Converged flag
111    pub converged: bool,
112    /// Damping parameter
113    pub lambda: f64,
114}
115
116/// Objective function trait for optimization
117pub trait ObjectiveFunction {
118    /// Evaluate objective function
119    fn evaluate(&self, params: &[f64; 4]) -> f64;
120
121    /// Check if parameters are within bounds
122    fn in_bounds(&self, _params: &[f64; 4]) -> bool {
123        true // Default: no bounds
124    }
125}
126
127/// Root finding function trait
128pub trait RootFunction {
129    /// Evaluate function f(x)
130    fn evaluate(&self, x: f64) -> f64;
131
132    /// Evaluate derivative f'(x)
133    fn derivative(&self, x: f64) -> f64;
134}
135
136/// Curve fitting function trait
137pub trait CurveFitFunction {
138    /// Evaluate model at given x with parameters
139    fn evaluate(&self, x: f64, params: &[f64; 4]) -> f64;
140
141    /// Evaluate Jacobian at given x with parameters
142    fn jacobian(&self, x: f64, params: &[f64; 4]) -> [f64; 4];
143}
144
145impl NelderMeadSimplex {
146    /// Create new Nelder-Mead optimizer
147    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    /// Initialize simplex around initial point
160    const fn initialize_simplex(initial_point: [f64; 4]) -> [[f64; 4]; 5] {
161        let mut simplex = [[0.0; 4]; 5];
162
163        // First vertex is the initial point
164        simplex[0] = initial_point;
165
166        // Other vertices are perturbed
167        let mut i = 1;
168        while i < 5 {
169            let mut vertex = initial_point;
170            vertex[i - 1] += 0.1; // Perturb one dimension
171            simplex[i] = vertex;
172            i += 1;
173        }
174
175        simplex
176    }
177
178    /// Optimize objective function
179    pub fn optimize<F>(&mut self, f: &F) -> SolverResult<OptimizationState>
180    where
181        F: ObjectiveFunction,
182    {
183        // Evaluate initial simplex
184        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            // Sort vertices by function value
193            self.sort_vertices();
194
195            // Update best point
196            self.best_point = self.vertices[0];
197            self.best_value = self.values[0];
198
199            // Check convergence
200            if self.check_convergence() {
201                self.solver_state.converged = true;
202                break;
203            }
204
205            // Perform Nelder-Mead operations
206            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    /// Sort vertices by function value
220    fn sort_vertices(&mut self) {
221        // Simple bubble sort (fixed size, no allocation)
222        for i in 0..4 {
223            for j in 0..4 - i {
224                if self.values[j] > self.values[j + 1] {
225                    // Swap vertices
226                    let temp_vertex = self.vertices[j];
227                    self.vertices[j] = self.vertices[j + 1];
228                    self.vertices[j + 1] = temp_vertex;
229
230                    // Swap values
231                    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    /// Check convergence
240    fn check_convergence(&self) -> bool {
241        let simplex_size = self.calculate_simplex_size();
242        simplex_size < self.config.tolerance
243    }
244
245    /// Calculate simplex size
246    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    /// Perform Nelder-Mead step
261    fn nelder_mead_step<F>(&mut self, f: &F) -> SolverResult<()>
262    where
263        F: ObjectiveFunction,
264    {
265        // Calculate centroid of best n vertices
266        let centroid = self.calculate_centroid();
267
268        // Reflection
269        let reflected = self.reflect(&centroid);
270        let reflected_value = f.evaluate(&reflected);
271
272        if reflected_value < self.values[0] {
273            // Expansion
274            let expanded = self.expand(&centroid, &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            // Accept reflection
286            self.vertices[4] = reflected;
287            self.values[4] = reflected_value;
288        } else {
289            // Contraction
290            if reflected_value < self.values[4] {
291                let contracted = self.contract(&centroid, &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(&centroid, &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    /// Calculate centroid of best n vertices
317    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    /// Reflection operation
334    fn reflect(&self, centroid: &[f64; 4]) -> [f64; 4] {
335        let mut reflected = [0.0; 4];
336        let alpha = 1.0; // Reflection coefficient
337
338        for i in 0..4 {
339            reflected[i] = centroid[i] + alpha * (centroid[i] - self.vertices[4][i]);
340        }
341
342        reflected
343    }
344
345    /// Expansion operation
346    fn expand(&self, centroid: &[f64; 4], reflected: &[f64; 4]) -> [f64; 4] {
347        let mut expanded = [0.0; 4];
348        let gamma = 2.0; // Expansion coefficient
349
350        for i in 0..4 {
351            expanded[i] = centroid[i] + gamma * (reflected[i] - centroid[i]);
352        }
353
354        expanded
355    }
356
357    /// Contraction operation
358    fn contract(&self, centroid: &[f64; 4], worst: &[f64; 4]) -> [f64; 4] {
359        let mut contracted = [0.0; 4];
360        let rho = 0.5; // Contraction coefficient
361
362        for i in 0..4 {
363            contracted[i] = centroid[i] + rho * (worst[i] - centroid[i]);
364        }
365
366        contracted
367    }
368
369    /// Shrink operation
370    fn shrink<F>(&mut self, f: &F) -> SolverResult<()>
371    where
372        F: ObjectiveFunction,
373    {
374        let sigma = 0.5; // Shrink coefficient
375
376        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    /// Get best solution
388    pub fn get_best_solution(&self) -> ([f64; 4], f64) {
389        (self.best_point, self.best_value)
390    }
391}
392
393impl BoundedNewtonRaphson {
394    /// Create new bounded Newton-Raphson solver
395    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    /// Find root of function
414    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            // Evaluate function and derivative
423            self.current_value = f.evaluate(self.current_guess);
424            self.current_derivative = f.derivative(self.current_guess);
425
426            // Check convergence
427            if self.current_value.abs() < self.config.tolerance {
428                self.solver_state.converged = true;
429                break;
430            }
431
432            // Check for zero derivative
433            if self.current_derivative.abs() < 1e-10 {
434                return Err(ExecutionError::ConvergenceFailed);
435            }
436
437            // Newton step
438            let new_guess = self.current_guess - self.current_value / self.current_derivative;
439
440            // Apply bounds
441            let bounded_guess = new_guess.clamp(self.lower_bound, self.upper_bound);
442
443            // Check for convergence in x
444            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    /// Get current root estimate
463    pub fn get_root(&self) -> f64 {
464        self.current_guess
465    }
466}
467
468impl LevenbergMarquardtStack {
469    /// Create new Levenberg-Marquardt optimizer
470    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    /// Fit curve to data points
484    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        // Initial evaluation
497        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            // Calculate Jacobian
502            self.calculate_jacobian(f, x_data)?;
503
504            // Solve for parameter update
505            self.solve_parameter_update()?;
506
507            // Try new parameters
508            let old_chi_squared = self.chi_squared;
509            let old_parameters = self.parameters;
510
511            // Update parameters
512            for i in 0..4 {
513                self.parameters[i] += self.delta_parameters[i];
514            }
515
516            // Evaluate new chi-squared
517            self.evaluate_residuals(f, x_data, y_data)?;
518            self.chi_squared = self.calculate_chi_squared();
519
520            // Check if improvement
521            if self.chi_squared < old_chi_squared {
522                // Accept update, decrease lambda
523                self.lambda *= 0.1;
524            } else {
525                // Reject update, increase lambda
526                self.lambda *= 10.0;
527                self.parameters = old_parameters;
528                self.chi_squared = old_chi_squared;
529            }
530
531            // Check convergence
532            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    /// Evaluate residuals
549    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    /// Calculate chi-squared
567    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    /// Calculate Jacobian matrix
578    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    /// Solve for parameter update using (J^T J + λI)δ = J^T r
593    fn solve_parameter_update(&mut self) -> SolverResult<()> {
594        // Calculate J^T J
595        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            // Add damping term
605            jtj[i][i] += self.lambda;
606        }
607
608        // Calculate J^T r
609        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        // Solve linear system (simplified 4x4 solver)
619        self.solve_4x4_system(&jtj, &jtr)
620    }
621
622    /// Solve 4x4 linear system (simplified)
623    fn solve_4x4_system(&mut self, matrix: &[[f64; 4]; 4], rhs: &[f64; 4]) -> SolverResult<()> {
624        // Gaussian elimination with partial pivoting
625        let mut a = *matrix;
626        let mut b = *rhs;
627        let mut pivot = [0; 4];
628
629        // Forward elimination
630        for i in 0..4 {
631            // Find pivot
632            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            // Swap rows
647            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            // Eliminate column
661            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        // Back substitution
673        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    /// Check convergence
685    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    /// Get fitted parameters
691    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    // Test function: f(x) = (x-1)² + (y-2)² + (z-3)² + (w-4)²
752    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    // Test root finding: f(x) = x³ - 2x - 5
783    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); // Known root
808    }
809
810    // Test curve fitting: y = a + bx + cx² + dx³
811    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        // Generate test data
829        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        // assert_eq!(core::mem::size_of::<NelderMeadSimplex>(), ...);
855        // assert_eq!(core::mem::size_of::<BoundedNewtonRaphson>(), ...);
856        // assert_eq!(core::mem::size_of::<LevenbergMarquardtStack>(), ...);
857    }
858}