mini_ode/optimizers/
cg.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 CG {
23 max_steps: usize,
25 gtol: Option<f64>,
27 ftol: Option<f64>,
29}
30
31impl CG {
32 pub fn new(max_steps: usize, gtol: Option<f64>, ftol: Option<f64>) -> Self {
33 Self {
34 max_steps,
35 gtol,
36 ftol,
37 }
38 }
39}
40
41impl Optimizer for CG {
42 fn optimize(
52 &self,
53 function: &dyn Fn(&Tensor) -> Tensor,
54 x0: &Tensor,
55 ) -> anyhow::Result<Tensor> {
56 if x0.size().len() != 1 {
58 return Err(anyhow!("`x0` must have rank 1"));
59 }
60
61 let mut prev3_step_norm = 0f64;
62 let mut prev2_step_norm = 0f64;
63 let mut prev_step_norm = 0f64;
64
65 let mut prev_grad = Tensor::f_zeros_like(&x0)?;
66 let mut prev_direction = Tensor::f_zeros_like(&x0)?;
67 let mut prev_y: Option<Tensor> = None;
68 let mut x = x0.copy();
69
70 let mut warned_nonfinite_grad = false;
71 let mut warned_beta_clamp = false;
72 let mut warned_nonfinite_iter = false;
73
74 for step_num in 0..self.max_steps {
75 let grad = match differentiation::differentiate(function, &x) {
76 Ok(grad) => grad,
77 Err(e) => {
78 return Err(anyhow!(
79 "Runtime error: Differentiation failed in CG optimizer: {}",
80 e
81 ));
82 }
83 };
84
85 if !warned_nonfinite_grad && grad.isfinite().f_all()?.f_int64_value(&[])? == 0 {
87 warn!("CG: non-finite gradient detected; function may be ill-defined");
88 warned_nonfinite_grad = true;
89 }
90
91 if let Some(gtol) = self.gtol {
93 if grad.norm().f_double_value(&[])? < gtol {
94 validation::validate_optimizer_output(&x, "CG")?;
96 return Ok(x);
97 }
98 } else {
99 if grad.norm().f_double_value(&[])? == 0. {
103 validation::validate_optimizer_output(&x, "CG")?;
105 return Ok(x);
106 }
107 }
108
109 let direction = match step_num {
111 0 => -&grad,
112 _ => {
113 let orthogonality_measure = grad
114 .f_reshape([-1])?
115 .f_dot(&prev_grad.f_reshape([-1])?)?
116 .f_abs()?
117 / grad.f_reshape([-1])?.f_dot(&grad.f_reshape([-1])?)?;
118 if orthogonality_measure.f_double_value(&[])? > 0.2 {
119 -&grad
121 } else {
122 let beta = grad
123 .f_reshape([-1])?
124 .f_dot(&(&grad - &prev_grad).f_reshape([-1])?)?
125 / prev_grad
126 .f_reshape([-1])?
127 .f_dot(&prev_grad.f_reshape([-1])?)?;
128 let beta = if beta.f_double_value(&[])? > 0. {
130 beta
131 } else {
132 tch::Tensor::f_zeros_like(&beta)?
133 };
134 let beta = if beta.f_double_value(&[])? > 1e12 {
136 if !warned_beta_clamp {
137 warn!("CG: beta clamped to 1e12; optimizer may be diverging");
138 warned_beta_clamp = true;
139 }
140 tch::Tensor::f_ones_like(&beta)? * 1e12
141 } else {
142 beta
143 };
144
145 -&grad + beta * &prev_direction
146 }
147 }
148 };
149
150 let linesearch_atol = linesearch::P0
152 .max(prev_step_norm.min(prev2_step_norm).min(prev3_step_norm) / 1000.);
153
154 let step =
157 linesearch::choose_step_golden_section(&x, &direction, &function, linesearch_atol)?;
158
159 prev3_step_norm = prev2_step_norm;
161 prev2_step_norm = prev_step_norm;
162 prev_step_norm = step.f_norm()?.f_double_value(&[])?;
163
164 x = x + step;
166
167 if !warned_nonfinite_iter && x.isfinite().f_all()?.f_int64_value(&[])? == 0 {
169 warn!("CG: non-finite iterate detected; step size may be too large");
170 warned_nonfinite_iter = true;
171 }
172
173 let y = function(&x);
175 if let (Some(prev_y), Some(ftol)) = (prev_y, self.ftol) {
176 if (&prev_y - &y).f_double_value(&[])? < ftol {
177 validation::validate_optimizer_output(&x, "CG")?;
179 return Ok(x);
180 }
181 }
182 prev_y = Some(y);
183
184 prev_grad = grad;
186 prev_direction = direction;
187 }
188
189 validation::validate_optimizer_output(&x, "CG")?;
191 Ok(x)
192 }
193}
194
195impl fmt::Display for CG {
196 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
197 let mut string = String::from("CG(");
198
199 string = string + "max_steps=" + self.max_steps.to_string().as_str();
200 if let Some(gtol) = self.gtol {
201 string = string + ", gtol=" + gtol.to_string().as_str();
202 }
203 if let Some(ftol) = self.ftol {
204 string = string + ", ftol=" + ftol.to_string().as_str();
205 }
206 string = string + ")";
207
208 write!(f, "{}", string)
209 }
210}