scivex_optim/minimize/
mod.rs1mod bfgs;
4mod gradient_descent;
5mod lbfgsb;
6mod nelder_mead;
7
8pub use bfgs::bfgs;
9pub use gradient_descent::gradient_descent;
10pub use lbfgsb::{Bounds, lbfgsb};
11pub use nelder_mead::nelder_mead;
12
13use scivex_core::{Float, Tensor};
14
15#[cfg_attr(
32 feature = "serde-support",
33 derive(serde::Serialize, serde::Deserialize)
34)]
35#[derive(Debug, Clone)]
36pub struct MinimizeResult<T: Float> {
37 pub x: Tensor<T>,
39 pub f_val: T,
41 pub grad: Option<Tensor<T>>,
43 pub iterations: usize,
45 pub f_evals: usize,
47 pub g_evals: usize,
49 pub converged: bool,
51}
52
53#[cfg_attr(
63 feature = "serde-support",
64 derive(serde::Serialize, serde::Deserialize)
65)]
66#[derive(Debug, Clone)]
67pub struct MinimizeOptions<T: Float> {
68 pub gtol: T,
70 pub xtol: T,
72 pub ftol: T,
74 pub max_iter: usize,
76 pub learning_rate: T,
78}
79
80impl<T: Float> Default for MinimizeOptions<T> {
81 fn default() -> Self {
82 Self {
83 gtol: T::from_f64(1e-8),
84 xtol: T::from_f64(1e-12),
85 ftol: T::from_f64(1e-12),
86 max_iter: 1000,
87 learning_rate: T::from_f64(0.01),
88 }
89 }
90}
91
92pub fn numerical_gradient<T: Float, F: Fn(&Tensor<T>) -> T>(f: &F, x: &Tensor<T>) -> Tensor<T> {
108 let n = x.numel();
109 let h = T::from_f64(1e-8);
110 let two_h = h * T::from_f64(2.0);
111 let mut grad_data = vec![T::zero(); n];
112
113 let x_data = x.as_slice();
114 let mut x_plus = x_data.to_vec();
115 let mut x_minus = x_data.to_vec();
116
117 for i in 0..n {
118 let orig = x_data[i];
119
120 x_plus[i] = orig + h;
121 x_minus[i] = orig - h;
122
123 let t_plus = Tensor::from_vec(x_plus.clone(), vec![n])
124 .expect("perturbed vector length matches original dimension n");
125 let t_minus = Tensor::from_vec(x_minus.clone(), vec![n])
126 .expect("perturbed vector length matches original dimension n");
127
128 grad_data[i] = (f(&t_plus) - f(&t_minus)) / two_h;
129
130 x_plus[i] = orig;
131 x_minus[i] = orig;
132 }
133
134 Tensor::from_vec(grad_data, vec![n]).expect("gradient vector length matches dimension n")
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_numerical_gradient_quadratic() {
143 let f = |x: &Tensor<f64>| {
145 let s = x.as_slice();
146 s[0] * s[0] + s[1] * s[1]
147 };
148
149 let x = Tensor::from_vec(vec![3.0, 4.0], vec![2]).unwrap();
150 let g = numerical_gradient(&f, &x);
151 let gs = g.as_slice();
152
153 assert!((gs[0] - 6.0).abs() < 1e-5, "got {}", gs[0]);
154 assert!((gs[1] - 8.0).abs() < 1e-5, "got {}", gs[1]);
155 }
156}