mini_ode/optimizers/
halley.rs1use anyhow::anyhow;
2use std::fmt;
3use tch::Tensor;
4
5use super::Optimizer;
6
7use crate::utils::differentiation;
8use crate::utils::linesearch;
9use crate::utils::validation;
10use crate::utils::warnings::warn;
11
12pub struct Halley {
22 max_steps: usize,
24 gtol: Option<f64>,
26 ftol: Option<f64>,
28}
29
30impl Halley {
31 pub fn new(max_steps: usize, gtol: Option<f64>, ftol: Option<f64>) -> Self {
41 Self {
42 max_steps,
43 gtol,
44 ftol,
45 }
46 }
47}
48
49impl Optimizer for Halley {
50 fn optimize(
51 &self,
52 function: &dyn Fn(&Tensor) -> Tensor,
53 x0: &Tensor,
54 ) -> anyhow::Result<Tensor> {
55 if x0.size().len() != 1 {
57 return Err(anyhow!("`x0` must have rank 1"));
58 }
59
60 let kind = x0.kind();
62 let device = x0.device();
63
64 let x0_length = x0.size()[0];
65
66 let _ = match Tensor::f_zeros([x0_length, x0_length, x0_length], (kind, device)) {
68 Ok(matrix) => matrix,
69 Err(tch::TchError::Torch(_)) => {
72 return Err(anyhow!(
73 "Could not allocate {}x{}x{} tensor. Maybe try less resourcefull algorithm.",
74 x0_length,
75 x0_length,
76 x0_length
77 ));
78 }
79 e => e.unwrap(),
80 };
81
82 let mut x = x0.copy();
83 let mut curr_y = function(&x);
84
85 if curr_y.size() != Vec::<i64>::new() {
87 return Err(anyhow!("Output of function `function` must be scalar"));
88 }
89
90 let mut warned_pinv_large = false;
91
92 for _ in 0..self.max_steps {
93 let (curr_grad, curr_hessian, curr_d3_tensor) =
94 match differentiation::derivative_tensors_123(function, &x) {
95 Ok(ghd3) => ghd3,
96 Err(e) => {
97 return Err(anyhow!(
98 "Runtime error: Differentiation failed in Halley optimizer: {}",
99 e
100 ));
101 }
102 };
103
104 if let Some(gtol) = self.gtol {
106 if curr_grad.f_norm()?.f_double_value(&[])? < gtol {
107 validation::validate_optimizer_output(&x, "Halley")?;
109 return Ok(x);
110 }
111 } else {
112 if curr_grad.f_norm()?.f_double_value(&[])? == 0. {
116 validation::validate_optimizer_output(&x, "Halley")?;
118 return Ok(x);
119 }
120 }
121
122 let hessian_pinv = curr_hessian.f_linalg_pinv(1e-14, false)?;
124
125 let pinv_norm = hessian_pinv.f_norm()?.f_double_value(&[])?;
127 if !warned_pinv_large && pinv_norm > 1e8 {
128 warn!(
129 "Halley: Hessian pseudoinverse norm is {:.3e}; Hessian may be ill-conditioned",
130 pinv_norm
131 );
132 warned_pinv_large = true;
133 }
134
135 let neg_newton_dir = hessian_pinv.f_mm(&curr_grad.f_reshape([-1, 1])?)?;
136 let direction = -hessian_pinv
137 .f_mm(
138 &(curr_grad.f_reshape([-1, 1])?
139 + curr_d3_tensor
140 .f_matmul(&neg_newton_dir)?
141 .f_reshape([x0_length, x0_length])?
142 .f_mm(&neg_newton_dir)?
143 * 0.5),
144 )?
145 .f_reshape([-1])?;
146
147 let step = linesearch::choose_step_backtracking(
150 &x, &direction, function, &curr_grad, 0.1, 0.9,
151 )?;
152
153 x = x + &step;
155
156 let y = function(&x);
158 if let Some(ftol) = self.ftol {
159 if (curr_y.f_double_value(&[])? - y.f_double_value(&[])?) < ftol {
160 validation::validate_optimizer_output(&x, "Halley")?;
162 return Ok(x);
163 }
164 }
165 curr_y = y;
166 }
167
168 validation::validate_optimizer_output(&x, "Halley")?;
170 Ok(x)
171 }
172}
173
174impl fmt::Display for Halley {
175 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
176 let mut string = String::from("Halley(");
177
178 string = string + "max_steps=" + self.max_steps.to_string().as_str();
179 if let Some(gtol) = self.gtol {
180 string = string + ", gtol=" + gtol.to_string().as_str();
181 }
182 if let Some(ftol) = self.ftol {
183 string = string + ", ftol=" + ftol.to_string().as_str();
184 }
185
186 string = string + ")";
187
188 write!(f, "{}", string)
189 }
190}